@@ -1,8 +1,57 @@ | |||
<component name="ProjectCodeStyleConfiguration"> | |||
<code_scheme name="Project" version="173"> | |||
<DBN-PSQL> | |||
<case-options enabled="true"> | |||
<option name="KEYWORD_CASE" value="lower" /> | |||
<option name="FUNCTION_CASE" value="lower" /> | |||
<option name="PARAMETER_CASE" value="lower" /> | |||
<option name="DATATYPE_CASE" value="lower" /> | |||
<option name="OBJECT_CASE" value="preserve" /> | |||
</case-options> | |||
<formatting-settings enabled="false" /> | |||
</DBN-PSQL> | |||
<DBN-SQL> | |||
<case-options enabled="true"> | |||
<option name="KEYWORD_CASE" value="lower" /> | |||
<option name="FUNCTION_CASE" value="lower" /> | |||
<option name="PARAMETER_CASE" value="lower" /> | |||
<option name="DATATYPE_CASE" value="lower" /> | |||
<option name="OBJECT_CASE" value="preserve" /> | |||
</case-options> | |||
<formatting-settings enabled="false"> | |||
<option name="STATEMENT_SPACING" value="one_line" /> | |||
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" /> | |||
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" /> | |||
</formatting-settings> | |||
</DBN-SQL> | |||
<JetCodeStyleSettings> | |||
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="true" /> | |||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | |||
</JetCodeStyleSettings> | |||
<DBN-PSQL> | |||
<case-options enabled="true"> | |||
<option name="KEYWORD_CASE" value="lower" /> | |||
<option name="FUNCTION_CASE" value="lower" /> | |||
<option name="PARAMETER_CASE" value="lower" /> | |||
<option name="DATATYPE_CASE" value="lower" /> | |||
<option name="OBJECT_CASE" value="preserve" /> | |||
</case-options> | |||
<formatting-settings enabled="false" /> | |||
</DBN-PSQL> | |||
<DBN-SQL> | |||
<case-options enabled="true"> | |||
<option name="KEYWORD_CASE" value="lower" /> | |||
<option name="FUNCTION_CASE" value="lower" /> | |||
<option name="PARAMETER_CASE" value="lower" /> | |||
<option name="DATATYPE_CASE" value="lower" /> | |||
<option name="OBJECT_CASE" value="preserve" /> | |||
</case-options> | |||
<formatting-settings enabled="false"> | |||
<option name="STATEMENT_SPACING" value="one_line" /> | |||
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" /> | |||
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" /> | |||
</formatting-settings> | |||
</DBN-SQL> | |||
<codeStyleSettings language="XML"> | |||
<indentOptions> | |||
<option name="CONTINUATION_INDENT_SIZE" value="4" /> | |||
@@ -117,6 +166,12 @@ | |||
</codeStyleSettings> | |||
<codeStyleSettings language="kotlin"> | |||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | |||
<option name="RIGHT_MARGIN" value="120" /> | |||
<option name="KEEP_LINE_BREAKS" value="false" /> | |||
<option name="CALL_PARAMETERS_WRAP" value="0" /> | |||
<option name="METHOD_PARAMETERS_WRAP" value="1" /> | |||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" /> | |||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" /> | |||
</codeStyleSettings> | |||
</code_scheme> | |||
</component> |
@@ -0,0 +1,461 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="4"> | |||
<component name="DBNavigator.Project.DataEditorManager"> | |||
<record-view-column-sorting-type value="BY_INDEX" /> | |||
<value-preview-text-wrapping value="true" /> | |||
<value-preview-pinned value="false" /> | |||
</component> | |||
<component name="DBNavigator.Project.DataExportManager"> | |||
<export-instructions> | |||
<create-header value="true" /> | |||
<friendly-headers value="false" /> | |||
<quote-values-containing-separator value="true" /> | |||
<quote-all-values value="false" /> | |||
<value-separator value="" /> | |||
<file-name value="" /> | |||
<file-location value="" /> | |||
<scope value="GLOBAL" /> | |||
<destination value="FILE" /> | |||
<format value="EXCEL" /> | |||
<charset value="UTF-8" /> | |||
</export-instructions> | |||
</component> | |||
<component name="DBNavigator.Project.DatabaseBrowserManager"> | |||
<autoscroll-to-editor value="false" /> | |||
<autoscroll-from-editor value="true" /> | |||
<show-object-properties value="true" /> | |||
<loaded-nodes /> | |||
</component> | |||
<component name="DBNavigator.Project.DatabaseFileManager"> | |||
<open-files /> | |||
</component> | |||
<component name="DBNavigator.Project.EditorStateManager"> | |||
<last-used-providers /> | |||
</component> | |||
<component name="DBNavigator.Project.ExecutionManager"> | |||
<retain-sticky-names value="false" /> | |||
</component> | |||
<component name="DBNavigator.Project.MethodExecutionManager"> | |||
<method-browser /> | |||
<execution-history> | |||
<group-entries value="true" /> | |||
<execution-inputs /> | |||
</execution-history> | |||
<argument-values-cache /> | |||
</component> | |||
<component name="DBNavigator.Project.ObjectDependencyManager"> | |||
<last-used-dependency-type value="INCOMING" /> | |||
</component> | |||
<component name="DBNavigator.Project.ObjectQuickFilterManager"> | |||
<last-used-operator value="EQUAL" /> | |||
<filters /> | |||
</component> | |||
<component name="DBNavigator.Project.ParserDiagnosticsManager"> | |||
<diagnostics-history /> | |||
</component> | |||
<component name="DBNavigator.Project.ScriptExecutionManager" clear-outputs="true"> | |||
<recently-used-interfaces /> | |||
</component> | |||
<component name="DBNavigator.Project.Settings"> | |||
<connections /> | |||
<browser-settings> | |||
<general> | |||
<display-mode value="TABBED" /> | |||
<navigation-history-size value="100" /> | |||
<show-object-details value="false" /> | |||
</general> | |||
<filters> | |||
<object-type-filter> | |||
<object-type name="SCHEMA" enabled="true" /> | |||
<object-type name="USER" enabled="true" /> | |||
<object-type name="ROLE" enabled="true" /> | |||
<object-type name="PRIVILEGE" enabled="true" /> | |||
<object-type name="CHARSET" enabled="true" /> | |||
<object-type name="TABLE" enabled="true" /> | |||
<object-type name="VIEW" enabled="true" /> | |||
<object-type name="MATERIALIZED_VIEW" enabled="true" /> | |||
<object-type name="NESTED_TABLE" enabled="true" /> | |||
<object-type name="COLUMN" enabled="true" /> | |||
<object-type name="INDEX" enabled="true" /> | |||
<object-type name="CONSTRAINT" enabled="true" /> | |||
<object-type name="DATASET_TRIGGER" enabled="true" /> | |||
<object-type name="DATABASE_TRIGGER" enabled="true" /> | |||
<object-type name="SYNONYM" enabled="true" /> | |||
<object-type name="SEQUENCE" enabled="true" /> | |||
<object-type name="PROCEDURE" enabled="true" /> | |||
<object-type name="FUNCTION" enabled="true" /> | |||
<object-type name="PACKAGE" enabled="true" /> | |||
<object-type name="TYPE" enabled="true" /> | |||
<object-type name="TYPE_ATTRIBUTE" enabled="true" /> | |||
<object-type name="ARGUMENT" enabled="true" /> | |||
<object-type name="DIMENSION" enabled="true" /> | |||
<object-type name="CLUSTER" enabled="true" /> | |||
<object-type name="DBLINK" enabled="true" /> | |||
</object-type-filter> | |||
</filters> | |||
<sorting> | |||
<object-type name="COLUMN" sorting-type="NAME" /> | |||
<object-type name="FUNCTION" sorting-type="NAME" /> | |||
<object-type name="PROCEDURE" sorting-type="NAME" /> | |||
<object-type name="ARGUMENT" sorting-type="POSITION" /> | |||
</sorting> | |||
<default-editors> | |||
<object-type name="VIEW" editor-type="SELECTION" /> | |||
<object-type name="PACKAGE" editor-type="SELECTION" /> | |||
<object-type name="TYPE" editor-type="SELECTION" /> | |||
</default-editors> | |||
</browser-settings> | |||
<navigation-settings> | |||
<lookup-filters> | |||
<lookup-objects> | |||
<object-type name="SCHEMA" enabled="true" /> | |||
<object-type name="USER" enabled="false" /> | |||
<object-type name="ROLE" enabled="false" /> | |||
<object-type name="PRIVILEGE" enabled="false" /> | |||
<object-type name="CHARSET" enabled="false" /> | |||
<object-type name="TABLE" enabled="true" /> | |||
<object-type name="VIEW" enabled="true" /> | |||
<object-type name="MATERIALIZED VIEW" enabled="true" /> | |||
<object-type name="INDEX" enabled="true" /> | |||
<object-type name="CONSTRAINT" enabled="true" /> | |||
<object-type name="DATASET TRIGGER" enabled="true" /> | |||
<object-type name="DATABASE TRIGGER" enabled="true" /> | |||
<object-type name="SYNONYM" enabled="false" /> | |||
<object-type name="SEQUENCE" enabled="true" /> | |||
<object-type name="PROCEDURE" enabled="true" /> | |||
<object-type name="FUNCTION" enabled="true" /> | |||
<object-type name="PACKAGE" enabled="true" /> | |||
<object-type name="TYPE" enabled="true" /> | |||
<object-type name="DIMENSION" enabled="false" /> | |||
<object-type name="CLUSTER" enabled="false" /> | |||
<object-type name="DBLINK" enabled="true" /> | |||
</lookup-objects> | |||
<force-database-load value="false" /> | |||
<prompt-connection-selection value="true" /> | |||
<prompt-schema-selection value="true" /> | |||
</lookup-filters> | |||
</navigation-settings> | |||
<dataset-grid-settings> | |||
<general> | |||
<enable-zooming value="true" /> | |||
<enable-column-tooltip value="true" /> | |||
</general> | |||
<sorting> | |||
<nulls-first value="true" /> | |||
<max-sorting-columns value="4" /> | |||
</sorting> | |||
<tracking-columns> | |||
<columnNames value="" /> | |||
<visible value="true" /> | |||
<editable value="false" /> | |||
</tracking-columns> | |||
</dataset-grid-settings> | |||
<dataset-editor-settings> | |||
<text-editor-popup> | |||
<active value="false" /> | |||
<active-if-empty value="false" /> | |||
<data-length-threshold value="100" /> | |||
<popup-delay value="1000" /> | |||
</text-editor-popup> | |||
<values-actions-popup> | |||
<show-popup-button value="true" /> | |||
<element-count-threshold value="1000" /> | |||
<data-length-threshold value="250" /> | |||
</values-actions-popup> | |||
<general> | |||
<fetch-block-size value="100" /> | |||
<fetch-timeout value="30" /> | |||
<trim-whitespaces value="true" /> | |||
<convert-empty-strings-to-null value="true" /> | |||
<select-content-on-cell-edit value="true" /> | |||
<large-value-preview-active value="true" /> | |||
</general> | |||
<filters> | |||
<prompt-filter-dialog value="true" /> | |||
<default-filter-type value="BASIC" /> | |||
</filters> | |||
<qualified-text-editor text-length-threshold="300"> | |||
<content-types> | |||
<content-type name="Text" enabled="true" /> | |||
<content-type name="Properties" enabled="true" /> | |||
<content-type name="XML" enabled="true" /> | |||
<content-type name="DTD" enabled="true" /> | |||
<content-type name="HTML" enabled="true" /> | |||
<content-type name="XHTML" enabled="true" /> | |||
<content-type name="Java" enabled="true" /> | |||
<content-type name="SQL" enabled="true" /> | |||
<content-type name="PL/SQL" enabled="true" /> | |||
<content-type name="JSON" enabled="true" /> | |||
<content-type name="JSON5" enabled="true" /> | |||
<content-type name="Groovy" enabled="true" /> | |||
<content-type name="AIDL" enabled="true" /> | |||
<content-type name="YAML" enabled="true" /> | |||
<content-type name="Manifest" enabled="true" /> | |||
</content-types> | |||
</qualified-text-editor> | |||
<record-navigation> | |||
<navigation-target value="VIEWER" /> | |||
</record-navigation> | |||
</dataset-editor-settings> | |||
<code-editor-settings> | |||
<general> | |||
<show-object-navigation-gutter value="false" /> | |||
<show-spec-declaration-navigation-gutter value="true" /> | |||
<enable-spellchecking value="true" /> | |||
<enable-reference-spellchecking value="false" /> | |||
</general> | |||
<confirmations> | |||
<save-changes value="false" /> | |||
<revert-changes value="true" /> | |||
</confirmations> | |||
</code-editor-settings> | |||
<code-completion-settings> | |||
<filters> | |||
<basic-filter> | |||
<filter-element type="RESERVED_WORD" id="keyword" selected="true" /> | |||
<filter-element type="RESERVED_WORD" id="function" selected="true" /> | |||
<filter-element type="RESERVED_WORD" id="parameter" selected="true" /> | |||
<filter-element type="RESERVED_WORD" id="datatype" selected="true" /> | |||
<filter-element type="RESERVED_WORD" id="exception" selected="true" /> | |||
<filter-element type="OBJECT" id="schema" selected="true" /> | |||
<filter-element type="OBJECT" id="role" selected="true" /> | |||
<filter-element type="OBJECT" id="user" selected="true" /> | |||
<filter-element type="OBJECT" id="privilege" selected="true" /> | |||
<user-schema> | |||
<filter-element type="OBJECT" id="table" selected="true" /> | |||
<filter-element type="OBJECT" id="view" selected="true" /> | |||
<filter-element type="OBJECT" id="materialized view" selected="true" /> | |||
<filter-element type="OBJECT" id="index" selected="true" /> | |||
<filter-element type="OBJECT" id="constraint" selected="true" /> | |||
<filter-element type="OBJECT" id="trigger" selected="true" /> | |||
<filter-element type="OBJECT" id="synonym" selected="false" /> | |||
<filter-element type="OBJECT" id="sequence" selected="true" /> | |||
<filter-element type="OBJECT" id="procedure" selected="true" /> | |||
<filter-element type="OBJECT" id="function" selected="true" /> | |||
<filter-element type="OBJECT" id="package" selected="true" /> | |||
<filter-element type="OBJECT" id="type" selected="true" /> | |||
<filter-element type="OBJECT" id="dimension" selected="true" /> | |||
<filter-element type="OBJECT" id="cluster" selected="true" /> | |||
<filter-element type="OBJECT" id="dblink" selected="true" /> | |||
</user-schema> | |||
<public-schema> | |||
<filter-element type="OBJECT" id="table" selected="false" /> | |||
<filter-element type="OBJECT" id="view" selected="false" /> | |||
<filter-element type="OBJECT" id="materialized view" selected="false" /> | |||
<filter-element type="OBJECT" id="index" selected="false" /> | |||
<filter-element type="OBJECT" id="constraint" selected="false" /> | |||
<filter-element type="OBJECT" id="trigger" selected="false" /> | |||
<filter-element type="OBJECT" id="synonym" selected="false" /> | |||
<filter-element type="OBJECT" id="sequence" selected="false" /> | |||
<filter-element type="OBJECT" id="procedure" selected="false" /> | |||
<filter-element type="OBJECT" id="function" selected="false" /> | |||
<filter-element type="OBJECT" id="package" selected="false" /> | |||
<filter-element type="OBJECT" id="type" selected="false" /> | |||
<filter-element type="OBJECT" id="dimension" selected="false" /> | |||
<filter-element type="OBJECT" id="cluster" selected="false" /> | |||
<filter-element type="OBJECT" id="dblink" selected="false" /> | |||
</public-schema> | |||
<any-schema> | |||
<filter-element type="OBJECT" id="table" selected="true" /> | |||
<filter-element type="OBJECT" id="view" selected="true" /> | |||
<filter-element type="OBJECT" id="materialized view" selected="true" /> | |||
<filter-element type="OBJECT" id="index" selected="true" /> | |||
<filter-element type="OBJECT" id="constraint" selected="true" /> | |||
<filter-element type="OBJECT" id="trigger" selected="true" /> | |||
<filter-element type="OBJECT" id="synonym" selected="true" /> | |||
<filter-element type="OBJECT" id="sequence" selected="true" /> | |||
<filter-element type="OBJECT" id="procedure" selected="true" /> | |||
<filter-element type="OBJECT" id="function" selected="true" /> | |||
<filter-element type="OBJECT" id="package" selected="true" /> | |||
<filter-element type="OBJECT" id="type" selected="true" /> | |||
<filter-element type="OBJECT" id="dimension" selected="true" /> | |||
<filter-element type="OBJECT" id="cluster" selected="true" /> | |||
<filter-element type="OBJECT" id="dblink" selected="true" /> | |||
</any-schema> | |||
</basic-filter> | |||
<extended-filter> | |||
<filter-element type="RESERVED_WORD" id="keyword" selected="true" /> | |||
<filter-element type="RESERVED_WORD" id="function" selected="true" /> | |||
<filter-element type="RESERVED_WORD" id="parameter" selected="true" /> | |||
<filter-element type="RESERVED_WORD" id="datatype" selected="true" /> | |||
<filter-element type="RESERVED_WORD" id="exception" selected="true" /> | |||
<filter-element type="OBJECT" id="schema" selected="true" /> | |||
<filter-element type="OBJECT" id="user" selected="true" /> | |||
<filter-element type="OBJECT" id="role" selected="true" /> | |||
<filter-element type="OBJECT" id="privilege" selected="true" /> | |||
<user-schema> | |||
<filter-element type="OBJECT" id="table" selected="true" /> | |||
<filter-element type="OBJECT" id="view" selected="true" /> | |||
<filter-element type="OBJECT" id="materialized view" selected="true" /> | |||
<filter-element type="OBJECT" id="index" selected="true" /> | |||
<filter-element type="OBJECT" id="constraint" selected="true" /> | |||
<filter-element type="OBJECT" id="trigger" selected="true" /> | |||
<filter-element type="OBJECT" id="synonym" selected="true" /> | |||
<filter-element type="OBJECT" id="sequence" selected="true" /> | |||
<filter-element type="OBJECT" id="procedure" selected="true" /> | |||
<filter-element type="OBJECT" id="function" selected="true" /> | |||
<filter-element type="OBJECT" id="package" selected="true" /> | |||
<filter-element type="OBJECT" id="type" selected="true" /> | |||
<filter-element type="OBJECT" id="dimension" selected="true" /> | |||
<filter-element type="OBJECT" id="cluster" selected="true" /> | |||
<filter-element type="OBJECT" id="dblink" selected="true" /> | |||
</user-schema> | |||
<public-schema> | |||
<filter-element type="OBJECT" id="table" selected="true" /> | |||
<filter-element type="OBJECT" id="view" selected="true" /> | |||
<filter-element type="OBJECT" id="materialized view" selected="true" /> | |||
<filter-element type="OBJECT" id="index" selected="true" /> | |||
<filter-element type="OBJECT" id="constraint" selected="true" /> | |||
<filter-element type="OBJECT" id="trigger" selected="true" /> | |||
<filter-element type="OBJECT" id="synonym" selected="true" /> | |||
<filter-element type="OBJECT" id="sequence" selected="true" /> | |||
<filter-element type="OBJECT" id="procedure" selected="true" /> | |||
<filter-element type="OBJECT" id="function" selected="true" /> | |||
<filter-element type="OBJECT" id="package" selected="true" /> | |||
<filter-element type="OBJECT" id="type" selected="true" /> | |||
<filter-element type="OBJECT" id="dimension" selected="true" /> | |||
<filter-element type="OBJECT" id="cluster" selected="true" /> | |||
<filter-element type="OBJECT" id="dblink" selected="true" /> | |||
</public-schema> | |||
<any-schema> | |||
<filter-element type="OBJECT" id="table" selected="true" /> | |||
<filter-element type="OBJECT" id="view" selected="true" /> | |||
<filter-element type="OBJECT" id="materialized view" selected="true" /> | |||
<filter-element type="OBJECT" id="index" selected="true" /> | |||
<filter-element type="OBJECT" id="constraint" selected="true" /> | |||
<filter-element type="OBJECT" id="trigger" selected="true" /> | |||
<filter-element type="OBJECT" id="synonym" selected="true" /> | |||
<filter-element type="OBJECT" id="sequence" selected="true" /> | |||
<filter-element type="OBJECT" id="procedure" selected="true" /> | |||
<filter-element type="OBJECT" id="function" selected="true" /> | |||
<filter-element type="OBJECT" id="package" selected="true" /> | |||
<filter-element type="OBJECT" id="type" selected="true" /> | |||
<filter-element type="OBJECT" id="dimension" selected="true" /> | |||
<filter-element type="OBJECT" id="cluster" selected="true" /> | |||
<filter-element type="OBJECT" id="dblink" selected="true" /> | |||
</any-schema> | |||
</extended-filter> | |||
</filters> | |||
<sorting enabled="true"> | |||
<sorting-element type="RESERVED_WORD" id="keyword" /> | |||
<sorting-element type="RESERVED_WORD" id="datatype" /> | |||
<sorting-element type="OBJECT" id="column" /> | |||
<sorting-element type="OBJECT" id="table" /> | |||
<sorting-element type="OBJECT" id="view" /> | |||
<sorting-element type="OBJECT" id="materialized view" /> | |||
<sorting-element type="OBJECT" id="index" /> | |||
<sorting-element type="OBJECT" id="constraint" /> | |||
<sorting-element type="OBJECT" id="trigger" /> | |||
<sorting-element type="OBJECT" id="synonym" /> | |||
<sorting-element type="OBJECT" id="sequence" /> | |||
<sorting-element type="OBJECT" id="procedure" /> | |||
<sorting-element type="OBJECT" id="function" /> | |||
<sorting-element type="OBJECT" id="package" /> | |||
<sorting-element type="OBJECT" id="type" /> | |||
<sorting-element type="OBJECT" id="dimension" /> | |||
<sorting-element type="OBJECT" id="cluster" /> | |||
<sorting-element type="OBJECT" id="dblink" /> | |||
<sorting-element type="OBJECT" id="schema" /> | |||
<sorting-element type="OBJECT" id="role" /> | |||
<sorting-element type="OBJECT" id="user" /> | |||
<sorting-element type="RESERVED_WORD" id="function" /> | |||
<sorting-element type="RESERVED_WORD" id="parameter" /> | |||
</sorting> | |||
<format> | |||
<enforce-code-style-case value="true" /> | |||
</format> | |||
</code-completion-settings> | |||
<execution-engine-settings> | |||
<statement-execution> | |||
<fetch-block-size value="100" /> | |||
<execution-timeout value="20" /> | |||
<debug-execution-timeout value="600" /> | |||
<focus-result value="false" /> | |||
<prompt-execution value="false" /> | |||
</statement-execution> | |||
<script-execution> | |||
<command-line-interfaces /> | |||
<execution-timeout value="300" /> | |||
</script-execution> | |||
<method-execution> | |||
<execution-timeout value="30" /> | |||
<debug-execution-timeout value="600" /> | |||
<parameter-history-size value="10" /> | |||
</method-execution> | |||
</execution-engine-settings> | |||
<operation-settings> | |||
<transactions> | |||
<uncommitted-changes> | |||
<on-project-close value="ASK" /> | |||
<on-disconnect value="ASK" /> | |||
<on-autocommit-toggle value="ASK" /> | |||
</uncommitted-changes> | |||
<multiple-uncommitted-changes> | |||
<on-commit value="ASK" /> | |||
<on-rollback value="ASK" /> | |||
</multiple-uncommitted-changes> | |||
</transactions> | |||
<session-browser> | |||
<disconnect-session value="ASK" /> | |||
<kill-session value="ASK" /> | |||
<reload-on-filter-change value="false" /> | |||
</session-browser> | |||
<compiler> | |||
<compile-type value="KEEP" /> | |||
<compile-dependencies value="ASK" /> | |||
<always-show-controls value="false" /> | |||
</compiler> | |||
<debugger> | |||
<debugger-type value="ASK" /> | |||
<use-generic-runners value="true" /> | |||
</debugger> | |||
</operation-settings> | |||
<ddl-file-settings> | |||
<extensions> | |||
<mapping file-type-id="VIEW" extensions="vw" /> | |||
<mapping file-type-id="TRIGGER" extensions="trg" /> | |||
<mapping file-type-id="PROCEDURE" extensions="prc" /> | |||
<mapping file-type-id="FUNCTION" extensions="fnc" /> | |||
<mapping file-type-id="PACKAGE" extensions="pkg" /> | |||
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" /> | |||
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" /> | |||
<mapping file-type-id="TYPE" extensions="tpe" /> | |||
<mapping file-type-id="TYPE_SPEC" extensions="tps" /> | |||
<mapping file-type-id="TYPE_BODY" extensions="tpb" /> | |||
</extensions> | |||
<general> | |||
<lookup-ddl-files value="true" /> | |||
<create-ddl-files value="false" /> | |||
<synchronize-ddl-files value="true" /> | |||
<use-qualified-names value="false" /> | |||
<make-scripts-rerunnable value="true" /> | |||
</general> | |||
</ddl-file-settings> | |||
<general-settings> | |||
<regional-settings> | |||
<date-format value="MEDIUM" /> | |||
<number-format value="UNGROUPED" /> | |||
<locale value="SYSTEM_DEFAULT" /> | |||
<use-custom-formats value="false" /> | |||
</regional-settings> | |||
<environment> | |||
<environment-types> | |||
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" /> | |||
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" /> | |||
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" /> | |||
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" /> | |||
</environment-types> | |||
<visibility-settings> | |||
<connection-tabs value="true" /> | |||
<dialog-headers value="true" /> | |||
<object-editor-tabs value="true" /> | |||
<script-editor-tabs value="false" /> | |||
<execution-result-tabs value="true" /> | |||
</visibility-settings> | |||
</environment> | |||
</general-settings> | |||
</component> | |||
<component name="DBNavigator.Project.StatementExecutionManager"> | |||
<execution-variables /> | |||
</component> | |||
</project> |
@@ -3,6 +3,7 @@ | |||
<component name="DesignSurface"> | |||
<option name="filePathToZoomLevelMap"> | |||
<map> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> | |||
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.22" /> | |||
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_fullscreen.xml" value="0.10144927536231885" /> | |||
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" /> | |||
@@ -10,7 +11,7 @@ | |||
</map> | |||
</option> | |||
</component> | |||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK"> | |||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK"> | |||
<output url="file://$PROJECT_DIR$/build/classes" /> | |||
</component> | |||
<component name="ProjectType"> |
@@ -12,6 +12,7 @@ | |||
<activity | |||
android:name=".module.splash.SplashActivity" | |||
android:configChanges="orientation|keyboardHidden|screenSize" | |||
android:screenOrientation="portrait" | |||
android:exported="true" | |||
android:theme="@style/Theme.Splash"> | |||
<intent-filter> |
@@ -1,46 +1,79 @@ | |||
package com.xkl.cdl.module.splash | |||
import android.annotation.SuppressLint | |||
import android.os.Bundle | |||
import android.os.Handler | |||
import android.os.Looper | |||
import android.os.Message | |||
import androidx.databinding.ObservableField | |||
import android.view.View | |||
import com.suliang.common.base.activity.BaseActivity | |||
import com.suliang.common.util.LogUtil | |||
import com.suliang.common.util.image.ImageLoader | |||
import com.suliang.common.util.media.* | |||
import com.suliang.common.util.media.MPManager.addPlayListener | |||
import com.suliang.common.util.thread.AppExecutors | |||
import com.xkl.cdl.R | |||
import com.xkl.cdl.databinding.ActivitySplashBinding | |||
@SuppressLint("CustomSplashScreen") | |||
class SplashActivity : BaseActivity<ActivitySplashBinding>() { | |||
val textvalue = ObservableField<String>() | |||
private val handler = object : Handler(Looper.getMainLooper()){ | |||
override fun handleMessage(msg: Message) { | |||
super.handleMessage(msg) | |||
binding.text.text = textvalue.get() | |||
} | |||
} | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
if(!isTaskRoot){ | |||
if (!isTaskRoot) { | |||
finish() | |||
return | |||
} | |||
super.onCreate(savedInstanceState) | |||
} | |||
var count = 1 | |||
override fun initActivity(savedInstanceState: Bundle?) { | |||
val x = Tvalue(textvalue) | |||
binding.name = x | |||
} | |||
handler.postDelayed(object : Runnable{ | |||
override fun run() { | |||
count ++ | |||
textvalue.set("$count") | |||
handler.postDelayed(this,1000) | |||
override fun loadData() { | |||
ImageLoader.loadImage(R.mipmap.ic_launcher) | |||
} | |||
val util:IMP = MPManager.apply { | |||
addPlayListener(object :IMPListener{ | |||
override fun onMpState(state: EMediaState) { | |||
LogUtil.e("SplashActivity 接收 ${Thread.currentThread() == Looper.getMainLooper().thread} ${Thread.currentThread()} --${state.name}") | |||
} | |||
},1000) | |||
}) | |||
} | |||
var position = 0 | |||
var seekToPosition = 1000 | |||
val source = listOf("http://ws.stream.qqmusic.qq.com/C4000017AcyB00D6Cr.m4a?guid=395745734&vkey=9CF75D8FDE45B1A7755C15F93C5F74CC18E444873F498DF3F5B9F0B41889778BE0EAC45305BD444F90D777C612BC5865DB0AF55A318854E1&uin=&fromtag=66" | |||
,"http://ws.stream.qqmusic.qq.com/C400001lePVO36SNSo.m4a?guid=748677131&vkey=A6077E31CB4A1C8A4F3D5D2BEDE8D3E16AB69FF2FEE95D9D48288EAD048AB89DC53B6846D184F842257617AF9714D97C864FB183BE05CC66&uin=&fromtag=66" | |||
,"http://ws.stream.qqmusic.qq.com/C400000ZFBf22vBvrf.m4a?guid=921100582&vkey=C9EA33972404E2AC1C80A479614AD44A9BF132A16F9B307D9515F595B41901AE53F6681E28BDA799FC047F30D624F8A19AE9C4856ADBEF4B&uin=&fromtag=66") | |||
fun play(view: View) { | |||
seekToPosition = 1000 | |||
util.play(source[0]) | |||
} | |||
fun continuePlay(view: View) { | |||
util.continuePlay() | |||
} | |||
fun stop(view: View) { | |||
util.stopPlay() | |||
} | |||
fun pause(view: View) { | |||
util.pausePlay() | |||
} | |||
fun seekTo(view: View) { | |||
util.seekTo(seekToPosition) | |||
seekToPosition += 30000 | |||
} | |||
fun release(view: View) { | |||
util.destroyPlay() | |||
} | |||
fun nextPlay(view: View) { | |||
var i = position + 1 | |||
if (i > 3){ | |||
i = 0 | |||
} | |||
position = i | |||
util.play(source[position]) | |||
} | |||
} | |||
data class Tvalue(val value : ObservableField<String>) |
@@ -4,9 +4,7 @@ | |||
xmlns:tools="http://schemas.android.com/tools"> | |||
<data> | |||
<variable | |||
name="name" | |||
type="com.xkl.cdl.module.splash.Tvalue" /> | |||
</data> | |||
<androidx.constraintlayout.widget.ConstraintLayout | |||
@@ -18,23 +16,91 @@ | |||
android:id="@+id/img" | |||
android:layout_width="match_parent" | |||
android:layout_height="0dp" | |||
android:src="@drawable/illustration_splash" | |||
app:layout_constraintBottom_toBottomOf="parent" | |||
app:layout_constraintDimensionRatio="1125:813" | |||
app:layout_constraintLeft_toLeftOf="parent" | |||
app:layout_constraintRight_toRightOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" | |||
app:layout_constraintBottom_toBottomOf="parent" | |||
app:layout_constraintVertical_bias="0.6" | |||
app:layout_constraintDimensionRatio="1125:813" | |||
android:src="@drawable/illustration_splash" | |||
/> | |||
<TextView | |||
android:id="@+id/text" | |||
app:layout_constraintVertical_bias="0.6" /> | |||
<Button | |||
android:id="@+id/button5" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:text="@={name.value}" | |||
android:textSize="18dp" | |||
app:layout_constraintBottom_toBottomOf="parent" | |||
android:layout_marginTop="8dp" | |||
android:text="下一曲" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/button" | |||
android:onClick="nextPlay"/> | |||
<Button | |||
android:id="@+id/button6" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:layout_marginStart="104dp" | |||
android:layout_marginTop="8dp" | |||
android:text="拖动" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintEnd_toEndOf="parent"/> | |||
app:layout_constraintTop_toBottomOf="@+id/button" | |||
android:onClick="seekTo"/> | |||
<Button | |||
android:id="@+id/button7" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:layout_marginStart="204dp" | |||
android:layout_marginTop="8dp" | |||
android:onClick="release" | |||
android:text="释放" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/button" /> | |||
<Button | |||
android:id="@+id/button" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:layout_marginStart="16dp" | |||
android:layout_marginTop="24dp" | |||
android:text="播放" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/img" | |||
android:onClick="play"/> | |||
<Button | |||
android:id="@+id/button2" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:layout_marginStart="24dp" | |||
android:layout_marginTop="24dp" | |||
android:text="暂停" | |||
app:layout_constraintStart_toEndOf="@+id/button" | |||
app:layout_constraintTop_toBottomOf="@+id/img" | |||
android:onClick="pause"/> | |||
<Button | |||
android:id="@+id/button3" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:layout_marginStart="8dp" | |||
android:layout_marginTop="24dp" | |||
android:text="停止" | |||
app:layout_constraintStart_toEndOf="@+id/button2" | |||
app:layout_constraintTop_toBottomOf="@+id/img" | |||
android:onClick="stop"/> | |||
<Button | |||
android:id="@+id/button4" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:layout_marginTop="24dp" | |||
android:text="继续" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintHorizontal_bias="0.555" | |||
app:layout_constraintStart_toEndOf="@+id/button3" | |||
app:layout_constraintTop_toBottomOf="@+id/img" | |||
android:onClick="continuePlay"/> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</layout> |
@@ -34,7 +34,8 @@ ext { | |||
core_ktx_version : "1.3.2", | |||
appcompat_version: "1.4.1", | |||
material_version : "1.3.0", | |||
lifecycle_version: "2.5.0-alpha02" | |||
lifecycle_version: "2.5.0-alpha02", | |||
glide_version : "4.1.0", | |||
] | |||
//必须依赖 | |||
dependencies_required = [ | |||
@@ -68,12 +69,10 @@ ext { | |||
dependencies_custom = [ | |||
//设置状态栏和导航栏的框架 https://github.com/Zackratos/UltimateBarX | |||
UltimateBarX: "com.gitee.zackratos:UltimateBarX:0.8.0", | |||
//Glide 依赖和配置 | |||
Glide : "com.github.bumptech.glide:glide:${versions.glide_version}", | |||
GlideCompiler : "com.github.bumptech.glide:compiler:${versions.glide_version}", | |||
] | |||
//注解 | |||
// dependencies_kapt = [ | |||
// lifecycle_compiler: "androidx.lifecycle:lifecycle-compiler:${versions.lifecycle_version}", | |||
// | |||
// ] | |||
} |
@@ -19,6 +19,12 @@ android { | |||
buildTypes { | |||
release { | |||
buildConfigField("Boolean","LOG_ENABLE","false") | |||
minifyEnabled false | |||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | |||
} | |||
debug { | |||
buildConfigField("Boolean","LOG_ENABLE","true") | |||
minifyEnabled false | |||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | |||
} | |||
@@ -40,39 +46,11 @@ dependencies { | |||
rootProject.ext.dependencies_required.each{ k,v -> implementation v} | |||
testImplementation rootProject.ext.dependencies_testImplementation.junit | |||
rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v} | |||
// api rootProject.ext.dependencies_custom.UltimateBarX | |||
// kapt rootProject.ext.dependencies_kapt.lifecycle_compiler | |||
def lifecycle_version = "2.5.0-alpha02" | |||
def arch_version = "2.1.0" | |||
//状态栏 | |||
api rootProject.ext.dependencies_custom.UltimateBarX | |||
//glide | |||
api rootProject.ext.dependencies_custom.Glide | |||
kapt rootProject.ext.dependencies_custom.GlideCompiler | |||
// // ViewModel | |||
// implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" | |||
// // ViewModel utilities for Compose | |||
// implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" | |||
// // LiveData | |||
// implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" | |||
// // Lifecycles only (without ViewModel or LiveData) | |||
// implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" | |||
// | |||
// // Saved state module for ViewModel | |||
// implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" | |||
// | |||
// // Annotation processor | |||
//// kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" | |||
// // alternately - if using Java8, use the following instead of lifecycle-compiler | |||
// implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" | |||
// // optional - helpers for implementing LifecycleOwner in a Service | |||
// implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version" | |||
// | |||
// // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process | |||
// implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" | |||
// | |||
// // optional - ReactiveStreams support for LiveData | |||
// implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version" | |||
// | |||
// // optional - Test helpers for LiveData | |||
// testImplementation "androidx.arch.core:core-testing:$arch_version" | |||
} |
@@ -1,5 +1,12 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |||
package="com.suliang.common"> | |||
<uses-permission android:name="android.permission.INTERNET"/> | |||
<!--networkSecurityConfig,usesCleartextTraffic 二选一都可以9.0-P以上高版本的http连接,否则连接失败 | |||
android:networkSecurityConfig="@xml/network_security_config" | |||
android:usesCleartextTraffic="true" | |||
--> | |||
<application | |||
android:networkSecurityConfig="@xml/network_security_config"/> | |||
</manifest> |
@@ -1,76 +1,82 @@ | |||
package com.suliang.common.base.activity | |||
import android.graphics.Color | |||
import android.os.Bundle | |||
import android.view.LayoutInflater | |||
import androidx.appcompat.app.AppCompatActivity | |||
import androidx.lifecycle.ViewModelProvider | |||
import androidx.lifecycle.ViewModelProviders | |||
import androidx.viewbinding.ViewBinding | |||
import com.suliang.common.extension.initBinding | |||
import com.suliang.common.util.ActivityStackManager | |||
import java.lang.reflect.ParameterizedType | |||
import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly | |||
/** | |||
* 基类Activity | |||
*/ | |||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() { | |||
abstract class BaseActivity<VB : ViewBinding> : LifecycleLogActivity() { | |||
lateinit var binding: VB | |||
private var _binding: VB? = null | |||
val binding : VB | |||
get() = _binding!! | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
super.onCreate(savedInstanceState) | |||
addStackManager() | |||
//入栈 | |||
ActivityStackManager.addActivity(this) | |||
initFirst() | |||
initActivity(savedInstanceState) } | |||
initActivity(savedInstanceState) | |||
loadData() | |||
} | |||
/*** | |||
* 实例化binding, | |||
*/ | |||
protected open fun initFirst() { | |||
setLayoutBefore() | |||
binding = initBinding(layoutInflater) | |||
_binding = initBinding(layoutInflater) | |||
setContentView(binding.root) | |||
initStatusBar() | |||
setLayoutAfter() | |||
} | |||
/** | |||
* 状态栏初始化方法 | |||
*/ | |||
open fun initStatusBar() { | |||
// statusBarOnly { | |||
// fitWindow = true | |||
// color = Color.WHITE | |||
// light = true | |||
// } | |||
statusBarOnly { | |||
fitWindow = true | |||
color = Color.WHITE | |||
light = true | |||
} | |||
} | |||
override fun onDestroy() { | |||
super.onDestroy() | |||
ActivityStackManager.removeActivity(this) | |||
} | |||
private fun addStackManager() { | |||
ActivityStackManager.addActivity(this) | |||
_binding = null | |||
} | |||
/** 布局前操作 : 空实现 */ | |||
public open fun setLayoutBefore() { | |||
open fun setLayoutBefore() { | |||
} | |||
/** | |||
* 布局后操作:空实现 | |||
*/ | |||
public open fun setLayoutAfter() { | |||
open fun setLayoutAfter() { | |||
} | |||
/** | |||
* onCreate中给与app中用户的具体实现 | |||
* binding 与 ViewModel都已实现好 | |||
* @param savedInstanceState Bundle? | |||
*/ | |||
abstract fun initActivity(savedInstanceState: Bundle?) | |||
/** onCreate()最后实现的数据加载调用 */ | |||
abstract fun loadData() | |||
// 反射实现 ViewBinding | |||
// @SuppressWarnings("unchecked") | |||
// fun initBinding(inflater: LayoutInflater): VB { | |||
// val vbClass = |
@@ -0,0 +1,57 @@ | |||
package com.suliang.common.base.activity | |||
import android.os.Bundle | |||
import androidx.appcompat.app.AppCompatActivity | |||
import com.suliang.common.util.LogUtil | |||
/** | |||
* author suliang | |||
* create 2022/3/7 10:47 | |||
* Describe: 打印生命周期的Activity | |||
*/ | |||
open class LifecycleLogActivity : AppCompatActivity() { | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
super.onCreate(savedInstanceState) | |||
LogUtil.d("onCreate()") | |||
} | |||
override fun onRestart() { | |||
super.onRestart() | |||
LogUtil.d("onRestart()") | |||
} | |||
override fun onStart() { | |||
super.onStart() | |||
LogUtil.d("onStart()") | |||
} | |||
override fun onResume() { | |||
super.onResume() | |||
LogUtil.d("onResume()") | |||
} | |||
override fun onPause() { | |||
super.onPause() | |||
LogUtil.d("onPause()") | |||
} | |||
override fun onStop() { | |||
super.onStop() | |||
LogUtil.d("onStop()") | |||
} | |||
override fun onDestroy() { | |||
super.onDestroy() | |||
LogUtil.d("onDestroy()") | |||
} | |||
override fun onSaveInstanceState(outState: Bundle) { | |||
super.onSaveInstanceState(outState) | |||
LogUtil.d("onSaveInstanceState()") | |||
} | |||
override fun onRestoreInstanceState(savedInstanceState: Bundle) { | |||
super.onRestoreInstanceState(savedInstanceState) | |||
LogUtil.d("onRestoreInstanceState()") | |||
} | |||
} |
@@ -7,20 +7,35 @@ import android.view.ViewGroup | |||
import androidx.databinding.ViewDataBinding | |||
import androidx.fragment.app.Fragment | |||
import com.suliang.common.extension.initBinding | |||
/** | |||
* Fragment ViewBinding基类,封装ViewDataBinding | |||
* Fragment ViewBinding基类,封装ViewDataBinding 自带懒加载 | |||
* | |||
* TabLayout+ViewPager懒加载: 关联使用 tabLayout.setupWithViewPager(ViewPager), | |||
* 适配器使用两参构造,第二参数使用:BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,否则会走setUserVisibleHint | |||
* | |||
* FragmentStatePagerAdapter会调用onSaveInstanceState方法来保存Fragment的状态 | |||
* | |||
* 通过add + show + hide 模式加载Fragment | |||
* | |||
* @property VB : ViewDataBinding | |||
* @property VM: ViewModel | |||
*/ | |||
abstract class BaseFragment<VB: ViewDataBinding>: Fragment(){ | |||
lateinit var binding: VB | |||
abstract class BaseFragment<VB : ViewDataBinding> : Fragment() { | |||
private var _binding: VB? = null | |||
val binding : VB | |||
get() = _binding!! | |||
//是否加载数据 | |||
private var isLoaded = false | |||
override fun onCreateView( | |||
inflater: LayoutInflater, | |||
container: ViewGroup?, | |||
savedInstanceState: Bundle? | |||
): View? { | |||
binding = initBinding(inflater,container) | |||
_binding = initBinding(inflater, container) | |||
return super.onCreateView(inflater, container, savedInstanceState) | |||
} | |||
@@ -30,7 +45,32 @@ abstract class BaseFragment<VB: ViewDataBinding>: Fragment(){ | |||
initFragment() | |||
} | |||
override fun onResume() { | |||
super.onResume() | |||
if (!isLoaded && !isHidden) { | |||
loadData() | |||
isLoaded = true | |||
} | |||
} | |||
override fun onDestroy() { | |||
super.onDestroy() | |||
_binding = null | |||
} | |||
/** | |||
* onViewCreated中进行的初始:多数时候可以是空实现,主要在使用VM时进行vm的初始 | |||
*/ | |||
abstract fun initFirst() | |||
/** | |||
* onViewCreated() 后进行的Fragment初始设置 | |||
*/ | |||
abstract fun initFragment() | |||
/** | |||
* 界面显示出来后再加载数据 | |||
*/ | |||
abstract fun loadData() | |||
} |
@@ -2,16 +2,18 @@ package com.suliang.common.base.fragment | |||
import androidx.databinding.ViewDataBinding | |||
import androidx.lifecycle.ViewModel | |||
/** | |||
* Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel | |||
* Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载 | |||
* @property VB : ViewDataBinding | |||
* @property VM: ViewModel | |||
*/ | |||
abstract class BaseFragmentVM<VB:ViewDataBinding,VM:ViewModel>:BaseFragment<VB>() { | |||
abstract class BaseFragmentVM<VB : ViewDataBinding, VM : ViewModel> : BaseFragment<VB>() { | |||
lateinit var viewModel: VM | |||
override fun initFirst() { | |||
viewModel = initViewModel() | |||
} | |||
abstract fun initViewModel():VM | |||
abstract fun initViewModel(): VM | |||
} |
@@ -0,0 +1,96 @@ | |||
package com.suliang.common.base.fragment | |||
import android.content.Context | |||
import android.os.Bundle | |||
import android.util.Log | |||
import android.view.LayoutInflater | |||
import android.view.View | |||
import android.view.ViewGroup | |||
import androidx.fragment.app.Fragment | |||
import com.suliang.common.util.LogUtil | |||
/** | |||
* author suliang | |||
* create 2022/3/7 14:14 | |||
* Describe: Fragment生命周期日志打印 | |||
*/ | |||
class LifecycleLogFragment : Fragment() { | |||
override fun onAttach(context: Context) { | |||
super.onAttach(context) | |||
LogUtil.d("onAttach") | |||
} | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
super.onCreate(savedInstanceState) | |||
LogUtil.d("onCreate") | |||
} | |||
override fun onCreateView( | |||
inflater: LayoutInflater, | |||
container: ViewGroup?, | |||
savedInstanceState: Bundle? | |||
): View? { | |||
LogUtil.d("onCreateView") | |||
return super.onCreateView(inflater, container, savedInstanceState) | |||
} | |||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | |||
super.onViewCreated(view, savedInstanceState) | |||
LogUtil.d("onViewCreated") | |||
} | |||
override fun onActivityCreated(savedInstanceState: Bundle?) { | |||
super.onActivityCreated(savedInstanceState) | |||
LogUtil.d("onActivityCreated") | |||
} | |||
override fun onStart() { | |||
super.onStart() | |||
LogUtil.d("onStart") | |||
} | |||
override fun onResume() { | |||
super.onResume() | |||
LogUtil.d("onResume") | |||
} | |||
override fun onStop() { | |||
super.onStop() | |||
LogUtil.d("onStop") | |||
} | |||
override fun onDestroyView() { | |||
super.onDestroyView() | |||
LogUtil.d("onDestroyView") | |||
} | |||
override fun onDetach() { | |||
super.onDetach() | |||
LogUtil.d("onDetach") | |||
} | |||
override fun onDestroy() { | |||
super.onDestroy() | |||
LogUtil.d("onDestroy") | |||
} | |||
override fun onHiddenChanged(hidden: Boolean) { | |||
super.onHiddenChanged(hidden) | |||
LogUtil.d("onHiddenChanged : hidden -> : $hidden" ) | |||
} | |||
override fun setUserVisibleHint(isVisibleToUser: Boolean) { | |||
super.setUserVisibleHint(isVisibleToUser) | |||
LogUtil.d("setUserVisibleHint: isVisibleToUser -> $isVisibleToUser ") | |||
} | |||
override fun onSaveInstanceState(outState: Bundle) { | |||
super.onSaveInstanceState(outState) | |||
LogUtil.d(javaClass.simpleName + " onSaveInstanceState()") | |||
} | |||
override fun onViewStateRestored(savedInstanceState: Bundle?) { | |||
super.onViewStateRestored(savedInstanceState) | |||
LogUtil.d(javaClass.simpleName + " onViewStateRestored()") | |||
} | |||
} |
@@ -1,10 +1,122 @@ | |||
package com.suliang.common.extension | |||
import androidx.annotation.IdRes | |||
import androidx.appcompat.app.AppCompatActivity | |||
import androidx.fragment.app.Fragment | |||
import androidx.fragment.app.FragmentActivity | |||
import androidx.fragment.app.FragmentManager | |||
import androidx.lifecycle.Lifecycle | |||
//inline fun <reified T : Fragment> AppCompatActivity.addFragment(@IdRes containerViewId:Int){ | |||
// supportFragmentManager.beginTransaction() | |||
// .add(containerViewId,newInstanceFragment<T>()).commit() | |||
//} | |||
/** | |||
* author suliang | |||
* create 2022/3/7 14:30 | |||
* Describe: 扩展: fragment的显示 隐藏 | |||
*/ | |||
/*----------------------------------Activity Start-------------------------------------*/ | |||
/** | |||
* Activity 级别 加载Fragment集合进入容器,并显示 | |||
* @receiver FragmentActivity | |||
* @param containerId Int | |||
* @param showPosition Int | |||
* @param fragment Array<out Fragment> | |||
*/ | |||
fun FragmentActivity.loadFragment(@IdRes containerId: Int,showPosition: Int = 0,vararg fragment: Fragment){ | |||
loadFragmentTransaction(containerId,showPosition,supportFragmentManager,*fragment) | |||
} | |||
/** | |||
* Activity 显示指定Fragment并隐藏其他Fragment | |||
* @receiver FragmentActivity | |||
* @param showFragment Fragment | |||
*/ | |||
fun FragmentActivity.showHideFragment(showFragment: Fragment){ | |||
showHideFragmentTransaction(supportFragmentManager,showFragment) | |||
} | |||
/*----------------------------------Activity End-------------------------------------*/ | |||
/*----------------------------------Fragment Start-------------------------------------*/ | |||
/** | |||
* Fragment 添加 子Fragment进入容器,并显示[showPosition]位置的fragment | |||
* @param containerId Int | |||
* @param showPosition Int | |||
* @param fragments Array<out Fragment> | |||
*/ | |||
fun Fragment.loadFragment(@IdRes containerId: Int,showPosition: Int,vararg fragments: Fragment){ | |||
loadFragmentTransaction(containerId,showPosition,childFragmentManager,*fragments) | |||
} | |||
/** | |||
* Fragment 显示指定的子Fragment | |||
* @receiver Fragment | |||
* @param showFragment Fragment | |||
*/ | |||
fun Fragment.showHideFragment(showFragment: Fragment){ | |||
showHideFragmentTransaction(childFragmentManager,showFragment) | |||
} | |||
/*----------------------------------Fragment End-------------------------------------*/ | |||
/** | |||
* 使用 add + show + hide 实现懒加载 | |||
* 默认显示[showPosition]位置的fragment,最大[Lifecycle]为[Lifecycle.State.RESUMED] | |||
* @param containerId Int 容器 | |||
* @param showPosition Int 显示位置 | |||
* @param supportFragmentManager FragmentManager | |||
* @param fragment Array<out Fragment> 集合 | |||
*/ | |||
private fun loadFragmentTransaction( | |||
@IdRes containerId: Int, | |||
showPosition: Int, | |||
fragmentManager: FragmentManager, | |||
vararg fragment : Fragment | |||
) { | |||
when(fragment.isNotEmpty()){ | |||
true -> { | |||
fragmentManager.beginTransaction().apply { | |||
fragment.forEachIndexed{ index, fragment -> | |||
//添加fragment,并设置有tag | |||
add(containerId,fragment,fragment::javaClass.name) | |||
//设置什么周期 | |||
when(index){ | |||
showPosition -> { | |||
setMaxLifecycle(fragment,Lifecycle.State.RESUMED) | |||
} | |||
else -> { | |||
hide(fragment) | |||
setMaxLifecycle(fragment,Lifecycle.State.STARTED) | |||
} | |||
} | |||
} | |||
}.commit() | |||
} | |||
else -> throw IllegalStateException("fragments must not empty!") | |||
} | |||
} | |||
/** | |||
* 显示指定Fragment且隐藏其他Fragment | |||
* @param fragmentManager FragmentManager | |||
* @param showFragment Fragment | |||
*/ | |||
private fun showHideFragmentTransaction(fragmentManager: FragmentManager, showFragment: Fragment) { | |||
fragmentManager.beginTransaction().apply { | |||
fragmentManager.fragments.forEach{ | |||
when(it){ | |||
showFragment -> { | |||
show(it) | |||
setMaxLifecycle(it,Lifecycle.State.RESUMED) | |||
} | |||
else -> { | |||
hide(it) | |||
setMaxLifecycle(it,Lifecycle.State.STARTED) | |||
} | |||
} | |||
} | |||
}.commit() | |||
} |
@@ -0,0 +1,58 @@ | |||
package com.suliang.common.util | |||
import android.util.Log | |||
import com.suliang.common.BuildConfig | |||
/** | |||
* author suliang | |||
* create 2022/3/7 10:55 | |||
* Describe: Log日志打印框架 | |||
*/ | |||
object LogUtil { | |||
@JvmStatic | |||
private val LogEnable = | |||
BuildConfig.LOG_ENABLE // 根据BuildConfig中的field LOG_ENABLE配置的值,确定是否打印 | |||
/** | |||
* 创建输出内容 | |||
* @param element StackTraceElement | |||
* @param message String | |||
* @return String | |||
*/ | |||
private fun createLog(element:StackTraceElement ,message : String) : String{ | |||
return StringBuffer().apply { | |||
append("====${element.methodName}() : ${element.lineNumber} ==== $message ====" ) | |||
}.toString() | |||
} | |||
fun v(message: String) { | |||
if (!LogEnable) return | |||
val stackTrace = Throwable().stackTrace[1] | |||
Log.v(stackTrace.fileName,createLog(stackTrace,message)) | |||
} | |||
fun d(message: String) { | |||
if (!LogEnable) return | |||
val stackTrace = Throwable().stackTrace[1] | |||
Log.d(stackTrace.fileName,createLog(stackTrace,message)) | |||
} | |||
fun e(message: String) { | |||
if (!LogEnable) return | |||
val stackTrace = Throwable().stackTrace[1] | |||
Log.e(stackTrace.fileName,createLog(stackTrace,message)) | |||
} | |||
fun i(message: String) { | |||
if (!LogEnable) return | |||
val stackTrace = Throwable().stackTrace[1] | |||
Log.i(stackTrace.fileName,createLog(stackTrace,message)) | |||
} | |||
fun w(message: String) { | |||
if (!LogEnable) return | |||
val stackTrace = Throwable().stackTrace[1] | |||
Log.w(stackTrace.fileName,createLog(stackTrace,message)) | |||
} | |||
} |
@@ -0,0 +1,464 @@ | |||
package com.suliang.common.util.file | |||
import android.os.Environment | |||
import com.suliang.common.util.AppGlobals | |||
import com.suliang.common.util.LogUtil | |||
import java.io.* | |||
import java.lang.Exception | |||
import java.text.DecimalFormat | |||
import java.util.zip.GZIPInputStream | |||
import java.util.zip.GZIPOutputStream | |||
/** | |||
* author suliang | |||
* create 2022/3/7 17:06 | |||
* Describe: File工具类 | |||
*/ | |||
object FileUtil { | |||
const val SIZETYPE_B = 1 // 获取文件大小单位为B的double值 | |||
const val SIZETYPE_KB = 2 // 获取文件大小单位为KB的double值 | |||
const val SIZETYPE_MB = 3 // 获取文件大小单位为MB的double值 | |||
const val SIZETYPE_GB = 4 // 获取文件大小单位为GB的double值 | |||
/** | |||
* 获取应用内置存储目录文件 | |||
*/ | |||
private fun getAppExternalFile(): File { | |||
val file = if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) { | |||
AppGlobals.application.getExternalFilesDir(null) | |||
} else { | |||
AppGlobals.application.filesDir | |||
} | |||
file?.let { | |||
if (it.exists()) file.mkdirs() | |||
} | |||
LogUtil.d("应用内置目录文件: ${file?.path}") | |||
return file ?: throw NullPointerException("Expression 'file' must not be null") | |||
} | |||
/** | |||
* 获取定义的用于保存的目录文件 [getAppExternalFile]/[saveDirName]/ | |||
* @param saveDirName String 保存的文件目录名称 | |||
* @return File | |||
*/ | |||
fun getSaveDirFile(saveDirName: String): File { | |||
return File(getAppExternalFile(), saveDirName).apply { | |||
if (!this.exists()) mkdirs() | |||
} | |||
} | |||
/** | |||
* 获取定义的用于保存的目录路径: [getAppExternalFile]/[saveDirName]/ | |||
* @param saveDirName String 保存的文件目录名称 | |||
* @return String | |||
*/ | |||
fun getSaveDirPath(saveDirName: String): String { | |||
return getSaveDirFile(saveDirName).path | |||
} | |||
/** | |||
* 创建一个新文件 | |||
* @param parentFile File 父文件 | |||
* @param fileName String 文件名 | |||
* @return File 创建的文件 | |||
*/ | |||
fun createNewFile(parentFile: File, fileName: String): File { | |||
if (!parentFile.exists()) parentFile.mkdirs() | |||
val newFile = File(parentFile, fileName) | |||
var createNewFile = false | |||
try { | |||
createNewFile = newFile.createNewFile() | |||
} catch (e: IOException) { | |||
e.printStackTrace() | |||
} | |||
LogUtil.d("创建的新文件结果:$createNewFile , 文件路径: ${newFile.path}") | |||
return newFile | |||
} | |||
/** | |||
* 创建一个新文件 | |||
* @param parentFilePath File 父文件路径 | |||
* @param fileName String 文件名 | |||
* @return File 创建的文件 | |||
*/ | |||
fun createNewFile(parentFilePath: String, fileName: String): File { | |||
return createNewFile(File(parentFilePath), fileName) | |||
} | |||
/** | |||
* 删除文件/文件夹 | |||
* @param filePath String 文件路径 | |||
*/ | |||
fun deleteFile(filePath: String) { | |||
deleteFile(File(filePath)) | |||
} | |||
/** | |||
* 删除文件/文件夹 | |||
* @param file File 被删除的文件 | |||
*/ | |||
fun deleteFile(file: File) { | |||
if (file.exists()) { | |||
synchronized(file) { | |||
if (file.isDirectory) { | |||
file.listFiles()?.forEach { | |||
if (file.isDirectory) { | |||
deleteFile(it) | |||
} else { | |||
it.delete() | |||
} | |||
} | |||
} | |||
file.delete() | |||
} | |||
} | |||
} | |||
/** | |||
* 将字节数组写入文件 | |||
* @param file File 被写入的文件 | |||
* @param content ByteArray 被写入的内容 | |||
*/ | |||
fun writeBytesToFile(file: File, content: ByteArray) { | |||
try { | |||
file.parentFile?.let { | |||
if (!it.exists()) { | |||
it.mkdirs() | |||
} | |||
} | |||
val inputStream = ByteArrayInputStream(content) | |||
val outputStream = FileOutputStream(file) | |||
writeStream(inputStream, outputStream) | |||
} catch (e: Exception) { | |||
LogUtil.e("文件写入失败: ${file.path}") | |||
} | |||
} | |||
/** | |||
* 将字节数组写入文件 | |||
* @param parentFile File 父文件 | |||
* @param fileName String 写入文件名称 | |||
* @param content ByteArray 内容 | |||
*/ | |||
fun writeBytesToFile(parentFile: File, fileName: String, content: ByteArray) { | |||
val file = File(parentFile, fileName) | |||
writeBytesToFile(file, content) | |||
} | |||
/** | |||
* 将字节数组写入文件 | |||
* @param parentFilePath String 父文件路径 | |||
* @param fileName String 写入文件名称 | |||
* @param content ByteArray 内容 | |||
*/ | |||
fun writeBytesToFile(parentFilePath: String, fileName: String, content: ByteArray) { | |||
val file = File(parentFilePath, fileName) | |||
writeBytesToFile(file, content) | |||
} | |||
/** | |||
* 写入方法 | |||
* @param inputStream InputStream 输入流 | |||
* @param outputStream OutputStream 输出流 | |||
*/ | |||
private fun writeStream(inputStream: InputStream, outputStream: OutputStream) { | |||
try { | |||
var len = 0 | |||
val buffer: ByteArray = ByteArray(1024) | |||
while ((inputStream.read(buffer).also { len = it }) != -1) { | |||
outputStream.write(buffer, 0, len) | |||
} | |||
} catch (e: Exception) { | |||
LogUtil.e("写入文件失败:${e.message}") | |||
e.printStackTrace() | |||
} finally { | |||
closeStream(inputStream) | |||
closeStream(outputStream) | |||
} | |||
} | |||
/** | |||
* 复制文件 | |||
* @param srcFile File 源文件 | |||
* @param destFile File 目标文件 | |||
* @param deleteSrcFile Boolean 是否需要删除源文件 | |||
*/ | |||
@Synchronized | |||
fun copyFile(srcFile: File, destFile: File, deleteSrcFile: Boolean): Boolean { | |||
//源文件部存在,直接返回false | |||
if (!srcFile.exists()) return false | |||
//目标文件父文件判断 | |||
destFile.parentFile?.let { | |||
if (!it.exists()) it.mkdirs() | |||
} | |||
try { | |||
val inputStream = FileInputStream(srcFile) | |||
val outputStream = FileOutputStream(destFile) | |||
writeStream(inputStream, outputStream) | |||
if (deleteSrcFile) srcFile.delete() | |||
return true | |||
} catch (e: Exception) { | |||
LogUtil.e("复制文件失败") | |||
e.printStackTrace() | |||
} | |||
return false | |||
} | |||
/** | |||
* 复制文件 | |||
* @param srcPath File 源文件路径 | |||
* @param destPath File 目标文件路径 | |||
* @param deleteSrcFile Boolean 是否需要删除源文件 | |||
*/ | |||
fun copyFile(srcPath: String, destPath: String, deleteSrcFile: Boolean): Boolean { | |||
return copyFile(File(srcPath), File(destPath), deleteSrcFile) | |||
} | |||
/** | |||
* 复制raw文件到本地 | |||
* @param rawId Int raw文件id | |||
* @param parentDirFile File 父文件 | |||
* @param copyFileName String 新文件名称 | |||
* @return Boolean 结果 | |||
*/ | |||
fun copyRaw(rawId: Int, parentDirFile: File, copyFileName: String): Boolean { | |||
try { | |||
if (!parentDirFile.exists()) parentDirFile.mkdirs() | |||
val inputStream = AppGlobals.application.resources.openRawResource(rawId) | |||
val outputStream = FileOutputStream(File(parentDirFile, copyFileName)) | |||
writeStream(inputStream, outputStream) | |||
} catch (e: Exception) { | |||
e.printStackTrace() | |||
return false | |||
} | |||
return true | |||
} | |||
/** | |||
* 复制asset文件进入本地 | |||
* @param assetName String | |||
* @param saveFile File | |||
* @return Boolean 结果 | |||
*/ | |||
fun copyAsset(assetName: String, saveFile: File): Boolean { | |||
saveFile.parentFile?.let { | |||
if (!it.exists()) it.mkdirs() | |||
} | |||
try { | |||
val inputStream = AppGlobals.application.assets.open(assetName) | |||
val outputStream = FileOutputStream(saveFile) | |||
writeStream(inputStream, outputStream) | |||
} catch (e: Exception) { | |||
e.printStackTrace() | |||
LogUtil.e("复制asset失败: $assetName") | |||
} | |||
return true | |||
} | |||
/** | |||
* 复制asset到本地 | |||
* @param assetName String asset名字 | |||
* @param parentFilePath String 父文件夹路径 | |||
* @param saveFileName String 复制的名称 默认为assetName | |||
* @return Boolean | |||
*/ | |||
fun copyAsset(assetName: String, parentFilePath: String, saveFileName: String = assetName): Boolean { | |||
return copyAsset(assetName, File(parentFilePath, saveFileName)) | |||
} | |||
/** | |||
* 读取文件 | |||
* @param file File 读的文件 | |||
* @return ByteArray 返回字节数组 | |||
*/ | |||
fun readFile(file: File): ByteArray { | |||
if (!file.exists()) return ByteArray(0) | |||
try { | |||
val fileInputStream = FileInputStream(file) | |||
val outputStream = ByteArrayOutputStream() | |||
var len = 0 | |||
var byteArray = ByteArray(1024) | |||
while (fileInputStream.read(byteArray).also { len = it } != -1) { | |||
outputStream.write(byteArray, 0, len) | |||
} | |||
byteArray = outputStream.toByteArray() | |||
closeStream(fileInputStream) | |||
closeStream(outputStream) | |||
return byteArray | |||
} catch (e: Exception) { | |||
LogUtil.e("读取文件是失败 : ${e.message}") | |||
e.printStackTrace() | |||
} | |||
return ByteArray(0) | |||
} | |||
/** | |||
* 关闭流 | |||
* @param io Closeable? | |||
*/ | |||
private fun closeStream(io: Closeable?) { | |||
io?.run { | |||
try { | |||
io.close() | |||
} catch (e: Exception) { | |||
e.printStackTrace() | |||
} | |||
} | |||
} | |||
// /** | |||
// TODO: 2022/3/8 此功能需要重新定义 进行版本适配 | |||
// * 保存图片到相册 | |||
// * @param context | |||
// * @param bitmap 图片 | |||
// * @param name 相册下保存的文件加名称 | |||
// * @return | |||
// */ | |||
// fun saveBitmapToDicm(context: Context, bitmap: Bitmap, name: String): String? { | |||
// //相册路径 | |||
// val parentfile = File( | |||
// Environment.getExternalStorageDirectory() | |||
// .toString() + File.separator + Environment.DIRECTORY_DCIM + File.separator + name | |||
// ) | |||
// if (!com.sl.lib_common.filecache.LibFileUtils.isExits(parentfile)) { | |||
// val mkdirs = parentfile.mkdirs() | |||
// } | |||
// var file: File? = null | |||
// var outputStream: FileOutputStream? = null | |||
// try { | |||
// file = File(parentfile, "IMG_" + System.currentTimeMillis() + ".jpg") | |||
// outputStream = FileOutputStream(file) | |||
// bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) | |||
// } catch (e: Exception) { | |||
// e.printStackTrace() | |||
// return null | |||
// } finally { | |||
// com.sl.lib_common.filecache.LibFileUtils.close(outputStream) | |||
// } | |||
// MediaStore.Images.Media.insertImage(context.contentResolver, bitmap, file!!.path, null) | |||
// val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) | |||
// val uri = Uri.fromFile(file) | |||
// intent.data = uri | |||
// context.sendBroadcast(intent) | |||
// return file.path | |||
// } | |||
/** | |||
* 字符串的GZip压缩 | |||
* @param str 待压缩的字符串 | |||
* @return 返回压缩后的字符串 | |||
* @throws IOException | |||
*/ | |||
@Throws(IOException::class) | |||
fun compressGZip(str: String): String { | |||
if (str.isEmpty()) return str | |||
// 创建一个新的输出流 | |||
val out = ByteArrayOutputStream() | |||
// 使用默认缓冲区大小创建新的输出流 | |||
val gzip = GZIPOutputStream(out) | |||
// 将字节写入此输出流 | |||
gzip.write(str.toByteArray(charset("utf-8"))) // 因为后台默认字符集有可能是GBK字符集,所以此处需指定一个字符集 | |||
// 使用指定的 charsetName,通过解码字节将缓冲区内容转换为字符串 | |||
val result = out.toString("ISO-8859-1") | |||
closeStream(gzip) | |||
closeStream(out) | |||
return result | |||
} | |||
/** | |||
* 字符串的GZip解压 | |||
* @param str 对字符串解压 | |||
* @return 返回解压缩后的字符串 | |||
* @throws IOException | |||
*/ | |||
@Throws(IOException::class) | |||
fun unCompressGZip(str: String): String { | |||
if (str.isEmpty()) return str | |||
// 创建一个新的输出流 | |||
val out = ByteArrayOutputStream() | |||
// 创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组 | |||
val inputStream = ByteArrayInputStream(str.toByteArray(charset("ISO-8859-1"))) | |||
// 使用默认缓冲区大小创建新的输入流 | |||
val gzip = GZIPInputStream(inputStream) | |||
val buffer = ByteArray(256) | |||
var n = 0 | |||
// 将未压缩数据读入字节数组 | |||
while (gzip.read(buffer).also { n = it } >= 0) { | |||
out.write(buffer, 0, n) | |||
} | |||
// 使用指定的 charsetName,通过解码字节将缓冲区内容转换为字符串 | |||
val result = out.toString("utf-8") | |||
closeStream(inputStream) | |||
closeStream(out) | |||
return result | |||
} | |||
@Throws(Exception::class) | |||
fun objectToBytes(data: Any): ByteArray { | |||
var oos: ObjectOutputStream? = null | |||
return try { | |||
val bos = ByteArrayOutputStream() | |||
oos = ObjectOutputStream(bos) | |||
oos.writeObject(data) | |||
bos.toByteArray() | |||
} finally { | |||
oos?.close() | |||
} | |||
} | |||
/** | |||
* 获取文件大小 | |||
* @param file File 需要获取大小的文件 | |||
* @return Long | |||
*/ | |||
fun getFileSize(file: File): Long { | |||
var result = 0L | |||
if (!file.exists()) return 0 | |||
if (file.isDirectory) { | |||
file.listFiles()?.forEach { | |||
result += getFileSize(it) | |||
} | |||
}else{ | |||
result += file.length() | |||
} | |||
return result | |||
} | |||
/* private fun getFileSize(file: File) : Int{ | |||
if (file.exists()){ | |||
LogUtil.e("文件不存在: $file") | |||
return 0 | |||
} | |||
val fileInputStream = FileInputStream(file) | |||
val size = fileInputStream.available() | |||
closeStream(fileInputStream) | |||
return size | |||
}*/ | |||
/** | |||
* 获取文件大小,默认保留两位小数,没有两位自动补0,为四舍五入模式 | |||
* @param file File 文件 | |||
* @param sizeType String 格式 B、KB、MB、GB | |||
* @return String 结果 | |||
*/ | |||
fun getFileSize(file: File, sizeType:String) : String { | |||
val fileSize = getFileSize(file) | |||
return when(sizeType.uppercase()){ | |||
"B" -> DecimalFormat("#.00").format(fileSize) | |||
"KB" -> DecimalFormat("#.00").format(fileSize/1024.0) | |||
"MB" -> DecimalFormat("#.00").format(fileSize/1048576.0) | |||
"GB" -> DecimalFormat("#.00").format(fileSize/1073741824.0) | |||
else -> "too large" | |||
} | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
package com.suliang.common.util.image | |||
import java.lang.Exception | |||
/** | |||
* author suliang | |||
* create 2022/3/11 16:14 | |||
* Describe: Glide 加载的具体实现 | |||
* https://juejin.cn/post/6844903463134953479#heading-3 | |||
*/ | |||
internal class GlideLoader: ILoaderStrategy { | |||
override fun loadImage(option: LoaderOptions) { | |||
when{ | |||
option.targetView == null -> throw Exception("Glide Option targetView is not empty") | |||
} | |||
} | |||
override fun clearMemoryCache() { | |||
} | |||
override fun clearDiskCache() { | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
package com.suliang.common.util.image | |||
import android.net.Uri | |||
import android.widget.ImageView | |||
import java.io.File | |||
/** | |||
* author suliang | |||
* create 2022/3/11 15:45 | |||
* Describe: 策略接口 | |||
*/ | |||
interface ILoaderStrategy { | |||
fun loadImage(option : LoaderOptions) | |||
/** | |||
* 清理内存缓存 | |||
*/ | |||
fun clearMemoryCache() | |||
/** | |||
* 清理磁盘缓存 | |||
*/ | |||
fun clearDiskCache() | |||
// fun getMemoryCacheSize() | |||
// | |||
// fun getDiskCacheSize() | |||
} |
@@ -0,0 +1,63 @@ | |||
package com.suliang.common.util.image | |||
import android.content.Context | |||
import android.net.Uri | |||
import android.widget.ImageView | |||
import androidx.annotation.DrawableRes | |||
import java.io.File | |||
import java.net.URL | |||
/** | |||
* author suliang | |||
* create 2022/3/11 16:10 | |||
* Describe: 外部调用,图片加载库 | |||
* | |||
* 使用: ImageLoader.load(url).into(View) | |||
* | |||
* 顺序判断,自动限制,前面参数有值了,则后面的值无效,如果都没有值,则抛异常 | |||
* url > file > drawableRes > uri | |||
* | |||
* https://blog.csdn.net/Coo123_/article/details/87950232 | |||
* https://juejin.cn/post/6844903463134953479#heading-3 | |||
* https://www.jianshu.com/p/19420b151507 | |||
* https://blog.csdn.net/EthanCo/article/details/102587193 | |||
*/ | |||
object ImageLoader : ILoaderStrategy { | |||
//具体实现的策略,外部可设置 | |||
private var mStrategy: ILoaderStrategy = GlideLoader() | |||
public fun setMStrategy(strategy: ILoaderStrategy) { | |||
this.mStrategy = mStrategy | |||
} | |||
fun loadImage(imageView: ImageView, url: String) { | |||
loadImage(LoaderOptions(url).apply { targetView = imageView }) | |||
} | |||
fun loadImage(imageView: ImageView, file: File) { | |||
loadImage( LoaderOptions(file = file).apply { targetView = imageView }) | |||
} | |||
fun loadImage(imageView: ImageView, drawableRes: Int) { | |||
loadImage(LoaderOptions(drawableResId = drawableRes).apply { targetView = imageView }) | |||
} | |||
fun loadImage(imageView: ImageView, uri: Uri) { | |||
loadImage(LoaderOptions(uri = uri).apply { targetView = imageView }) | |||
} | |||
override fun loadImage(option: LoaderOptions) { | |||
mStrategy.loadImage(option) | |||
} | |||
override fun clearMemoryCache() { | |||
mStrategy.clearMemoryCache() | |||
} | |||
override fun clearDiskCache() { | |||
mStrategy.clearDiskCache() | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
package com.suliang.common.util.image | |||
import android.graphics.drawable.Drawable | |||
import android.net.Uri | |||
import android.widget.ImageView | |||
import androidx.annotation.IdRes | |||
import java.io.File | |||
import java.net.URL | |||
/** | |||
* author suliang | |||
* create 2022/3/11 15:45 | |||
* Describe: 加载配置 | |||
* 提取通用的View、加载路径等多参数 | |||
*/ | |||
class LoaderOptions @JvmOverloads constructor(var url: String? = null, var file: File? = null, | |||
var drawableResId: Int = -1, var uri: Uri? = null) { | |||
/** 预加载图 */ | |||
var placeholderResId: Int = -1 | |||
var placeHolderDrawable: Drawable? = null | |||
/** 错误图 */ | |||
var errorResId: Int = -1 | |||
/**是否居中裁剪*/ | |||
var centerCropEnable: Boolean = false | |||
/** 居中全显 */ | |||
var centerInsideEnable: Boolean = false | |||
/**是否缓存到本地*/ | |||
var skipLocalCache: Boolean = false | |||
var skipNetCache: Boolean = false | |||
var targetWidth: Int = -1 | |||
var targetHeight: Int = -1 | |||
//圆角角度 | |||
var bitmapAngle: Float = -1f | |||
//图片 | |||
var targetView: ImageView? = null | |||
//回调 | |||
// var callBack: BitmapCallBack? = null | |||
// fun into(view: ImageView) { | |||
// this.targetView = view | |||
// ImageLoader.loadImage(this) | |||
// } | |||
// fun bitmap(callBack: BitmapCallBack) { | |||
// this.callBack = callBack | |||
// ImageLoader.loadOption(this) | |||
// } | |||
} |
@@ -0,0 +1,28 @@ | |||
package com.suliang.common.util.media | |||
/** | |||
* author suliang | |||
* create 2022/3/8 16:41 | |||
* Describe: MediaPlayer定义的状态 | |||
*/ | |||
enum class EMediaState { | |||
/**空置状态:设置了监听挺,mediaPlayer进行了初始化,但没有设置数据 */ | |||
IDLE, | |||
/** 初始状态: 在idle后,设置了数据源的状态 */ | |||
INITIALIZED , | |||
/** 准备状态:设置数据源后,调用了 prepareAsync()方法 */ | |||
PREPARING, | |||
/** 准备状态:可以开始播放了 */ | |||
PREPARED , | |||
/** 调用了播放,进入运行状态 */ | |||
RUNNING, | |||
/** 暂停 */ | |||
PAUSE, | |||
/** 停止 */ | |||
STOP, | |||
/** 播放完成状态 */ | |||
COMPLETE, | |||
/** 错误状态 */ | |||
ERROR | |||
} |
@@ -0,0 +1,76 @@ | |||
package com.suliang.common.util.media | |||
/** | |||
* author suliang | |||
* create 2022/3/8 16:21 | |||
* Describe: MediaPlayer操作接口 | |||
*/ | |||
interface IMP { | |||
/** | |||
* 开始播放,从指定位置开始 | |||
* @param url String 本地路径或网络url | |||
* @param position Int 指定位置 , 默认为0 | |||
* @param listener 默认为空 | |||
*/ | |||
fun play(url: String, position: Int = 0,listener: IMPListener? = null) | |||
/** 暂停播放 ,如果没有播放文件则会忽略*/ | |||
fun pausePlay() | |||
/** 继续播放,如果没有播放文件则会忽略 */ | |||
fun continuePlay() | |||
/** 停止播放,清空当前播放信息,如果没有播放文件信息,则忽略 */ | |||
fun stopPlay() | |||
/** 释放销毁*/ | |||
fun destroyPlay() | |||
/** | |||
* 拖动到指定位置播放 | |||
* @param targetPosition Int | |||
*/ | |||
fun seekTo(targetPosition : Int) | |||
/**是否支持seekTo */ | |||
fun seekToEnable() : Boolean | |||
/** | |||
* 获取播放路径 | |||
* @return String | |||
*/ | |||
fun getPlayPath(): String | |||
/** | |||
* 获取总时间 | |||
* @return Long | |||
*/ | |||
fun getDuration(): Int | |||
/** | |||
* 获取当前播放位置 | |||
* @return Long | |||
*/ | |||
fun getCurrentPosition(): Int | |||
/** 是否播放中 */ | |||
fun isPlaying(): Boolean | |||
/** | |||
* 获取当前播放状态 | |||
* @return MediaPlayerStatus | |||
*/ | |||
fun getMediaState(): EMediaState | |||
/** 添加监听 */ | |||
fun addPlayListener(listener: IMPListener) | |||
/** 移除监听 */ | |||
fun removePlayListener() | |||
} |
@@ -0,0 +1,14 @@ | |||
package com.suliang.common.util.media | |||
/** | |||
* author suliang | |||
* create 2022/3/8 16:53 | |||
* Describe: 播放监听 | |||
*/ | |||
interface IMPListener { | |||
/** | |||
* MediaPlayer的状态监听 | |||
* @param state EMediaStatu | |||
*/ | |||
fun onMpState(state : EMediaState) | |||
} |
@@ -0,0 +1,71 @@ | |||
package com.suliang.common.util.media | |||
/** | |||
* author suliang | |||
* create 2022/3/10 17:20 | |||
* Describe: MediaPlayer管理类 | |||
* 不要直接调用MPUtil | |||
* | |||
*/ | |||
object MPManager:IMP { | |||
private val mpUtil : IMP = MPUtil() | |||
override fun play(url: String, position: Int, listener: IMPListener?) { | |||
mpUtil.play(url,position,listener) | |||
} | |||
override fun pausePlay() { | |||
mpUtil.pausePlay() | |||
} | |||
override fun continuePlay() { | |||
mpUtil.continuePlay() | |||
} | |||
override fun stopPlay() { | |||
mpUtil.stopPlay() | |||
} | |||
override fun destroyPlay() { | |||
mpUtil.destroyPlay() | |||
} | |||
override fun seekTo(targetPosition: Int) { | |||
mpUtil.seekTo(targetPosition) | |||
} | |||
override fun seekToEnable(): Boolean { | |||
return mpUtil.seekToEnable() | |||
} | |||
override fun getPlayPath(): String { | |||
return mpUtil.getPlayPath() | |||
} | |||
override fun getDuration(): Int { | |||
return mpUtil.getDuration() | |||
} | |||
override fun getCurrentPosition(): Int { | |||
return mpUtil.getCurrentPosition() | |||
} | |||
override fun isPlaying(): Boolean { | |||
return mpUtil.isPlaying() | |||
} | |||
override fun getMediaState(): EMediaState { | |||
return mpUtil.getMediaState() | |||
} | |||
override fun addPlayListener(listener: IMPListener) { | |||
mpUtil.addPlayListener(listener) | |||
} | |||
override fun removePlayListener() { | |||
mpUtil.removePlayListener() | |||
} | |||
} |
@@ -0,0 +1,338 @@ | |||
package com.suliang.common.util.media | |||
import android.media.MediaPlayer | |||
import android.os.Handler | |||
import android.os.Looper | |||
import com.suliang.common.util.LogUtil | |||
import kotlin.concurrent.thread | |||
/** | |||
* author suliang | |||
* create 2022/3/8 17:04 | |||
* Describe: MediaPlayer播放器 | |||
* 正常使用:先调用play 退出时需释放 | |||
* seekTo 播放时,可直接使用, 暂停时,需在回调后调用start | |||
* internal 模块内可见 | |||
* | |||
*/ | |||
internal class MPUtil : IMP,MediaPlayer.OnPreparedListener,MediaPlayer.OnErrorListener,MediaPlayer.OnCompletionListener,MediaPlayer.OnInfoListener,MediaPlayer.OnSeekCompleteListener{ | |||
private var mediaPlayer: MediaPlayer? = null //播放器 | |||
private var urlPath: String? = null //播放路径 | |||
private var listener: IMPListener? = null //播放监听器 | |||
private var currentState: EMediaState = EMediaState.ERROR //播放状态 | |||
private var mTargetPosition = -1 //拖动位置 | |||
private fun MediaPlayer.resetPlayer() { | |||
setOnPreparedListener(null) | |||
setOnErrorListener(null) | |||
setOnCompletionListener(null) | |||
setOnInfoListener(null) | |||
setOnSeekCompleteListener(null) | |||
mTargetPosition = -1 | |||
try { | |||
reset() | |||
} catch (e: Exception) { | |||
LogUtil.e("MediaPlayer reset释放异常 $currentState") | |||
e.printStackTrace() | |||
} | |||
} | |||
override fun play(url: String, position: Int,listener: IMPListener?) { | |||
listener?.let { | |||
this.listener = it | |||
} | |||
if (url.isEmpty()) { | |||
handleError("url路径为空") | |||
return | |||
} | |||
//初始化MediaPlayer | |||
initMedia() | |||
urlPath = url | |||
//拖动到开始进行播放 | |||
seekTo(0) | |||
} | |||
/** | |||
* MediaPlayer初始化,设置监听 | |||
*/ | |||
private fun initMedia() { | |||
mediaPlayer?.resetPlayer() ?: let { | |||
it.mediaPlayer = MediaPlayer() | |||
} | |||
mediaPlayer?.apply { | |||
setOnPreparedListener(mOnPreparedLister) | |||
setOnErrorListener(this@MPUtil) | |||
setOnCompletionListener(this@MPUtil) | |||
setOnInfoListener(this@MPUtil) | |||
setOnSeekCompleteListener(this@MPUtil) | |||
} | |||
currentState = EMediaState.IDLE | |||
handleListener() | |||
urlPath = null | |||
} | |||
/** mediaPlayer的seekTo 完成后有监听回调,在播放中会跳到指定地方,暂停时跳到指定位置后需手动调用播放 | |||
* 1、initMedia后调用该方法 | |||
* 2、在外部直接调用该方法(必须是播放或暂停状态才有效果) | |||
* */ | |||
override fun seekTo(targetPosition: Int) { | |||
//路径错误,返回报错 | |||
if (urlPath.isNullOrEmpty()) { | |||
handleError("seek url路径为空") | |||
return | |||
} | |||
//修改定位标志 | |||
mTargetPosition = if (targetPosition < 0) 0 else targetPosition | |||
//根据状态开始操作 | |||
if (currentState == EMediaState.IDLE) { //空闲状态,开始设置资源 | |||
try { | |||
mediaPlayer?.setDataSource(urlPath) //设置资源 | |||
currentState = EMediaState.INITIALIZED //idle -> init | |||
handleListener() | |||
} catch (e: Exception) { | |||
handleError("MediaPlayer设置资源失败") | |||
e.printStackTrace() | |||
return | |||
} | |||
} | |||
when (currentState) { | |||
EMediaState.INITIALIZED -> { // 设置好资源了,开始播放准备 | |||
currentState = EMediaState.PREPARING | |||
handleListener() | |||
mediaPlayer?.prepareAsync() | |||
} | |||
EMediaState.PREPARED -> { //准备好了,进行操作 | |||
handlePrepared() | |||
} | |||
EMediaState.RUNNING, EMediaState.PAUSE -> { //播放或暂停时,直接跳到指定位置 | |||
mediaPlayer?.seekTo(mTargetPosition) // 拖动播放(播放或暂停时重新检查状态处理) | |||
} | |||
else -> { | |||
handleError("seekTo时状态异常" ) | |||
} | |||
} | |||
} | |||
private val mOnPreparedLister: MediaPlayer.OnPreparedListener = MediaPlayer.OnPreparedListener { | |||
} | |||
/** 异步准备完毕 ,开始播放*/ | |||
private fun handlePrepared() { | |||
if (currentState == EMediaState.PREPARED) { //修改为播放状态,调用播放 | |||
LogUtil.i("准备完成,开始播放") | |||
mediaPlayer?.start() | |||
currentState = EMediaState.RUNNING | |||
handleListener() | |||
checkSeekPlay() | |||
} else { | |||
handleError("状态异常,这里只能是 PREPARED 而现在是:$currentState") | |||
} | |||
} | |||
// private val mOnSeekCompleteListener: MediaPlayer.OnSeekCompleteListener = MediaPlayer.OnSeekCompleteListener { | |||
// LogUtil.i("mOnSeekCompleteListener 回调") | |||
// handleSeekComplete() | |||
// } | |||
// | |||
// | |||
// private val mOnCompleteListener: MediaPlayer.OnCompletionListener = MediaPlayer.OnCompletionListener { | |||
// handleComplete() | |||
// } | |||
// | |||
// | |||
// private val mOnErrorListener: MediaPlayer.OnErrorListener = MediaPlayer.OnErrorListener { _, what, extra -> | |||
// handleError("mOnErrorListener 异常调用 $extra") | |||
// true | |||
// } | |||
// | |||
// private val mOnInfoListener: MediaPlayer.OnInfoListener = MediaPlayer.OnInfoListener { mp, what, extra -> | |||
// false | |||
// } | |||
private fun handleSeekComplete() { | |||
when (currentState) { | |||
EMediaState.PREPARED -> handlePrepared() | |||
EMediaState.RUNNING,EMediaState.PAUSE -> checkSeekPlay() | |||
else -> { | |||
handleError("seekComplete state error : ${currentState.name}") | |||
} | |||
} | |||
} | |||
private fun checkSeekPlay() { | |||
mediaPlayer?.let { | |||
//拖动只有播放中才有效 | |||
if (currentState == EMediaState.RUNNING || currentState == EMediaState.PAUSE) { | |||
if (mTargetPosition >= 0) { //如果拖动大于等于0 | |||
it.seekTo(mTargetPosition) | |||
mTargetPosition = -1 | |||
} | |||
if (!it.isPlaying) { //如果暂停,则开始播放 | |||
it.start() | |||
} | |||
} else { | |||
handleError("checkSeekPlay 非播放状态") | |||
} | |||
} | |||
} | |||
private fun handleError(errorMessage: String) { | |||
LogUtil.e(errorMessage) | |||
currentState = EMediaState.ERROR | |||
handleListener() | |||
} | |||
override fun pausePlay() { | |||
mediaPlayer?.let { | |||
if (currentState == EMediaState.RUNNING) { | |||
try { | |||
it.pause() | |||
currentState = EMediaState.PAUSE | |||
handleListener() | |||
} catch (e: Exception) { | |||
e.printStackTrace() | |||
handleError("暂停播放异常调用") | |||
} | |||
} | |||
} | |||
} | |||
override fun continuePlay() { | |||
mediaPlayer?.let { | |||
when (currentState) { | |||
EMediaState.PAUSE -> { | |||
try { | |||
it.start() | |||
currentState = EMediaState.RUNNING | |||
handleListener() | |||
} catch (e: Exception) { | |||
handleError("mp.start()异常 ${e.message}") | |||
e.printStackTrace() | |||
} | |||
} | |||
else ->{ | |||
handleError("继续播放异常调用") | |||
} | |||
} | |||
} | |||
} | |||
override fun stopPlay() { | |||
mediaPlayer?.let { | |||
if (currentState != EMediaState.STOP) { | |||
try { | |||
it.stop() | |||
currentState = EMediaState.STOP | |||
handleListener() | |||
} catch (e: Exception) { | |||
handleError("mp.stop()异常 ${e.message}") | |||
e.printStackTrace() | |||
} | |||
} else { | |||
handleError("停止播放状态异常 ${currentState.name}") | |||
} | |||
} | |||
} | |||
override fun destroyPlay() { | |||
mediaPlayer?.let { | |||
when(currentState){ | |||
EMediaState.RUNNING,EMediaState.PAUSE,EMediaState.COMPLETE -> it.stop() | |||
else -> {} | |||
} | |||
it.release() | |||
} | |||
removePlayListener() | |||
mediaPlayer = null | |||
} | |||
override fun seekToEnable(): Boolean { | |||
return true | |||
} | |||
override fun getPlayPath(): String { | |||
return urlPath ?: "" | |||
} | |||
override fun getDuration(): Int { | |||
return mediaPlayer?.duration ?: -1 | |||
} | |||
override fun getCurrentPosition(): Int { | |||
return mediaPlayer?.currentPosition ?: -1 | |||
} | |||
override fun isPlaying(): Boolean { | |||
return mediaPlayer?.isPlaying ?: false | |||
} | |||
override fun getMediaState(): EMediaState { | |||
return currentState | |||
} | |||
override fun addPlayListener(listener: IMPListener) { | |||
this.listener = listener | |||
} | |||
override fun removePlayListener() { | |||
listener = null | |||
} | |||
/** 状态回调 默认状态回调是在调用MediaPlayer的线程 | |||
* */ | |||
private fun handleListener() { | |||
val temp = currentState | |||
synchronized(temp) { | |||
listener?.let { | |||
LogUtil.e("MPUtil发送 --》 ${Thread.currentThread()}") | |||
Handler(Looper.getMainLooper()).post { | |||
LogUtil.e("MPUtil发送 --》 ${temp.name}") | |||
it.onMpState(temp) | |||
} | |||
} | |||
} | |||
} | |||
override fun onPrepared(mp: MediaPlayer?) { | |||
LogUtil.i("mOnPreparedLister 回调") | |||
if (currentState == EMediaState.INITIALIZED || currentState == EMediaState.PREPARING) { | |||
currentState = EMediaState.PREPARED | |||
handlePrepared() | |||
} else { | |||
handleError("mOnPreparedLister 状态异常") | |||
} | |||
} | |||
override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { | |||
handleError("mOnErrorListener 异常调用 $extra") | |||
return true | |||
} | |||
override fun onCompletion(mp: MediaPlayer?) { | |||
currentState = EMediaState.COMPLETE | |||
handleListener() | |||
} | |||
override fun onInfo(mp: MediaPlayer?, what: Int, extra: Int): Boolean { | |||
return false | |||
} | |||
override fun onSeekComplete(mp: MediaPlayer?) { | |||
when (currentState) { | |||
EMediaState.PREPARED -> handlePrepared() | |||
EMediaState.RUNNING,EMediaState.PAUSE -> checkSeekPlay() | |||
else -> { | |||
handleError("seekComplete state error : ${currentState.name}") | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
package com.suliang.common.util.os | |||
import android.app.Activity | |||
import android.app.AppOpsManager | |||
import android.content.Context | |||
import android.content.Intent | |||
import android.net.Uri | |||
import android.os.Binder | |||
import android.provider.Settings | |||
import java.lang.Exception | |||
/** | |||
* author suliang | |||
* create 2022/3/8 15:52 | |||
* Describe: 悬浮框工具 | |||
*/ | |||
class FloatingWindowUtil { | |||
/** | |||
* 检查悬浮框权限 | |||
* @param context Context 上下文 | |||
* @return Boolean 是否开启了悬浮框权限 | |||
*/ | |||
fun checkOverLayerPermission(context: Context): Boolean { | |||
return if (VersionUtil.versionMorLater()) Settings.canDrawOverlays(context) else checkOp(context, 24) | |||
} | |||
private fun checkOp(context: Context, op: Int): Boolean { | |||
val manager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager | |||
try { | |||
val method = | |||
AppOpsManager::class.java.getDeclaredMethod("checkOp", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java) | |||
return AppOpsManager.MODE_ALLOWED == method.invoke(manager, op, Binder.getCallingUid(), context.packageName) as Int | |||
} catch (e: Exception) { | |||
e.printStackTrace() | |||
} | |||
return false | |||
} | |||
/** | |||
* 跳转打开悬浮窗权限设置界面 | |||
* @param activity Activity | |||
*/ | |||
fun applyOverLayerPermission(activity: Activity) { | |||
if (VersionUtil.versionMorLater()) { | |||
val intent = Intent() | |||
intent.action = Settings.ACTION_MANAGE_OVERLAY_PERMISSION | |||
intent.data = Uri.parse("package:" + activity.packageName) | |||
activity.startActivityForResult(intent, 0) | |||
} else { | |||
val intent = Intent() | |||
intent.action = Settings.ACTION_MANAGE_OVERLAY_PERMISSION | |||
intent.data = Uri.fromParts("package", activity.packageName, null) | |||
activity.startActivityForResult(intent, 0) | |||
} | |||
} | |||
} |
@@ -0,0 +1,70 @@ | |||
package com.suliang.common.util.os | |||
import android.content.Context | |||
import android.content.Intent | |||
import android.net.Uri | |||
/** | |||
* Created by 苏亮 on 2017/11/28. | |||
* Intent启动封装工具 | |||
*/ | |||
object IntentUtils { | |||
/** | |||
* 打开系统浏览器 | |||
*/ | |||
fun startOsWebView(context: Context, url: String?) { | |||
val uri = Uri.parse(url) | |||
val intent = Intent(Intent.ACTION_VIEW, uri) | |||
context.startActivity(intent) | |||
} | |||
// /** | |||
// * 发送广播让相册扫描文件进入相册 | |||
// */ | |||
// fun mediaScannerFile(file: File?, mContext: Context) { | |||
// val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) | |||
// val uri = Uri.fromFile(file) | |||
// intent.setData(uri) | |||
// mContext.sendBroadcast(intent) | |||
// } | |||
// | |||
// fun cropImageUri(activity: Activity, path: String?, outputX: Int, outputY: Int, aspectX: Int, aspectY: Int, | |||
// requestCode: Int): File { | |||
// val intent = Intent("com.android.camera.action.CROP") | |||
// val file = File(path) | |||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | |||
// val dataUri: Uri = LibFileUtils.getImageContentUri(file, activity.getApplicationContext()) | |||
// intent.setDataAndType(dataUri, "image/*") | |||
// } else { | |||
// intent.setDataAndType(Uri.fromFile(file), "image/*") | |||
// } | |||
// intent.putExtra("crop", "true") | |||
// intent.putExtra("aspectX", aspectX) | |||
// intent.putExtra("aspectY", aspectY) | |||
// intent.putExtra("outputX", outputX) | |||
// intent.putExtra("outputY", outputY) | |||
// intent.putExtra("scale", true) | |||
// val outFile: File = File(LibFileUtils.getSaveDir(activity.getApplicationContext(), "crop"), "crop.jpg") | |||
// outFile.delete() | |||
// val outUri = Uri.fromFile(outFile) | |||
// intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri) | |||
// intent.putExtra("return-data", false) | |||
// intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) | |||
// intent.putExtra("noFaceDetection", true) | |||
// activity.startActivityForResult(intent, requestCode) | |||
// | |||
///* | |||
// UCrop.Options options = new UCrop.Options(); | |||
// options.setStatusBarColor(Color.BLACK); | |||
// | |||
// UCrop.of(Uri.fromFile(file), Uri.fromFile(outFile)) | |||
// .withOptions() | |||
// .withAspectRatio(aspectX, aspectY) | |||
// .withMaxResultSize(outputX, outputY) | |||
// .start(activity,requestCode);*/return outFile | |||
// } | |||
// | |||
// fun cropImageUri(activity: Activity, path: String?, outputX: Int, outputY: Int, requestCode: Int): File { | |||
// return cropImageUri(activity, path, outputX, outputY, 1, 1, requestCode) | |||
// } | |||
} |
@@ -0,0 +1,32 @@ | |||
package com.suliang.common.util.os | |||
import android.content.Context | |||
import android.view.View | |||
import android.view.inputmethod.InputMethodManager | |||
/** | |||
* author suliang | |||
* create 2022/3/8 15:26 | |||
* Describe: 键盘工具 | |||
*/ | |||
object KeyboardUtil { | |||
/** | |||
* 显示软键盘 | |||
* @param view View | |||
*/ | |||
fun showKeyboard(view:View){ | |||
val inputMethodManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager | |||
view.requestFocus() | |||
inputMethodManager.showSoftInput(view,0) | |||
} | |||
/** | |||
* 隐藏软键盘 | |||
* @param view View | |||
*/ | |||
fun hideKeyboard(view: View){ | |||
val inputMethodManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager | |||
inputMethodManager.hideSoftInputFromWindow(view.windowToken,0) | |||
view.clearFocus() | |||
} | |||
} |
@@ -0,0 +1,142 @@ | |||
package com.suliang.common.util.os | |||
import android.annotation.TargetApi | |||
import android.app.Activity | |||
import android.content.Context | |||
import android.content.res.Resources | |||
import android.os.Build | |||
import android.util.DisplayMetrics | |||
import android.view.ViewConfiguration | |||
import android.view.WindowManager | |||
import com.suliang.common.util.AppGlobals | |||
import com.suliang.common.util.LogUtil | |||
import java.lang.Exception | |||
/** | |||
* author suliang | |||
* create 2022/3/8 15:41 | |||
* Describe: 屏幕工具 | |||
*/ | |||
object ScreenUtil { | |||
private var screenWidth = 0 | |||
private var screenHeight = 0 | |||
/** | |||
* 获取屏幕宽度 | |||
* @return | |||
*/ | |||
fun getScreenWidth(): Int { | |||
if (screenWidth == 0) { | |||
screenWidth = AppGlobals.application.resources.displayMetrics.widthPixels | |||
} | |||
return screenWidth | |||
} | |||
/** | |||
* 获取屏幕高度 | |||
* @return | |||
*/ | |||
fun getScreenHeight(): Int { | |||
if (screenHeight == 0) { | |||
screenHeight = AppGlobals.application.resources.displayMetrics.heightPixels | |||
} | |||
return screenHeight | |||
} | |||
/** 获取状态栏高度 */ | |||
fun getStatusBarHeight(): Int { | |||
var statusBarHeight = -1 | |||
val resources: Resources = AppGlobals.application.resources | |||
//获取status_bar_height的资源Id | |||
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") | |||
if (resourceId > 0) { | |||
//根据资源Id获取相应的尺寸 | |||
statusBarHeight = resources.getDimensionPixelSize(resourceId) | |||
} | |||
LogUtil.d(" 状态栏高度 navigationBarHeight = $statusBarHeight") | |||
return statusBarHeight | |||
} | |||
/** | |||
* 获取虚拟按键的高度 | |||
*/ | |||
fun getNavigationBarHeight(): Int { | |||
var result = 0 | |||
if (hasNavBar(AppGlobals.application)) { | |||
val res: Resources = AppGlobals.application.resources | |||
val resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android") | |||
if (resourceId > 0) { | |||
result = res.getDimensionPixelSize(resourceId) | |||
} | |||
} | |||
LogUtil.d(" 虚拟按键高度 navigationBarHeight = $result") | |||
return result | |||
} | |||
/** | |||
* 检查是否存在虚拟按键栏 | |||
*/ | |||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) | |||
private fun hasNavBar(context: Context): Boolean { | |||
val res = context.resources | |||
val resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android") | |||
return if (resourceId != 0) { | |||
var hasNav = res.getBoolean(resourceId) | |||
val sNavBarOverride = getNavBarOverride() | |||
if ("1" == sNavBarOverride) { | |||
hasNav = false | |||
} else if ("0" == sNavBarOverride) { | |||
hasNav = true | |||
} | |||
hasNav | |||
} else { | |||
!ViewConfiguration.get(context).hasPermanentMenuKey() | |||
} | |||
} | |||
/** | |||
* 判断虚拟按键栏是否重写 | |||
*/ | |||
private fun getNavBarOverride(): String? { | |||
var sNavBarOverride: String? = null | |||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |||
try { | |||
val c = Class.forName("android.os.SystemProperties") | |||
val m = c.getDeclaredMethod("get", String::class.java) | |||
m.isAccessible = true | |||
sNavBarOverride = m.invoke(null, "qemu.hw.mainkeys") as String | |||
} catch (e: Exception) { | |||
e.printStackTrace() | |||
} | |||
} | |||
return sNavBarOverride | |||
} | |||
/** | |||
* dp2px | |||
*/ | |||
fun dp2px(dpValue: Float): Int { | |||
val scale = AppGlobals.application.resources.displayMetrics.density | |||
return (dpValue * scale + 0.5f).toInt() | |||
} | |||
/** | |||
* 屏幕变暗 alpha 0.3 | |||
* @param activity Activity | |||
*/ | |||
fun lightOff(activity: Activity) { | |||
val lp = activity.window.attributes | |||
lp.alpha = 0.3f | |||
activity.window.attributes = lp | |||
} | |||
/** | |||
* 屏幕变亮 alpha 1 | |||
* @param activity Activity | |||
*/ | |||
fun lightOn(activity: Activity) { | |||
val lp = activity.window.attributes | |||
lp.alpha = 1f | |||
activity.window.attributes = lp | |||
} | |||
} |
@@ -0,0 +1,122 @@ | |||
package com.suliang.common.util.os | |||
import android.app.Activity | |||
import android.content.Context | |||
import android.content.Intent | |||
import android.os.Build | |||
import android.content.res.Configuration | |||
import android.net.Uri | |||
import android.provider.Settings | |||
import androidx.annotation.RequiresApi | |||
import androidx.core.content.FileProvider | |||
import com.suliang.common.util.AppGlobals | |||
import com.suliang.common.util.LogUtil | |||
import java.io.File | |||
/** | |||
* author suliang | |||
* create 2022/3/7 15:06 | |||
* Describe: 系统工具 | |||
*/ | |||
object SystemUtil { | |||
/** | |||
* 判断当前设备时手机还是平板 | |||
* @param context Context 上下文 | |||
* @return Boolean pad: true , phone: false | |||
*/ | |||
fun belongToPadOrPhone(context: Context): Boolean { | |||
val b = | |||
context.resources.configuration.screenLayout.and(Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE | |||
LogUtil.d("是否是平板设备: $b") | |||
return b | |||
} | |||
/** 安装 */ | |||
fun install(context: Context, mFilePath: String?) { | |||
val intent = Intent(Intent.ACTION_VIEW) | |||
if (VersionUtil.versionOorLater()) { | |||
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION //添加这一句表示对目标应用临时授权该Uri所代表的文件 | |||
val apkUri = FileProvider.getUriForFile( | |||
context, | |||
"com.xuekaole.education.camera", | |||
File(mFilePath) | |||
) | |||
intent.setDataAndType(apkUri, "application/vnd.android.package-archive") | |||
} else { | |||
intent.setDataAndType( | |||
Uri.fromFile(File(mFilePath)), | |||
"application/vnd.android.package-archive" | |||
) | |||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK | |||
} | |||
context.startActivity(intent) | |||
} | |||
@RequiresApi(api = Build.VERSION_CODES.O) | |||
fun isHasInstallPermissionWithO(context: Context?): Boolean { | |||
return context?.packageManager?.canRequestPackageInstalls() ?: false | |||
} | |||
/** | |||
* 开启设置安装未知来源应用权限界面 | |||
* | |||
* @param context | |||
*/ | |||
@RequiresApi(api = Build.VERSION_CODES.O) | |||
fun startInstallPermissionSettingActivity(context: Context?) { | |||
if (context == null) { | |||
return | |||
} | |||
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) | |||
(context as Activity).startActivityForResult(intent, 100) | |||
} | |||
/** | |||
* 安装Apk 注8.0以下,8.0以上需要获取权限 | |||
*/ | |||
fun installApk(mContext: Context, path: String) { | |||
val install = Intent(Intent.ACTION_VIEW) | |||
val file = File(path) | |||
if (VersionUtil.versionOorLater()) { | |||
val apkUri = | |||
FileProvider.getUriForFile(mContext, "com.xuekaole.education.download", file) | |||
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) //添加这一句表示对目标应用临时授权该Uri所代表的文件 | |||
install.setDataAndType(apkUri, "application/vnd.android.package-archive") | |||
} else { | |||
install.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive") | |||
install.flags = Intent.FLAG_ACTIVITY_NEW_TASK | |||
} | |||
mContext.startActivity(install) | |||
} | |||
/** 打开系统通知设置权限 */ | |||
fun skipToNotifySetting(activity: Activity) { | |||
val intent = Intent() | |||
if (VersionUtil.versionOorLater()) { | |||
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS" | |||
intent.putExtra( | |||
"android.provider.extra.APP_PACKAGE", | |||
VersionUtil.getPackageName() | |||
) | |||
} else if (VersionUtil.versionLorLater()) { | |||
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS" | |||
intent.putExtra("app_package", VersionUtil.getPackageName()) | |||
intent.putExtra("app_uid", activity.applicationInfo.uid) | |||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { | |||
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS | |||
intent.addCategory(Intent.CATEGORY_DEFAULT) | |||
intent.data = Uri.parse("package:" + VersionUtil.getPackageName()) | |||
} else if (Build.VERSION.SDK_INT >= 15) { | |||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | |||
intent.action = "android.settings.APPLICATION_DETAILS_SETTINGS" | |||
intent.data = | |||
Uri.fromParts("package", VersionUtil.getPackageName(), null) | |||
} | |||
activity.startActivityForResult(intent, 10000) | |||
} | |||
} |
@@ -0,0 +1,107 @@ | |||
package com.suliang.common.util.os | |||
import android.os.Build | |||
import androidx.annotation.RequiresApi | |||
import com.suliang.common.util.AppGlobals | |||
/** | |||
* author suliang | |||
* create 2022/3/7 16:03 | |||
* Describe: | |||
*/ | |||
object VersionUtil { | |||
/** | |||
* Android12 S 31 和 以后的版本 | |||
* @receiver Build | |||
* @return Boolean | |||
*/ | |||
fun versionSorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S | |||
/** | |||
* Android11 R 30 和 以后的版本 | |||
* @receiver Build | |||
* @return Boolean | |||
*/ | |||
fun versionRorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R | |||
/** | |||
* Android10 Q 29和 以后的版本 | |||
* @receiver Build | |||
* @return Boolean | |||
*/ | |||
fun versionQorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q | |||
/** | |||
* Android9 P 28和 以后的版本 | |||
* @receiver Build | |||
* @return Boolean | |||
*/ | |||
fun versionPorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P | |||
/** | |||
* Android8 O 26 27 和 以后的版本 | |||
* @receiver Build | |||
* @return Boolean | |||
*/ | |||
fun versionOorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O | |||
/** | |||
* Android7.0 N 24 和 以后的版本 | |||
* @receiver Build | |||
* @return Boolean | |||
*/ | |||
fun versionNorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N | |||
/** | |||
* Android6.0 M 23 和 以后的版本 | |||
* @receiver Build | |||
* @return Boolean | |||
*/ | |||
fun versionMorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M | |||
/** | |||
* Android5.0 21和 以后的版本 | |||
* @receiver Build | |||
* @return Boolean | |||
*/ | |||
fun versionLorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP | |||
/** | |||
* 获取versionName | |||
* @return String | |||
*/ | |||
fun getVersionName(): String { | |||
return AppGlobals.application.packageManager.getPackageInfo( | |||
AppGlobals.application.packageName, | |||
0 | |||
).versionName | |||
} | |||
/** | |||
* 获取versionCode | |||
* @return Long | |||
*/ | |||
fun getVersionCode(): Long { | |||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | |||
AppGlobals.application.packageManager.getPackageInfo( | |||
AppGlobals.application.packageName, | |||
0 | |||
).longVersionCode | |||
} else { | |||
AppGlobals.application.packageManager.getPackageInfo( | |||
AppGlobals.application.packageName, | |||
0 | |||
).versionCode.toLong() | |||
} | |||
} | |||
fun getPackageName(): String { | |||
return AppGlobals.application.packageManager.getPackageInfo( | |||
AppGlobals.application.packageName, | |||
0 | |||
).packageName | |||
} | |||
} |
@@ -0,0 +1,86 @@ | |||
package com.suliang.common.util.thread | |||
import java.util.concurrent.* | |||
/** | |||
* author suliang | |||
* create 2022/3/11 9:56 | |||
* Describe: 线程池 | |||
* 参考url : https://blog.csdn.net/weixin_43115440/article/details/90479752 | |||
* newFixedThreadPool(int) : 创建固定数目线程的线程池 | |||
* newCacheThreadPool() : 创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。 | |||
* newSingleThreadExecutor():创建一个单线程化的Executor。 | |||
* newScheduledThreadPool(int corePoolSize):创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。 | |||
* | |||
* FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM | |||
* CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 | |||
* | |||
*/ | |||
object AppExecutors { | |||
/* 磁盘IO线程池 和磁盘操作有关的进行使用此线程(如独写数据库,独写文件) 禁止延迟,避免等待,此线程池不用考虑同步问题 */ | |||
val diskIO: ExecutorService = Executors.newSingleThreadExecutor() | |||
/** 网络线程池 网络请求、异步任务等适用此线程 不建议在此线程 sleep 或者 wait */ | |||
val netWork: ExecutorService = Executors.newCachedThreadPool() | |||
/**主线程*/ | |||
val mainThread: Executor = MainThreadExecutor() | |||
/**定时任务线程池 替代Timer,执行定时任务、延时任务 * */ | |||
val scheduledExecutor: ScheduledExecutorService = | |||
ScheduledThreadPoolExecutor(5, ThreadFactoryImp("executors_scheduled"), ThreadPoolExecutor.AbortPolicy()) | |||
/* private static ScheduledExecutorService scheduledThreadPoolExecutor(){ | |||
1-- new ScheduledThreadPoolExecutor(5, new MyThreadFactory("scheduled"), new ThreadPoolExecutor.AbortPolicy()) ; | |||
2-- return new ScheduledThreadPoolExecutor(16, new ThreadFactory() { | |||
@Override | |||
public Thread newThread(Runnable r) { | |||
return new Thread(r, "scheduled_executor"); | |||
} | |||
}, new RejectedExecutionHandler() { | |||
@Override | |||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { | |||
CommonLog.e(TAG,"rejectedExecution: scheduled executor queue overflow"); | |||
} | |||
}); | |||
}*/ | |||
/* | |||
private static ExecutorService diskIoExecutor(){ | |||
Executors.newSingleThreadExecutor(new MyThreadFactory("single")) ; | |||
return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>(1024), new ThreadFactory() { | |||
@Override | |||
public Thread newThread(Runnable r) { | |||
return new Thread(r, "disk_executor"); | |||
} | |||
}, new RejectedExecutionHandler() { | |||
@Override | |||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { | |||
CommonLog.e(TAG,"rejectedExecution: diskIo executor queue overflow"); | |||
} | |||
}); | |||
} | |||
private static ExecutorService netWorkExecutor(){ | |||
Executors.newFixedThreadPool(3, new MyThreadFactory("fixed")); | |||
return new ThreadPoolExecutor(3, 6, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(6), new ThreadFactory() { | |||
@Override | |||
public Thread newThread(Runnable r) { | |||
return new Thread(r, "network_executor"); | |||
} | |||
}, new RejectedExecutionHandler() { | |||
@Override | |||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { | |||
CommonLog.e(TAG,"rejectedExecution: network executor queue overflow"); | |||
} | |||
}); | |||
} | |||
*/ | |||
} |
@@ -0,0 +1,23 @@ | |||
package com.suliang.common.util.thread | |||
import android.os.Handler | |||
import android.os.Looper | |||
import java.util.concurrent.Executor | |||
/** | |||
* author suliang | |||
* create 2022/3/11 11:31 | |||
* Describe: | |||
*/ | |||
internal class MainThreadExecutor : Executor { | |||
companion object{ | |||
private val mainThreadHandler = Handler(Looper.getMainLooper()) | |||
} | |||
override fun execute(command: Runnable?) { | |||
command?.let { | |||
mainThreadHandler.post(command) | |||
} | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
package com.suliang.common.util.thread | |||
import java.util.concurrent.ThreadFactory | |||
import java.util.concurrent.atomic.AtomicLong | |||
/** | |||
* author suliang | |||
* create 2022/3/11 10:41 | |||
* Describe: 创建线程工具类 | |||
*/ | |||
internal class ThreadFactoryImp @JvmOverloads constructor(val threadNamePrefix:String, var daemon:Boolean = false): ThreadFactory { | |||
companion object{ | |||
private val threadIndex : AtomicLong = AtomicLong(0) | |||
} | |||
override fun newThread(r: Runnable?): Thread { | |||
return Thread(r,"$threadNamePrefix-thread-${threadIndex.incrementAndGet()}").apply { | |||
isDaemon = daemon | |||
} | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<network-security-config xmlns:tools="http://schemas.android.com/tools" | |||
xmlns:android="http://schemas.android.com/apk/res/android"> | |||
<base-config cleartextTrafficPermitted = "true" | |||
tools:ignore="InsecureBaseConfiguration" /> | |||
</network-security-config> |