first commit
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
||||||
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
1
.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
|||||||
|
WallpaperGallery
|
||||||
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
440
.idea/dbnavigator.xml
generated
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DBNavigator.Project.DDLFileAttachmentManager">
|
||||||
|
<mappings />
|
||||||
|
<preferences />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.DatabaseAssistantManager">
|
||||||
|
<assistants />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.DatabaseFileManager">
|
||||||
|
<open-files />
|
||||||
|
</component>
|
||||||
|
<component name="DBNavigator.Project.Settings">
|
||||||
|
<connections />
|
||||||
|
<browser-settings>
|
||||||
|
<general>
|
||||||
|
<display-mode value="TABBED" />
|
||||||
|
<navigation-history-size value="100" />
|
||||||
|
<show-object-details value="false" />
|
||||||
|
<enable-sticky-paths value="true" />
|
||||||
|
<enable-quick-filters 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="JSON_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="JAVA_CLASS" enabled="true" />
|
||||||
|
<object-type name="JAVA_FIELD" enabled="true" />
|
||||||
|
<object-type name="JAVA_METHOD" enabled="true" />
|
||||||
|
<object-type name="JAVA_RESOURCE" enabled="true" />
|
||||||
|
<object-type name="DIMENSION" enabled="true" />
|
||||||
|
<object-type name="CLUSTER" enabled="true" />
|
||||||
|
<object-type name="DBLINK" enabled="true" />
|
||||||
|
<object-type name="CREDENTIAL" enabled="true" />
|
||||||
|
<object-type name="AI_PROFILE" 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" />
|
||||||
|
<object-type name="TYPE ATTRIBUTE" 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="JSON 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="JAVA CLASS" enabled="true" />
|
||||||
|
<object-type name="INNER CLASS" enabled="true" />
|
||||||
|
<object-type name="JAVA FIELD" enabled="true" />
|
||||||
|
<object-type name="JAVA METHOD" enabled="true" />
|
||||||
|
<object-type name="JAVA PARAMETER" enabled="true" />
|
||||||
|
<object-type name="JAVA RESOURCE" enabled="true" />
|
||||||
|
<object-type name="DIMENSION" enabled="false" />
|
||||||
|
<object-type name="CLUSTER" enabled="false" />
|
||||||
|
<object-type name="DBLINK" enabled="false" />
|
||||||
|
<object-type name="CREDENTIAL" enabled="false" />
|
||||||
|
</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>
|
||||||
|
<audit-columns>
|
||||||
|
<column-names value="" />
|
||||||
|
<visible value="true" />
|
||||||
|
<editable value="false" />
|
||||||
|
</audit-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" />
|
||||||
|
<exit-on-changes value="ASK" />
|
||||||
|
</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="json 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="json 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="json 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="json 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="json 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="json 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="json 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>
|
||||||
|
</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" />
|
||||||
|
<mapping file-type-id="JAVA_SOURCE" extensions="sql" />
|
||||||
|
</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>
|
||||||
|
<assistant-settings>
|
||||||
|
<credential-settings>
|
||||||
|
<credentials />
|
||||||
|
</credential-settings>
|
||||||
|
</assistant-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>
|
||||||
|
</project>
|
||||||
10
.idea/deploymentTargetSelector.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
19
.idea/gradle.xml
generated
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/migrations.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/misc.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
17
.idea/runConfigurations.xml
generated
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
BIN
app/Wallpaper Gallery
Normal file
166
app/build.gradle.kts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
id("kotlin-kapt")
|
||||||
|
id("com.google.gms.google-services")
|
||||||
|
id("com.google.firebase.crashlytics")
|
||||||
|
}
|
||||||
|
val timestamp = SimpleDateFormat("MM_dd_HH_mm").format(Date())
|
||||||
|
android {
|
||||||
|
namespace = "com.gallery.free.wallpaper"
|
||||||
|
compileSdk = 36
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.gallery.free.wallpaper"
|
||||||
|
minSdk = 24
|
||||||
|
targetSdk = 36
|
||||||
|
versionCode = 2
|
||||||
|
versionName = "1.1"
|
||||||
|
|
||||||
|
setProperty("archivesBaseName", "WallpaperGallery_V" + versionName + "(${versionCode})_$timestamp")
|
||||||
|
testInstrumentationRunner = "androidx.live.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = true
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
|
buildFeatures{
|
||||||
|
viewBinding = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("androidx.cardview:cardview:1.0.0")
|
||||||
|
implementation("androidx.viewpager2:viewpager2:1.0.0")
|
||||||
|
implementation("com.google.android.material:material:1.10.0")
|
||||||
|
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||||
|
implementation("com.google.code.gson:gson:2.8.9")
|
||||||
|
implementation ("com.github.bumptech.glide:glide:5.0.5")
|
||||||
|
implementation ("jp.wasabeef:glide-transformations:4.3.0") // Glide Transformations
|
||||||
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-process:2.6.2")// 添加生命周期扩展
|
||||||
|
implementation("androidx.lifecycle:lifecycle-common-java8:2.6.2")
|
||||||
|
implementation("androidx.work:work-runtime-ktx:2.8.1")// 添加 WorkManager(用于后台任务)
|
||||||
|
|
||||||
|
// 添加协程依赖
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
|
||||||
|
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.appcompat)
|
||||||
|
implementation(libs.material)
|
||||||
|
implementation(libs.androidx.activity)
|
||||||
|
implementation(libs.androidx.constraintlayout)
|
||||||
|
implementation(libs.androidx.ui.graphics)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|
||||||
|
implementation(files("libs/TradPlusLibrary_01_04_12_20-release.aar"))
|
||||||
|
implementation(files("libs/UpLoadLibrary_12_03_15_13-release.aar"))
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||||
|
implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
|
||||||
|
implementation("com.google.android.gms:play-services-location:21.0.1")
|
||||||
|
implementation("com.google.android.gms:play-services-appset:16.0.1")
|
||||||
|
// Import the Firebase BoM
|
||||||
|
implementation(platform("com.google.firebase:firebase-bom:34.6.0"))
|
||||||
|
implementation("com.google.firebase:firebase-crashlytics-ndk")
|
||||||
|
implementation("com.google.firebase:firebase-analytics")
|
||||||
|
// okhttp
|
||||||
|
implementation ("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
|
||||||
|
|
||||||
|
// TradPlus
|
||||||
|
implementation("com.tradplusad:tradplus:15.2.0.1")
|
||||||
|
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||||
|
implementation("androidx.appcompat:appcompat:1.3.0-alpha02")
|
||||||
|
|
||||||
|
// IronSource
|
||||||
|
implementation("com.ironsource.sdk:mediationsdk:9.0.0")
|
||||||
|
implementation("com.tradplusad:tradplus-ironsource:10.15.2.0.1")
|
||||||
|
|
||||||
|
// Pangle
|
||||||
|
implementation("com.tradplusad:tradplus-pangle:19.15.2.0.1")
|
||||||
|
implementation("com.pangle.global:pag-sdk:7.8.0.7")
|
||||||
|
|
||||||
|
// UnityAds
|
||||||
|
implementation("com.tradplusad:tradplus-unity:5.15.2.0.1")
|
||||||
|
implementation("com.unity3d.ads:unity-ads:4.16.3")
|
||||||
|
|
||||||
|
// Chartboost
|
||||||
|
implementation("com.tradplusad:tradplus-chartboostx:15.15.2.0.1")
|
||||||
|
implementation("com.chartboost:chartboost-sdk:9.10.0")
|
||||||
|
implementation("com.google.android.gms:play-services-ads-identifier:17.0.0")
|
||||||
|
implementation("com.google.android.gms:play-services-base:17.4.0")
|
||||||
|
|
||||||
|
//上面新版本下载失败用旧版本
|
||||||
|
// implementation("com.tradplusad:tradplus-chartboostx:15.14.5.0.1")
|
||||||
|
// implementation("com.chartboost:chartboost-sdk:9.8.3")
|
||||||
|
// implementation("com.google.android.gms:play-services-ads-identifier:17.0.0")
|
||||||
|
// implementation("com.google.android.gms:play-services-base:17.4.0")
|
||||||
|
|
||||||
|
// InMobi
|
||||||
|
implementation("com.tradplusad:tradplus-inmobix:23.15.2.0.1")
|
||||||
|
implementation("com.inmobi.monetization:inmobi-ads-kotlin:11.0.0")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:3.14.9")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
|
||||||
|
implementation("androidx.core:core-ktx:1.5.0")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.0")
|
||||||
|
|
||||||
|
implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
|
||||||
|
implementation("com.google.android.gms:play-services-location:21.0.1") // optional
|
||||||
|
implementation("androidx.browser:browser:1.8.0")
|
||||||
|
implementation("com.squareup.picasso:picasso:2.8")
|
||||||
|
implementation("androidx.viewpager:viewpager:1.0.0")
|
||||||
|
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||||
|
|
||||||
|
// Fyber
|
||||||
|
implementation("com.fyber:marketplace-sdk:8.4.0")
|
||||||
|
implementation("com.tradplusad:tradplus-fyber:24.15.2.0.1")
|
||||||
|
implementation("com.google.android.gms:play-services-ads-identifier:17.0.0")
|
||||||
|
implementation("com.google.android.gms:play-services-base:17.4.0")
|
||||||
|
|
||||||
|
// Mintegral
|
||||||
|
implementation("com.tradplusad:tradplus-mintegralx_overseas:18.15.2.0.1")
|
||||||
|
implementation("androidx.recyclerview:recyclerview:1.1.0")
|
||||||
|
implementation("com.mbridge.msdk.oversea:mbridge_android_sdk:16.10.11")
|
||||||
|
|
||||||
|
// Liftoff (Vungle)
|
||||||
|
implementation("com.tradplusad:tradplus-vunglex:7.15.2.0.1")
|
||||||
|
implementation("com.vungle:vungle-ads:7.6.0")
|
||||||
|
|
||||||
|
// Bigo
|
||||||
|
implementation("com.bigossp:bigo-ads:5.5.2")
|
||||||
|
implementation("com.tradplusad:tradplus-bigo:57.15.2.0.1")
|
||||||
|
|
||||||
|
// Cross Promotion
|
||||||
|
implementation("com.tradplusad:tradplus-crosspromotion:27.15.2.0.1")
|
||||||
|
|
||||||
|
// TP Exchange(注意与主包版本同步)
|
||||||
|
implementation("com.google.code.gson:gson:2.8.6")
|
||||||
|
implementation("com.tradplusad:tp_exchange:40.15.2.0.1")
|
||||||
|
|
||||||
|
// Google UMP
|
||||||
|
implementation ("com.google.android.ump:user-messaging-platform:3.2.0")
|
||||||
|
}
|
||||||
29
app/google-services.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "141099185558",
|
||||||
|
"project_id": "wallpaper-gallery-6ac52",
|
||||||
|
"storage_bucket": "wallpaper-gallery-6ac52.firebasestorage.app"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:141099185558:android:53c2a086efb56b3e8159b7",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.gallery.free.wallpaper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyB_9h8FLSf3Uv-_5JMlkhcm-Kmuwv84jhk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
BIN
app/libs/TradPlusLibrary_01_04_12_20-release.aar
Normal file
BIN
app/libs/UpLoadLibrary_12_03_15_13-release.aar
Normal file
35
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
|
-keep class com.gallery.free.wallpaper.WallpaperJson { *; }
|
||||||
|
-keep class com.gallery.free.wallpaper.JsonUtils { *; }
|
||||||
|
-keep class com.gallery.free.wallpaper.Links { *; }
|
||||||
|
-keep class com.gallery.free.wallpaper.Urls { *; }
|
||||||
|
-keep class com.gallery.free.wallpaper.User { *; }
|
||||||
|
-keep class com.gallery.free.wallpaper.WallpaperItem { *; }
|
||||||
|
-keep class com.google.gson.** { *; }
|
||||||
|
-keep class com.google.gson.reflect.TypeToken { *; }
|
||||||
|
-keep class * extends com.google.gson.reflect.TypeToken
|
||||||
|
-keepattributes Signature
|
||||||
|
-keepattributes AnnotationDefault,RuntimeVisibleAnnotations
|
||||||
|
|
||||||
|
-keep public class com.tradplus.** { *; }
|
||||||
|
-keep class com.tradplus.ads.** { *; }
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package gallery.free.wallpaper
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("com.gallery.free.wallpaper", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
70
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.SET_WALLPAPER" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<!-- 针对 Android 13+ 的媒体权限 -->
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.AD_ID" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".MyApplication"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:allowNativeHeapPointerTagging="false"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:largeHeap="true"
|
||||||
|
android:persistableMode="persistAcrossReboots"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:supportsPictureInPicture="true"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
tools:replace="networkSecurityConfig"
|
||||||
|
android:networkSecurityConfig="@xml/net"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||||
|
tools:targetApi="30">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.startapp.sdk.MIXED_AUDIENCE"
|
||||||
|
android:value="true"/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".LocalPreActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
||||||
|
<activity
|
||||||
|
android:name=".DetailwpActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||||
|
<activity
|
||||||
|
android:name=".SplaActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
20703
app/src/main/assets/Film_two.json
Normal file
20702
app/src/main/assets/Nature_two.json
Normal file
20702
app/src/main/assets/Travel_two.json
Normal file
90
app/src/main/assets/privacy_policy_one.html
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'AlimamaShuheiti';
|
||||||
|
src: url('file:///android_asset/fonts/alimama_shuheiti.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'AlimamaShuheiti', sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 0 20px 20px 20px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #555555;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.last-updated {
|
||||||
|
color: #666666;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
ul { padding-left: 20px; }
|
||||||
|
li {
|
||||||
|
margin-bottom: 7px;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p class="last-updated">Last Updated: November 27, 2025</p>
|
||||||
|
|
||||||
|
<h2>1. Information Collection</h2>
|
||||||
|
<p>Our wallpaper application respects your privacy. We commit to:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Not collecting any personally identifiable information</li>
|
||||||
|
<li>Not requiring user registration or login</li>
|
||||||
|
<li>Not tracking user browsing habits</li>
|
||||||
|
<li>Not accessing your personal files or data</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>2. Wallpaper Download and Storage</h2>
|
||||||
|
<p>When you download wallpapers:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Wallpapers are saved only in your device's local storage</li>
|
||||||
|
<li>We cannot access the wallpapers you download</li>
|
||||||
|
<li>Wallpapers are not uploaded to our servers</li>
|
||||||
|
<li>You can delete downloaded wallpapers at any time</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>3. Permission Explanations</h2>
|
||||||
|
<p>Required app permissions and their purposes:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Storage Permission</strong>: For saving downloaded wallpapers to your photo gallery</li>
|
||||||
|
<li><strong>Network Permission</strong>: For loading and displaying wallpaper resources</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>4. Third-Party Services</h2>
|
||||||
|
<p>We may use the following third-party services:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Analytics</strong>: For app usage statistics (anonymous data)</li>
|
||||||
|
<li><strong>Advertising Services</strong>: If the app contains ads, relevant advertisements may be displayed</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>5. Data Security</h2>
|
||||||
|
<p>We take reasonable security measures to protect your information.</p>
|
||||||
|
|
||||||
|
<h2>6. Children's Privacy</h2>
|
||||||
|
<p>Our services are not directed to children under 13, and we do not knowingly collect personal information from children.</p>
|
||||||
|
|
||||||
|
<h2>7. Contact Us</h2>
|
||||||
|
<p>If you have any questions about this Privacy Policy, please contact us:</p>
|
||||||
|
<p>Email: xxx@xxx.com</p>
|
||||||
|
|
||||||
|
<h2>8. Policy Changes</h2>
|
||||||
|
<p>We may update this Privacy Policy from time to time. The updated version will be posted on this page.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
app/src/main/java/com/gallery/free/wallpaper/BaseActivity.kt
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
open class BaseActivity: AppCompatActivity() {
|
||||||
|
protected var backPressedCallback: OnBackPressedCallback? = null
|
||||||
|
/** 子类是否需要拦截返回 */
|
||||||
|
protected open fun shouldInterceptBackPress(): Boolean = false
|
||||||
|
|
||||||
|
/** 子类定义拦截后的操作(例如弹窗) */
|
||||||
|
protected open fun onInterceptBackPressed() {}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setupBackPressedCallback()//初始化back事件
|
||||||
|
}
|
||||||
|
private fun setupBackPressedCallback() {
|
||||||
|
backPressedCallback = object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
if (shouldInterceptBackPress()) {
|
||||||
|
// 由子类处理拦截动作
|
||||||
|
onInterceptBackPressed()
|
||||||
|
} else {
|
||||||
|
// 不拦截:关闭自己
|
||||||
|
isEnabled = false
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackPressedDispatcher.addCallback(this, backPressedCallback!!)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
1010
app/src/main/java/com/gallery/free/wallpaper/DetailwpActivity.kt
Normal file
61
app/src/main/java/com/gallery/free/wallpaper/Dialog_SetWp.kt
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
|
||||||
|
class Dialog_SetWp : DialogFragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TYPE_HOME = 1
|
||||||
|
const val TYPE_LOCK = 2
|
||||||
|
const val TYPE_BOTH = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnWallpaperTypeSelectedListener {
|
||||||
|
fun onWallpaperTypeSelected(type: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var listener: OnWallpaperTypeSelectedListener? = null
|
||||||
|
|
||||||
|
fun setListener(listener: OnWallpaperTypeSelectedListener) {
|
||||||
|
this.listener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.dialog_setwp, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val dialog = super.onCreateDialog(savedInstanceState)
|
||||||
|
// 设置对话框样式
|
||||||
|
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
view.findViewById<View>(R.id.layout_home).setOnClickListener {
|
||||||
|
listener?.onWallpaperTypeSelected(TYPE_HOME)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
view.findViewById<View>(R.id.layout_lock).setOnClickListener {
|
||||||
|
listener?.onWallpaperTypeSelected(TYPE_LOCK)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
view.findViewById<View>(R.id.layout_both).setOnClickListener {
|
||||||
|
listener?.onWallpaperTypeSelected(TYPE_BOTH)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
class GridItemDecoration(
|
||||||
|
private val spanCount: Int,
|
||||||
|
private val spacing: Int,
|
||||||
|
private val includeEdge: Boolean = true
|
||||||
|
) : RecyclerView.ItemDecoration() {
|
||||||
|
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
val position = parent.getChildAdapterPosition(view)
|
||||||
|
val column = position % spanCount
|
||||||
|
|
||||||
|
if (includeEdge) {
|
||||||
|
// 计算左右间距,两列宽度一致
|
||||||
|
outRect.left = spacing - column * spacing / spanCount
|
||||||
|
outRect.right = (column + 1) * spacing / spanCount
|
||||||
|
|
||||||
|
// 上下间距保持一致
|
||||||
|
if (position < spanCount) {
|
||||||
|
outRect.top = spacing
|
||||||
|
}
|
||||||
|
outRect.bottom = spacing
|
||||||
|
} else {
|
||||||
|
outRect.left = column * spacing / spanCount
|
||||||
|
outRect.right = spacing - (column + 1) * spacing / spanCount
|
||||||
|
if (position >= spanCount) {
|
||||||
|
outRect.top = spacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import com.gallery.free.wallpaper.fragment.WpGridFragment
|
||||||
|
|
||||||
|
class HoPagerAdapter(
|
||||||
|
fragment: Fragment,
|
||||||
|
private val categories: List<String> = listOf("Film", "Nature", "Travel")
|
||||||
|
) : FragmentStateAdapter(fragment) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "HomePagerAdapter"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val fragments = mutableMapOf<Int, WpGridFragment>()
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = categories.size
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
val categoryName = categories[position]
|
||||||
|
val fragment = WpGridFragment.newInstance(categoryName)
|
||||||
|
fragments[position] = fragment
|
||||||
|
|
||||||
|
Log.d(TAG, "创建Fragment - 位置: $position, 分类: $categoryName")
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTabTitle(position: Int): String {
|
||||||
|
return if (position in categories.indices) {
|
||||||
|
categories[position]
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFragment(position: Int): WpGridFragment? {
|
||||||
|
return fragments[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新分类列表
|
||||||
|
*/
|
||||||
|
fun updateCategories(newCategories: List<String>) {
|
||||||
|
Log.d(TAG, "更新分类列表: $newCategories")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有 Fragment
|
||||||
|
*/
|
||||||
|
fun getAllFragments(): List<WpGridFragment> {
|
||||||
|
return fragments.values.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除 Fragment 缓存
|
||||||
|
*/
|
||||||
|
fun clearFragmentCache() {
|
||||||
|
fragments.clear()
|
||||||
|
Log.d(TAG, "已清除Fragment缓存")
|
||||||
|
}
|
||||||
|
}
|
||||||
624
app/src/main/java/com/gallery/free/wallpaper/LocalPreActivity.kt
Normal file
@ -0,0 +1,624 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import android.app.WallpaperManager
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Matrix
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.view.WindowInsetsController
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
import com.gallery.free.wallpaper.databinding.ActivityLocalpreBinding
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
class LocalPreActivity : AppCompatActivity(),
|
||||||
|
Dialog_SetWp.OnWallpaperTypeSelectedListener {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityLocalpreBinding
|
||||||
|
private lateinit var wallpaperManager: WallpaperManager
|
||||||
|
private var setWallpaperDialog: Dialog_SetWp? = null
|
||||||
|
private var isSettingWallpaper = false
|
||||||
|
private var currentWallpaperBitmap: Bitmap? = null
|
||||||
|
private var imageUri: Uri? = null
|
||||||
|
private var screenWidth = 0
|
||||||
|
private var screenHeight = 0
|
||||||
|
private var isLaunchedFromSetting = false
|
||||||
|
private var hasShownSuccessToast = false
|
||||||
|
private var isInGallerySelection = false
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "LocalImagePreview"
|
||||||
|
private const val SET_WALLPAPER_STATE_IDLE = 0
|
||||||
|
private const val SET_WALLPAPER_STATE_LOADING = 1
|
||||||
|
private const val SET_WALLPAPER_STATE_SUCCESS = 2
|
||||||
|
private const val SET_WALLPAPER_STATE_FAILED = 3
|
||||||
|
private const val DELAY_BEFORE_RETURN = 800L
|
||||||
|
private const val REQUEST_GALLERY_AGAIN = 1001
|
||||||
|
|
||||||
|
const val EXTRA_IMAGE_URI = "selected_image_uri"
|
||||||
|
const val EXTRA_FROM_SETTING = "from_setting"
|
||||||
|
const val RESULT_SET_SUCCESS = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
private var setWallpaperState = SET_WALLPAPER_STATE_IDLE
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
Utils_com.initFull(this, true)
|
||||||
|
|
||||||
|
setTheme(R.style.TransparentPreviewTheme)
|
||||||
|
|
||||||
|
overridePendingTransition(0, 0)
|
||||||
|
|
||||||
|
binding = ActivityLocalpreBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
overridePendingTransition(0, 0)
|
||||||
|
|
||||||
|
binding = ActivityLocalpreBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
handleBackButtonClick()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Log.d(TAG, "=== LocalImagePreviewActivity_onCreate ===")
|
||||||
|
Log.d(TAG, "Activity实例: ${this.hashCode()}")
|
||||||
|
|
||||||
|
showLoading(true)
|
||||||
|
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
screenWidth = displayMetrics.widthPixels
|
||||||
|
screenHeight = displayMetrics.heightPixels
|
||||||
|
Log.d(TAG, "Screen size: $screenWidth x $screenHeight")
|
||||||
|
|
||||||
|
wallpaperManager = WallpaperManager.getInstance(this)
|
||||||
|
|
||||||
|
// 获取图片URI
|
||||||
|
val intentUri = intent.getStringExtra(EXTRA_IMAGE_URI)?.let { Uri.parse(it) }
|
||||||
|
if (intentUri != null) {
|
||||||
|
imageUri = intentUri
|
||||||
|
Log.d(TAG, "从Intent获取图片URI: $imageUri")
|
||||||
|
}
|
||||||
|
|
||||||
|
isLaunchedFromSetting = intent.getBooleanExtra(EXTRA_FROM_SETTING, true)
|
||||||
|
Log.d(TAG, "是否从设置页面启动: $isLaunchedFromSetting")
|
||||||
|
|
||||||
|
if (imageUri == null) {
|
||||||
|
Toast.makeText(this, "No image selected", Toast.LENGTH_SHORT).show()
|
||||||
|
showLoading(false)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否是重新创建的情况
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
Log.d(TAG, "Activity被重新创建")
|
||||||
|
setWallpaperState = savedInstanceState.getInt("setWallpaperState", SET_WALLPAPER_STATE_IDLE)
|
||||||
|
isLaunchedFromSetting = savedInstanceState.getBoolean("isLaunchedFromSetting", true)
|
||||||
|
isInGallerySelection = savedInstanceState.getBoolean("isInGallerySelection", false)
|
||||||
|
updateSetWallpaperButtonState()
|
||||||
|
}
|
||||||
|
|
||||||
|
hideSystemUI()
|
||||||
|
setupViews()
|
||||||
|
loadImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putInt("setWallpaperState", setWallpaperState)
|
||||||
|
outState.putBoolean("isLaunchedFromSetting", isLaunchedFromSetting)
|
||||||
|
outState.putBoolean("isInGallerySelection", isInGallerySelection)
|
||||||
|
Log.d(TAG, "保存状态: setWallpaperState=$setWallpaperState, isLaunchedFromSetting=$isLaunchedFromSetting, isInGallerySelection=$isInGallerySelection")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
Log.d(TAG, "onNewIntent被调用")
|
||||||
|
|
||||||
|
// 当Activity已经存在时,更新URI
|
||||||
|
val newUri = intent?.getStringExtra(EXTRA_IMAGE_URI)?.let { Uri.parse(it) }
|
||||||
|
if (newUri != null && newUri != imageUri) {
|
||||||
|
imageUri = newUri
|
||||||
|
currentWallpaperBitmap = null
|
||||||
|
setWallpaperState = SET_WALLPAPER_STATE_IDLE
|
||||||
|
|
||||||
|
// 检查是否从设置页面启动
|
||||||
|
isLaunchedFromSetting = intent.getBooleanExtra(EXTRA_FROM_SETTING, false)
|
||||||
|
|
||||||
|
Log.d(TAG, "更新图片URI: $imageUri, 是否从设置页面启动: $isLaunchedFromSetting")
|
||||||
|
loadImage()
|
||||||
|
updateSetWallpaperButtonState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideSystemUI() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
window.insetsController?.let { controller ->
|
||||||
|
controller.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
||||||
|
controller.systemBarsBehavior =
|
||||||
|
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
window.decorView.systemUiVisibility = (
|
||||||
|
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
||||||
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
||||||
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViews() {
|
||||||
|
Log.d(TAG, "setupViews")
|
||||||
|
|
||||||
|
// 返回按钮
|
||||||
|
binding.buttonBack.setOnClickListener {
|
||||||
|
Log.d(TAG, "Back button clicked")
|
||||||
|
handleBackButtonClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置壁纸按钮
|
||||||
|
binding.buttonSetWallpaper.setOnClickListener {
|
||||||
|
Log.d(TAG, "Set wallpaper button clicked")
|
||||||
|
if (setWallpaperState != SET_WALLPAPER_STATE_LOADING) {
|
||||||
|
showSetWallpaperDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSetWallpaperButtonState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSetWallpaperButtonState() {
|
||||||
|
Log.d(TAG, "更新按钮状态: $setWallpaperState")
|
||||||
|
|
||||||
|
when (setWallpaperState) {
|
||||||
|
SET_WALLPAPER_STATE_IDLE -> {
|
||||||
|
binding.buttonSetWallpaper.setImageResource(R.drawable.ic_setwp)
|
||||||
|
binding.buttonSetWallpaper.isEnabled = true
|
||||||
|
binding.buttonBack.isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_WALLPAPER_STATE_LOADING -> {
|
||||||
|
binding.buttonSetWallpaper.setImageResource(R.drawable.loading_animation)
|
||||||
|
binding.buttonSetWallpaper.isEnabled = false
|
||||||
|
binding.buttonBack.isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_WALLPAPER_STATE_SUCCESS -> {
|
||||||
|
binding.buttonSetWallpaper.setImageResource(R.drawable.ic_success)
|
||||||
|
binding.buttonSetWallpaper.isEnabled = false
|
||||||
|
binding.buttonBack.isEnabled = false
|
||||||
|
|
||||||
|
if (!hasShownSuccessToast) {
|
||||||
|
Toast.makeText(this, "setting successfully!", Toast.LENGTH_SHORT).show()
|
||||||
|
hasShownSuccessToast = true
|
||||||
|
}
|
||||||
|
|
||||||
|
setResult(RESULT_SET_SUCCESS)
|
||||||
|
|
||||||
|
handler.postDelayed({
|
||||||
|
returnToSettingFragment()
|
||||||
|
}, DELAY_BEFORE_RETURN)
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_WALLPAPER_STATE_FAILED -> {
|
||||||
|
binding.buttonSetWallpaper.setImageResource(R.drawable.close_detailwp)
|
||||||
|
binding.buttonSetWallpaper.isEnabled = true
|
||||||
|
binding.buttonBack.isEnabled = true
|
||||||
|
|
||||||
|
// 设置失败:显示Toast
|
||||||
|
Toast.makeText(this, "setting failed...", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
handler.postDelayed({
|
||||||
|
setWallpaperState = SET_WALLPAPER_STATE_IDLE
|
||||||
|
updateSetWallpaperButtonState()
|
||||||
|
}, DELAY_BEFORE_RETURN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun returnToSettingFragment() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "返回到SettingFragment,isLaunchedFromSetting=$isLaunchedFromSetting")
|
||||||
|
|
||||||
|
if (isLaunchedFromSetting) {
|
||||||
|
val settingIntent = Intent(this, MainActivity::class.java).apply {
|
||||||
|
putExtra("SHOW_SETTING_FRAGMENT", true)
|
||||||
|
|
||||||
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(settingIntent)
|
||||||
|
finish()
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
|
Log.d(TAG, "已启动MainActivity显示SettingFragment")
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "不是从设置页面启动,直接结束")
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "返回SettingFragment失败: ${e.message}")
|
||||||
|
finish() // 如果失败,至少正常结束
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var isFinishingByUser = false
|
||||||
|
private var isProcessingBack = false
|
||||||
|
|
||||||
|
private fun handleBackButtonClick() {
|
||||||
|
// 如果正在设置中,禁止返回
|
||||||
|
if (setWallpaperState == SET_WALLPAPER_STATE_LOADING ||
|
||||||
|
setWallpaperState == SET_WALLPAPER_STATE_SUCCESS
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防止重复点击
|
||||||
|
if (isProcessingBack) return
|
||||||
|
isProcessingBack = true
|
||||||
|
|
||||||
|
// 标记为用户主动关闭
|
||||||
|
isFinishingByUser = true
|
||||||
|
|
||||||
|
Toast.makeText(this, "setting cancelled...", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
currentWallpaperBitmap = null
|
||||||
|
|
||||||
|
handler.postDelayed({
|
||||||
|
try {
|
||||||
|
if (isInGallerySelection) {
|
||||||
|
// 如果用户正在重新选择图片(已经在图库中),返回到设置页面
|
||||||
|
Log.d(TAG, "用户在图库中点击返回,返回到设置页面")
|
||||||
|
returnToSettingFragment()
|
||||||
|
} else {
|
||||||
|
// 用户在预览页面点击返回,重新打开图库
|
||||||
|
Log.d(TAG, "用户在预览页面点击返回,重新打开图库")
|
||||||
|
isInGallerySelection = true // 标记为正在重新选择
|
||||||
|
openGalleryForNewImage()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
returnToSettingFragment() // 如果出错,也返回到设置页面
|
||||||
|
} finally {
|
||||||
|
isProcessingBack = false
|
||||||
|
isFinishingByUser = false
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openGalleryForNewImage() {
|
||||||
|
try {
|
||||||
|
val galleryIntent = Intent(Intent.ACTION_PICK)
|
||||||
|
galleryIntent.type = "image/*"
|
||||||
|
|
||||||
|
galleryIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
|
||||||
|
|
||||||
|
startActivityForResult(galleryIntent, REQUEST_GALLERY_AGAIN)
|
||||||
|
Log.d(TAG, "打开图库选择新图片")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "打开图库失败: ${e.message}")
|
||||||
|
Toast.makeText(this, "无法打开图库", Toast.LENGTH_SHORT).show()
|
||||||
|
returnToSettingFragment() // 失败时返回到设置页面
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
Log.d(TAG, "onActivityResult: requestCode=$requestCode, resultCode=$resultCode, isLaunchedFromSetting=$isLaunchedFromSetting, isInGallerySelection=$isInGallerySelection")
|
||||||
|
|
||||||
|
when (requestCode) {
|
||||||
|
REQUEST_GALLERY_AGAIN -> {
|
||||||
|
// 重置状态
|
||||||
|
isInGallerySelection = false
|
||||||
|
|
||||||
|
if (resultCode == android.app.Activity.RESULT_OK && data != null) {
|
||||||
|
val uri = data.data
|
||||||
|
if (uri != null) {
|
||||||
|
// 用户重新选择了图片
|
||||||
|
Log.d(TAG, "用户重新选择了图片: $uri")
|
||||||
|
|
||||||
|
// 启动新的预览页面
|
||||||
|
val newIntent = Intent(this, LocalPreActivity::class.java).apply {
|
||||||
|
putExtra(EXTRA_IMAGE_URI, uri.toString())
|
||||||
|
putExtra(EXTRA_FROM_SETTING, isLaunchedFromSetting)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 结束当前预览页面
|
||||||
|
finish()
|
||||||
|
|
||||||
|
// 延迟启动新预览页面
|
||||||
|
handler.postDelayed({
|
||||||
|
startActivity(newIntent)
|
||||||
|
overridePendingTransition(0, 0)
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
// 没有获取到图片,返回到设置页面
|
||||||
|
Log.d(TAG, "图库返回但没有选择图片,返回到设置页面")
|
||||||
|
returnToSettingFragment()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 用户取消了图库选择,返回到设置页面
|
||||||
|
Log.d(TAG, "用户取消了图库选择,返回到设置页面")
|
||||||
|
returnToSettingFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadImage() {
|
||||||
|
Log.d(TAG, "开始加载图片: $imageUri")
|
||||||
|
|
||||||
|
// 先显示加载状态
|
||||||
|
showLoading(true)
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
setWallpaperState = SET_WALLPAPER_STATE_IDLE
|
||||||
|
updateSetWallpaperButtonState()
|
||||||
|
|
||||||
|
// 加载预览图
|
||||||
|
Glide.with(this)
|
||||||
|
.load(imageUri)
|
||||||
|
.into(binding.imageViewPreview)
|
||||||
|
|
||||||
|
// 加载Bitmap供设置壁纸使用
|
||||||
|
Glide.with(this)
|
||||||
|
.asBitmap()
|
||||||
|
.load(imageUri)
|
||||||
|
.into(object : CustomTarget<Bitmap>() {
|
||||||
|
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||||
|
currentWallpaperBitmap = resource
|
||||||
|
showLoading(false)
|
||||||
|
Log.d(TAG, "图片加载成功,大小: ${resource.width}x${resource.height}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
|
showLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||||
|
super.onLoadFailed(errorDrawable)
|
||||||
|
showLoading(false)
|
||||||
|
Toast.makeText(
|
||||||
|
this@LocalPreActivity,
|
||||||
|
"Failed to load image",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
Log.e(TAG, "图片加载失败: $imageUri")
|
||||||
|
|
||||||
|
// 根据启动源返回不同的结果
|
||||||
|
if (isLaunchedFromSetting) {
|
||||||
|
setResult(android.app.Activity.RESULT_CANCELED)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLoading(show: Boolean) {
|
||||||
|
binding.progressBar.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSetWallpaperDialog() {
|
||||||
|
if (setWallpaperDialog == null) {
|
||||||
|
setWallpaperDialog = Dialog_SetWp()
|
||||||
|
setWallpaperDialog?.setListener(this)
|
||||||
|
}
|
||||||
|
if (setWallpaperDialog?.isAdded != true) {
|
||||||
|
setWallpaperDialog?.show(supportFragmentManager, "SetWallpaperDialog")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWallpaperTypeSelected(type: Int) {
|
||||||
|
setWallpaper(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setWallpaper(type: Int) {
|
||||||
|
if (isSettingWallpaper || setWallpaperState == SET_WALLPAPER_STATE_SUCCESS) return
|
||||||
|
|
||||||
|
Log.d(TAG, "开始设置壁纸,类型: $type, 是否从设置页面启动: $isLaunchedFromSetting")
|
||||||
|
|
||||||
|
isSettingWallpaper = true
|
||||||
|
setWallpaperState = SET_WALLPAPER_STATE_LOADING
|
||||||
|
updateSetWallpaperButtonState()
|
||||||
|
|
||||||
|
if (currentWallpaperBitmap != null) {
|
||||||
|
SetWallpaperTask(this, type, currentWallpaperBitmap!!).execute()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果Bitmap还没缓存,重新加载
|
||||||
|
Glide.with(this)
|
||||||
|
.asBitmap()
|
||||||
|
.load(imageUri)
|
||||||
|
.into(object : CustomTarget<Bitmap>() {
|
||||||
|
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||||
|
currentWallpaperBitmap = resource
|
||||||
|
SetWallpaperTask(this@LocalPreActivity, type, resource).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||||
|
super.onLoadFailed(errorDrawable)
|
||||||
|
isSettingWallpaper = false
|
||||||
|
setWallpaperState = SET_WALLPAPER_STATE_FAILED
|
||||||
|
updateSetWallpaperButtonState()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SetWallpaperTask(
|
||||||
|
activity: LocalPreActivity,
|
||||||
|
private val type: Int,
|
||||||
|
private val bitmap: Bitmap
|
||||||
|
) : AsyncTask<Void, Void, Boolean>() {
|
||||||
|
|
||||||
|
private val activityRef = WeakReference(activity)
|
||||||
|
|
||||||
|
private fun createCenteredWallpaperBitmap(
|
||||||
|
originalBitmap: Bitmap,
|
||||||
|
screenWidth: Int,
|
||||||
|
screenHeight: Int
|
||||||
|
): Bitmap {
|
||||||
|
val bitmapWidth = originalBitmap.width.toFloat()
|
||||||
|
val bitmapHeight = originalBitmap.height.toFloat()
|
||||||
|
|
||||||
|
// 计算缩放比例,使图片至少能覆盖整个屏幕
|
||||||
|
val scaleX = screenWidth / bitmapWidth
|
||||||
|
val scaleY = screenHeight / bitmapHeight
|
||||||
|
val scale = maxOf(scaleX, scaleY)
|
||||||
|
|
||||||
|
// 创建缩放后的图片
|
||||||
|
val matrix = Matrix()
|
||||||
|
matrix.postScale(scale, scale)
|
||||||
|
val scaledBitmap = Bitmap.createBitmap(
|
||||||
|
originalBitmap,
|
||||||
|
0, 0,
|
||||||
|
originalBitmap.width, originalBitmap.height,
|
||||||
|
matrix, true
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从缩放后的图片中裁剪出居中区域
|
||||||
|
val scaledWidth = scaledBitmap.width
|
||||||
|
val scaledHeight = scaledBitmap.height
|
||||||
|
|
||||||
|
// 计算裁剪的起始点(居中)
|
||||||
|
val startX = (scaledWidth - screenWidth) / 2
|
||||||
|
val startY = (scaledHeight - screenHeight) / 2
|
||||||
|
|
||||||
|
// 确保裁剪区域在图片范围内
|
||||||
|
val safeStartX = maxOf(0, startX)
|
||||||
|
val safeStartY = maxOf(0, startY)
|
||||||
|
val safeEndX = minOf(safeStartX + screenWidth, scaledWidth)
|
||||||
|
val safeEndY = minOf(safeStartY + screenHeight, scaledHeight)
|
||||||
|
val safeWidth = safeEndX - safeStartX
|
||||||
|
val safeHeight = safeEndY - safeStartY
|
||||||
|
|
||||||
|
// 创建最终居中裁剪的图片
|
||||||
|
return Bitmap.createBitmap(
|
||||||
|
scaledBitmap,
|
||||||
|
safeStartX, safeStartY,
|
||||||
|
safeWidth, safeHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doInBackground(vararg params: Void?): Boolean {
|
||||||
|
val activity = activityRef.get() ?: return false
|
||||||
|
|
||||||
|
return try {
|
||||||
|
Log.d("SetWallpaperTask", "开始处理壁纸图片")
|
||||||
|
|
||||||
|
// 创建居中裁剪的壁纸
|
||||||
|
val centeredBitmap = createCenteredWallpaperBitmap(
|
||||||
|
bitmap, // 注意:这里的bitmap是Glide管理的,不要回收它
|
||||||
|
activity.screenWidth,
|
||||||
|
activity.screenHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d("SetWallpaperTask", "裁剪后图片大小: ${centeredBitmap.width}x${centeredBitmap.height}")
|
||||||
|
|
||||||
|
// 根据选择的类型设置壁纸
|
||||||
|
when (type) {
|
||||||
|
Dialog_SetWp.TYPE_HOME -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
activity.wallpaperManager.setBitmap(
|
||||||
|
centeredBitmap,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
WallpaperManager.FLAG_SYSTEM
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
activity.wallpaperManager.setBitmap(centeredBitmap)
|
||||||
|
}
|
||||||
|
Log.d("SetWallpaperTask", "设置主屏幕壁纸")
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog_SetWp.TYPE_LOCK -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
activity.wallpaperManager.setBitmap(
|
||||||
|
centeredBitmap,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
WallpaperManager.FLAG_LOCK
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
activity.wallpaperManager.setBitmap(centeredBitmap)
|
||||||
|
}
|
||||||
|
Log.d("SetWallpaperTask", "设置锁屏壁纸")
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog_SetWp.TYPE_BOTH -> {
|
||||||
|
activity.wallpaperManager.setBitmap(centeredBitmap)
|
||||||
|
Log.d("SetWallpaperTask", "设置主屏幕和锁屏壁纸")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键修复点2:只回收自己创建的centeredBitmap
|
||||||
|
// 不回收传入的bitmap(因为它是Glide管理的)
|
||||||
|
centeredBitmap.recycle()
|
||||||
|
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("SetWallpaperTask", "设置壁纸失败: ${e.message}", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(success: Boolean) {
|
||||||
|
val activity = activityRef.get() ?: return
|
||||||
|
|
||||||
|
activity.isSettingWallpaper = false
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 设置成功
|
||||||
|
activity.setWallpaperState = SET_WALLPAPER_STATE_SUCCESS
|
||||||
|
Log.d("SetWallpaperTask", "壁纸设置成功,启动源: ${activity.isLaunchedFromSetting}")
|
||||||
|
} else {
|
||||||
|
// 设置失败
|
||||||
|
activity.setWallpaperState = SET_WALLPAPER_STATE_FAILED
|
||||||
|
Log.d("SetWallpaperTask", "壁纸设置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.updateSetWallpaperButtonState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun finish() {
|
||||||
|
super.finish()
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
Log.d(TAG, "Activity销毁: ${this.hashCode()}, 是否从设置页面启动: $isLaunchedFromSetting")
|
||||||
|
|
||||||
|
isSettingWallpaper = false
|
||||||
|
if (!isDestroyed && !isFinishing) {
|
||||||
|
Glide.with(this).clear(binding.imageViewPreview)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Activity已销毁或正在结束,跳过Glide清理")
|
||||||
|
}
|
||||||
|
currentWallpaperBitmap = null
|
||||||
|
|
||||||
|
handler.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
230
app/src/main/java/com/gallery/free/wallpaper/MainActivity.kt
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.gallery.free.wallpaper.databinding.ActivityMainBinding
|
||||||
|
import com.gallery.free.wallpaper.fragment.ColleFragment
|
||||||
|
import com.gallery.free.wallpaper.fragment.HoFragment
|
||||||
|
import com.gallery.free.wallpaper.fragment.SeFragment
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
private lateinit var vb: ActivityMainBinding
|
||||||
|
|
||||||
|
// 保存 Fragment 实例和状态
|
||||||
|
private var homeFragment: HoFragment? = null
|
||||||
|
private var collectionFragment: ColleFragment? = null
|
||||||
|
private var settingFragment: SeFragment? = null
|
||||||
|
private var currentFragmentTag: String = "home"
|
||||||
|
|
||||||
|
// 添加协程作用域
|
||||||
|
private val activityScope = CoroutineScope(Dispatchers.Main + Job())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY_CURRENT_FRAGMENT = "current_fragment"
|
||||||
|
private const val TAG_HOME = "home"
|
||||||
|
private const val TAG_COLLECTION = "collection"
|
||||||
|
private const val TAG_SETTING = "setting"
|
||||||
|
const val PERMISSION_GALLERY_REQUEST_CODE = 1002
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
vb = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
Utils_com.initFull(this, true)
|
||||||
|
setContentView(vb.root)
|
||||||
|
|
||||||
|
// 初始化 Application 数据
|
||||||
|
initApplicationData()
|
||||||
|
|
||||||
|
// 恢复状态
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
currentFragmentTag = savedInstanceState.getString(KEY_CURRENT_FRAGMENT, TAG_HOME)
|
||||||
|
// 恢复 Fragment 实例
|
||||||
|
homeFragment = supportFragmentManager.findFragmentByTag(TAG_HOME) as? HoFragment
|
||||||
|
collectionFragment = supportFragmentManager.findFragmentByTag(TAG_COLLECTION) as? ColleFragment
|
||||||
|
settingFragment = supportFragmentManager.findFragmentByTag(TAG_SETTING) as? SeFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
setupBottomNavigation()
|
||||||
|
checkIfShouldShowSettingFragment()
|
||||||
|
|
||||||
|
// 显示初始 Fragment
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
showFragment(currentFragmentTag)
|
||||||
|
} else {
|
||||||
|
showFragment(currentFragmentTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun checkIfShouldShowSettingFragment() {
|
||||||
|
intent?.let { intent ->
|
||||||
|
if (intent.hasExtra("SHOW_SETTING_FRAGMENT")) {
|
||||||
|
Log.d("MainActivity", "接收到SHOW_SETTING_FRAGMENT标志,显示SettingFragment")
|
||||||
|
currentFragmentTag = TAG_SETTING
|
||||||
|
updateBottomNavigation(2) // 选中设置按钮
|
||||||
|
intent.removeExtra("SHOW_SETTING_FRAGMENT") // 清除标志
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
Log.d("MainActivity", "onNewIntent被调用")
|
||||||
|
|
||||||
|
// ✅ 处理从预览页面返回的情况
|
||||||
|
this.intent = intent
|
||||||
|
checkIfShouldShowSettingFragment()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 初始化 Application 数据
|
||||||
|
*/
|
||||||
|
private fun initApplicationData() {
|
||||||
|
activityScope.launch {
|
||||||
|
try {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 Application 实例
|
||||||
|
val myApp = application as? MyApplication
|
||||||
|
myApp?.let {
|
||||||
|
Log.d("MainActivity", "Application 数据初始化完成")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "Application 数据初始化失败", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putString(KEY_CURRENT_FRAGMENT, currentFragmentTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupBottomNavigation() {
|
||||||
|
// 设置首页按钮选中状态
|
||||||
|
updateBottomNavigation(0)
|
||||||
|
|
||||||
|
// 首页按钮点击
|
||||||
|
vb.homeLayout.setOnClickListener {
|
||||||
|
showFragment(TAG_HOME)
|
||||||
|
updateBottomNavigation(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收藏按钮点击
|
||||||
|
vb.collectionLayout.setOnClickListener {
|
||||||
|
showFragment(TAG_COLLECTION)
|
||||||
|
updateBottomNavigation(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置按钮点击
|
||||||
|
vb.settingsLayout.setOnClickListener {
|
||||||
|
showFragment(TAG_SETTING)
|
||||||
|
updateBottomNavigation(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateBottomNavigation(selectedPosition: Int) {
|
||||||
|
// 重置所有按钮状态
|
||||||
|
vb.homeLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.white))
|
||||||
|
vb.collectionLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.white))
|
||||||
|
vb.settingsLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.white))
|
||||||
|
|
||||||
|
vb.homeIcon.setImageResource(R.drawable.ic_home_disabled)
|
||||||
|
vb.collectionIcon.setImageResource(R.drawable.ic_collection_disabled)
|
||||||
|
vb.settingsIcon.setImageResource(R.drawable.ic_settings_disabled)
|
||||||
|
|
||||||
|
// 设置选中按钮状态
|
||||||
|
when (selectedPosition) {
|
||||||
|
0 -> {
|
||||||
|
vb.homeLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.light_purple))
|
||||||
|
vb.homeIcon.setImageResource(R.drawable.ic_home)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
vb.collectionLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.light_purple))
|
||||||
|
vb.collectionIcon.setImageResource(R.drawable.ic_collection)
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
vb.settingsLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.light_purple))
|
||||||
|
vb.settingsIcon.setImageResource(R.drawable.ic_settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showFragment(tag: String) {
|
||||||
|
Log.d("MainActivity", "显示Fragment: $tag")
|
||||||
|
|
||||||
|
currentFragmentTag = tag
|
||||||
|
|
||||||
|
val transaction = supportFragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
// 隐藏所有 Fragment
|
||||||
|
homeFragment?.let { transaction.hide(it) }
|
||||||
|
collectionFragment?.let { transaction.hide(it) }
|
||||||
|
settingFragment?.let { transaction.hide(it) }
|
||||||
|
|
||||||
|
when (tag) {
|
||||||
|
TAG_HOME -> {
|
||||||
|
if (homeFragment == null) {
|
||||||
|
homeFragment = HoFragment.newInstance()
|
||||||
|
transaction.add(R.id.fragment_container, homeFragment!!, TAG_HOME)
|
||||||
|
} else {
|
||||||
|
transaction.show(homeFragment!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TAG_COLLECTION -> {
|
||||||
|
if (collectionFragment == null) {
|
||||||
|
collectionFragment = ColleFragment.newInstance()
|
||||||
|
transaction.add(R.id.fragment_container, collectionFragment!!, TAG_COLLECTION)
|
||||||
|
} else {
|
||||||
|
transaction.show(collectionFragment!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TAG_SETTING -> {
|
||||||
|
if (settingFragment == null) {
|
||||||
|
settingFragment = SeFragment.newInstance()
|
||||||
|
transaction.add(R.id.fragment_container, settingFragment!!, TAG_SETTING)
|
||||||
|
} else {
|
||||||
|
transaction.show(settingFragment!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取应用实例
|
||||||
|
*/
|
||||||
|
fun getMyApplication(): MyApplication? {
|
||||||
|
return application as? MyApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理权限请求结果
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
|
||||||
|
// 将权限结果传递给当前显示的Fragment
|
||||||
|
when (currentFragmentTag) {
|
||||||
|
TAG_SETTING -> {
|
||||||
|
settingFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
// 取消所有协程
|
||||||
|
activityScope.coroutineContext.cancelChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
582
app/src/main/java/com/gallery/free/wallpaper/MyApplication.kt
Normal file
@ -0,0 +1,582 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.ComponentCallbacks2
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.LruCache
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleObserver
|
||||||
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import com.gallery.free.wallpaper.database.FavorWpManager
|
||||||
|
import com.up.uploadlibrary.UpLoadManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class MyApplication : Application() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var instance: MyApplication? = null
|
||||||
|
val FIXED_CATEGORIES = listOf("Film", "Nature", "Travel")
|
||||||
|
private const val JSON_SUFFIX = "_two.json"
|
||||||
|
private const val PREFS_NAME = "wallpaper_app_settings"
|
||||||
|
private const val KEY_FIRST_LAUNCH = "first_launch"
|
||||||
|
private const val KEY_DEFAULT_CATEGORY = "default_category"
|
||||||
|
private const val KEY_IMAGE_QUALITY = "image_quality"
|
||||||
|
private const val KEY_LAST_CLEANUP = "last_cleanup_time"
|
||||||
|
private const val MAX_WALLPAPERS_PER_CATEGORY = 200
|
||||||
|
private const val MAX_CACHE_CATEGORIES = 3
|
||||||
|
private const val CACHE_STATS_KEY = "cache_stats"
|
||||||
|
}
|
||||||
|
// 协程作用域
|
||||||
|
private val applicationScope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
|
// 管理器
|
||||||
|
private lateinit var favoriteManager: FavorWpManager
|
||||||
|
// 缓存
|
||||||
|
private val wallpaperCache = LruCache<String, List<WallpaperJson>>(1)
|
||||||
|
private val wallpaperItemCache = LruCache<String, List<WpItem>>(1)
|
||||||
|
// 配置
|
||||||
|
private lateinit var appConfig: AppConfig
|
||||||
|
// 缓存统计
|
||||||
|
private var cacheHits = 0
|
||||||
|
private var totalLoads = 0
|
||||||
|
data class AppConfig(
|
||||||
|
val fixedCategories: List<String> = FIXED_CATEGORIES,
|
||||||
|
val preloadCategories: List<String> = FIXED_CATEGORIES, // 预加载也使用固定的三个分类
|
||||||
|
val cacheSizeLimit: Int = MAX_CACHE_CATEGORIES,
|
||||||
|
val enableAutoCleanup: Boolean = true,
|
||||||
|
val imageQuality: String = "balanced",
|
||||||
|
val maxWallpapersPerCategory: Int = MAX_WALLPAPERS_PER_CATEGORY
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
instance = this
|
||||||
|
|
||||||
|
Log.d("MyApplication", "=== 应用启动 ===")
|
||||||
|
Log.d("MyApplication", "包名: ${packageName}")
|
||||||
|
Log.d("MyApplication", "数据库路径: ${getDatabasePath("FavoriteWallpapers.db")?.absolutePath}")
|
||||||
|
Log.d("MyApplication", "缓存配置: 最多缓存 $MAX_CACHE_CATEGORIES 个分类,每分类 $MAX_WALLPAPERS_PER_CATEGORY 张")
|
||||||
|
Log.d("MyApplication", "固定分类: ${FIXED_CATEGORIES.joinToString(", ")}")
|
||||||
|
|
||||||
|
appConfig = AppConfig()
|
||||||
|
initApplication()
|
||||||
|
startMemoryMonitoring()
|
||||||
|
|
||||||
|
// 验证固定分类文件
|
||||||
|
validateFixedCategories()
|
||||||
|
|
||||||
|
UpLoadManager.init(this,"ocean", callback = { _, _ -> })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证固定分类文件是否存在
|
||||||
|
*/
|
||||||
|
private fun validateFixedCategories() {
|
||||||
|
applicationScope.launch {
|
||||||
|
try {
|
||||||
|
val assets = assets.list("") ?: emptyArray()
|
||||||
|
val availableFiles = assets.filter { it.endsWith(".json") }
|
||||||
|
|
||||||
|
Log.d("MyApplication", "=== 验证固定分类文件 ===")
|
||||||
|
Log.d("MyApplication", "Assets中的JSON文件: ${availableFiles.joinToString(", ")}")
|
||||||
|
|
||||||
|
for (category in FIXED_CATEGORIES) {
|
||||||
|
val expectedFileName = "${category}${JSON_SUFFIX}"
|
||||||
|
if (availableFiles.contains(expectedFileName)) {
|
||||||
|
Log.d("MyApplication", "✅ [$category]: $expectedFileName 存在")
|
||||||
|
} else {
|
||||||
|
Log.e("MyApplication", "❌ [$category]: $expectedFileName 不存在 - 将无法加载数据")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MyApplication", "验证分类文件失败", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initApplication() {
|
||||||
|
Log.d("MyApplication", "开始初始化应用")
|
||||||
|
initDatabase() // 1. 初始化数据库
|
||||||
|
preloadWallpaperData() // 2. 预加载固定的三个分类
|
||||||
|
configureImageLoader() // 3. 配置图片加载库
|
||||||
|
initAppLifecycleObserver() // 4. 初始化应用状态监听
|
||||||
|
initGlobalSettings() // 5. 其他全局初始化
|
||||||
|
Log.d("MyApplication", "应用初始化完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动内存监控
|
||||||
|
*/
|
||||||
|
private fun startMemoryMonitoring() {
|
||||||
|
applicationScope.launch {
|
||||||
|
while (true) {
|
||||||
|
kotlinx.coroutines.delay(60000)
|
||||||
|
|
||||||
|
val runtime = Runtime.getRuntime()
|
||||||
|
val usedMemoryMB = (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)
|
||||||
|
val maxMemoryMB = runtime.maxMemory() / (1024 * 1024)
|
||||||
|
val usagePercent = (usedMemoryMB.toFloat() / maxMemoryMB.toFloat()) * 100
|
||||||
|
|
||||||
|
Log.d("MemoryMonitor", "内存使用: ${"%.1f".format(usagePercent)}% ($usedMemoryMB/$maxMemoryMB MB)")
|
||||||
|
if (usagePercent > 85) {
|
||||||
|
Log.w("MemoryMonitor", "内存使用率超过85%,执行清理")
|
||||||
|
clearExcessCaches()
|
||||||
|
}
|
||||||
|
if (totalLoads > 0 && totalLoads % 200 == 0) {
|
||||||
|
logCacheStatistics()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录缓存统计
|
||||||
|
*/
|
||||||
|
private fun logCacheStatistics() {
|
||||||
|
val hitRate = if (totalLoads > 0) {
|
||||||
|
(cacheHits.toFloat() / totalLoads.toFloat()) * 100
|
||||||
|
} else 0f
|
||||||
|
|
||||||
|
val cachedCategories = wallpaperItemCache.snapshot().keys
|
||||||
|
val totalWallpapers = cachedCategories.sumOf { category ->
|
||||||
|
wallpaperItemCache.get(category)?.size ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("CacheStats", "=== 缓存统计 ===")
|
||||||
|
Log.d("CacheStats", "命中率: ${"%.1f".format(hitRate)}% ($cacheHits/$totalLoads)")
|
||||||
|
Log.d("CacheStats", "缓存分类数: ${cachedCategories.size}/$MAX_CACHE_CATEGORIES")
|
||||||
|
Log.d("CacheStats", "总壁纸数: $totalWallpapers 张")
|
||||||
|
Log.d("CacheStats", "缓存分类: ${cachedCategories.joinToString()}")
|
||||||
|
|
||||||
|
// 保存统计
|
||||||
|
getSharedPreferences(CACHE_STATS_KEY, Context.MODE_PRIVATE).edit()
|
||||||
|
.putInt("totalLoads", totalLoads)
|
||||||
|
.putInt("cacheHits", cacheHits)
|
||||||
|
.putLong("lastCheck", System.currentTimeMillis())
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理多余缓存
|
||||||
|
*/
|
||||||
|
private fun clearExcessCaches() {
|
||||||
|
Log.d("MemoryMonitor", "清理多余缓存...")
|
||||||
|
|
||||||
|
// 清理Glide内存缓存
|
||||||
|
Glide.get(this).clearMemory()
|
||||||
|
|
||||||
|
// 如果缓存分类超过限制,清理最少使用的
|
||||||
|
if (wallpaperItemCache.size() > MAX_CACHE_CATEGORIES) {
|
||||||
|
val snapshot = wallpaperItemCache.snapshot()
|
||||||
|
val keys = snapshot.keys.toList()
|
||||||
|
|
||||||
|
// 保留最近使用的,清理其他的(但不是全部清理)
|
||||||
|
val excessCount = keys.size - MAX_CACHE_CATEGORIES
|
||||||
|
if (excessCount > 0) {
|
||||||
|
for (i in 0 until excessCount) {
|
||||||
|
wallpaperItemCache.remove(keys[i])
|
||||||
|
wallpaperCache.remove(keys[i])
|
||||||
|
}
|
||||||
|
Log.d("MemoryMonitor", "清理了 $excessCount 个分类缓存")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (System.currentTimeMillis() % 5 == 0L) {
|
||||||
|
System.gc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化数据库
|
||||||
|
*/
|
||||||
|
private fun initDatabase() {
|
||||||
|
applicationScope.launch {
|
||||||
|
try {
|
||||||
|
favoriteManager = FavorWpManager.getInstance(this@MyApplication)
|
||||||
|
|
||||||
|
val favoriteCount = favoriteManager.getFavoriteCount()
|
||||||
|
Log.d("MyApplication", "✅ 数据库初始化完成")
|
||||||
|
Log.d("MyApplication", " 数据库路径: ${getDatabasePath("FavoriteWallpapers.db")?.absolutePath}")
|
||||||
|
Log.d("MyApplication", " 收藏数量: $favoriteCount")
|
||||||
|
|
||||||
|
if (favoriteCount > 0) {
|
||||||
|
Log.d("MyApplication", " 数据库中有 $favoriteCount 条收藏记录,数据已保留")
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MyApplication", "❌ 数据库初始化失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预加载壁纸数据(每个分类只加载200张)
|
||||||
|
*/
|
||||||
|
private fun preloadWallpaperData() {
|
||||||
|
applicationScope.launch {
|
||||||
|
try {
|
||||||
|
Log.d("MyApplication", "开始预加载壁纸数据(固定三个分类,每分类${appConfig.maxWallpapersPerCategory}张)")
|
||||||
|
|
||||||
|
// 只预加载固定的三个分类
|
||||||
|
FIXED_CATEGORIES.forEach { category ->
|
||||||
|
preloadWallpaperCategory(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MyApplication", "壁纸数据预加载完成")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MyApplication", "壁纸数据预加载失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预加载特定分类的壁纸数据(只加载200张)
|
||||||
|
*/
|
||||||
|
private fun preloadWallpaperCategory(category: String) {
|
||||||
|
applicationScope.launch {
|
||||||
|
try {
|
||||||
|
Log.d("MyApplication", "预加载固定分类: $category (限${appConfig.maxWallpapersPerCategory}张)")
|
||||||
|
|
||||||
|
val fileName = "${category}${JSON_SUFFIX}"
|
||||||
|
|
||||||
|
// JsonUtils 加载数据
|
||||||
|
val allWallpapers = Utils_json.loadWallpapersFromAssets(
|
||||||
|
this@MyApplication,
|
||||||
|
fileName
|
||||||
|
)
|
||||||
|
|
||||||
|
if (allWallpapers.isNotEmpty()) {
|
||||||
|
val limitedWallpapers = if (allWallpapers.size > appConfig.maxWallpapersPerCategory) {
|
||||||
|
Log.d("MyApplication", "分类 [$category] 有 ${allWallpapers.size} 张,只加载前 ${appConfig.maxWallpapersPerCategory} 张")
|
||||||
|
allWallpapers.take(appConfig.maxWallpapersPerCategory)
|
||||||
|
} else {
|
||||||
|
allWallpapers
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存原始 JSON 数据
|
||||||
|
synchronized(wallpaperCache) {
|
||||||
|
wallpaperCache.put(category, limitedWallpapers)
|
||||||
|
}
|
||||||
|
|
||||||
|
val wallpaperItems = limitedWallpapers.map { wallpaperJson ->
|
||||||
|
Utils_json.convertToWallpaperItem(wallpaperJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized(wallpaperItemCache) {
|
||||||
|
wallpaperItemCache.put(category, wallpaperItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MyApplication", "✅ 预加载分类 [$category] 完成,数量: ${limitedWallpapers.size}")
|
||||||
|
} else {
|
||||||
|
Log.w("MyApplication", "⚠️ 分类 [$category] 没有数据或加载失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MyApplication", "❌ 预加载分类 [$category] 失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取壁纸数据(优先从缓存获取,最多200张)
|
||||||
|
*/
|
||||||
|
suspend fun getWallpaperData(category: String): List<WallpaperJson> {
|
||||||
|
totalLoads++
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val categoryName = if (category.endsWith("_two.json")) {
|
||||||
|
category.replace("_two.json", "")
|
||||||
|
} else {
|
||||||
|
category
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先从缓存获取
|
||||||
|
synchronized(wallpaperCache) {
|
||||||
|
val cachedData = wallpaperCache.get(categoryName)
|
||||||
|
if (cachedData != null) {
|
||||||
|
cacheHits++
|
||||||
|
Log.d("MyApplication", "缓存命中: [$categoryName] 数量: ${cachedData.size}")
|
||||||
|
return@withContext cachedData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存中没有,则加载
|
||||||
|
Log.d("MyApplication", "缓存未命中: [$categoryName],开始加载")
|
||||||
|
val fileName = if (category.endsWith(".json")) category else "${categoryName}_two.json"
|
||||||
|
|
||||||
|
val allWallpapers = Utils_json.loadWallpapersFromAssets(
|
||||||
|
this@MyApplication,
|
||||||
|
fileName
|
||||||
|
)
|
||||||
|
val wallpapers = if (allWallpapers.size > appConfig.maxWallpapersPerCategory) {
|
||||||
|
Log.d("MyApplication", "分类 [$categoryName] 有 ${allWallpapers.size} 张,只返回前 ${appConfig.maxWallpapersPerCategory} 张")
|
||||||
|
allWallpapers.take(appConfig.maxWallpapersPerCategory)
|
||||||
|
} else {
|
||||||
|
allWallpapers
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新缓存(如果数据有效)
|
||||||
|
if (wallpapers.isNotEmpty()) {
|
||||||
|
synchronized(wallpaperCache) {
|
||||||
|
wallpaperCache.put(categoryName, wallpapers)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MyApplication", "已缓存分类: [$categoryName] 数量: ${wallpapers.size}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return@withContext wallpapers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取转换后的壁纸项目(优先从缓存获取,最多200张)
|
||||||
|
*/
|
||||||
|
suspend fun getWallpaperItems(category: String): List<WpItem> {
|
||||||
|
totalLoads++
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val categoryName = if (category.endsWith("_two.json")) {
|
||||||
|
category.replace("_two.json", "")
|
||||||
|
} else {
|
||||||
|
category
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先从缓存获取
|
||||||
|
synchronized(wallpaperItemCache) {
|
||||||
|
val cachedItems = wallpaperItemCache.get(categoryName)
|
||||||
|
if (cachedItems != null) {
|
||||||
|
cacheHits++
|
||||||
|
Log.d("MyApplication", "缓存命中: [$categoryName] 数量: ${cachedItems.size}")
|
||||||
|
return@withContext cachedItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存中没有,则获取数据并转换
|
||||||
|
val wallpapers = getWallpaperData(category)
|
||||||
|
val wallpaperItems = wallpapers.map { wallpaperJson ->
|
||||||
|
Utils_json.convertToWallpaperItem(wallpaperJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
if (wallpaperItems.isNotEmpty()) {
|
||||||
|
synchronized(wallpaperItemCache) {
|
||||||
|
wallpaperItemCache.put(categoryName, wallpaperItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return@withContext wallpaperItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 配置图片加载库
|
||||||
|
*/
|
||||||
|
private fun configureImageLoader() {
|
||||||
|
try {
|
||||||
|
val requestOptions = RequestOptions()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||||
|
.timeout(10000)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
|
||||||
|
Log.d("MyApplication", "✅ 图片加载库配置完成")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MyApplication", "❌ 图片加载库配置失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化应用生命周期监听
|
||||||
|
*/
|
||||||
|
private fun initAppLifecycleObserver() {
|
||||||
|
try {
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
|
||||||
|
Log.d("MyApplication", "✅ 应用生命周期监听已初始化")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MyApplication", "❌ 应用生命周期监听初始化失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化全局设置
|
||||||
|
*/
|
||||||
|
private fun initGlobalSettings() {
|
||||||
|
applicationScope.launch {
|
||||||
|
try {
|
||||||
|
initDefaultPreferences()
|
||||||
|
|
||||||
|
if (appConfig.enableAutoCleanup) {
|
||||||
|
cleanTempFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MyApplication", "✅ 全局设置初始化完成")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MyApplication", "❌ 全局设置初始化失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化默认的首选项设置
|
||||||
|
*/
|
||||||
|
private fun initDefaultPreferences() {
|
||||||
|
val sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
val isFirstLaunch = !sharedPrefs.contains(KEY_FIRST_LAUNCH) ||
|
||||||
|
sharedPrefs.getBoolean(KEY_FIRST_LAUNCH, true)
|
||||||
|
|
||||||
|
if (isFirstLaunch) {
|
||||||
|
Log.d("MyApplication", "🎉 首次启动应用")
|
||||||
|
}
|
||||||
|
|
||||||
|
with(sharedPrefs.edit()) {
|
||||||
|
if (!sharedPrefs.contains(KEY_FIRST_LAUNCH)) {
|
||||||
|
putBoolean(KEY_FIRST_LAUNCH, true)
|
||||||
|
}
|
||||||
|
if (!sharedPrefs.contains(KEY_DEFAULT_CATEGORY)) {
|
||||||
|
putString(KEY_DEFAULT_CATEGORY, "Film")
|
||||||
|
}
|
||||||
|
if (!sharedPrefs.contains(KEY_IMAGE_QUALITY)) {
|
||||||
|
putString(KEY_IMAGE_QUALITY, "balanced")
|
||||||
|
}
|
||||||
|
if (!sharedPrefs.contains(KEY_LAST_CLEANUP)) {
|
||||||
|
putLong(KEY_LAST_CLEANUP, System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MyApplication", "✅ 默认首选项设置完成")
|
||||||
|
|
||||||
|
if (isFirstLaunch) {
|
||||||
|
sharedPrefs.edit().putBoolean(KEY_FIRST_LAUNCH, false).apply()
|
||||||
|
Log.d("MyApplication", "已将首次启动标记设置为 false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理临时文件
|
||||||
|
*/
|
||||||
|
private fun cleanTempFiles() {
|
||||||
|
try {
|
||||||
|
val sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val lastCleanupTime = sharedPrefs.getLong(KEY_LAST_CLEANUP, 0)
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
|
||||||
|
if (currentTime - lastCleanupTime > 7 * 24 * 60 * 60 * 1000L) {
|
||||||
|
applicationScope.launch {
|
||||||
|
Glide.get(this@MyApplication).clearDiskCache()
|
||||||
|
|
||||||
|
val snapshot = wallpaperItemCache.snapshot()
|
||||||
|
val keys = snapshot.keys.toList()
|
||||||
|
|
||||||
|
if (keys.size > MAX_CACHE_CATEGORIES) {
|
||||||
|
for (i in 0 until keys.size - MAX_CACHE_CATEGORIES) {
|
||||||
|
wallpaperItemCache.remove(keys[i])
|
||||||
|
wallpaperCache.remove(keys[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedPrefs.edit().putLong(KEY_LAST_CLEANUP, currentTime).apply()
|
||||||
|
Log.d("MyApplication", "✅ 临时文件清理完成")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("MyApplication", "清理临时文件时出错: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用生命周期观察者
|
||||||
|
*/
|
||||||
|
inner class AppLifecycleObserver : LifecycleObserver {
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||||
|
fun onAppForegrounded() {
|
||||||
|
Log.d("MyApplication", "📱 应用进入前台")
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
|
fun onAppBackgrounded() {
|
||||||
|
Log.d("MyApplication", "📱 应用进入后台")
|
||||||
|
// 进入后台时,清理Glide内存缓存
|
||||||
|
Glide.get(this@MyApplication).clearMemory()
|
||||||
|
System.gc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当系统内存不足时调用
|
||||||
|
*/
|
||||||
|
override fun onTrimMemory(level: Int) {
|
||||||
|
super.onTrimMemory(level)
|
||||||
|
|
||||||
|
Log.d("MyApplication", "🔄 内存紧张,级别: $level")
|
||||||
|
|
||||||
|
when (level) {
|
||||||
|
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
|
||||||
|
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
|
||||||
|
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
|
||||||
|
// 清理Glide内存缓存
|
||||||
|
Glide.get(this).clearMemory()
|
||||||
|
|
||||||
|
// 清理应用缓存
|
||||||
|
synchronized(wallpaperCache) {
|
||||||
|
wallpaperCache.trimToSize(0)
|
||||||
|
}
|
||||||
|
synchronized(wallpaperItemCache) {
|
||||||
|
wallpaperItemCache.trimToSize(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强制垃圾回收
|
||||||
|
System.gc()
|
||||||
|
System.runFinalization()
|
||||||
|
|
||||||
|
Log.d("MyApplication", "清理了所有缓存")
|
||||||
|
}
|
||||||
|
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
|
||||||
|
ComponentCallbacks2.TRIM_MEMORY_MODERATE,
|
||||||
|
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
|
||||||
|
// 清理所有资源
|
||||||
|
synchronized(wallpaperCache) {
|
||||||
|
wallpaperCache.evictAll()
|
||||||
|
}
|
||||||
|
synchronized(wallpaperItemCache) {
|
||||||
|
wallpaperItemCache.evictAll()
|
||||||
|
}
|
||||||
|
Glide.get(this).clearMemory()
|
||||||
|
|
||||||
|
Log.d("MyApplication", "应用在后台,清理了所有资源")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当应用终止时调用
|
||||||
|
*/
|
||||||
|
override fun onTerminate() {
|
||||||
|
// 清理资源
|
||||||
|
synchronized(wallpaperCache) {
|
||||||
|
wallpaperCache.evictAll()
|
||||||
|
}
|
||||||
|
synchronized(wallpaperItemCache) {
|
||||||
|
wallpaperItemCache.evictAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消所有协程
|
||||||
|
applicationScope.coroutineContext.cancelChildren()
|
||||||
|
|
||||||
|
instance = null
|
||||||
|
super.onTerminate()
|
||||||
|
Log.d("MyApplication", "🛑 应用终止")
|
||||||
|
}
|
||||||
|
}
|
||||||
55
app/src/main/java/com/gallery/free/wallpaper/SplaActivity.kt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.ad.tradpluslibrary.TPAdManager
|
||||||
|
import com.gallery.free.wallpaper.databinding.ActivitySplaBinding
|
||||||
|
|
||||||
|
class SplaActivity : AppCompatActivity() {
|
||||||
|
private var countDownTimer: CountDownTimer? = null
|
||||||
|
private var vb: ActivitySplaBinding? = null
|
||||||
|
private val totalTime: Long = 15000
|
||||||
|
|
||||||
|
@SuppressLint("MissingInflatedId")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
vb = ActivitySplaBinding.inflate(layoutInflater)
|
||||||
|
Utils_com.initFull(this, true)
|
||||||
|
setContentView(vb?.getRoot())
|
||||||
|
TPAdManager.init(
|
||||||
|
this,
|
||||||
|
"ocean",
|
||||||
|
"EF4D3DDD83B3D1EF16ED3E6995AF0E11",
|
||||||
|
"58E5429FFDD5BDF2034D6D78B157C012",
|
||||||
|
"8DB63FE93F30FEA99D2D275BFC974C12",
|
||||||
|
"09E55308FBBE7CD2C0C5571EFCEB8312"
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
countDownTimer = TPAdManager.showWelcomeAd(this, totalTime, { millisUntilFinished ->
|
||||||
|
val progressPercentage = ((100 * millisUntilFinished) / totalTime).toInt()
|
||||||
|
|
||||||
|
val countdownPercentage = 100 - progressPercentage
|
||||||
|
|
||||||
|
vb?.progressbar?.progress = countdownPercentage
|
||||||
|
}) {
|
||||||
|
vb?.progressbar?.progress = 100
|
||||||
|
val intent = Intent(this@SplaActivity, MainActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
countDownTimer?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
if (countDownTimer != null) {
|
||||||
|
countDownTimer!!.cancel()
|
||||||
|
countDownTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
|
|
||||||
|
class StaggeredGridSpacingItemDecoration(
|
||||||
|
private val spanCount: Int,
|
||||||
|
private val spacing: Int
|
||||||
|
) : RecyclerView.ItemDecoration() {
|
||||||
|
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
val position = parent.getChildAdapterPosition(view)
|
||||||
|
val layoutParams = view.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||||
|
val spanIndex = layoutParams?.spanIndex ?: 0
|
||||||
|
|
||||||
|
// 根据列数动态计算间距
|
||||||
|
when (spanCount) {
|
||||||
|
2 -> {
|
||||||
|
// 两列布局
|
||||||
|
when (spanIndex) {
|
||||||
|
0 -> {
|
||||||
|
// 第一列:左边间距大,右边间距小
|
||||||
|
outRect.left = spacing
|
||||||
|
outRect.right = spacing / 2
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
// 第二列:左边间距小,右边间距大
|
||||||
|
outRect.left = spacing / 2
|
||||||
|
outRect.right = spacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 垂直方向间距
|
||||||
|
outRect.top = if (isFirstRow(position, spanCount)) 0 else spacing
|
||||||
|
outRect.bottom = spacing
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// 其他列数使用均匀间距
|
||||||
|
setupUniformSpacing(outRect, spanIndex, spanCount, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun setupUniformSpacing(
|
||||||
|
outRect: Rect,
|
||||||
|
spanIndex: Int,
|
||||||
|
spanCount: Int,
|
||||||
|
position: Int
|
||||||
|
) {
|
||||||
|
// 水平间距
|
||||||
|
val column = spanIndex % spanCount
|
||||||
|
|
||||||
|
// 左边距:第一列有完整间距,其他列使用一半间距
|
||||||
|
outRect.left = if (column == 0) spacing else spacing / 2
|
||||||
|
|
||||||
|
// 右边距:最后一列有完整间距,其他列使用一半间距
|
||||||
|
outRect.right = if (column == spanCount - 1) spacing else spacing / 2
|
||||||
|
|
||||||
|
// 垂直间距
|
||||||
|
outRect.top = if (isFirstRow(position, spanCount)) 0 else spacing
|
||||||
|
outRect.bottom = spacing
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isFirstRow(position: Int, spanCount: Int): Boolean {
|
||||||
|
return position < spanCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object GridDecorationUtils {
|
||||||
|
/**
|
||||||
|
* 为 RecyclerView 快速添加间距装饰器
|
||||||
|
*/
|
||||||
|
fun applySpacing(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
spacingDp: Int,
|
||||||
|
spanCount: Int = 2
|
||||||
|
) {
|
||||||
|
val spacingInPixels = recyclerView.context.resources
|
||||||
|
.getDimensionPixelSize(spacingDp)
|
||||||
|
|
||||||
|
recyclerView.addItemDecoration(
|
||||||
|
StaggeredGridSpacingItemDecoration(spanCount, spacingInPixels)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算网格列数
|
||||||
|
*/
|
||||||
|
fun calculateSpanCount(context: Context, itemWidthDp: Int = 180): Int {
|
||||||
|
val displayMetrics = context.resources.displayMetrics
|
||||||
|
val screenWidthPx = displayMetrics.widthPixels
|
||||||
|
val screenWidthDp = screenWidthPx / displayMetrics.density
|
||||||
|
|
||||||
|
return maxOf(2, (screenWidthDp / itemWidthDp).toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/src/main/java/com/gallery/free/wallpaper/Utils_com.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package com.gallery.free.wallpaper;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
public class Utils_com {
|
||||||
|
public static void initFull(Activity activity, boolean light) {
|
||||||
|
Window window = activity.getWindow();
|
||||||
|
View decorView = window.getDecorView();
|
||||||
|
if (light) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||||
|
}
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
app/src/main/java/com/gallery/free/wallpaper/Utils_json.kt
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
object Utils_json {
|
||||||
|
|
||||||
|
private val gson = Gson()
|
||||||
|
private const val TAG = "JsonUtils"
|
||||||
|
|
||||||
|
|
||||||
|
fun loadWallpapersFromAssets(context: Context, fileName: String): List<WallpaperJson> {
|
||||||
|
return try {
|
||||||
|
Log.d(TAG, "Loading wallpapers from: $fileName")
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
val fileList = context.assets.list("")
|
||||||
|
if (fileList?.contains(fileName) != true) {
|
||||||
|
Log.e(TAG, "File not found in assets: $fileName")
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val inputStream: InputStream = context.assets.open(fileName)
|
||||||
|
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
||||||
|
|
||||||
|
if (jsonString.isBlank()) {
|
||||||
|
Log.e(TAG, "Empty JSON file: $fileName")
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val listType = object : TypeToken<List<WallpaperJson>>() {}.type
|
||||||
|
val result = gson.fromJson<List<WallpaperJson>>(jsonString, listType)
|
||||||
|
|
||||||
|
Log.d(TAG, "Successfully loaded ${result.size} wallpapers from $fileName")
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error loading wallpapers from $fileName", e)
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertToWallpaperItem(wallpaperJson: WallpaperJson): WpItem {
|
||||||
|
val name = wallpaperJson.altDescription ?: "Unknown"
|
||||||
|
|
||||||
|
val smallUrl = wallpaperJson.urls.small
|
||||||
|
val fullUrl = wallpaperJson.urls.full
|
||||||
|
|
||||||
|
val finalSmallUrl = if (smallUrl.isEmpty()) {
|
||||||
|
wallpaperJson.urls.small
|
||||||
|
} else {
|
||||||
|
smallUrl
|
||||||
|
}
|
||||||
|
val description = wallpaperJson.altDescription ?: wallpaperJson.user.name ?: ""
|
||||||
|
|
||||||
|
return WpItem(
|
||||||
|
smallUrl = finalSmallUrl,
|
||||||
|
fullUrl = fullUrl,
|
||||||
|
description = description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
98
app/src/main/java/com/gallery/free/wallpaper/WpAdapter.kt
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
|
|
||||||
|
class WpAdapter(
|
||||||
|
private val wallpaperList: List<WpItem>,
|
||||||
|
private val onItemClick: (Int, WpItem) -> Unit
|
||||||
|
) : RecyclerView.Adapter<WpAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val imageView: ImageView = itemView.findViewById(R.id.imageView)
|
||||||
|
val descriptionText: TextView = itemView.findViewById(R.id.descriptionText)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_wp, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, @SuppressLint("RecyclerView") position: Int) {
|
||||||
|
val wallpaper = wallpaperList[position]
|
||||||
|
|
||||||
|
// 设置描述文字
|
||||||
|
val displayText = if (wallpaper.description.isNotEmpty()) {
|
||||||
|
wallpaper.description
|
||||||
|
} else {
|
||||||
|
"Unnamed Wallpaper"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 截断过长的描述文字
|
||||||
|
val maxLength = 50
|
||||||
|
val finalText = if (displayText.length > maxLength) {
|
||||||
|
displayText.substring(0, maxLength) + "..."
|
||||||
|
} else {
|
||||||
|
displayText
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.descriptionText.text = finalText
|
||||||
|
|
||||||
|
// 加载图片
|
||||||
|
if (wallpaper.smallUrl.isNotEmpty()) {
|
||||||
|
Glide.with(holder.itemView.context)
|
||||||
|
.load(wallpaper.smallUrl)
|
||||||
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
|
.addListener(object : RequestListener<Drawable> {
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable?>?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
Log.d("WallpaperAdapter", "Smallnail load failed: ${e?.message}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: Drawable?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable?>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
Log.d("WallpaperAdapter", "Thumbnail load successful")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.placeholder(R.drawable.placeho_homepage)
|
||||||
|
.error(R.drawable.placeho_homepage)
|
||||||
|
.centerCrop()
|
||||||
|
.into(holder.imageView)
|
||||||
|
} else {
|
||||||
|
// 如果 URL 为空,使用本地资源
|
||||||
|
holder.imageView.setImageResource(wallpaper.imageRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClick(position, wallpaper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = wallpaperList.size
|
||||||
|
}
|
||||||
39
app/src/main/java/com/gallery/free/wallpaper/WpItem.kt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
|
||||||
|
data class WpItem(
|
||||||
|
val smallUrl: String, // 缩略图URL
|
||||||
|
val fullUrl: String, // 全尺寸URL
|
||||||
|
val description: String = "", // 描述字段
|
||||||
|
val imageRes: Int = 0
|
||||||
|
) : Parcelable {
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
parcel.readString() ?: "",
|
||||||
|
parcel.readString() ?: "",
|
||||||
|
parcel.readString() ?: "",
|
||||||
|
parcel.readInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(smallUrl)
|
||||||
|
parcel.writeString(fullUrl)
|
||||||
|
parcel.writeString(description)
|
||||||
|
parcel.writeInt(imageRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<WpItem> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): WpItem {
|
||||||
|
return WpItem(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<WpItem?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/src/main/java/com/gallery/free/wallpaper/WpJson.kt
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class WallpaperJson(
|
||||||
|
@SerializedName("alt_description")
|
||||||
|
val altDescription: String?,
|
||||||
|
|
||||||
|
@SerializedName("links")
|
||||||
|
val links: Links,
|
||||||
|
|
||||||
|
@SerializedName("urls")
|
||||||
|
val urls: Urls,
|
||||||
|
|
||||||
|
@SerializedName("user")
|
||||||
|
val user: User
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Links(
|
||||||
|
@SerializedName("download")
|
||||||
|
val download: String,
|
||||||
|
|
||||||
|
@SerializedName("download_location")
|
||||||
|
val downloadLocation: String,
|
||||||
|
|
||||||
|
@SerializedName("html")
|
||||||
|
val html: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Urls(
|
||||||
|
@SerializedName("full")
|
||||||
|
val full: String,
|
||||||
|
|
||||||
|
@SerializedName("raw")
|
||||||
|
val raw: String,
|
||||||
|
|
||||||
|
@SerializedName("regular")
|
||||||
|
val regular: String,
|
||||||
|
|
||||||
|
@SerializedName("small")
|
||||||
|
val small: String,
|
||||||
|
|
||||||
|
@SerializedName("thumb")
|
||||||
|
val thumb: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class User(
|
||||||
|
@SerializedName("authorHtml")
|
||||||
|
val authorHtml: String,
|
||||||
|
|
||||||
|
@SerializedName("header_large")
|
||||||
|
val headerLarge: String,
|
||||||
|
|
||||||
|
@SerializedName("header_medium")
|
||||||
|
val headerMedium: String,
|
||||||
|
|
||||||
|
@SerializedName("header_small")
|
||||||
|
val headerSmall: String,
|
||||||
|
|
||||||
|
@SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@SerializedName("portfolio_url")
|
||||||
|
val portfolioUrl: String
|
||||||
|
)
|
||||||
162
app/src/main/java/com/gallery/free/wallpaper/WpPagerAdapter.kt
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package com.gallery.free.wallpaper
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
|
|
||||||
|
|
||||||
|
class WpPagerAdapter(
|
||||||
|
private val wallpaperItems: List<WpItem>,
|
||||||
|
private val loadListener: OnImageLoadListener? = null // 添加加载监听器
|
||||||
|
) : RecyclerView.Adapter<WpPagerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
// 添加加载监听接口
|
||||||
|
interface OnImageLoadListener {
|
||||||
|
fun onImageLoadSuccess(position: Int)
|
||||||
|
fun onImageLoadFailed(position: Int, error: Throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val imageView: ImageView = itemView.findViewById(R.id.pager_image_view)
|
||||||
|
val loadingProgress: ImageView = itemView.findViewById(R.id.loading_progress)
|
||||||
|
|
||||||
|
// 添加标识,防止错乱的UI更新
|
||||||
|
var currentPosition: Int = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_wp_pager, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RecyclerView")
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val wallpaperItem = wallpaperItems[position]
|
||||||
|
|
||||||
|
Log.d("WallpaperPagerAdapter", "onBindViewHolder position=$position")
|
||||||
|
|
||||||
|
// 设置当前position标识
|
||||||
|
holder.currentPosition = position
|
||||||
|
|
||||||
|
// 1. 立即显示loading
|
||||||
|
holder.loadingProgress.visibility = View.VISIBLE
|
||||||
|
holder.loadingProgress.bringToFront()
|
||||||
|
|
||||||
|
// 2. 立即显示smallUrl(使用单独的Target)
|
||||||
|
val smallUrlTarget = object : CustomTarget<Drawable>() {
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: android.graphics.drawable.Drawable,
|
||||||
|
transition: com.bumptech.glide.request.transition.Transition<in android.graphics.drawable.Drawable>?
|
||||||
|
) {
|
||||||
|
Log.d("WallpaperPagerAdapter", "smallUrl加载完成 position=$position")
|
||||||
|
// 确保还是当前position
|
||||||
|
if (holder.currentPosition == position) {
|
||||||
|
holder.imageView.setImageDrawable(resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: android.graphics.drawable.Drawable?) {
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFailed(errorDrawable: android.graphics.drawable.Drawable?) {
|
||||||
|
Log.e("WallpaperPagerAdapter", "smallUrl加载失败 position=$position")
|
||||||
|
if (holder.currentPosition == position && holder.itemView.isAttachedToWindow) {
|
||||||
|
holder.imageView.setBackgroundColor(R.color.light_gray)
|
||||||
|
holder.imageView.setImageResource(R.drawable.ic_loadingpic_failed)
|
||||||
|
holder.imageView.scaleType = ImageView.ScaleType.CENTER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 先清除可能的错误状态
|
||||||
|
holder.imageView.setBackgroundColor(0x00000000)
|
||||||
|
holder.imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
|
||||||
|
Glide.with(holder.itemView.context)
|
||||||
|
.load(wallpaperItem.smallUrl)
|
||||||
|
.dontAnimate()
|
||||||
|
.skipMemoryCache(true) // 跳过内存缓存,确保每次都触发回调
|
||||||
|
.into(smallUrlTarget)
|
||||||
|
|
||||||
|
// 3. 加载fullUrl
|
||||||
|
Glide.with(holder.itemView.context)
|
||||||
|
.load(wallpaperItem.fullUrl)
|
||||||
|
.dontAnimate()
|
||||||
|
.diskCacheStrategy(com.bumptech.glide.load.engine.DiskCacheStrategy.ALL)
|
||||||
|
.listener(object : RequestListener<android.graphics.drawable.Drawable> {
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<android.graphics.drawable.Drawable>?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
Log.e("WallpaperPagerAdapter", "fullUrl加载失败 position=$position: ${e?.message}")
|
||||||
|
if (holder.currentPosition == position && holder.itemView.isAttachedToWindow) {
|
||||||
|
holder.loadingProgress.visibility = View.GONE
|
||||||
|
|
||||||
|
// 自定义错误显示:设置灰色背景和居中图标
|
||||||
|
holder.imageView.setBackgroundColor(R.color.light_gray)
|
||||||
|
holder.imageView.setImageResource(R.drawable.ic_loadingpic_failed)
|
||||||
|
holder.imageView.scaleType = ImageView.ScaleType.CENTER
|
||||||
|
}
|
||||||
|
// 通知加载失败
|
||||||
|
loadListener?.onImageLoadFailed(position, e ?: Exception("图片加载失败"))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: android.graphics.drawable.Drawable?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<android.graphics.drawable.Drawable>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
Log.d("WallpaperPagerAdapter", "fullUrl加载完成 position=$position")
|
||||||
|
if (holder.currentPosition == position && holder.itemView.isAttachedToWindow) {
|
||||||
|
holder.loadingProgress.visibility = View.GONE
|
||||||
|
// 清除可能的错误状态
|
||||||
|
holder.imageView.setBackgroundColor(0x00000000) // 透明背景
|
||||||
|
holder.imageView.scaleType = ImageView.ScaleType.CENTER_CROP // 恢复原来的缩放模式
|
||||||
|
}
|
||||||
|
// 通知加载成功
|
||||||
|
loadListener?.onImageLoadSuccess(position)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(holder.imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
|
super.onViewRecycled(holder)
|
||||||
|
|
||||||
|
// 清理ImageView,Glide会自动取消相关请求
|
||||||
|
Glide.with(holder.itemView.context).clear(holder.imageView)
|
||||||
|
|
||||||
|
// 重置标识
|
||||||
|
holder.currentPosition = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = wallpaperItems.size
|
||||||
|
|
||||||
|
// 添加重新加载方法
|
||||||
|
fun retryLoadImage(position: Int) {
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAllCache() {
|
||||||
|
// 如果确实需要清除所有缓存,可以在Activity中调用但通常不需要手动清理
|
||||||
|
// Glide.get(context).clearMemory()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,263 @@
|
|||||||
|
package com.gallery.free.wallpaper.database
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
|
||||||
|
class FavorWpDbHelper(context: Context) :
|
||||||
|
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DATABASE_VERSION = 2
|
||||||
|
const val DATABASE_NAME = "FavoriteWallpapers.db"
|
||||||
|
|
||||||
|
const val TABLE_NAME = "favorite_wallpapers"
|
||||||
|
const val COLUMN_ID = "id"
|
||||||
|
const val COLUMN_IMAGE_URL = "image_url"
|
||||||
|
const val COLUMN_DESCRIPTION = "description"
|
||||||
|
const val COLUMN_TIMESTAMP = "timestamp"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
|
val createTable = """
|
||||||
|
CREATE TABLE $TABLE_NAME (
|
||||||
|
$COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
$COLUMN_IMAGE_URL TEXT UNIQUE NOT NULL,
|
||||||
|
$COLUMN_DESCRIPTION TEXT DEFAULT '',
|
||||||
|
$COLUMN_TIMESTAMP INTEGER DEFAULT (strftime('%s','now'))
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
db.execSQL(createTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
if (oldVersion < 2) {
|
||||||
|
db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN $COLUMN_DESCRIPTION TEXT DEFAULT ''")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加壁纸到收藏
|
||||||
|
fun addFavorite(imageUrl: String, description: String = ""): Boolean {
|
||||||
|
val db = writableDatabase
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(COLUMN_IMAGE_URL, imageUrl)
|
||||||
|
if (description.isNotEmpty()) {
|
||||||
|
put(COLUMN_DESCRIPTION, description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val result = db.insertWithOnConflict(
|
||||||
|
TABLE_NAME,
|
||||||
|
null,
|
||||||
|
values,
|
||||||
|
SQLiteDatabase.CONFLICT_IGNORE
|
||||||
|
)
|
||||||
|
result != -1L
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新已存在的收藏信息(更新描述)
|
||||||
|
fun updateFavoriteDescription(imageUrl: String, description: String): Boolean {
|
||||||
|
val db = writableDatabase
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(COLUMN_DESCRIPTION, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val result = db.update(
|
||||||
|
TABLE_NAME,
|
||||||
|
values,
|
||||||
|
"$COLUMN_IMAGE_URL = ?",
|
||||||
|
arrayOf(imageUrl)
|
||||||
|
)
|
||||||
|
result > 0
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从收藏中移除壁纸
|
||||||
|
fun removeFavorite(imageUrl: String): Boolean {
|
||||||
|
val db = writableDatabase
|
||||||
|
return try {
|
||||||
|
val result = db.delete(
|
||||||
|
TABLE_NAME,
|
||||||
|
"$COLUMN_IMAGE_URL = ?",
|
||||||
|
arrayOf(imageUrl)
|
||||||
|
)
|
||||||
|
result > 0
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查壁纸是否在收藏中
|
||||||
|
fun isFavorite(imageUrl: String): Boolean {
|
||||||
|
val db = readableDatabase
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
|
||||||
|
return try {
|
||||||
|
cursor = db.query(
|
||||||
|
TABLE_NAME,
|
||||||
|
arrayOf(COLUMN_IMAGE_URL),
|
||||||
|
"$COLUMN_IMAGE_URL = ?",
|
||||||
|
arrayOf(imageUrl),
|
||||||
|
null, null, null
|
||||||
|
)
|
||||||
|
cursor?.count ?: 0 > 0
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FavoriteInfo(
|
||||||
|
val imageUrl: String,
|
||||||
|
val description: String
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取所有收藏的壁纸信息(URL和描述)
|
||||||
|
fun getAllFavoritesWithInfo(): List<FavoriteInfo> {
|
||||||
|
val db = readableDatabase
|
||||||
|
val favorites = mutableListOf<FavoriteInfo>()
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
cursor = db.query(
|
||||||
|
TABLE_NAME,
|
||||||
|
arrayOf(COLUMN_IMAGE_URL, COLUMN_DESCRIPTION),
|
||||||
|
null, null, null, null,
|
||||||
|
"$COLUMN_TIMESTAMP DESC"
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor?.let {
|
||||||
|
val urlIndex = it.getColumnIndex(COLUMN_IMAGE_URL)
|
||||||
|
val descIndex = it.getColumnIndex(COLUMN_DESCRIPTION)
|
||||||
|
while (it.moveToNext()) {
|
||||||
|
if (urlIndex != -1) {
|
||||||
|
val url = it.getString(urlIndex)
|
||||||
|
val description = if (descIndex != -1) it.getString(descIndex) else ""
|
||||||
|
favorites.add(FavoriteInfo(url, description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return favorites
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有收藏的壁纸URL(保持向后兼容)
|
||||||
|
fun getAllFavorites(): List<String> {
|
||||||
|
val db = readableDatabase
|
||||||
|
val favorites = mutableListOf<String>()
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
cursor = db.query(
|
||||||
|
TABLE_NAME,
|
||||||
|
arrayOf(COLUMN_IMAGE_URL),
|
||||||
|
null, null, null, null,
|
||||||
|
"$COLUMN_TIMESTAMP DESC"
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor?.let {
|
||||||
|
val urlIndex = it.getColumnIndex(COLUMN_IMAGE_URL)
|
||||||
|
while (it.moveToNext()) {
|
||||||
|
if (urlIndex != -1) {
|
||||||
|
favorites.add(it.getString(urlIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return favorites
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取收藏数量
|
||||||
|
fun getFavoriteCount(): Int {
|
||||||
|
val db = readableDatabase
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
|
||||||
|
return try {
|
||||||
|
cursor = db.rawQuery("SELECT COUNT(*) FROM $TABLE_NAME", null)
|
||||||
|
if (cursor?.moveToFirst() == true) {
|
||||||
|
cursor.getInt(0)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
0
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有指定URL的收藏
|
||||||
|
fun hasFavorite(imageUrl: String): Boolean {
|
||||||
|
return isFavorite(imageUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量获取收藏状态
|
||||||
|
fun getFavoriteStatus(urls: List<String>): Map<String, Boolean> {
|
||||||
|
val db = readableDatabase
|
||||||
|
val result = mutableMapOf<String, Boolean>()
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 为每个URL创建一个占位符
|
||||||
|
val placeholders = urls.joinToString(",") { "?" }
|
||||||
|
val query = """
|
||||||
|
SELECT $COLUMN_IMAGE_URL
|
||||||
|
FROM $TABLE_NAME
|
||||||
|
WHERE $COLUMN_IMAGE_URL IN ($placeholders)
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
cursor = db.rawQuery(query, urls.toTypedArray())
|
||||||
|
|
||||||
|
while (cursor?.moveToNext() == true) {
|
||||||
|
val urlIndex = cursor.getColumnIndex(COLUMN_IMAGE_URL)
|
||||||
|
if (urlIndex != -1) {
|
||||||
|
result[cursor.getString(urlIndex)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置不在结果中的URL为false
|
||||||
|
urls.forEach { url ->
|
||||||
|
if (!result.containsKey(url)) {
|
||||||
|
result[url] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package com.gallery.free.wallpaper.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
class FavorWpManager private constructor(context: Context) {
|
||||||
|
|
||||||
|
private val dbHelper = FavorWpDbHelper(context)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: FavorWpManager? = null
|
||||||
|
|
||||||
|
fun getInstance(context: Context): FavorWpManager {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
val instance = FavorWpManager(context)
|
||||||
|
INSTANCE = instance
|
||||||
|
instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加收藏,包含描述
|
||||||
|
fun addFavorite(imageUrl: String, description: String = ""): Boolean {
|
||||||
|
return dbHelper.addFavorite(imageUrl, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新收藏描述
|
||||||
|
fun updateFavoriteDescription(imageUrl: String, description: String): Boolean {
|
||||||
|
return dbHelper.updateFavoriteDescription(imageUrl, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加或更新收藏(如果存在则更新描述)
|
||||||
|
fun addOrUpdateFavorite(imageUrl: String, description: String = ""): Boolean {
|
||||||
|
return if (isFavorite(imageUrl)) {
|
||||||
|
updateFavoriteDescription(imageUrl, description)
|
||||||
|
} else {
|
||||||
|
addFavorite(imageUrl, description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFavorite(imageUrl: String): Boolean {
|
||||||
|
return dbHelper.removeFavorite(imageUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isFavorite(imageUrl: String): Boolean {
|
||||||
|
return dbHelper.isFavorite(imageUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有收藏的URL(向后兼容)
|
||||||
|
fun getAllFavorites(): List<String> {
|
||||||
|
return dbHelper.getAllFavorites()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有收藏的详细信息
|
||||||
|
fun getAllFavoritesWithInfo(): List<FavorWpDbHelper.FavoriteInfo> {
|
||||||
|
return dbHelper.getAllFavoritesWithInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFavoriteCount(): Int {
|
||||||
|
return dbHelper.getFavoriteCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,261 @@
|
|||||||
|
package com.gallery.free.wallpaper.fragment
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.Build
|
||||||
|
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 androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
|
import com.gallery.free.wallpaper.DetailwpActivity
|
||||||
|
import com.gallery.free.wallpaper.R
|
||||||
|
import com.gallery.free.wallpaper.StaggeredGridSpacingItemDecoration
|
||||||
|
import com.gallery.free.wallpaper.WpAdapter
|
||||||
|
import com.gallery.free.wallpaper.WpItem
|
||||||
|
import com.gallery.free.wallpaper.database.FavorWpDbHelper
|
||||||
|
import com.gallery.free.wallpaper.database.FavorWpManager
|
||||||
|
import com.gallery.free.wallpaper.databinding.FragmentColleBinding
|
||||||
|
|
||||||
|
class ColleFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentColleBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var favoriteManager: FavorWpManager
|
||||||
|
private var adapter: WpAdapter? = null
|
||||||
|
private var currentFavoriteInfos: List<FavorWpDbHelper.FavoriteInfo> = emptyList()
|
||||||
|
|
||||||
|
// 存储当前显示的壁纸项列表
|
||||||
|
private var currentWallpaperItems: List<WpItem> = emptyList()
|
||||||
|
|
||||||
|
// 添加广播接收器
|
||||||
|
private val favoriteChangeReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
when (intent?.action) {
|
||||||
|
DetailwpActivity.Companion.ACTION_FAVORITE_CHANGED -> {
|
||||||
|
Log.d("CollectionFragment", "收到收藏变更广播")
|
||||||
|
activity?.runOnUiThread {
|
||||||
|
refreshDataFromDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(): ColleFragment {
|
||||||
|
return ColleFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentColleBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
// 初始化数据库管理
|
||||||
|
favoriteManager = FavorWpManager.Companion.getInstance(requireContext())
|
||||||
|
|
||||||
|
// 注册广播接收器
|
||||||
|
registerFavoriteChangeReceiver()
|
||||||
|
|
||||||
|
setupWallpaperGrid()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
|
private fun registerFavoriteChangeReceiver() {
|
||||||
|
val filter = IntentFilter(DetailwpActivity.Companion.ACTION_FAVORITE_CHANGED)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
requireActivity().registerReceiver(favoriteChangeReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||||
|
} else {
|
||||||
|
requireActivity().registerReceiver(favoriteChangeReceiver, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unregisterFavoriteChangeReceiver() {
|
||||||
|
try {
|
||||||
|
requireActivity().unregisterReceiver(favoriteChangeReceiver)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
// 如果接收器未注册,忽略异常
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupWallpaperGrid() {
|
||||||
|
// 获取收藏列表(包含详细信息)
|
||||||
|
val newFavoriteInfos = favoriteManager.getAllFavoritesWithInfo()
|
||||||
|
|
||||||
|
// 检查是否有变化,没有变化则不重新加载
|
||||||
|
if (newFavoriteInfos == currentFavoriteInfos && adapter != null) {
|
||||||
|
Log.d("CollectionFragment", "收藏无变化,跳过重新加载")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFavoriteInfos = newFavoriteInfos
|
||||||
|
updateFavoriteCount(newFavoriteInfos.size)
|
||||||
|
|
||||||
|
if (newFavoriteInfos.isEmpty()) {
|
||||||
|
showEmptyState()
|
||||||
|
} else {
|
||||||
|
showWallpaperGrid(newFavoriteInfos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLikedWallpapers(favoriteInfos: List<FavorWpDbHelper.FavoriteInfo>): List<WpItem> {
|
||||||
|
// 创建完整的 WpItem 对象列表
|
||||||
|
val wallpaperItems = favoriteInfos.map { info ->
|
||||||
|
// 使用描述,如果描述为空则使用默认文本
|
||||||
|
val displayDescription = if (info.description.isNotEmpty()) {
|
||||||
|
info.description
|
||||||
|
} else {
|
||||||
|
"Collected Wallpaper"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建完整的 WpItem 对象
|
||||||
|
// 注意:收藏的壁纸只有完整的URL,所以smallUrl和fullUrl使用相同的URL
|
||||||
|
WpItem(
|
||||||
|
smallUrl = info.imageUrl,
|
||||||
|
fullUrl = info.imageUrl,
|
||||||
|
description = displayDescription
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储当前显示的壁纸项
|
||||||
|
currentWallpaperItems = wallpaperItems
|
||||||
|
|
||||||
|
return wallpaperItems
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新收藏数量显示
|
||||||
|
private fun updateFavoriteCount(count: Int) {
|
||||||
|
if (count > 0) {
|
||||||
|
binding.textFavoriteCount.text = "$count photo collected"
|
||||||
|
binding.textFavoriteCountContainer.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.textFavoriteCountContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEmptyState() {
|
||||||
|
val emptyLayout = binding.root.findViewById<View>(R.id.empty_likes_layout)
|
||||||
|
emptyLayout?.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
binding.wallpaperRecyclerView.visibility = View.GONE
|
||||||
|
binding.textFavoriteCountContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showWallpaperGrid(favoriteInfos: List<FavorWpDbHelper.FavoriteInfo>) {
|
||||||
|
val emptyLayout = binding.root.findViewById<View>(R.id.empty_likes_layout)
|
||||||
|
emptyLayout?.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.wallpaperRecyclerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
val wallpaperList = getLikedWallpapers(favoriteInfos)
|
||||||
|
|
||||||
|
// 使用StaggeredGridLayoutManager瀑布流,每行2列
|
||||||
|
val spanCount = 2
|
||||||
|
val layoutManager = StaggeredGridLayoutManager(
|
||||||
|
spanCount,
|
||||||
|
StaggeredGridLayoutManager.VERTICAL
|
||||||
|
)
|
||||||
|
|
||||||
|
layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE
|
||||||
|
|
||||||
|
// 只在第一次或布局管理器为空时设置
|
||||||
|
if (binding.wallpaperRecyclerView.layoutManager == null) {
|
||||||
|
binding.wallpaperRecyclerView.layoutManager = layoutManager
|
||||||
|
|
||||||
|
// 使用StaggeredGridSpacingItemDecoration来设置间距
|
||||||
|
val spacingInPixels = resources.getDimensionPixelSize(R.dimen.grid_spacing)
|
||||||
|
binding.wallpaperRecyclerView.addItemDecoration(
|
||||||
|
StaggeredGridSpacingItemDecoration(spanCount, spacingInPixels)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 传递 WpItem 对象列表,而不是URL列表
|
||||||
|
adapter = WpAdapter(wallpaperList) { position, wallpaper ->
|
||||||
|
// 检查是否有数据
|
||||||
|
if (currentWallpaperItems.isEmpty()) return@WpAdapter
|
||||||
|
|
||||||
|
// 获取所有壁纸的完整 WpItem 列表
|
||||||
|
val intent = Intent(requireContext(), DetailwpActivity::class.java).apply {
|
||||||
|
putExtra("wallpaper_url", wallpaper.fullUrl)
|
||||||
|
putExtra("wallpaper_description", wallpaper.description)
|
||||||
|
// 传递完整的 WpItem 列表
|
||||||
|
putParcelableArrayListExtra("wallpaper_item_list", ArrayList(currentWallpaperItems))
|
||||||
|
putExtra("current_position", position)
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
requireActivity().overridePendingTransition(
|
||||||
|
android.R.anim.fade_in,
|
||||||
|
android.R.anim.fade_out
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.wallpaperRecyclerView.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
Log.d("CollectionFragment", "onResume 调用")
|
||||||
|
|
||||||
|
// 只检查是否有变化,不强制重新加载
|
||||||
|
val newFavoriteInfos = favoriteManager.getAllFavoritesWithInfo()
|
||||||
|
|
||||||
|
if (newFavoriteInfos != currentFavoriteInfos) {
|
||||||
|
Log.d("CollectionFragment", "收藏有变化,重新加载")
|
||||||
|
// 如果收藏有变化,才重新加载
|
||||||
|
currentFavoriteInfos = newFavoriteInfos
|
||||||
|
updateFavoriteCount(newFavoriteInfos.size)
|
||||||
|
|
||||||
|
if (newFavoriteInfos.isEmpty()) {
|
||||||
|
showEmptyState()
|
||||||
|
} else {
|
||||||
|
showWallpaperGrid(newFavoriteInfos)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("CollectionFragment", "onResume 中收藏无变化")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接从数据库刷新数据的方法
|
||||||
|
private fun refreshDataFromDatabase() {
|
||||||
|
Log.d("CollectionFragment", "从数据库刷新数据")
|
||||||
|
val newFavoriteInfos = favoriteManager.getAllFavoritesWithInfo()
|
||||||
|
|
||||||
|
// 即使没有变化也强制更新UI,确保数据同步
|
||||||
|
currentFavoriteInfos = newFavoriteInfos
|
||||||
|
updateFavoriteCount(newFavoriteInfos.size)
|
||||||
|
|
||||||
|
if (newFavoriteInfos.isEmpty()) {
|
||||||
|
showEmptyState()
|
||||||
|
} else {
|
||||||
|
showWallpaperGrid(newFavoriteInfos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
|
||||||
|
// 注销广播接收器
|
||||||
|
unregisterFavoriteChangeReceiver()
|
||||||
|
|
||||||
|
// 清除适配器和数据
|
||||||
|
adapter = null
|
||||||
|
binding.wallpaperRecyclerView.adapter = null
|
||||||
|
currentWallpaperItems = emptyList() // 清理数据
|
||||||
|
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,333 @@
|
|||||||
|
package com.gallery.free.wallpaper.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.OvershootInterpolator
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import com.gallery.free.wallpaper.HoPagerAdapter
|
||||||
|
import com.gallery.free.wallpaper.MyApplication
|
||||||
|
import com.gallery.free.wallpaper.MyApplication.Companion.FIXED_CATEGORIES
|
||||||
|
import com.gallery.free.wallpaper.R
|
||||||
|
import com.gallery.free.wallpaper.databinding.FragmentHoBinding
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class HoFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentHoBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var viewPager: ViewPager2
|
||||||
|
private lateinit var pagerAdapter: HoPagerAdapter
|
||||||
|
|
||||||
|
private var currentViewPagerPosition = 0
|
||||||
|
private val VIEW_PAGER_POSITION_KEY = "view_pager_position"
|
||||||
|
|
||||||
|
// 刷新状态
|
||||||
|
private var isRefreshing = false
|
||||||
|
|
||||||
|
// 添加协程作用域
|
||||||
|
private val fragmentScope = CoroutineScope(Dispatchers.Main + Job())
|
||||||
|
private var categories: List<String> = FIXED_CATEGORIES
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "HomeFragment"
|
||||||
|
|
||||||
|
fun newInstance(): HoFragment {
|
||||||
|
return HoFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentHoBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
// 恢复状态
|
||||||
|
savedInstanceState?.let {
|
||||||
|
currentViewPagerPosition = it.getInt(VIEW_PAGER_POSITION_KEY, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先异步加载分类数据
|
||||||
|
loadCategoriesAndSetupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步加载分类数据并设置UI
|
||||||
|
*/
|
||||||
|
private fun loadCategoriesAndSetupUI() {
|
||||||
|
fragmentScope.launch {
|
||||||
|
try {
|
||||||
|
// 使用固定分类
|
||||||
|
categories = FIXED_CATEGORIES
|
||||||
|
Log.d(TAG, "使用固定分类: $categories")
|
||||||
|
|
||||||
|
// 初始化 ViewPager 和适配器
|
||||||
|
setupViewPager()
|
||||||
|
setupTabs()
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "初始化分类失败,使用默认分类", e)
|
||||||
|
categories = FIXED_CATEGORIES
|
||||||
|
setupViewPager()
|
||||||
|
setupTabs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置按钮和状态
|
||||||
|
setupRefreshButton()
|
||||||
|
setupScrollToTopButton()
|
||||||
|
|
||||||
|
binding.viewPager.post {
|
||||||
|
if (currentViewPagerPosition > 0 && _binding != null) {
|
||||||
|
binding.viewPager.setCurrentItem(currentViewPagerPosition, false)
|
||||||
|
}
|
||||||
|
updateScrollToTopButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putInt(VIEW_PAGER_POSITION_KEY, binding.viewPager.currentItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViewPager() {
|
||||||
|
// 传递分类列表给适配器
|
||||||
|
pagerAdapter = HoPagerAdapter(this, categories)
|
||||||
|
viewPager = binding.viewPager
|
||||||
|
viewPager.adapter = pagerAdapter
|
||||||
|
viewPager.offscreenPageLimit = 2
|
||||||
|
|
||||||
|
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
super.onPageSelected(position)
|
||||||
|
currentViewPagerPosition = position
|
||||||
|
animateTabSelection(position)
|
||||||
|
// 切换标签页时更新滚动状态
|
||||||
|
updateScrollToTopButton()
|
||||||
|
|
||||||
|
// 异步预加载相邻页面的数据
|
||||||
|
preloadAdjacentPages(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
|
super.onPageScrollStateChanged(state)
|
||||||
|
if (state == ViewPager2.SCROLL_STATE_IDLE) {
|
||||||
|
updateScrollToTopButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预加载相邻页面的数据
|
||||||
|
*/
|
||||||
|
private fun preloadAdjacentPages(currentPosition: Int) {
|
||||||
|
val myApp = requireActivity().application as? MyApplication ?: return
|
||||||
|
|
||||||
|
fragmentScope.launch {
|
||||||
|
try {
|
||||||
|
// 预加载前一个页面
|
||||||
|
if (currentPosition > 0) {
|
||||||
|
val prevCategory = categories[currentPosition - 1]
|
||||||
|
val prevFileName = "${prevCategory}_two.json"
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
myApp.getWallpaperItems(prevFileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预加载后一个页面
|
||||||
|
if (currentPosition < categories.size - 1) {
|
||||||
|
val nextCategory = categories[currentPosition + 1]
|
||||||
|
val nextFileName = "${nextCategory}_two.json"
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
myApp.getWallpaperItems(nextFileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "预加载相邻页面数据失败", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupTabs() {
|
||||||
|
val tabLayout = binding.tabLayout
|
||||||
|
val tabTitles = categories.toTypedArray()
|
||||||
|
|
||||||
|
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||||
|
tab.text = tabTitles[position]
|
||||||
|
}.attach()
|
||||||
|
|
||||||
|
// 初始选中第一个标签
|
||||||
|
animateTabSelection(currentViewPagerPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRefreshButton() {
|
||||||
|
binding.btnRefresh.setOnClickListener {
|
||||||
|
if (!isRefreshing) {
|
||||||
|
startRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupScrollToTopButton() {
|
||||||
|
binding.btnScrollToTop.setOnClickListener {
|
||||||
|
scrollToTop()
|
||||||
|
}
|
||||||
|
binding.btnScrollToTop.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startRefresh() {
|
||||||
|
isRefreshing = true
|
||||||
|
binding.btnRefresh.isEnabled = false
|
||||||
|
|
||||||
|
startRefreshAnimation()
|
||||||
|
|
||||||
|
// 获取当前 Fragment 并刷新
|
||||||
|
val currentFragment = getCurrentFragment()
|
||||||
|
if (currentFragment != null) {
|
||||||
|
Log.d(TAG, "开始刷新,当前Fragment: $currentFragment")
|
||||||
|
currentFragment.refreshWallpapers {
|
||||||
|
stopRefresh()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "获取不到当前Fragment,无法刷新")
|
||||||
|
stopRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopRefresh() {
|
||||||
|
isRefreshing = false
|
||||||
|
if (_binding != null) {
|
||||||
|
binding.btnRefresh.isEnabled = true
|
||||||
|
}
|
||||||
|
stopRefreshAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startRefreshAnimation() {
|
||||||
|
if (_binding != null) {
|
||||||
|
binding.btnRefresh.setImageResource(R.drawable.loading_animation)
|
||||||
|
}
|
||||||
|
Log.d(TAG, "开始刷新动画")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopRefreshAnimation() {
|
||||||
|
if (_binding != null) {
|
||||||
|
binding.btnRefresh.setImageResource(R.drawable.ic_refreshit)
|
||||||
|
}
|
||||||
|
Log.d(TAG, "停止刷新动画")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollToTop() {
|
||||||
|
val currentFragment = getCurrentFragment()
|
||||||
|
if (currentFragment != null) {
|
||||||
|
Log.d(TAG, "滚动到顶部,当前Fragment: $currentFragment")
|
||||||
|
currentFragment.scrollToTop()
|
||||||
|
// 隐藏回到顶部按钮
|
||||||
|
if (_binding != null) {
|
||||||
|
binding.btnScrollToTop.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "获取不到当前Fragment,无法滚动到顶部")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCurrentFragment(): WpGridFragment? {
|
||||||
|
if (!isAdded || _binding == null) return null
|
||||||
|
|
||||||
|
return try {
|
||||||
|
// 通过适配器获取当前 Fragment
|
||||||
|
val fragment = pagerAdapter.getFragment(viewPager.currentItem)
|
||||||
|
if (fragment == null) {
|
||||||
|
Log.w(TAG, "通过适配器获取Fragment失败,尝试备用方法")
|
||||||
|
// 备用方法:通过ViewPager的当前项获取Fragment
|
||||||
|
val fragmentTag = "f${viewPager.currentItem}"
|
||||||
|
childFragmentManager.findFragmentByTag(fragmentTag) as? WpGridFragment
|
||||||
|
} else {
|
||||||
|
fragment
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
Log.e(TAG, "获取Fragment异常: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateScrollToTopButton() {
|
||||||
|
// 检查绑定是否有效
|
||||||
|
if (!isAdded || _binding == null) {
|
||||||
|
Log.d(TAG, "HomeFragment 未附加或绑定为空,跳过更新滚动按钮")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentFragment = getCurrentFragment()
|
||||||
|
if (currentFragment != null) {
|
||||||
|
Log.d(TAG, "更新滚动按钮状态,当前Fragment: $currentFragment")
|
||||||
|
currentFragment.setOnScrollListener { canScrollUp ->
|
||||||
|
// 再次检查绑定是否仍然有效
|
||||||
|
if (!isAdded || _binding == null) return@setOnScrollListener
|
||||||
|
|
||||||
|
val shouldShowButton = canScrollUp
|
||||||
|
binding.btnScrollToTop.visibility = if (shouldShowButton) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
Log.d(TAG, "滚动状态改变: canScrollUp = $canScrollUp, 按钮可见性 = ${if (shouldShowButton) "VISIBLE" else "GONE"}")
|
||||||
|
}
|
||||||
|
currentFragment.checkCurrentScrollState()
|
||||||
|
} else {
|
||||||
|
binding.btnScrollToTop.visibility = View.GONE
|
||||||
|
Log.w(TAG, "获取不到当前Fragment,隐藏回到顶部按钮")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateTabSelection(position: Int) {
|
||||||
|
if (!isAdded || _binding == null) return
|
||||||
|
|
||||||
|
val tabCount = binding.tabLayout.tabCount
|
||||||
|
|
||||||
|
for (i in 0 until tabCount) {
|
||||||
|
val tab = binding.tabLayout.getTabAt(i)
|
||||||
|
val tabView = tab?.view
|
||||||
|
|
||||||
|
if (i == position) {
|
||||||
|
tabView?.animate()
|
||||||
|
?.scaleX(1.1f)
|
||||||
|
?.scaleY(1.1f)
|
||||||
|
?.setDuration(200)
|
||||||
|
?.setInterpolator(OvershootInterpolator())
|
||||||
|
?.start()
|
||||||
|
} else {
|
||||||
|
tabView?.animate()
|
||||||
|
?.scaleX(1.0f)
|
||||||
|
?.scaleY(1.0f)
|
||||||
|
?.setDuration(150)
|
||||||
|
?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScrollToTopButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
stopRefreshAnimation()
|
||||||
|
_binding = null
|
||||||
|
|
||||||
|
// 取消所有协程
|
||||||
|
fragmentScope.coroutineContext.cancelChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,356 @@
|
|||||||
|
package com.gallery.free.wallpaper.fragment
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.R
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.gallery.free.wallpaper.LocalPreActivity
|
||||||
|
import com.gallery.free.wallpaper.MainActivity
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
|
||||||
|
class SeFragment : Fragment() {
|
||||||
|
|
||||||
|
private var currentRating = 0
|
||||||
|
private var loadingJob: Job? = null
|
||||||
|
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REQUEST_GALLERY_PERMISSION = 1001
|
||||||
|
private const val REQUEST_PREVIEW = 1002
|
||||||
|
private const val TAG = "SettingFragment"
|
||||||
|
const val EXTRA_FROM_PREVIEW = "from_preview"
|
||||||
|
fun newInstance(): SeFragment {
|
||||||
|
return SeFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val galleryLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
Log.d(TAG, "galleryLauncher返回: resultCode=${result.resultCode}")
|
||||||
|
|
||||||
|
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
|
||||||
|
val uri = result.data?.data
|
||||||
|
if (uri != null) {
|
||||||
|
Log.d(TAG, "从图库选择了图片: $uri")
|
||||||
|
|
||||||
|
val intent = Intent(requireContext(), LocalPreActivity::class.java).apply {
|
||||||
|
putExtra(LocalPreActivity.Companion.EXTRA_IMAGE_URI, uri.toString())
|
||||||
|
putExtra(LocalPreActivity.Companion.EXTRA_FROM_SETTING, true)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
activity?.overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
|
||||||
|
activity?.finish()
|
||||||
|
} else {
|
||||||
|
showToast("Failed to get image")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "用户取消了图库选择")
|
||||||
|
val intent = Intent(requireContext(), MainActivity::class.java).apply {
|
||||||
|
putExtra("SHOW_SETTING_FRAGMENT", true)
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
}
|
||||||
|
requireContext().startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
Log.d(TAG, "onActivityResult: requestCode=$requestCode, resultCode=$resultCode")
|
||||||
|
|
||||||
|
when (requestCode) {
|
||||||
|
LocalPreActivity.Companion.RESULT_SET_SUCCESS -> {
|
||||||
|
// 处理预览页面返回结果
|
||||||
|
when (resultCode) {
|
||||||
|
LocalPreActivity.Companion.RESULT_SET_SUCCESS -> {
|
||||||
|
Log.d(TAG, "壁纸设置成功,已返回设置页面")
|
||||||
|
showToast("壁纸设置成功!")
|
||||||
|
}
|
||||||
|
Activity.RESULT_CANCELED -> {
|
||||||
|
Log.d(TAG, "用户取消了壁纸设置")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "预览页面返回未知结果: $resultCode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(com.gallery.free.wallpaper.R.layout.fragment_se, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setVersionNumber(view)
|
||||||
|
setupRateUsClickListeners(view)
|
||||||
|
setupChoosePhotosClickListeners(view)
|
||||||
|
|
||||||
|
checkIfReturnedFromPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkIfReturnedFromPreview() {
|
||||||
|
val activity = activity as? MainActivity
|
||||||
|
activity?.intent?.let { intent ->
|
||||||
|
if (intent.hasExtra("SHOW_SETTING_FRAGMENT")) {
|
||||||
|
Log.d(TAG, "从预览页面返回,显示SettingFragment")
|
||||||
|
intent.removeExtra("SHOW_SETTING_FRAGMENT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
Log.d(TAG, "SettingFragment恢复显示")
|
||||||
|
checkIfReturnedFromPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
loadingJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
private fun setVersionNumber(view: View) {
|
||||||
|
try {
|
||||||
|
val packageInfo = requireContext().packageManager
|
||||||
|
.getPackageInfo(requireContext().packageName, 0)
|
||||||
|
|
||||||
|
val versionName = packageInfo.versionName
|
||||||
|
|
||||||
|
val versionValue = view.findViewById<TextView>(com.gallery.free.wallpaper.R.id.versionValue)
|
||||||
|
if (versionValue != null) {
|
||||||
|
versionValue.text = "$versionName"
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
val versionValue = view.findViewById<TextView>(com.gallery.free.wallpaper.R.id.versionValue)
|
||||||
|
if (versionValue != null) {
|
||||||
|
versionValue.text = "1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupChoosePhotosClickListeners(view: View) {
|
||||||
|
val choosePhotosLayout = view.findViewById<View>(com.gallery.free.wallpaper.R.id.choosePhotosLayout)
|
||||||
|
val choosePhotosText = view.findViewById<View>(com.gallery.free.wallpaper.R.id.choosePhotosText)
|
||||||
|
val choosePhotosIcon = view.findViewById<View>(com.gallery.free.wallpaper.R.id.choosePhotosIcon)
|
||||||
|
val choosePhotosArrow = view.findViewById<View>(com.gallery.free.wallpaper.R.id.choosePhotosArrow)
|
||||||
|
|
||||||
|
val clickListener = View.OnClickListener {
|
||||||
|
openGalleryForImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
choosePhotosLayout?.setOnClickListener(clickListener)
|
||||||
|
choosePhotosText?.setOnClickListener(clickListener)
|
||||||
|
choosePhotosIcon?.setOnClickListener(clickListener)
|
||||||
|
choosePhotosArrow?.setOnClickListener(clickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRateUsClickListeners(view: View) {
|
||||||
|
val rateUsLayout = view.findViewById<View>(com.gallery.free.wallpaper.R.id.rateUsLayout)
|
||||||
|
val rateUsText = view.findViewById<View>(com.gallery.free.wallpaper.R.id.rateUsText)
|
||||||
|
val rateUsIcon = view.findViewById<View>(com.gallery.free.wallpaper.R.id.rateUsIcon)
|
||||||
|
val rateUsArrow = view.findViewById<View>(com.gallery.free.wallpaper.R.id.rateUsArrow)
|
||||||
|
|
||||||
|
val clickListener = View.OnClickListener { showRateUsDialog() }
|
||||||
|
|
||||||
|
rateUsLayout?.setOnClickListener(clickListener)
|
||||||
|
rateUsText?.setOnClickListener(clickListener)
|
||||||
|
rateUsIcon?.setOnClickListener(clickListener)
|
||||||
|
rateUsArrow?.setOnClickListener(clickListener)
|
||||||
|
|
||||||
|
if (rateUsLayout == null && rateUsText == null && rateUsIcon == null && rateUsArrow == null) {
|
||||||
|
println("Warning: No rate us views found in fragment_setting_two.xml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openGalleryForImage() {
|
||||||
|
// 检查权限
|
||||||
|
if (hasGalleryPermission()) {
|
||||||
|
launchGallery()
|
||||||
|
} else {
|
||||||
|
requestGalleryPermission()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getGalleryPermission(): String {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
Manifest.permission.READ_MEDIA_IMAGES
|
||||||
|
} else {
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasGalleryPermission(): Boolean {
|
||||||
|
return ContextCompat.checkSelfPermission(
|
||||||
|
requireContext(),
|
||||||
|
getGalleryPermission()
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestGalleryPermission() {
|
||||||
|
val permission = getGalleryPermission()
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), permission)) {
|
||||||
|
// 解释为什么需要权限
|
||||||
|
showPermissionRationaleDialog(permission)
|
||||||
|
} else {
|
||||||
|
// 直接请求权限
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
requireActivity(),
|
||||||
|
arrayOf(permission),
|
||||||
|
REQUEST_GALLERY_PERMISSION
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPermissionRationaleDialog(permission: String) {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Permission Required")
|
||||||
|
.setMessage("Need permission to access your photos to set as wallpaper")
|
||||||
|
.setPositiveButton("OK") { _, _ ->
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
requireActivity(),
|
||||||
|
arrayOf(permission),
|
||||||
|
REQUEST_GALLERY_PERMISSION
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchGallery() {
|
||||||
|
try {
|
||||||
|
val intent = Intent(Intent.ACTION_PICK)
|
||||||
|
intent.type = "image/*"
|
||||||
|
// 使用registerForActivityResult的方式
|
||||||
|
galleryLauncher.launch(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
showToast("Cannot open gallery")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showToast(message: String) {
|
||||||
|
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
when (requestCode) {
|
||||||
|
REQUEST_GALLERY_PERMISSION -> {
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
launchGallery()
|
||||||
|
} else {
|
||||||
|
showToast("Permission denied")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showRateUsDialog() {
|
||||||
|
try {
|
||||||
|
val dialogView = LayoutInflater.from(requireContext()).inflate(com.gallery.free.wallpaper.R.layout.dialog_rateus, null)
|
||||||
|
|
||||||
|
val dialog = AlertDialog.Builder(requireContext())
|
||||||
|
.setView(dialogView)
|
||||||
|
.setCancelable(true)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.window?.setBackgroundDrawableResource(R.color.transparent)
|
||||||
|
|
||||||
|
currentRating = 0
|
||||||
|
val btnRate = dialogView.findViewById<Button>(com.gallery.free.wallpaper.R.id.btn_rate)
|
||||||
|
btnRate?.isEnabled = false
|
||||||
|
|
||||||
|
setupStarClickListeners(dialogView, btnRate)
|
||||||
|
|
||||||
|
val btnCancel = dialogView.findViewById<View>(com.gallery.free.wallpaper.R.id.btn_cancel)
|
||||||
|
btnCancel?.setOnClickListener {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnRate?.setOnClickListener {
|
||||||
|
if (currentRating > 0) {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupStarClickListeners(dialogView: View, rateButton: Button?) {
|
||||||
|
val stars = listOf(
|
||||||
|
dialogView.findViewById<ImageView>(com.gallery.free.wallpaper.R.id.star1),
|
||||||
|
dialogView.findViewById<ImageView>(com.gallery.free.wallpaper.R.id.star2),
|
||||||
|
dialogView.findViewById<ImageView>(com.gallery.free.wallpaper.R.id.star3),
|
||||||
|
dialogView.findViewById<ImageView>(com.gallery.free.wallpaper.R.id.star4),
|
||||||
|
dialogView.findViewById<ImageView>(com.gallery.free.wallpaper.R.id.star5)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (stars.any { it == null }) {
|
||||||
|
println("Warning: Some star views are missing in dialog_rate_us_two.xml")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stars.forEachIndexed { index, star ->
|
||||||
|
star?.setOnClickListener {
|
||||||
|
val clickedRating = index + 1
|
||||||
|
updateStarRating(stars, clickedRating, rateButton)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateStarRating(stars: List<ImageView?>, rating: Int, rateButton: Button?) {
|
||||||
|
currentRating = rating
|
||||||
|
|
||||||
|
stars.forEachIndexed { index, star ->
|
||||||
|
if (index < rating) {
|
||||||
|
star?.setImageResource(com.gallery.free.wallpaper.R.drawable.star_picked)
|
||||||
|
} else {
|
||||||
|
star?.setImageResource(com.gallery.free.wallpaper.R.drawable.star_disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rating > 0) {
|
||||||
|
rateButton?.isEnabled = true
|
||||||
|
rateButton?.setBackgroundResource(com.gallery.free.wallpaper.R.drawable.button_rateit)
|
||||||
|
} else {
|
||||||
|
rateButton?.isEnabled = false
|
||||||
|
rateButton?.setBackgroundResource(com.gallery.free.wallpaper.R.drawable.button_rateit_disa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,390 @@
|
|||||||
|
package com.gallery.free.wallpaper.fragment
|
||||||
|
|
||||||
|
import android.R
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
|
import com.gallery.free.wallpaper.DetailwpActivity
|
||||||
|
import com.gallery.free.wallpaper.MyApplication
|
||||||
|
import com.gallery.free.wallpaper.StaggeredGridSpacingItemDecoration
|
||||||
|
import com.gallery.free.wallpaper.Utils_json
|
||||||
|
import com.gallery.free.wallpaper.WpAdapter
|
||||||
|
import com.gallery.free.wallpaper.WpItem
|
||||||
|
import com.gallery.free.wallpaper.databinding.FragmentWpgridBinding
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class WpGridFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentWpgridBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var category: String
|
||||||
|
private var wallpaperList: List<WpItem> = emptyList()
|
||||||
|
private var adapter: WpAdapter? = null
|
||||||
|
|
||||||
|
// 滚动监听回调
|
||||||
|
private var scrollListener: ((Boolean) -> Unit)? = null
|
||||||
|
|
||||||
|
// 添加协程作用域
|
||||||
|
private val fragmentScope = CoroutineScope(Dispatchers.Main + Job())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_CATEGORY = "category"
|
||||||
|
private const val TAG = "WallpaperGridFragment"
|
||||||
|
|
||||||
|
fun newInstance(category: String): WpGridFragment {
|
||||||
|
val fragment = WpGridFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString(ARG_CATEGORY, category)
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
arguments?.let {
|
||||||
|
category = it.getString(ARG_CATEGORY, "Film")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentWpgridBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupWallpaperGrid()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置滚动监听回调
|
||||||
|
fun setOnScrollListener(listener: (Boolean) -> Unit) {
|
||||||
|
this.scrollListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkCurrentScrollState() {
|
||||||
|
if (!isAdded || _binding == null) {
|
||||||
|
// Fragment 未附加或绑定为null,直接返回
|
||||||
|
scrollListener?.invoke(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val canScrollUp = binding.wallpaperRecyclerView.canScrollVertically(-1)
|
||||||
|
Log.d(TAG, "checkCurrentScrollState: canScrollUp = $canScrollUp")
|
||||||
|
scrollListener?.invoke(canScrollUp)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
scrollListener?.invoke(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新壁纸
|
||||||
|
fun refreshWallpapers(onComplete: () -> Unit) {
|
||||||
|
fragmentScope.launch {
|
||||||
|
try {
|
||||||
|
if (!isAdded || _binding == null) {
|
||||||
|
onComplete()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Application 获取数据
|
||||||
|
val fileName = when (category) {
|
||||||
|
"Film" -> "Film_two.json"
|
||||||
|
"Nature" -> "Nature_two.json"
|
||||||
|
"Travel" -> "Travel_two.json"
|
||||||
|
else -> "Film_two.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
val myApp = activity?.application as? MyApplication
|
||||||
|
if (myApp != null) {
|
||||||
|
// 从 Application 缓存获取数据
|
||||||
|
val wallpaperItems = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
myApp.getWallpaperItems(fileName)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 如果缓存失败,回退到原始方法
|
||||||
|
Log.w(TAG, "从缓存获取数据失败,回退到原始方法", e)
|
||||||
|
val wallpaperJsonList = Utils_json.loadWallpapersFromAssets(requireContext(), fileName)
|
||||||
|
wallpaperJsonList.map { json ->
|
||||||
|
Utils_json.convertToWallpaperItem(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wallpaperItems.isEmpty()) {
|
||||||
|
showErrorState()
|
||||||
|
onComplete()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
wallpaperList = wallpaperItems
|
||||||
|
|
||||||
|
// 重新打乱壁纸顺序
|
||||||
|
val shuffledList = wallpaperList.shuffled()
|
||||||
|
|
||||||
|
val newAdapter = WpAdapter(shuffledList) { position: Int, wallpaper: WpItem ->
|
||||||
|
if (!isAdded) return@WpAdapter
|
||||||
|
|
||||||
|
val intent = Intent(requireContext(), DetailwpActivity::class.java).apply {
|
||||||
|
putExtra("wallpaper_url", wallpaper.fullUrl)
|
||||||
|
putExtra("wallpaper_description", wallpaper.description)
|
||||||
|
// 传递完整的WpItem列表
|
||||||
|
putParcelableArrayListExtra(
|
||||||
|
"wallpaper_item_list",
|
||||||
|
ArrayList(shuffledList)
|
||||||
|
)
|
||||||
|
putExtra("current_position", position)
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
requireActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新适配器
|
||||||
|
binding.wallpaperRecyclerView.adapter = newAdapter
|
||||||
|
adapter = newAdapter
|
||||||
|
|
||||||
|
// 检查滚动状态
|
||||||
|
checkCurrentScrollState()
|
||||||
|
|
||||||
|
onComplete()
|
||||||
|
} else {
|
||||||
|
// Application 不可用,使用原始方法
|
||||||
|
loadWallpapersWithOriginalMethod(onComplete)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
if (isAdded && _binding != null) {
|
||||||
|
showErrorState()
|
||||||
|
}
|
||||||
|
onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用原始方法加载壁纸(兼容性回退)
|
||||||
|
private fun loadWallpapersWithOriginalMethod(onComplete: () -> Unit) {
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
try {
|
||||||
|
if (!isAdded || _binding == null) {
|
||||||
|
onComplete()
|
||||||
|
return@postDelayed
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileName = when (category) {
|
||||||
|
"Film" -> "Film_two.json"
|
||||||
|
"Nature" -> "Nature_two.json"
|
||||||
|
"Travel" -> "Travel_two.json"
|
||||||
|
else -> "Film_two.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
val wallpaperJsonList = Utils_json.loadWallpapersFromAssets(requireContext(), fileName)
|
||||||
|
if (wallpaperJsonList.isEmpty()) {
|
||||||
|
showErrorState()
|
||||||
|
onComplete()
|
||||||
|
return@postDelayed
|
||||||
|
}
|
||||||
|
|
||||||
|
wallpaperList = wallpaperJsonList.map { json ->
|
||||||
|
Utils_json.convertToWallpaperItem(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新打乱壁纸顺序
|
||||||
|
val shuffledList = wallpaperList.shuffled()
|
||||||
|
|
||||||
|
val newAdapter = WpAdapter(shuffledList) { position: Int, wallpaper: WpItem ->
|
||||||
|
if (!isAdded) return@WpAdapter
|
||||||
|
|
||||||
|
val intent = Intent(requireContext(), DetailwpActivity::class.java).apply {
|
||||||
|
putExtra("wallpaper_url", wallpaper.fullUrl)
|
||||||
|
putExtra("wallpaper_description", wallpaper.description)
|
||||||
|
// 传递完整的WpItem列表
|
||||||
|
putParcelableArrayListExtra("wallpaper_item_list", ArrayList(shuffledList))
|
||||||
|
putExtra("current_position", position)
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
requireActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新适配器
|
||||||
|
binding.wallpaperRecyclerView.adapter = newAdapter
|
||||||
|
adapter = newAdapter
|
||||||
|
|
||||||
|
checkCurrentScrollState()
|
||||||
|
onComplete()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
if (isAdded && _binding != null) {
|
||||||
|
showErrorState()
|
||||||
|
}
|
||||||
|
onComplete()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回到顶部
|
||||||
|
fun scrollToTop() {
|
||||||
|
if (!isAdded || _binding == null) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
binding.wallpaperRecyclerView.smoothScrollToPosition(0)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupWallpaperGrid() {
|
||||||
|
fragmentScope.launch {
|
||||||
|
try {
|
||||||
|
val fileName = when (category) {
|
||||||
|
"Film" -> "Film_two.json"
|
||||||
|
"Nature" -> "Nature_two.json"
|
||||||
|
"Travel" -> "Travel_two.json"
|
||||||
|
else -> "Film_two.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Application 获取数据
|
||||||
|
val myApp = activity?.application as? MyApplication
|
||||||
|
val wallpaperItems = if (myApp != null) {
|
||||||
|
// 从 Application 缓存获取数据
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
myApp.getWallpaperItems(fileName)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 如果缓存失败,回退到原始方法
|
||||||
|
Log.w(TAG, "从缓存获取数据失败,回退到原始方法", e)
|
||||||
|
val wallpaperJsonList = Utils_json.loadWallpapersFromAssets(requireContext(), fileName)
|
||||||
|
wallpaperJsonList.map { json ->
|
||||||
|
Utils_json.convertToWallpaperItem(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Application 不可用,使用原始方法
|
||||||
|
val wallpaperJsonList = Utils_json.loadWallpapersFromAssets(requireContext(), fileName)
|
||||||
|
wallpaperJsonList.map { json ->
|
||||||
|
Utils_json.convertToWallpaperItem(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wallpaperItems.isEmpty()) {
|
||||||
|
showErrorState()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
wallpaperList = wallpaperItems
|
||||||
|
val shuffledList = wallpaperList.shuffled()
|
||||||
|
|
||||||
|
// 使用StaggeredGridLayoutManager瀑布流 每行2列
|
||||||
|
val spanCount = 2
|
||||||
|
val layoutManager = StaggeredGridLayoutManager(
|
||||||
|
spanCount,
|
||||||
|
StaggeredGridLayoutManager.VERTICAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// 设置间距均匀
|
||||||
|
layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE
|
||||||
|
|
||||||
|
binding.wallpaperRecyclerView.layoutManager = layoutManager
|
||||||
|
|
||||||
|
val spacingInPixels = resources.getDimensionPixelSize(com.gallery.free.wallpaper.R.dimen.grid_spacing)
|
||||||
|
binding.wallpaperRecyclerView.addItemDecoration(
|
||||||
|
StaggeredGridSpacingItemDecoration(spanCount, spacingInPixels)
|
||||||
|
)
|
||||||
|
|
||||||
|
adapter = WpAdapter(shuffledList) { position: Int, wallpaper: WpItem ->
|
||||||
|
if (!isAdded) return@WpAdapter
|
||||||
|
|
||||||
|
val intent = Intent(requireContext(), DetailwpActivity::class.java).apply {
|
||||||
|
putExtra("wallpaper_url", wallpaper.fullUrl) // 传递全尺寸URL
|
||||||
|
putExtra("wallpaper_description", wallpaper.description) // 传递描述
|
||||||
|
putParcelableArrayListExtra("wallpaper_item_list", ArrayList(shuffledList))
|
||||||
|
putExtra("current_position", position)
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
requireActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
|
||||||
|
}
|
||||||
|
binding.wallpaperRecyclerView.adapter = adapter
|
||||||
|
|
||||||
|
// 设置滚动监听
|
||||||
|
setupScrollListener()
|
||||||
|
|
||||||
|
// 初始检查滚动状态
|
||||||
|
binding.wallpaperRecyclerView.post {
|
||||||
|
checkCurrentScrollState()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "设置壁纸网格失败", e)
|
||||||
|
showErrorState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupScrollListener() {
|
||||||
|
binding.wallpaperRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
// 检查 Fragment 是否仍然有效
|
||||||
|
if (!isAdded || _binding == null) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val canScrollUp = recyclerView.canScrollVertically(-1)
|
||||||
|
Log.d(TAG, "onScrolled: canScrollUp = $canScrollUp, dy = $dy")
|
||||||
|
scrollListener?.invoke(canScrollUp)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState)
|
||||||
|
if (!isAdded || _binding == null) return
|
||||||
|
|
||||||
|
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||||
|
try {
|
||||||
|
val canScrollUp = recyclerView.canScrollVertically(-1)
|
||||||
|
Log.d(TAG, "scroll state changed: canScrollUp = $canScrollUp")
|
||||||
|
scrollListener?.invoke(canScrollUp)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showErrorState() {
|
||||||
|
if (isAdded) {
|
||||||
|
Toast.makeText(requireContext(), "加载失败...", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
// 清除滚动监听回调
|
||||||
|
scrollListener = null
|
||||||
|
_binding = null
|
||||||
|
adapter = null
|
||||||
|
|
||||||
|
// 取消所有协程
|
||||||
|
fragmentScope.coroutineContext.cancelChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable/arrow_setting.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
6
app/src/main/res/drawable/bg_icon.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#CCCCCCFF" />
|
||||||
|
<corners android:radius="20dp" />
|
||||||
|
</shape>
|
||||||
BIN
app/src/main/res/drawable/bgimage.png
Normal file
|
After Width: | Height: | Size: 817 KiB |
8
app/src/main/res/drawable/button_cancel.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
<solid android:color="#F5F5F5" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#E0E0E0" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/button_rateit.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
<solid android:color="@color/rate_button" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/button_rateit_disa.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
<solid android:color="#CCCCCC" />
|
||||||
|
</shape>
|
||||||
BIN
app/src/main/res/drawable/close_detailwp.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
5
app/src/main/res/drawable/dialog_bg.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
<solid android:color="@color/light_purple" />
|
||||||
|
</shape>
|
||||||
BIN
app/src/main/res/drawable/dialog_bothtwo.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/drawable/dialog_homescreen.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/drawable/dialog_lockscreen.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/drawable/ic_collection.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
app/src/main/res/drawable/ic_collection_disabled.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/drawable/ic_home.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/drawable/ic_home_disabled.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/drawable/ic_left.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable/ic_left_disabled.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable/ic_like.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/drawable/ic_like_disabled.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
app/src/main/res/drawable/ic_loadingpic.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/main/res/drawable/ic_loadingpic_failed.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
app/src/main/res/drawable/ic_refreshit.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
app/src/main/res/drawable/ic_right.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable/ic_right_disabled.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable/ic_savewp.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/drawable/ic_settings.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
app/src/main/res/drawable/ic_settings_disabled.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/drawable/ic_setwp.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
app/src/main/res/drawable/ic_success.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable/ic_top.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
6
app/src/main/res/drawable/loading_animation.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:drawable="@drawable/ic_loadingpic"
|
||||||
|
android:pivotX="50%"
|
||||||
|
android:pivotY="50%"
|
||||||
|
android:repeatCount="infinite" />
|
||||||
BIN
app/src/main/res/drawable/localpho_settings.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/drawable/placeho_homepage.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
app/src/main/res/drawable/rate_settings.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
app/src/main/res/drawable/splaimage.jpg
Normal file
|
After Width: | Height: | Size: 509 KiB |
BIN
app/src/main/res/drawable/star_disabled.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
app/src/main/res/drawable/star_picked.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/drawable/version_settings.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
7
app/src/main/res/font/alimama_shuheiti.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<font
|
||||||
|
android:fontStyle="normal"
|
||||||
|
android:fontWeight="400"
|
||||||
|
android:font="@font/alimama_shuheiti_" />
|
||||||
|
</font-family>
|
||||||
BIN
app/src/main/res/font/alimama_shuheiti_.otf
Normal file
99
app/src/main/res/layout/activity_detailwp.xml
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="com.gallery.free.wallpaper.DetailwpActivity">
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/wallpaperViewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||||
|
android:id="@+id/button_prev"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:background="@drawable/bg_icon"
|
||||||
|
android:src="@drawable/ic_left_disabled"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:saturation="1.0"
|
||||||
|
app:brightness="0.0"
|
||||||
|
app:contrast="1.0" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||||
|
android:id="@+id/button_next"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:background="@drawable/bg_icon"
|
||||||
|
android:src="@drawable/ic_right_disabled"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:saturation="1.0"
|
||||||
|
app:brightness="0.0"
|
||||||
|
app:contrast="1.0" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageview_back"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:background="@drawable/bg_icon"
|
||||||
|
android:src="@drawable/close_detailwp"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginBottom="80dp"
|
||||||
|
android:background="@drawable/bg_icon"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="12dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/button_like"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:src="@drawable/ic_like_disabled"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/button_set_wallpaper"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:src="@drawable/ic_setwp"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/button_save"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:src="@drawable/ic_savewp"
|
||||||
|
android:padding="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
71
app/src/main/res/layout/activity_localpre.xml
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="com.gallery.free.wallpaper.LocalPreActivity">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView_preview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:contentDescription="Preview image"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||||
|
android:id="@+id/button_back"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:background="@drawable/bg_icon"
|
||||||
|
android:src="@drawable/close_detailwp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:saturation="1.0"
|
||||||
|
app:brightness="0.0"
|
||||||
|
app:contrast="1.0" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="40dp"
|
||||||
|
android:layout_marginEnd="40dp"
|
||||||
|
android:layout_marginBottom="80dp"
|
||||||
|
android:background="@drawable/bg_icon"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="12dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||||
|
android:id="@+id/button_set_wallpaper"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:src="@drawable/ic_setwp"
|
||||||
|
android:padding="8dp"
|
||||||
|
app:saturation="1.0"
|
||||||
|
app:brightness="0.0"
|
||||||
|
app:contrast="1.0" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
86
app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/bgimage"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Fragment 容器 -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<!-- 底部导航栏 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:elevation="8dp">
|
||||||
|
|
||||||
|
<!-- Home 按钮 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/homeLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/homeIcon"
|
||||||
|
android:layout_width="35dp"
|
||||||
|
android:layout_height="35dp"
|
||||||
|
android:src="@drawable/ic_home_disabled" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Collection 按钮 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/collectionLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/collectionIcon"
|
||||||
|
android:layout_width="35dp"
|
||||||
|
android:layout_height="35dp"
|
||||||
|
android:src="@drawable/ic_collection_disabled" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Settings 按钮 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/settingsLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/settingsIcon"
|
||||||
|
android:layout_width="35dp"
|
||||||
|
android:layout_height="35dp"
|
||||||
|
android:src="@drawable/ic_settings_disabled" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
61
app/src/main/res/layout/activity_spla.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="com.gallery.free.wallpaper.SplaActivity">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/splaimage" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageview_app_icon"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="250dp"
|
||||||
|
android:contentDescription="@string/app_name"
|
||||||
|
android:src="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textview_appname"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/imageview_app_icon"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="18dp"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:shadowColor="#000000"
|
||||||
|
android:shadowDx="2"
|
||||||
|
android:shadowDy="2"
|
||||||
|
android:shadowRadius="4"
|
||||||
|
android:textColor="#FFD8B4FF"
|
||||||
|
android:textSize="30sp"
|
||||||
|
app:apply_font="true" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressbar"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:layout_width="250dp"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
android:layout_below="@id/textview_appname"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="26dp"
|
||||||
|
android:max="100"
|
||||||
|
android:progress="1"
|
||||||
|
android:progressBackgroundTint="@color/under_splash"
|
||||||
|
android:progressTint="@color/progressbar_color" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
114
app/src/main/res/layout/dialog_rateus.xml
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp"
|
||||||
|
android:background="@drawable/dialog_bg">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Rate us"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="We hope this app is useful for you, if it does, would you please give us a 5 star and a nice review on Google Play, it really helps!"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:lineSpacingExtra="4dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="18dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/star_rating_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="18dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/star1"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:src="@drawable/star_disabled"
|
||||||
|
android:tag="1" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/star2"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:src="@drawable/star_disabled"
|
||||||
|
android:tag="2" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/star3"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:src="@drawable/star_disabled"
|
||||||
|
android:tag="3" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/star4"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:src="@drawable/star_disabled"
|
||||||
|
android:tag="4" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/star5"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:src="@drawable/star_disabled"
|
||||||
|
android:tag="5" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 按钮布局 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_cancel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="cancel"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:background="@drawable/button_cancel"
|
||||||
|
android:textAllCaps="true" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_rate"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="RATE IT"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:background="@drawable/button_rateit_disa"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:enabled="false" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
101
app/src/main/res/layout/dialog_setwp.xml
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/dialog_bg">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_home"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="54dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="34dp"
|
||||||
|
android:layout_height="34dp"
|
||||||
|
android:src="@drawable/dialog_homescreen" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:text="Home Screen"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginStart="25dp"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:background="#B3000000" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_lock"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="54dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:src="@drawable/dialog_lockscreen" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lock"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:text="Lock Screen"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginStart="25dp"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:background="#B3000000" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_both"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="54dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="31dp"
|
||||||
|
android:layout_height="31dp"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:src="@drawable/dialog_bothtwo" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/both"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:text="Both"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
44
app/src/main/res/layout/empty_colle.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/content_container"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.45">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:src="@drawable/ic_like_disabled"
|
||||||
|
android:tint="@color/light_gray" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="No Favourite Wallpaper"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Go Back To Homepage and Find More"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
90
app/src/main/res/layout/fragment_colle.xml
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context="com.gallery.free.wallpaper.fragment.ColleFragment">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="40dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:text="Collection"
|
||||||
|
android:letterSpacing="0.04"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/text_favorite_count_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@color/text_color"
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:layout_marginEnd="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_favorite_count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0 photos collected"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@color/text_color"
|
||||||
|
android:layout_marginEnd="30dp"
|
||||||
|
android:layout_marginStart="16dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/wallpaperRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="1dp"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<include
|
||||||
|
android:id="@+id/empty_likes_layout"
|
||||||
|
layout="@layout/empty_colle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
80
app/src/main/res/layout/fragment_ho.xml
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:tabBackground="@android:color/transparent"
|
||||||
|
app:tabGravity="start"
|
||||||
|
app:tabIndicatorHeight="0dp"
|
||||||
|
app:tabInlineLabel="true"
|
||||||
|
app:tabMinWidth="0dp"
|
||||||
|
app:tabMode="scrollable"
|
||||||
|
app:tabPaddingEnd="20dp"
|
||||||
|
app:tabRippleColor="@android:color/transparent"
|
||||||
|
app:tabSelectedTextColor="@color/black"
|
||||||
|
app:tabTextAppearance="@style/MyTabTextAppearance"
|
||||||
|
app:tabTextColor="#A0A0A0" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 悬浮按钮容器 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="80dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/btn_scroll_to_top"
|
||||||
|
android:layout_width="45dp"
|
||||||
|
android:layout_height="45dp"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:background="@drawable/bg_icon"
|
||||||
|
android:src="@drawable/ic_top"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/btn_refresh"
|
||||||
|
android:layout_width="45dp"
|
||||||
|
android:layout_height="45dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="@drawable/bg_icon"
|
||||||
|
android:src="@drawable/ic_refreshit"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
200
app/src/main/res/layout/fragment_se.xml
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="38dp"
|
||||||
|
tools:context="com.gallery.free.wallpaper.fragment.SeFragment">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:letterSpacing="0.04"
|
||||||
|
android:text="Settings"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="24sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<!-- Choose from Photos -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/choosePhotosLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="30dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/titleText">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/choosePhotosIcon"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:src="@drawable/localpho_settings"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/choosePhotosText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="Choose from Local"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/choosePhotosArrow"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/choosePhotosIcon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/choosePhotosArrow"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/arrow_setting"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/dividerChoosePhotos"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1.3dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="#B3000000"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/choosePhotosLayout" />
|
||||||
|
|
||||||
|
<!-- Rate Us -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/rateUsLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/dividerChoosePhotos">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/rateUsIcon"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:src="@drawable/rate_settings"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/rateUsText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="Rate us"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/rateUsArrow"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/rateUsIcon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/rateUsArrow"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/arrow_setting"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1.3dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="#B3000000"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/rateUsLayout" />
|
||||||
|
|
||||||
|
<!-- Version -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/versionLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/divider1">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/versionIcon"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:src="@drawable/version_settings"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/versionText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="Version"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/versionIcon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/versionValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1.3dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="#B3000000"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/versionLayout" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
30
app/src/main/res/layout/fragment_wpgrid.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_wallpaper_count"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/text_color"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/wallpaperRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="2dp"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
65
app/src/main/res/layout/item_wp.xml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="2dp"
|
||||||
|
app:cardPreventCornerOverlap="false"
|
||||||
|
app:cardUseCompatPadding="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="0dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:maxHeight="500dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/textContainer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:paddingVertical="6dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/imageView">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/descriptionText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lineSpacingExtra="1dp"
|
||||||
|
android:fontFamily="@font/alimama_shuheiti"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
20
app/src/main/res/layout/item_wp_pager.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pager_image_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/loading_progress"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/loading_animation"
|
||||||
|
android:visibility="visible" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 275 KiB |