first commit

This commit is contained in:
ocean 2025-12-29 10:52:12 +08:00
commit 782d0fa7bf
173 changed files with 174027 additions and 0 deletions

15
.gitignore vendored Normal file
View 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
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/AndroidProjectSystem.xml generated Normal file
View 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
View 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
View 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>

18
.idea/deploymentTargetSelector.xml generated Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-12-11T01:18:24.118141300Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Administrator\.android\avd\Pixel_9_Pro.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

13
.idea/deviceManager.xml generated Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

19
.idea/gradle.xml generated Normal file
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
/build

115
app/build.gradle.kts Normal file
View File

@ -0,0 +1,115 @@
import java.util.Date
import java.text.SimpleDateFormat
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("io.objectbox")
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.idle.wall"
compileSdk = 36
defaultConfig {
applicationId = "com.idle.wall"
minSdk = 24
targetSdk = 36
versionCode = 2
versionName = "1.1"
setProperty("archivesBaseName", "IdleWall_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_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildFeatures {
dataBinding=true
viewBinding = true
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.github.bumptech.glide:glide:5.0.5")
implementation("androidx.activity:activity:1.12.0")
kapt("com.github.bumptech.glide:compiler:4.16.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.test:monitor:1.8.0")
implementation("androidx.test.ext:junit:1.3.0")
testImplementation("junit:junit:4.13.2")
implementation("jp.wasabeef:glide-transformations:4.3.0") // Glide图片变换库
implementation("com.squareup.okhttp3:okhttp:5.3.1")
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.1.10.1")
//noinspection GradleCompatible
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.1.10.1")
// Pangle
implementation("com.tradplusad:tradplus-pangle:19.15.1.10.1")
implementation("com.pangle.global:pag-sdk:7.7.0.2")
// UnityAds
implementation("com.tradplusad:tradplus-unity:5.15.1.10.1")
implementation("com.unity3d.ads:unity-ads:4.16.3")
//optional dependency for better targeting
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")
// Mintegral
implementation("com.tradplusad:tradplus-mintegralx_overseas:18.15.1.10.1")
implementation("androidx.recyclerview:recyclerview:1.1.0")
implementation("com.mbridge.msdk.oversea:mbridge_android_sdk:16.10.11")
// Liftoff
implementation("com.tradplusad:tradplus-vunglex:7.15.1.10.1")
implementation("com.vungle:vungle-ads:7.6.0")
// Cross Promotion
implementation("com.tradplusad:tradplus-crosspromotion:27.15.1.10.1")
// TP Exchange
// 请注意保持与主包版本同步更新
implementation("com.google.code.gson:gson:2.8.6")
implementation("com.tradplusad:tp_exchange:40.15.1.10.1")
}

29
app/google-services.json Normal file
View File

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "609806380444",
"project_id": "idlewall",
"storage_bucket": "idlewall.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:609806380444:android:ae858dc343beed929b2c59",
"android_client_info": {
"package_name": "com.idle.wall"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAhFtzRzNVqaZbKE3f-eD04Kj2i3S4J8vs"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

BIN
app/idlewall.jks Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,203 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "9:4050950209350309695",
"lastPropertyId": "16:8262053217686213535",
"name": "IdleData",
"properties": [
{
"id": "1:682631703250359234",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:1881542362545041492",
"name": "categoryName",
"type": 9
},
{
"id": "3:350758865501435345",
"name": "imId",
"type": 9
},
{
"id": "4:4444268813482397951",
"name": "description",
"type": 9
},
{
"id": "5:2820582153477981548",
"name": "fullUrl",
"type": 9
},
{
"id": "6:8411258972644943298",
"name": "previewUrl1080",
"type": 9
},
{
"id": "7:4954875405683755945",
"name": "previewUrl400",
"type": 9
},
{
"id": "8:8728898486188019474",
"name": "previewUrl200",
"type": 9
},
{
"id": "9:2277589823244218755",
"name": "authorName",
"type": 9
},
{
"id": "10:3261626327998945702",
"name": "authorHeader",
"type": 9
},
{
"id": "11:7573973023093176086",
"name": "authorHtml",
"type": 9
},
{
"id": "12:695532241367665647",
"name": "isLike",
"type": 1
},
{
"id": "13:3993431378160843918",
"name": "isDownload",
"type": 1
},
{
"id": "14:2812823538363998566",
"name": "downloadTime",
"type": 6
},
{
"id": "15:8547006360739864416",
"name": "likeTime",
"type": 6
},
{
"id": "16:8262053217686213535",
"name": "viewTime",
"type": 6
}
],
"relations": []
}
],
"lastEntityId": "9:4050950209350309695",
"lastIndexId": "2:608135772507829403",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [
8119453540460508569,
8776066878034520742,
817335537070466014,
5374318059423987080,
6686897826437564736,
8653997226256447908,
4481900207338271736,
1249910083429458453
],
"retiredIndexUids": [
517334799177243468,
608135772507829403
],
"retiredPropertyUids": [
8848640050018073106,
4870222079156330783,
4966768060561274983,
8892036187720267027,
1670319685727744805,
3855211567189961055,
7567671966987007993,
2590164264280025677,
8912833100894718986,
7138929457594592554,
3957870060050722570,
2943285943884558608,
7262008615134929676,
7105947400861196483,
1954934496493857304,
8582031355111471002,
8410380097928190493,
3380197792593924336,
5750724995960958985,
5907193265594068445,
7643711915014989832,
5454072153734548882,
8866886314389627366,
6647852625511577303,
8481286751506557167,
615207224772228883,
2114128640869665061,
4004858028833616093,
1649948666606108567,
4925649389754620618,
6869613325061909824,
1127054227079914732,
7583347258203607289,
1874919077310718019,
3560534746373995189,
5323174779797549688,
6493016523726214639,
6241934991596178294,
697564122644877580,
5123147135686386853,
8153050789414477927,
4430301556301172703,
7683657851560967678,
2111285075400493750,
2037948162911967828,
5013578949085393547,
5408284309630409396,
6635766303651707557,
4342422943730370041,
3298743955069977105,
2213421576691773001,
1761374873324963391,
7035223913750449698,
2778534831751310584,
1554807725758930901,
2143075629380316240,
8257544604178079575,
1294040731405970998,
7311149724354485262,
1320180233997561822,
1539880221357131326,
1463781112302159019,
747315404376095562,
3770866994993592689,
1409213434738850174,
7292182286845179552,
8732091947252252711,
2180823300826152623,
1458684869191472427,
2474288114639403742,
150200440439745154,
1158407396280830025,
2910924377851538709,
9171629707420038796,
7913343306488238169,
6753345520924704399,
3764638446780428471,
2328154298111188088,
8050957901029823469,
2709038604121802995,
1968810481269862047,
3089333775310515089,
3562862017624979150
],
"retiredRelationUids": [],
"version": 1
}

View File

@ -0,0 +1,186 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "8:1249910083429458453",
"lastPropertyId": "20:3562862017624979150",
"name": "PinkData",
"properties": [
{
"id": "1:2180823300826152623",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:1458684869191472427",
"name": "categoryName",
"type": 9
},
{
"id": "3:2474288114639403742",
"name": "imId",
"type": 9
},
{
"id": "4:150200440439745154",
"name": "description",
"type": 9
},
{
"id": "5:1158407396280830025",
"name": "fullUrl",
"type": 9
},
{
"id": "6:2910924377851538709",
"name": "previewUrl1080",
"type": 9
},
{
"id": "7:9171629707420038796",
"name": "previewUrl400",
"type": 9
},
{
"id": "8:7913343306488238169",
"name": "previewUrl200",
"type": 9
},
{
"id": "9:6753345520924704399",
"name": "authorName",
"type": 9
},
{
"id": "10:3764638446780428471",
"name": "authorHeader",
"type": 9
},
{
"id": "11:2328154298111188088",
"name": "authorHtml",
"type": 9
},
{
"id": "12:8050957901029823469",
"name": "isLike",
"type": 1
},
{
"id": "13:2709038604121802995",
"name": "isDownload",
"type": 1
},
{
"id": "18:1968810481269862047",
"name": "likeTime",
"type": 6
},
{
"id": "19:3089333775310515089",
"name": "viewTime",
"type": 6
},
{
"id": "20:3562862017624979150",
"name": "downloadTime",
"type": 6
}
],
"relations": []
}
],
"lastEntityId": "8:1249910083429458453",
"lastIndexId": "2:608135772507829403",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [
8119453540460508569,
8776066878034520742,
817335537070466014,
5374318059423987080,
6686897826437564736,
8653997226256447908,
4481900207338271736
],
"retiredIndexUids": [
517334799177243468,
608135772507829403
],
"retiredPropertyUids": [
8848640050018073106,
4870222079156330783,
4966768060561274983,
8892036187720267027,
1670319685727744805,
3855211567189961055,
7567671966987007993,
2590164264280025677,
8912833100894718986,
7138929457594592554,
3957870060050722570,
2943285943884558608,
7262008615134929676,
7105947400861196483,
1954934496493857304,
8582031355111471002,
8410380097928190493,
3380197792593924336,
5750724995960958985,
5907193265594068445,
7643711915014989832,
5454072153734548882,
8866886314389627366,
6647852625511577303,
8481286751506557167,
615207224772228883,
2114128640869665061,
4004858028833616093,
1649948666606108567,
4925649389754620618,
6869613325061909824,
1127054227079914732,
7583347258203607289,
1874919077310718019,
3560534746373995189,
5323174779797549688,
6493016523726214639,
6241934991596178294,
697564122644877580,
5123147135686386853,
8153050789414477927,
4430301556301172703,
7683657851560967678,
2111285075400493750,
2037948162911967828,
5013578949085393547,
5408284309630409396,
6635766303651707557,
4342422943730370041,
3298743955069977105,
2213421576691773001,
1761374873324963391,
7035223913750449698,
2778534831751310584,
1554807725758930901,
2143075629380316240,
8257544604178079575,
1294040731405970998,
7311149724354485262,
1320180233997561822,
1539880221357131326,
1463781112302159019,
747315404376095562,
3770866994993592689,
1409213434738850174,
7292182286845179552,
8732091947252252711
],
"retiredRelationUids": [],
"version": 1
}

34
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,34 @@
# 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.idle.wall.idlebean.** { *; }
-keep class com.google.gson.** { *; }
-keep class com.idle.wall.IdleWallpaper { *; }
-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.** { *; }

View File

@ -0,0 +1,21 @@
package com.hd.wallpaper.free.walls;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under live.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.hd.kwallpapers.wallpapers.live", appContext.getPackageName());
}
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.BLUETOOTH"
tools:node="remove" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
tools:node="remove" />
<uses-permission android:name="android.permission.INTERNET" />
<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.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.AD_ID" />
<application
android:name=".IdleApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/logo_idle"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo_idle"
android:supportsRtl="true"
android:theme="@style/Theme.Wallpaper"
tools:replace="networkSecurityConfig"
android:networkSecurityConfig="@xml/net"
tools:targetApi="31">
<activity
android:name=".idleactivity.IdleLikeActivity"
android:exported="false" />
<activity
android:name=".idleactivity.IdleRecentActivity"
android:exported="false" />
<activity
android:name=".idleactivity.IdleSearchActivity"
android:exported="false" />
<activity
android:name=".idleactivity.IdleListActivity"
android:exported="false" />
<activity
android:name=".idleactivity.IdleWallActivity"
android:exported="false" />
<activity
android:name=".idleactivity.IdleMainActivity"
android:exported="false" />
<activity
android:name=".idleactivity.SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

20703
app/src/main/assets/Film.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

View File

@ -0,0 +1,38 @@
package com.idle.wall;
import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
import com.bumptech.glide.load.engine.cache.LruResourceCache;
import com.bumptech.glide.module.AppGlideModule;
// 全局Glide配置
@GlideModule
public class GlideConfig extends AppGlideModule {
private static final int DISK_CACHE_SIZE = 100 * 1024 * 1024;
private static final int MEMORY_CACHE_SIZE = (int) (Runtime.getRuntime().maxMemory() / 8);
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// 1. 增大磁盘缓存
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, DISK_CACHE_SIZE));
// 2. 增大内存缓存
builder.setMemoryCache(new LruResourceCache(MEMORY_CACHE_SIZE));
// 3. 降低日志级别提升性能
builder.setLogLevel(android.util.Log.ERROR);
}
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
// 可添加自定义网络加载器如OkHttp提升网络请求速度
// registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
}
@Override
public boolean isManifestParsingEnabled() {
return false; // 禁用Manifest解析提升初始化速度
}
}

View File

@ -0,0 +1,230 @@
package com.idle.wall;
import android.app.Application;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idletool.Comutils;
import com.idle.wall.idletool.ObectManager;
import com.idle.wall.idletool.JsonData;
import com.tradplus.ads.open.TradPlusSdk;
import com.up.uploadlibrary.UpLoadManager;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class IdleApp extends Application {
public static Context mAppContext;
public static final String TAG = "-------MyApp---------";
public static Typeface defaultFont;
private static final String CORE_CATEGORY_FILE = "Featured.json";
private static final String[] ASYNC_JSON_FILES = {
"Animals.json", "Experimental.json", "Film.json",
"Nature.json", "Patterns.json", "Street.json", "Travel.json"
};
private static final List<String> CATEGORY_NAMES = new ArrayList<>();
private ExecutorService executor;
private final AtomicInteger loadCount = new AtomicInteger(0);
private static volatile boolean isCoreDataLoaded = false;
private static volatile boolean isAllDataLoaded = false;
private static List<OnDataLoadedListener> dataLoadedListeners = new ArrayList<>();
public interface OnDataLoadedListener {
void onCoreDataLoaded();
void onAllDataLoaded();
}
public static void registerDataLoadedListener(OnDataLoadedListener listener) {
synchronized (dataLoadedListeners) {
// 核心数据已加载立即回调
if (isCoreDataLoaded) {
listener.onCoreDataLoaded();
}
if (isAllDataLoaded) {
listener.onAllDataLoaded();
}
dataLoadedListeners.add(listener);
}
}
public static void unregisterDataLoadedListener(OnDataLoadedListener listener) {
synchronized (dataLoadedListeners) {
dataLoadedListeners.remove(listener);
}
}
private void notifyCoreDataLoaded() {
isCoreDataLoaded = true;
synchronized (dataLoadedListeners) {
for (OnDataLoadedListener listener : dataLoadedListeners) {
listener.onCoreDataLoaded();
}
}
}
private void notifyAllDataLoaded() {
isAllDataLoaded = true;
synchronized (dataLoadedListeners) {
for (OnDataLoadedListener listener : dataLoadedListeners) {
listener.onAllDataLoaded();
}
dataLoadedListeners.clear();
}
}
@Override
public void onCreate() {
super.onCreate();
mAppContext = getApplicationContext();
// 加载字体非核心不影响数据加载
try {
defaultFont = Typeface.createFromAsset(getAssets(), "custfont.ttf");
} catch (Exception e) {
defaultFont = Typeface.DEFAULT;
}
ObectManager.init(this);
loadCoreCategorySync();
int corePoolSize = Math.min(2, Runtime.getRuntime().availableProcessors());
executor = Executors.newFixedThreadPool(corePoolSize);
loadAsyncCategoryData();
UpLoadManager.INSTANCE.init(this, "ocean", (s, s2) -> null);
TradPlusSdk.initSdk(this, "F2D3DB0F6557E1864BAFE4934E3E2E11");
}
private void loadCoreCategorySync() {
synchronized (CATEGORY_NAMES) {
CATEGORY_NAMES.clear();
}
InputStream open = null;
try {
// 主线程仅加载核心分类
open = getAssets().open(CORE_CATEGORY_FILE);
String jsonStr = Comutils.getCovertStr(open);
if (!jsonStr.isEmpty()) {
String categoryName = CORE_CATEGORY_FILE.substring(0, CORE_CATEGORY_FILE.lastIndexOf("."));
List<IdleData> dataList = JsonData.INSTANCE.getData(jsonStr, categoryName);
if (!dataList.isEmpty()) {
Collections.shuffle(dataList);
synchronized (ObectManager.class) {
ObectManager.addData(dataList);
}
synchronized (CATEGORY_NAMES) {
CATEGORY_NAMES.add(categoryName);
}
}
}
} catch (IOException e) {
} finally {
if (open != null) {
try {
open.close();
} catch (IOException e) {
}
}
// 核心数据加载完成通知UI
notifyCoreDataLoaded();
}
}
private void loadAsyncCategoryData() {
loadCount.set(0);
for (String jsonFileName : ASYNC_JSON_FILES) {
executor.execute(() -> {
InputStream open = null;
try {
open = getAssets().open(jsonFileName);
String jsonStr = Comutils.getCovertStr(open);
if (!jsonStr.isEmpty()) {
String categoryName = jsonFileName.substring(0, jsonFileName.lastIndexOf("."));
List<IdleData> dataList = JsonData.INSTANCE.getData(jsonStr, categoryName);
if (!dataList.isEmpty()) {
Collections.shuffle(dataList);
// 批量添加数据减少锁竞争
synchronized (ObectManager.class) {
ObectManager.addData(dataList);
}
// 添加分类名称
synchronized (CATEGORY_NAMES) {
if (!CATEGORY_NAMES.contains(categoryName)) {
CATEGORY_NAMES.add(categoryName);
}
}
}
}
} catch (IOException e) {
} finally {
if (open != null) {
try {
open.close();
} catch (IOException e) {
}
}
// 所有异步任务完成后通知
int currentCount = loadCount.incrementAndGet();
if (currentCount == ASYNC_JSON_FILES.length) {
executor.shutdown();
// 切换到主线程通知UI
new Handler(Looper.getMainLooper()).post(this::notifyAllDataLoaded);
}
}
});
}
}
public static List<String> getCategoryNames() {
synchronized (CATEGORY_NAMES) {
return new ArrayList<>(CATEGORY_NAMES);
}
}
public static boolean isCoreDataLoaded() {
return isCoreDataLoaded;
}
public static boolean isAllDataLoaded() {
return isAllDataLoaded;
}
@Override
public void onLowMemory() {
super.onLowMemory();
if (executor != null && !executor.isShutdown()) {
executor.shutdownNow();
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= TRIM_MEMORY_MODERATE && executor != null && !executor.isShutdown()) {
executor.shutdownNow();
}
}
@Override
public void onTerminate() {
super.onTerminate();
if (executor != null && !executor.isShutdown()) {
executor.shutdownNow();
}
synchronized (CATEGORY_NAMES) {
CATEGORY_NAMES.clear();
}
synchronized (dataLoadedListeners) {
dataLoadedListeners.clear();
}
mAppContext = null;
defaultFont = null;
}
}

View File

@ -0,0 +1,46 @@
package com.idle.wall;
public class IdleWallpaper {
private String id;
private String imageUrl;
private String title;
private long viewTime;
public IdleWallpaper() {
}
public IdleWallpaper(String id, String imageUrl) {
this.id = id;
this.imageUrl = imageUrl;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public long getViewTime() {
return viewTime;
}
public void setViewTime(long viewTime) {
this.viewTime = viewTime;
}
}

View File

@ -0,0 +1,115 @@
package com.idle.wall;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import com.idle.wall.idleitem.ItemIdlePage;
import java.util.List;
public class ItemPageAdapter extends RecyclerView.Adapter<ItemPageAdapter.ViewHolder> {
private Context context;
private List<ItemIdlePage> itemList;
private OnItemClickListener onItemClickListener;
private RequestManager glideRequestManager;
private RequestOptions glideOptions;
// 新增占位符标记常量和Fragment中的local_placeholder对应
private static final String LOCAL_PLACEHOLDER = "local_placeholder";
public interface OnItemClickListener {
void onItemClick(ItemIdlePage item);
}
public ItemPageAdapter(Context context, List<ItemIdlePage> itemList, OnItemClickListener listener) {
this.context = context;
this.itemList = itemList;
this.onItemClickListener = listener;
// 初始化Glide配置
glideRequestManager = Glide.with(context);
// 修改将占位图/错误图替换为 mo_ren
glideOptions = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.skipMemoryCache(false)
.placeholder(R.drawable.mo_ren) // 核心修改替换为mo_ren
.error(R.drawable.mo_ren) // 核心修改替换为mo_ren
.dontAnimate();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_idle_page, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
bindViewHolder(holder, position, null);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (!payloads.isEmpty()) {
// 仅刷新图片
String imageUrl = itemList.get(position).getImageUrl();
loadImage(holder.ivImage, imageUrl);
return;
}
// 完整刷新
bindViewHolder(holder, position, payloads);
}
private void bindViewHolder(@NonNull ViewHolder holder, int position, @Nullable List<Object> payloads) {
ItemIdlePage item = itemList.get(position);
holder.tvName.setText(item.getName());
loadImage(holder.ivImage, item.getImageUrl());
holder.itemView.setOnClickListener(v -> {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(item);
}
});
}
/**
* 核心修改优化图片加载逻辑识别local_placeholder并加载mo_ren
*/
private void loadImage(ImageView imageView, String url) {
// 判断是否是本地占位符标记
if (LOCAL_PLACEHOLDER.equals(url)) {
// 直接加载本地mo_ren资源不走Glide网络加载
imageView.setImageResource(R.drawable.mo_ren);
} else {
glideRequestManager
.load(url)
.apply(glideOptions)
.into(imageView);
}
}
@Override
public int getItemCount() {
return itemList != null ? itemList.size() : 0;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView ivImage;
TextView tvName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
ivImage = itemView.findViewById(R.id.iv_category); // 确认布局ID正确
tvName = itemView.findViewById(R.id.tv_category_name); // 确认布局ID正确
}
}
}

View File

@ -0,0 +1,186 @@
package com.idle.wall;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import com.idle.wall.idleactivity.IdleWallActivity;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idletool.Comutils;
import java.util.List;
import java.util.ArrayList;
public class RecentAdapter extends BaseAdapter {
private Context context;
private List<IdleWallpaper> wallpaperList = new ArrayList<>();
private LayoutInflater inflater;
private int itemWidth;
private final int cornerRadius = 12;
private RequestOptions glideOptions;
private RequestOptions preloadOptions;
private Handler mainHandler;
public RecentAdapter(Context context, List<IdleWallpaper> wallpaperList) {
this.context = context;
this.wallpaperList = (wallpaperList != null) ? wallpaperList : new ArrayList<>();
this.inflater = LayoutInflater.from(context);
this.mainHandler = new Handler(Looper.getMainLooper());
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
int columnCount = 2;
int spacing = (int) context.getResources().getDimension(R.dimen.grid_spacing);
this.itemWidth = (screenWidth - (columnCount + 1) * spacing) / columnCount;
// 优化后的Glide配置
initGlideOptions();
}
private void initGlideOptions() {
int radiusPx = dp2px(context, cornerRadius);
RoundedCorners roundedCorners = new RoundedCorners(radiusPx);
glideOptions = new RequestOptions()
.format(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? DecodeFormat.PREFER_ARGB_8888 : DecodeFormat.PREFER_RGB_565)
.override(itemWidth, itemWidth)
.centerCrop()
.transform(roundedCorners)
.skipMemoryCache(false)
.priority(Priority.HIGH)
.timeout(5000)
.placeholder(R.mipmap.im_placeholder)
.error(R.mipmap.im_placeholder);
preloadOptions = new RequestOptions()
.override(itemWidth, itemWidth)
.centerCrop()
.transform(roundedCorners)
.priority(Priority.NORMAL)
.skipMemoryCache(false);
}
@Override
public int getCount() {
return wallpaperList.size();
}
@Override
public Object getItem(int position) {
return wallpaperList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_recent_wallpaper, parent, false);
holder = new ViewHolder();
holder.ivWallpaper = convertView.findViewById(R.id.iv_wallpaper);
ViewGroup.LayoutParams params = holder.ivWallpaper.getLayoutParams();
params.width = itemWidth;
params.height = itemWidth;
holder.ivWallpaper.setLayoutParams(params);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
IdleWallpaper wallpaper = wallpaperList.get(position);
String imagePath = wallpaper.getImageUrl();
// 优化后的图片加载逻辑
loadImageOptimized(holder.ivWallpaper, imagePath);
convertView.setOnClickListener(v -> {
IdleData idleData = new IdleData();
idleData.setImId(wallpaper.getId());
idleData.setFullUrl(wallpaper.getImageUrl());
idleData.setPreviewUrl400(wallpaper.getImageUrl());
idleData.setDescription(wallpaper.getTitle() != null ? wallpaper.getTitle() : "");
Intent intent = new Intent(context, IdleWallActivity.class);
intent.putExtra(Comutils.key_info, idleData);
context.startActivity(intent);
});
return convertView;
}
private void loadImageOptimized(ImageView imageView, String imageUrl) {
if (imageUrl == null || imageUrl.isEmpty()) {
Glide.with(context)
.load(R.mipmap.im_placeholder)
.into(imageView);
return;
}
RequestBuilder<Bitmap> thumbnailRequest = Glide.with(context)
.asBitmap()
.load(imageUrl)
.apply(RequestOptions.overrideOf(itemWidth / 4, itemWidth / 4)
.priority(Priority.IMMEDIATE));
Glide.with(context)
.asBitmap()
.load(imageUrl)
.apply(glideOptions)
.thumbnail(thumbnailRequest)
.into(imageView);
}
static class ViewHolder {
ImageView ivWallpaper;
}
public void updateData(List<IdleWallpaper> newList) {
this.wallpaperList = (newList != null) ? newList : new ArrayList<>();
notifyDataSetChanged();
mainHandler.postDelayed(() -> preloadImagesOptimized(5), 100);
}
private void preloadImagesOptimized(int count) {
if (wallpaperList.isEmpty() || context == null) return;
int preloadCount = Math.min(count, wallpaperList.size());
for (int i = 0; i < preloadCount; i++) {
String imageUrl = wallpaperList.get(i).getImageUrl();
if (imageUrl == null || imageUrl.isEmpty()) continue;
Glide.with(context)
.load(imageUrl)
.apply(preloadOptions)
.preload(itemWidth, itemWidth);
}
}
// 原有dp转px无修改
private int dp2px(Context context, int dp) {
float density = context.getResources().getDisplayMetrics().density;
return (int) (dp * density + 0.5f);
}
public void release() {
if (mainHandler != null) {
mainHandler.removeCallbacksAndMessages(null);
}
this.context = null;
}
}

View File

@ -0,0 +1,54 @@
package com.idle.wall;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class RecentManager {
private static final String PREF_NAME = "recent_wallpapers";
private static final String KEY_RECENT_LIST = "recent_list";
private static RecentManager instance;
private SharedPreferences sp;
private Gson gson;
private RecentManager(Context context) {
sp = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
gson = new Gson();
}
public static synchronized RecentManager getInstance(Context context) {
if (instance == null) {
instance = new RecentManager(context.getApplicationContext());
}
return instance;
}
public void addRecentWallpaper(IdleWallpaper wallpaper) {
List<IdleWallpaper> list = getRecentWallpapers();
list.removeIf(item -> item.getId().equals(wallpaper.getId()));
list.add(0, wallpaper);
if (list.size() > 20) {
list = list.subList(0, 20);
}
SharedPreferences.Editor editor = sp.edit();
editor.putString(KEY_RECENT_LIST, gson.toJson(list));
editor.apply();
}
public List<IdleWallpaper> getRecentWallpapers() {
String json = sp.getString(KEY_RECENT_LIST, "");
Type type = new TypeToken<ArrayList<IdleWallpaper>>() {}.getType();
List<IdleWallpaper> list = gson.fromJson(json, type);
return list == null ? new ArrayList<>() : list;
}
// 清空最近浏览记录
public void clearRecentWallpapers() {
SharedPreferences.Editor editor = sp.edit();
editor.remove(KEY_RECENT_LIST);
editor.apply();
}
}

View File

@ -0,0 +1,83 @@
package com.idle.wall.ad
import android.app.Activity
import android.util.Log
import com.idle.wall.ad.InstAdCacheManager
import com.idle.wall.ad.LoadListener
import com.tradplus.ads.base.bean.TPAdError
import com.tradplus.ads.base.bean.TPAdInfo
import com.tradplus.ads.open.interstitial.InterstitialAdListener
import com.tradplus.ads.open.interstitial.TPInterstitial
class AdInstLoad {
private var mPlace: String
private var adLoadListener: LoadListener? = null
private var activity: Activity? = null
constructor(activity: Activity, place: String, listener: LoadListener?) {
this.mPlace = place
this.adLoadListener = listener
this.activity = activity
init()
}
constructor(place: String, listener: LoadListener?) {
this.mPlace = place
this.adLoadListener = listener
init()
}
private fun init() {
val tpInterstitial = TPInterstitial(activity, mPlace)
tpInterstitial.setAdListener(object : InterstitialAdListener {
//广告加载完成 首个广告源加载成功时回调 一次加载流程只会回调一次
override fun onAdLoaded(tpAdInfo: TPAdInfo?) {
if (tpAdInfo != null) {
Log.d("ocean", "$mPlace 广告load成功tpAdInfo有值")
InstAdCacheManager.Companion.instance.setAdCache(mPlace, tpInterstitial)
adLoadListener?.loaded(tpAdInfo)
} else {
adLoadListener?.loadFailed("tpAdInfo没有值")
Log.d("ocean", "$mPlace tpAdInfo没有值")
}
}
// 广告被点击
override fun onAdClicked(tpAdInfo: TPAdInfo?) {
Log.d("ocean", "$mPlace tradplus onAdClicked")
}
// 广告成功展示在页面上
override fun onAdImpression(tpAdInfo: TPAdInfo?) {
Log.d("ocean", "$mPlace tradplus onAdImpression")
}
// 广告加载失败
override fun onAdFailed(error: TPAdError?) {
adLoadListener?.loadFailed("code->${error?.errorCode}message->${error?.errorMsg}")
Log.d("ocean", "$mPlace load ad onError->code->${error?.errorCode}message->${error?.errorMsg}")
}
// 广告被关闭
override fun onAdClosed(tpAdInfo: TPAdInfo?) {
Log.d("ocean", "$mPlace tradplus onAdClosed")
}
// 视频播放开始(部分广告源支持)
override fun onAdVideoStart(tpAdInfo: TPAdInfo?) {
Log.d("ocean", "$mPlace tradplus onAdVideoStart")
}
//视频播放结束(部分广告源支持)
override fun onAdVideoEnd(tpAdInfo: TPAdInfo?) {
Log.d("ocean", "$mPlace tradplus onAdVideoEnd")
}
//视频播放失败(部分广告源支持)
override fun onAdVideoError(tpAdInfo: TPAdInfo?, error: TPAdError?) {
Log.d("ocean", "$mPlace onAdVideoError code->${error?.errorCode}message->${error?.errorMsg}")
}
})
tpInterstitial.loadAd()
}
}

View File

@ -0,0 +1,72 @@
package com.idle.wall.ad
import android.app.Activity
import android.util.Log
import com.idle.wall.ad.AdShowFailed
import com.idle.wall.ad.InstAdCacheManager
import com.idle.wall.ad.ShowListener
import com.tradplus.ads.base.bean.TPAdError
import com.tradplus.ads.base.bean.TPAdInfo
import com.tradplus.ads.open.interstitial.InterstitialAdListener
class AdInstShower {
private var mPlace: String
private var showListener: ShowListener? = null
private var activity: Activity? = null
constructor(activity: Activity, place: String, showListener: ShowListener?) {
this.mPlace = place
this.showListener = showListener
this.activity = activity
init()
}
constructor(place: String, showListener: ShowListener?) {
this.mPlace = place
this.showListener = showListener
init()
}
private fun init() {
val interstitialAd = InstAdCacheManager.Companion.instance.getAdCache(mPlace)
interstitialAd?.setAdListener(object : InterstitialAdListener {
//广告加载完成 首个广告源加载成功时回调 一次加载流程只会回调一次
override fun onAdLoaded(tpAdInfo: TPAdInfo?) {}
// 广告被点击
override fun onAdClicked(tpAdInfo: TPAdInfo?) {
showListener?.onAdClicked()
Log.d("ocean", "AdInstShower 广告点击回调")
}
// 广告成功展示在页面上
override fun onAdImpression(tpAdInfo: TPAdInfo?) {
showListener?.onAdShown(tpAdInfo)
Log.d("ocean", "AdInstShower 广告展示回调")
}
// 广告加载失败
override fun onAdFailed(error: TPAdError?) {}
// 广告被关闭
override fun onAdClosed(tpAdInfo: TPAdInfo?) {
showListener?.onAdClosed()
Log.d("ocean", "AdInstShower 广告关闭回调")
}
// 视频播放开始(部分广告源支持)
override fun onAdVideoStart(tpAdInfo: TPAdInfo?) {}
//视频播放结束(部分广告源支持)
override fun onAdVideoEnd(tpAdInfo: TPAdInfo?) {}
//视频播放失败(部分广告源支持)
override fun onAdVideoError(tpAdInfo: TPAdInfo?, error: TPAdError?) {
Log.d("ocean", "AdInstShower 视频广告播放失败回调->${error}")
showListener?.onAdShowFailed(AdShowFailed(error?.errorMsg.toString()))
}
})
interstitialAd?.showAd(activity!!, mPlace)
}
}

View File

@ -0,0 +1,5 @@
package com.idle.wall.ad
data class AdShowFailed(
val msg: String = "",
)

View File

@ -0,0 +1,30 @@
package com.idle.wall.ad
import android.app.Activity
import com.idle.wall.ad.AdInstLoad
import com.idle.wall.ad.AdInstShower
object AdsInsUtil {
object Placement {
const val TOP_ON_AD_ONE = "F625C64CEF1F167289F1F50E27276A12"
const val TOP_ON_AD_TOW = "303439D05F7C6E4E0E0CA0D9FE803212"
const val TOP_ON_AD_THREE = "B94497028A74E381D75C0356BBA54512"
}
fun loadAd(
act: Activity,
adID: String,
loadListener: LoadListener?
): AdInstLoad {
return AdInstLoad(act, adID, loadListener)
}
fun showAd(
act: Activity,
adID: String,
listener: ShowListener?
): AdInstShower {
return AdInstShower(act, adID, listener)
}
}

View File

@ -0,0 +1,35 @@
package com.idle.wall.ad
import com.tradplus.ads.open.interstitial.TPInterstitial
class InstAdCacheManager {
private val mAdCacheDict: MutableMap<String, TPInterstitial> = mutableMapOf()
companion object {
val instance: InstAdCacheManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
InstAdCacheManager()
}
}
fun setAdCache(place: String, adCache: TPInterstitial) {
mAdCacheDict[place] = adCache
}
fun getAdCache(place: String): TPInterstitial? {
return mAdCacheDict[place]
}
fun getLoadedInstCount(): Int {
var count = 0
try {
mAdCacheDict.forEach { (key, value) ->
if (value.isReady) {
count += 1
}
}
} catch (_: Exception) {
}
return count
}
}

View File

@ -0,0 +1,8 @@
package com.idle.wall.ad
import com.tradplus.ads.base.bean.TPAdInfo
interface LoadListener {
fun loadFailed(error: String) {}
fun loaded(ad: TPAdInfo) {}
}

View File

@ -0,0 +1,10 @@
package com.idle.wall.ad
import com.tradplus.ads.base.bean.TPAdInfo
interface ShowListener {
fun onAdShown(ad: TPAdInfo?) {}
fun onAdShowFailed(error: AdShowFailed?) {}
fun onAdClosed() {}
fun onAdClicked() {}
}

View File

@ -0,0 +1,36 @@
package com.idle.wall.idleactivity
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!!)
}
}

View File

@ -0,0 +1,182 @@
package com.idle.wall.idleactivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import com.idle.wall.IdleApp;
import com.idle.wall.R;
import com.idle.wall.ad.AdsInsUtil;
import com.idle.wall.ad.LoadListener;
import com.idle.wall.ad.ShowListener;
import com.idle.wall.databinding.ActivityIdleLikeBinding;
import com.idle.wall.idleadapter.OnItemclick;
import com.idle.wall.idleadapter.OnLikelist;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idleitem.ItemIdleLike;
import com.idle.wall.idletool.Comutils;
import com.idle.wall.idletool.Itemhelper;
import com.idle.wall.idletool.ObectManager;
import com.tradplus.ads.base.bean.TPAdInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.WeakHashMap;
import io.objectbox.reactive.DataSubscription;
public class IdleLikeActivity extends BaseActivity implements OnItemclick {
private ActivityIdleLikeBinding vb;
private DataSubscription dataSubscription;
private ItemIdleLike itemLike;
// 弱引用监听避免内存泄漏
private static class LikeListListener implements OnLikelist {
private final WeakHashMap<IdleLikeActivity, Object> activityRef;
public LikeListListener(IdleLikeActivity activity) {
activityRef = new WeakHashMap<>();
activityRef.put(activity, null);
}
@Override
public void onLikeList(List<IdleData> dataList) {
IdleLikeActivity activity = activityRef.keySet().iterator().next();
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
return;
}
activity.updateListUi(dataList);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Comutils.initFull(this, false);
vb = ActivityIdleLikeBinding.inflate(getLayoutInflater());
setContentView(vb.getRoot());
initTitleBar();
initList();
initNoDataBtn();
}
private void initTitleBar() {
if (vb == null) return;
vb.tvPageTitle.setText(getString(R.string.like_page_title));
vb.ivBack.setOnClickListener(v -> {
onInterceptBackPressed();
});
}
private void initNoDataBtn() {
if (vb == null) return;
vb.tvGoCollect.setOnClickListener(v -> {
Intent intent = new Intent(IdleLikeActivity.this, IdleMainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("KEY_SWITCH_TO_HOME_TAB", true);
startActivity(intent);
Toast.makeText(this, R.string.go_collect_tip, Toast.LENGTH_SHORT).show();
finish();
});
}
private void initList() {
itemLike = new ItemIdleLike(this);
itemLike.setInfoWallpaperListener(this);
int spanCount = 2;
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, spanCount);
vb.listRecycler.setLayoutManager(gridLayoutManager);
vb.listRecycler.addItemDecoration(new Itemhelper(10, 10, 15));
vb.listRecycler.setAdapter(itemLike);
vb.listRecycler.setItemAnimator(null);
LikeListListener listener = new LikeListListener(this);
dataSubscription = ObectManager.setLikeDataListener(listener);
}
private void updateListUi(List<IdleData> dataList) {
if (vb == null) return;
if (dataList == null || dataList.isEmpty()) {
vb.layoutNoData.setVisibility(View.VISIBLE);
vb.listRecycler.setVisibility(View.GONE);
vb.tvTitle.setText(getString(R.string.no_like_data));
} else {
vb.layoutNoData.setVisibility(View.GONE);
vb.listRecycler.setVisibility(View.VISIBLE);
vb.tvTitle.setText("");
itemLike.setDataInfo(dataList);
}
}
@Override
public void onItemClickAction(IdleData info) {
if (info == null) return;
Intent intent = new Intent(this, IdleWallActivity.class);
intent.putExtra(Comutils.key_info, info);
startActivity(intent);
}
@Override
protected void onPause() {
super.onPause();
if (vb != null && vb.listRecycler != null) {
vb.listRecycler.stopScroll();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (dataSubscription != null) {
dataSubscription.cancel();
dataSubscription = null;
}
vb = null;
}
public static void startLikeActivity(IdleMainActivity context) {
if (context == null) return;
Intent intent = new Intent(context, IdleLikeActivity.class);
context.startActivity(intent);
}
@Override
protected boolean shouldInterceptBackPress() {
return true;
}
@Override
protected void onInterceptBackPressed() {
AdsInsUtil.INSTANCE.showAd(this, AdsInsUtil.Placement.TOP_ON_AD_TOW, new ShowListener() {
@Override
public void onAdShown(@Nullable TPAdInfo ad) {
loadAd();
}
@Override
public void onAdClosed() {
loadAd();
}
});
if (getBackPressedCallback() != null) {
getBackPressedCallback().setEnabled(false);
}
getOnBackPressedDispatcher().onBackPressed();
}
private void loadAd(){
AdsInsUtil.INSTANCE.loadAd(this, AdsInsUtil.Placement.TOP_ON_AD_TOW, new LoadListener() {
@Override
public void loadFailed(@NotNull String error) {
}
});
}
}

View File

@ -0,0 +1,88 @@
package com.idle.wall.idleactivity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Toast;
import com.idle.wall.R;
import com.idle.wall.databinding.ActivityIdleListBinding;
import com.idle.wall.idleadapter.OnItemclick;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idleitem.ItemIdleList;
import com.idle.wall.idletool.Comutils;
import com.idle.wall.idletool.Itemhelper;
import com.idle.wall.idletool.ObectManager;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import androidx.recyclerview.widget.GridLayoutManager;
public class IdleListActivity extends BaseActivity implements OnItemclick {
private ActivityIdleListBinding vb;
private String categoryName;
public static final String key_index = "index";
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final Handler mainHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Comutils.initFull(this, false);
vb = ActivityIdleListBinding.inflate(getLayoutInflater());
setContentView(vb.getRoot());
categoryName = getIntent().getStringExtra(key_index);
vb.name.setText(categoryName);
vb.imageviewBack.setOnClickListener(v -> finish());
loadDataAndInitList();
}
private void loadDataAndInitList() {
vb.list.setVisibility(View.GONE);
executor.execute(() -> {
try {
List<IdleData> data = ObectManager.queryCategoryAll(categoryName);
mainHandler.post(() -> {
initRecyclerView(data);
vb.list.setVisibility(View.VISIBLE);
});
} catch (Exception e) {
mainHandler.post(() -> {
Toast.makeText(IdleListActivity.this, R.string.falsed, Toast.LENGTH_SHORT).show();
finish();
});
e.printStackTrace();
}
});
}
private void initRecyclerView(List<IdleData> data) {
Itemhelper itemHelper = new Itemhelper(10, 10, 0);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
vb.list.addItemDecoration(itemHelper);
vb.list.setLayoutManager(gridLayoutManager);
int height = Comutils.dp2Px(180);
ItemIdleList itemList = new ItemIdleList(this, height, false);
itemList.setDataInfo(data);
itemList.setInfoWallpaperListener(this);
vb.list.setAdapter(itemList);
}
@Override
public void onItemClickAction(IdleData info) {
Intent intent = new Intent(this, IdleWallActivity.class);
intent.putExtra(Comutils.key_info, info);
startActivity(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
executor.shutdown(); // 关闭线程池
mainHandler.removeCallbacksAndMessages(null); // 清空未执行的回调
}
}

View File

@ -0,0 +1,600 @@
package com.idle.wall.idleactivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.idle.wall.IdleApp;
import com.idle.wall.R;
import com.idle.wall.ad.AdsInsUtil;
import com.idle.wall.ad.LoadListener;
import com.idle.wall.idlefragment.FragmentCategoryPage;
import com.idle.wall.idlefragment.FragmentIdlePage;
import com.idle.wall.idlefragment.FragmentIdleSetting;
import com.idle.wall.idletool.Comutils;
import com.tradplus.ads.base.bean.TPAdInfo;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class IdleMainActivity extends AppCompatActivity {
private LinearLayout homeTabContainer;
private ViewPager2 mainViewPager;
private List<String> categoryNames = new ArrayList<>();
private int currentCategoryPos = 0;
private List<TextView> categoryTabList = new ArrayList<>();
private ImageView tvAllCategoryBtn;
private LinearLayout llDropdownCategoryList;
private HorizontalScrollView categoryScrollView;
private boolean isDropdownExpanded = false;
private final float NORMAL_TEXT_SIZE = 14f;
private final float SELECTED_TEXT_SIZE = 18f;
private int unifiedTabWidth;
private ImageView tabHome,tabMine, tabSetting;
private int currentBottomTab = 0;
private ImageView imSearch;
// 新增顶部标题文本View
private TextView textviewWallpaper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Comutils.initFull(this, false);
setContentView(R.layout.activity_idle_main);
initViews();
homeTabContainer.setVisibility(View.GONE);
IdleApp.registerDataLoadedListener(new IdleApp.OnDataLoadedListener() {
@Override
public void onCoreDataLoaded() {
runOnUiThread(() -> {
loadCategoryData();
calculateUnifiedTabWidth();
createCategoryTabs();
initDropdownCategoryList();
initAllCategoryButton();
initMainViewPager();
updateCategoryTabSelection(currentCategoryPos);
homeTabContainer.setVisibility(View.VISIBLE);
});
}
@Override
public void onAllDataLoaded() {
runOnUiThread(() -> {
loadCategoryData();
calculateUnifiedTabWidth();
createCategoryTabs();
initDropdownCategoryList();
updateCategoryTabSelection(currentCategoryPos);
if (mainViewPager != null && mainViewPager.getAdapter() != null) {
mainViewPager.getAdapter().notifyDataSetChanged();
}
scrollToCategoryTab(0);
});
}
});
initBottomTabClick();
handleIntentParams(getIntent());
}
@Override
protected void onDestroy() {
super.onDestroy();
IdleApp.unregisterDataLoadedListener(new IdleApp.OnDataLoadedListener() {
@Override
public void onCoreDataLoaded() {}
@Override
public void onAllDataLoaded() {}
});
if (mainViewPager != null) {
mainViewPager.unregisterOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {});
}
categoryTabList.clear();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
handleIntentParams(intent);
}
private void handleIntentParams(Intent intent) {
if (intent == null) return;
boolean switchToHome = intent.getBooleanExtra("KEY_SWITCH_TO_HOME_TAB", false);
if (switchToHome && currentBottomTab != 0) {
currentBottomTab = 0;
updateBottomTabSelection(0);
// 新增切换到首页时更新标题
updateTopTitleText(0);
homeTabContainer.setVisibility(View.VISIBLE);
initMainViewPager();
mainViewPager.setCurrentItem(0, false);
updateCategoryTabSelection(0);
if (isDropdownExpanded) {
toggleDropdown(false);
}
if (imSearch != null) {
imSearch.setVisibility(View.VISIBLE);
}
}
}
private void initViews() {
categoryScrollView = findViewById(R.id.category_scroll_view);
homeTabContainer = findViewById(R.id.home_tab_container);
tvAllCategoryBtn = findViewById(R.id.tv_all_category_btn);
llDropdownCategoryList = findViewById(R.id.ll_dropdown_category_list);
mainViewPager = findViewById(R.id.viewpager2);
tabHome = findViewById(R.id.tab_home);
tabMine = findViewById(R.id.tab_mine);
tabSetting = findViewById(R.id.tab_setting);
imSearch = findViewById(R.id.im_search);
// 新增初始化顶部标题View
textviewWallpaper = findViewById(R.id.textview_wallpaper);
if (imSearch != null) {
imSearch.setOnClickListener(v -> {
Intent intent = new Intent(IdleMainActivity.this, IdleSearchActivity.class);
startActivity(intent);
});
imSearch.setVisibility(View.VISIBLE);
}
updateBottomTabSelection(0);
// 新增初始化首页标题
updateTopTitleText(0);
homeTabContainer.setVisibility(View.VISIBLE);
if (categoryScrollView != null) {
categoryScrollView.setHorizontalScrollBarEnabled(false);
categoryScrollView.setVerticalScrollBarEnabled(false);
categoryScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
}
// 新增更新顶部标题文本的方法
private void updateTopTitleText(int tabType) {
if (textviewWallpaper == null) return;
switch (tabType) {
case 0: // 首页
textviewWallpaper.setText(R.string.app_name);
break;
case 1: // 我的
textviewWallpaper.setText(R.string.my_name);
break;
case 2: // 设置
textviewWallpaper.setText(R.string.setting);
break;
default:
textviewWallpaper.setText(R.string.app_name);
break;
}
}
private void loadCategoryData() {
categoryNames = IdleApp.getCategoryNames();
homeTabContainer.removeAllViews();
categoryTabList.clear();
}
private void calculateUnifiedTabWidth() {
if (categoryNames.isEmpty()) return;
String longestName = "";
for (String name : categoryNames) {
if (name.length() > longestName.length()) {
longestName = name;
}
}
TextView tempTab = new TextView(this);
tempTab.setText(longestName);
tempTab.setTextSize(NORMAL_TEXT_SIZE);
tempTab.setPadding(
Comutils.dp2px(this, 14),
Comutils.dp2px(this, 10),
Comutils.dp2px(this, 14),
Comutils.dp2px(this, 10)
);
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
tempTab.measure(widthMeasureSpec, heightMeasureSpec);
unifiedTabWidth = tempTab.getMeasuredWidth() + Comutils.dp2px(this, 10);
}
private void createCategoryTabs() {
if (categoryNames.isEmpty() || unifiedTabWidth == 0) return;
LinearLayout.LayoutParams tabParams = new LinearLayout.LayoutParams(
unifiedTabWidth,
ViewGroup.LayoutParams.WRAP_CONTENT
);
int margin = Comutils.dp2px(this, 8);
tabParams.setMargins(margin, 0, margin, 0);
tabParams.gravity = Gravity.CENTER_VERTICAL;
for (int i = 0; i < categoryNames.size(); i++) {
String cateName = categoryNames.get(i);
TextView tabView = new TextView(this);
tabView.setText(cateName);
tabView.setTextSize(NORMAL_TEXT_SIZE);
tabView.setPadding(
Comutils.dp2px(this, 12),
Comutils.dp2px(this, 8),
Comutils.dp2px(this, 12),
Comutils.dp2px(this, 8)
);
tabView.setLayoutParams(tabParams);
tabView.setClickable(true);
tabView.setFocusable(true);
tabView.setTag(i);
tabView.setGravity(Gravity.CENTER);
tabView.setSingleLine(true);
tabView.setOnClickListener(v -> {
if (currentBottomTab != 0) return;
int position = (int) v.getTag();
switchToCategory(position);
if (isDropdownExpanded) {
toggleDropdown(false);
}
});
homeTabContainer.addView(tabView);
categoryTabList.add(tabView);
}
}
private void initDropdownCategoryList() {
if (llDropdownCategoryList == null || categoryNames.isEmpty()) return;
llDropdownCategoryList.removeAllViews();
int itemMargin = Comutils.dp2px(this, 4);
for (int i = 0; i < categoryNames.size(); i++) {
String cateName = categoryNames.get(i);
TextView tvItem = new TextView(this);
tvItem.setText(cateName);
tvItem.setTextSize(14);
tvItem.setTextColor(currentCategoryPos == i ?
getResources().getColor(R.color.white) :
getResources().getColor(R.color.black));
tvItem.setBackgroundResource(currentCategoryPos == i ?
R.drawable.tab_selected_bg1 :
R.drawable.bg_idle_round);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
params.setMargins(itemMargin, itemMargin, itemMargin, itemMargin);
tvItem.setLayoutParams(params);
tvItem.setTag(i);
tvItem.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
tvItem.setSingleLine(true);
tvItem.setOnClickListener(v -> {
int position = (int) v.getTag();
switchToCategory(position);
toggleDropdown(false);
});
llDropdownCategoryList.addView(tvItem);
}
}
private void initAllCategoryButton() {
if (tvAllCategoryBtn == null) return;
tvAllCategoryBtn.setImageResource(R.drawable.all);
tvAllCategoryBtn.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
tvAllCategoryBtn.setBackgroundResource(R.drawable.bg_idle_round);
tvAllCategoryBtn.setPadding(
Comutils.dp2px(this, 6),
Comutils.dp2px(this, 4),
Comutils.dp2px(this, 6),
Comutils.dp2px(this, 4)
);
tvAllCategoryBtn.setOnClickListener(v -> {
if (currentBottomTab != 0) return;
isDropdownExpanded = !isDropdownExpanded;
toggleDropdown(isDropdownExpanded);
});
}
private void toggleDropdown(boolean expand) {
if (llDropdownCategoryList == null || tvAllCategoryBtn == null) return;
llDropdownCategoryList.setVisibility(expand ? View.VISIBLE : View.GONE);
if (expand) {
tvAllCategoryBtn.setImageResource(R.drawable.all_two);
tvAllCategoryBtn.setBackgroundResource(R.drawable.tab_selected_bg1);
tvAllCategoryBtn.setPadding(
Comutils.dp2px(this, 7),
Comutils.dp2px(this, 5),
Comutils.dp2px(this, 7),
Comutils.dp2px(this, 5)
);
} else {
tvAllCategoryBtn.setImageResource(R.drawable.all);
tvAllCategoryBtn.setBackgroundResource(R.drawable.bg_idle_round);
tvAllCategoryBtn.setPadding(
Comutils.dp2px(this, 6),
Comutils.dp2px(this, 4),
Comutils.dp2px(this, 6),
Comutils.dp2px(this, 4)
);
}
}
private void switchToCategory(int position) {
if (currentCategoryPos != position) {
currentCategoryPos = position;
if (position < categoryTabList.size()) {
updateCategoryTabSelection(position);
} else {
resetCategoryTabSelection();
}
updateDropdownSelection(position);
mainViewPager.setCurrentItem(position, true);
if (position < categoryTabList.size()) {
scrollToCategoryTab(position);
}
}
}
private void updateDropdownSelection(int selectedPos) {
if (llDropdownCategoryList == null) return;
for (int i = 0; i < llDropdownCategoryList.getChildCount(); i++) {
View itemView = llDropdownCategoryList.getChildAt(i);
if (itemView instanceof TextView) {
TextView tvItem = (TextView) itemView;
int position = (int) tvItem.getTag();
if (position == selectedPos) {
tvItem.setTextColor(getResources().getColor(R.color.white));
tvItem.setBackgroundResource(R.drawable.tab_selected_bg1);
} else {
tvItem.setTextColor(getResources().getColor(R.color.black));
tvItem.setBackgroundResource(R.drawable.bg_idle_round);
}
}
}
}
private void resetCategoryTabSelection() {
for (TextView tab : categoryTabList) {
tab.setTextSize(NORMAL_TEXT_SIZE);
tab.setTextColor(getResources().getColor(R.color.sub_title_color));
tab.setBackgroundResource(R.drawable.bg_idle_round);
tab.setPadding(
Comutils.dp2px(this, 12),
Comutils.dp2px(this, 8),
Comutils.dp2px(this, 12),
Comutils.dp2px(this, 8)
);
}
}
private void updateCategoryTabSelection(int selectedPos) {
if (categoryTabList.isEmpty() || selectedPos >= categoryTabList.size()) return;
for (int i = 0; i < categoryTabList.size(); i++) {
TextView tab = categoryTabList.get(i);
if (i == selectedPos) {
tab.setTextSize(SELECTED_TEXT_SIZE);
tab.setTextColor(getResources().getColor(R.color.white));
tab.setBackgroundResource(R.drawable.tab_selected_bg1);
tab.setPadding(
Comutils.dp2px(this, 14),
Comutils.dp2px(this, 10),
Comutils.dp2px(this, 14),
Comutils.dp2px(this, 10)
);
} else {
tab.setTextSize(NORMAL_TEXT_SIZE);
tab.setTextColor(getResources().getColor(R.color.sub_title_color));
tab.setBackgroundResource(R.drawable.bg_idle_round);
tab.setPadding(
Comutils.dp2px(this, 12),
Comutils.dp2px(this, 8),
Comutils.dp2px(this, 12),
Comutils.dp2px(this, 8)
);
}
}
}
private void initMainViewPager() {
mainViewPager.setAdapter(new FragmentStateAdapter(this) {
@Override
public int getItemCount() {
return currentBottomTab == 0 ? categoryNames.size() : 1;
}
@Override
public Fragment createFragment(int position) {
switch (currentBottomTab) {
case 0:
return FragmentCategoryPage.newInstance(categoryNames.get(position));
case 1:
return new FragmentIdlePage();
case 2:
return new FragmentIdleSetting();
default:
return FragmentCategoryPage.newInstance(categoryNames.get(0));
}
}
});
mainViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (currentBottomTab == 0 && currentCategoryPos != position) {
switchToCategory(position);
if (isDropdownExpanded) {
toggleDropdown(false);
}
}
}
});
mainViewPager.setOffscreenPageLimit(2);
mainViewPager.setUserInputEnabled(currentBottomTab == 0);
}
private void scrollToCategoryTab(int position) {
if (position < categoryTabList.size() && categoryScrollView != null) {
TextView selectedTab = categoryTabList.get(position);
int scrollX = selectedTab.getLeft() - (categoryScrollView.getWidth() / 2) + (selectedTab.getWidth() / 2);
categoryScrollView.smoothScrollTo(scrollX, 0);
}
}
private void initBottomTabClick() {
tabHome.setOnClickListener(v -> {
if (currentBottomTab != 0) {
currentBottomTab = 0;
updateBottomTabSelection(0);
// 新增切换到首页更新标题
updateTopTitleText(0);
homeTabContainer.setVisibility(View.VISIBLE);
initMainViewPager();
mainViewPager.setCurrentItem(0, false);
updateCategoryTabSelection(0);
if (isDropdownExpanded) {
toggleDropdown(false);
}
// 切换到首页显示搜索图标
if (imSearch != null) {
imSearch.setVisibility(View.VISIBLE);
}
}
});
tabMine.setOnClickListener(v -> {
if (currentBottomTab != 1) {
currentBottomTab = 1;
updateBottomTabSelection(1);
// 新增切换到我的更新标题
updateTopTitleText(1);
homeTabContainer.setVisibility(View.GONE);
initMainViewPager();
mainViewPager.setCurrentItem(0, false);
if (isDropdownExpanded) {
toggleDropdown(false);
}
// 切换到我的隐藏搜索图标
if (imSearch != null) {
imSearch.setVisibility(View.GONE);
}
}
});
tabSetting.setOnClickListener(v -> {
if (currentBottomTab != 2) {
currentBottomTab = 2;
updateBottomTabSelection(2);
// 新增切换到设置更新标题
updateTopTitleText(2);
homeTabContainer.setVisibility(View.GONE);
initMainViewPager();
mainViewPager.setCurrentItem(0, false);
if (isDropdownExpanded) {
toggleDropdown(false);
}
if (imSearch != null) {
imSearch.setVisibility(View.GONE);
}
}
});
}
private void updateBottomTabSelection(int selectedTab) {
resetBottomTabStyle();
switch (selectedTab) {
case 0:
tabHome.setImageResource(R.drawable.home_n);
tabMine.setImageResource(R.drawable.mine_y);
tabSetting.setImageResource(R.drawable.set_y);
tabHome.setBackgroundResource(R.drawable.tab_selected_bg1);
if (tvAllCategoryBtn != null) {
tvAllCategoryBtn.setVisibility(View.VISIBLE);
}
break;
case 1:
tabHome.setImageResource(R.drawable.home_y);
tabMine.setImageResource(R.drawable.mine_n);
tabSetting.setImageResource(R.drawable.set_y);
tabMine.setBackgroundResource(R.drawable.tab_selected_bg1);
if (tvAllCategoryBtn != null) {
tvAllCategoryBtn.setVisibility(View.GONE);
}
if (isDropdownExpanded) {
toggleDropdown(false);
}
break;
case 2:
tabHome.setImageResource(R.drawable.home_y);
tabMine.setImageResource(R.drawable.mine_y);
tabSetting.setImageResource(R.drawable.set_n);
tabSetting.setBackgroundResource(R.drawable.tab_selected_bg1);
if (tvAllCategoryBtn != null) {
tvAllCategoryBtn.setVisibility(View.GONE);
}
if (isDropdownExpanded) {
toggleDropdown(false);
}
break;
}
}
private void resetBottomTabStyle() {
tabHome.setImageResource(R.drawable.home_n);
tabHome.setBackgroundResource(R.drawable.bg_idle_round);
tabMine.setBackgroundResource(R.drawable.bg_idle_round);
tabMine.setImageResource(R.drawable.mine_n);
tabSetting.setBackgroundResource(R.drawable.bg_idle_round);
tabSetting.setImageResource(R.drawable.set_n);
}
@Override
protected void onResume() {
super.onResume();
loadAd();
}
private void loadAd() {
AdsInsUtil.INSTANCE.loadAd(this, AdsInsUtil.Placement.TOP_ON_AD_THREE, new LoadListener() {
@Override
public void loaded(@NotNull TPAdInfo ad) {
}
@Override
public void loadFailed(@NotNull String error) {
}
});
AdsInsUtil.INSTANCE.loadAd(this, AdsInsUtil.Placement.TOP_ON_AD_TOW, new LoadListener() {
@Override
public void loaded(@NotNull TPAdInfo ad) {
}
@Override
public void loadFailed(@NotNull String error) {
}
});
}
}

View File

@ -0,0 +1,88 @@
package com.idle.wall.idleactivity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Window;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.idle.wall.R;
public class IdleRateDialog extends Dialog {
private ImageView star1, star2, star3, star4, star5;
private int selectedStars = 0;
private String packageName;
public IdleRateDialog(@NonNull Context context) {
super(context, android.R.style.Theme_Translucent_NoTitleBar);
this.packageName = context.getPackageName();
initView();
}
private void initView() {
setContentView(R.layout.activity_idle_rate_dialog);
Window window = getWindow();
if (window != null) {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
window.getDecorView().setPadding(0, 0, 0, 0);
window.setLayout(
(int) (getContext().getResources().getDisplayMetrics().widthPixels * 0.8),
android.view.WindowManager.LayoutParams.WRAP_CONTENT
);
}
setCanceledOnTouchOutside(true);
setCancelable(true);
star1 = findViewById(R.id.iv_star1);
star2 = findViewById(R.id.iv_star2);
star3 = findViewById(R.id.iv_star3);
star4 = findViewById(R.id.iv_star4);
star5 = findViewById(R.id.iv_star5);
star1.setOnClickListener(v -> setStars(1));
star2.setOnClickListener(v -> setStars(2));
star3.setOnClickListener(v -> setStars(3));
star4.setOnClickListener(v -> setStars(4));
star5.setOnClickListener(v -> setStars(5));
findViewById(R.id.btn_cancel).setOnClickListener(v -> dismiss());
findViewById(R.id.btn_rate).setOnClickListener(v -> {
if (selectedStars > 0) {
dismiss();
} else {
Toast.makeText(getContext(), R.string.star, Toast.LENGTH_SHORT).show();
}
});
}
private void setStars(int count) {
selectedStars = count;
star1.setImageResource(R.drawable.star_unselected);
star2.setImageResource(R.drawable.star_unselected);
star3.setImageResource(R.drawable.star_unselected);
star4.setImageResource(R.drawable.star_unselected);
star5.setImageResource(R.drawable.star_unselected);
for (int i = 1; i <= count; i++) {
switch (i) {
case 1:
star1.setImageResource(R.drawable.star_selected);
break;
case 2:
star2.setImageResource(R.drawable.star_selected);
break;
case 3:
star3.setImageResource(R.drawable.star_selected);
break;
case 4:
star4.setImageResource(R.drawable.star_selected);
break;
case 5:
star5.setImageResource(R.drawable.star_selected);
break;
}
}
}
}

View File

@ -0,0 +1,88 @@
package com.idle.wall.idleactivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentTransaction;
import com.idle.wall.R;
import com.idle.wall.ad.AdsInsUtil;
import com.idle.wall.ad.LoadListener;
import com.idle.wall.ad.ShowListener;
import com.idle.wall.idlefragment.FragmentIdleRecent;
import com.idle.wall.RecentManager;
import com.idle.wall.idletool.Comutils;
import com.tradplus.ads.base.bean.TPAdInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IdleRecentActivity extends BaseActivity {
private ImageView ivBack;
private TextView tvClearAll;
private FragmentIdleRecent recentFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_idle_recent);
Comutils.initFull(this, false);
ivBack = findViewById(R.id.iv_back);
tvClearAll = findViewById(R.id.tv_clear_all);
initFragment();
ivBack.setOnClickListener(v -> onInterceptBackPressed());
tvClearAll.setOnClickListener(v -> clearAllRecentWallpapers());
}
private void initFragment() {
recentFragment = FragmentIdleRecent.newInstance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, recentFragment);
transaction.commit();
}
private void clearAllRecentWallpapers() {
RecentManager.getInstance(this).clearRecentWallpapers();
Toast.makeText(this, R.string.recentclean, Toast.LENGTH_SHORT).show();
// 刷新Fragment中的列表
if (recentFragment != null) {
recentFragment.refreshWallpaperList();
}
}
@Override
protected boolean shouldInterceptBackPress() {
return true;
}
@Override
protected void onInterceptBackPressed() {
AdsInsUtil.INSTANCE.showAd(this, AdsInsUtil.Placement.TOP_ON_AD_TOW, new ShowListener() {
@Override
public void onAdShown(@Nullable TPAdInfo ad) {
loadAd();
}
@Override
public void onAdClosed() {
loadAd();
}
});
if (getBackPressedCallback() != null) {
getBackPressedCallback().setEnabled(false);
}
getOnBackPressedDispatcher().onBackPressed();
}
private void loadAd(){
AdsInsUtil.INSTANCE.loadAd(this, AdsInsUtil.Placement.TOP_ON_AD_TOW, new LoadListener() {
@Override
public void loadFailed(@NotNull String error) {
}
});
}
}

View File

@ -0,0 +1,119 @@
package com.idle.wall.idleactivity
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.TextView.OnEditorActionListener
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import com.idle.wall.R
import com.idle.wall.databinding.ActivityIdleSearchBinding
import com.idle.wall.idleadapter.OnItemclick
import com.idle.wall.idleitem.ItemIdleList
import com.idle.wall.idlebean.IdleData
import com.idle.wall.idletool.Comutils
import com.idle.wall.idletool.Itemhelper
import com.idle.wall.idletool.ObectManager
class IdleSearchActivity : AppCompatActivity(), OnItemclick {
private lateinit var adapterWallapper: ItemIdleList
private lateinit var binding: ActivityIdleSearchBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityIdleSearchBinding.inflate(layoutInflater)
setContentView(binding.root)
Comutils.initFull(this, false)
initList()
// 返回按钮点击事件
binding.imageviewBack.setOnClickListener {
finish()
}
}
private fun initList() {
val height = Comutils.dp2Px(180)
adapterWallapper = ItemIdleList(this, height, true)
adapterWallapper.setInfoWallpaperListener(this)
val recyclerItemHelper = Itemhelper(10, 10, 15)
val gridLayoutManager = GridLayoutManager(this, 2)
binding.resultRecycler.run {
layoutManager = gridLayoutManager
addItemDecoration(recyclerItemHelper)
adapter = adapterWallapper
}
// 搜索框回车触发搜索
binding.et.setOnEditorActionListener(OnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
startSearch()
true
} else {
false
}
})
// 搜索按钮点击触发搜索
binding.btnSearch.setOnClickListener { startSearch() }
}
// 关闭软键盘
private fun closeKeyboard() {
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(binding.et.windowToken, 0)
}
// 核心修复替换错误的search方法为正确的searchByDescription
private fun startSearch() {
closeKeyboard()
binding.searchPb.visibility = View.VISIBLE
val keyword = binding.et.text.toString().trim() // 增加trim(),避免空格干扰
if (keyword.isNotEmpty()) {
val searchResult: List<IdleData> = ObectManager.search(keyword)
binding.searchPb.visibility = View.GONE
if (searchResult.isNotEmpty()) {
showEmpty(false)
adapterWallapper.setDataInfo(searchResult) // 刷新列表数据
} else {
showEmpty(true)
Toast.makeText(this, getString(R.string.no_search_result), Toast.LENGTH_SHORT).show()
}
} else {
binding.searchPb.visibility = View.GONE
Toast.makeText(
this,
getString(R.string.please_input_keyword),
Toast.LENGTH_SHORT
).show()
}
}
// 显示/隐藏空页面
private fun showEmpty(showEmpty: Boolean) {
if (showEmpty) {
binding.emptyLayout.visibility = View.VISIBLE
binding.resultRecycler.visibility = View.GONE
} else {
binding.emptyLayout.visibility = View.GONE
binding.resultRecycler.visibility = View.VISIBLE
}
}
// 列表项点击事件:跳转到预览页
override fun onItemClickAction(info: IdleData?) {
info ?: return // 空值保护,避免空指针
val intent = Intent(this, IdleWallActivity::class.java)
intent.putExtra(Comutils.key_info, info)
startActivity(intent)
}
}

View File

@ -0,0 +1,376 @@
package com.idle.wall.idleactivity;
import android.app.WallpaperManager;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.DecodeFormat;
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.RequestOptions;
import com.bumptech.glide.request.target.Target;
import com.idle.wall.IdleWallpaper;
import com.idle.wall.ad.AdsInsUtil;
import com.idle.wall.ad.LoadListener;
import com.idle.wall.ad.ShowListener;
import com.idle.wall.databinding.ActivityIdleWallBinding;
import com.idle.wall.idleadapter.OnSelecttype;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idlefragment.Idledialog;
import com.idle.wall.idletool.Comutils;
import com.idle.wall.idletool.ObectManager;
import com.idle.wall.R;
import com.idle.wall.idletool.JsonData;
import com.idle.wall.RecentManager; // 新增导入最近浏览管理类
import com.tradplus.ads.base.bean.TPAdInfo;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileInputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import okhttp3.Call;
public class IdleWallActivity extends AppCompatActivity implements OnSelecttype {
private static final String TAG = "PreviewActivity"; // 新增日志标签
private IdleData data;
private WallpaperManager wallpaperManager;
private final int permissionCode = 1;
private String mSavePath;
private String imId;
private String fullUrl;
private Idledialog dialogType;
private File downloadFile;
private Call call;
private ActivityIdleWallBinding vb;
private Timer clockTimer;
private Handler mainHandler;
private final SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.CHINA);
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd EEEE", Locale.ENGLISH);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityIdleWallBinding.inflate(getLayoutInflater());
Comutils.initFull(this, true);
setContentView(vb.getRoot());
AdsInsUtil.INSTANCE.showAd(this, AdsInsUtil.Placement.TOP_ON_AD_THREE, new ShowListener() {
@Override
public void onAdShown(@org.jetbrains.annotations.Nullable TPAdInfo ad) {
loadAd();
}
@Override
public void onAdClosed() {
loadAd();
}
});
data = (IdleData) getIntent().getSerializableExtra(Comutils.key_info);
if (data == null) {
finish();
return;
}
imId = data.getImId();
mSavePath = JsonData.INSTANCE.getSaveFilePath(this, imId);
fullUrl = data.getFullUrl();
wallpaperManager = WallpaperManager.getInstance(this);
mainHandler = new Handler(Looper.getMainLooper());
addToRecentWallpapers();
ObectManager.updateViewHistory(data);
initClick();
loadDetail();
vb.tvContent.setText(data.getDescription());
initClockWeather();
startClockRefresh();
}
private void addToRecentWallpapers() {
if (data == null) {
return;
}
IdleWallpaper wallpaper = new IdleWallpaper();
String wallpaperId = data.getImId() != null ? data.getImId() : System.currentTimeMillis() + "";
wallpaper.setId(wallpaperId);
String title = data.getDescription() != null ? data.getDescription() : "Wallpaper " + wallpaperId;
wallpaper.setTitle(title);
String imageUrl = data.getFullUrl() != null ? data.getFullUrl() : (data.getPreviewUrl1080() != null ? data.getPreviewUrl1080() : "");
wallpaper.setImageUrl(imageUrl);
wallpaper.setViewTime(System.currentTimeMillis());
RecentManager.getInstance(this).addRecentWallpaper(wallpaper);
}
private void initClockWeather() {
updateClockWeather();
vb.tvWeather.setText(R.string.time3);
}
private void startClockRefresh() {
if (clockTimer != null) {
clockTimer.cancel();
}
clockTimer = new Timer();
clockTimer.schedule(new TimerTask() {
@Override
public void run() {
if (!isDestroyed() && mainHandler != null) {
mainHandler.post(IdleWallActivity.this::updateClockWeather);
}
}
}, 0, 60 * 1000);
}
private void updateClockWeather() {
Date currentTime = new Date();
vb.tvClock.setText(timeFormat.format(currentTime));
vb.tvDate.setText(dateFormat.format(currentTime));
int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
if (hour >= 18 || hour < 6) {
vb.tvWeather.setText(R.string.time4);
} else if (hour >= 12 && hour < 18) {
vb.tvWeather.setText(R.string.time5);
} else {
vb.tvWeather.setText(R.string.time6);
}
}
private void loadDetail() {
String previewUrl400 = data.getPreviewUrl400();
String previewUrl1080 = data.getPreviewUrl1080();
File file = new File(mSavePath);
RequestOptions options = new RequestOptions().format(DecodeFormat.PREFER_RGB_565);
if (file.exists()) {
Glide.with(this)
.load(file)
.apply(options)
.placeholder(R.mipmap.im_placeholder)
.error(R.mipmap.im_placeholder)
.transition(DrawableTransitionOptions.withCrossFade(300))
.fitCenter()
.into(vb.imageviewPreview);
} else {
Glide.with(this)
.asDrawable()
.load(previewUrl1080)
.apply(options)
.placeholder(R.mipmap.im_placeholder)
.error(R.mipmap.im_placeholder)
.thumbnail(Glide.with(this)
.asDrawable()
.load(previewUrl400)
.apply(options)
.placeholder(R.mipmap.im_placeholder)
.fitCenter())
.transition(DrawableTransitionOptions.withCrossFade(300))
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
return false;
}
})
.fitCenter()
.into(vb.imageviewPreview);
}
vb.imageFavorite.setSelected(ObectManager.queryIsLike(imId));
}
private void initClick() {
vb.imageviewBack.setOnClickListener(v -> finish());
vb.buttonSet.setOnClickListener(v -> {
if (dialogType == null) {
dialogType = new Idledialog();
dialogType.setListener(this);
}
if (!dialogType.isAdded()) {
dialogType.show(getSupportFragmentManager(), "");
}
});
vb.imageFavorite.setOnClickListener(v -> {
boolean isSelected = vb.imageFavorite.isSelected();
vb.imageFavorite.setSelected(!isSelected);
data.setLike(!isSelected);
ObectManager.updateLike(data);
});
vb.layoutDownload.setOnClickListener(v -> {
if (JsonData.INSTANCE.requestPermission(this, permissionCode)) {
startSaveToAlbum();
}
});
}
private void startSaveToAlbum() {
showDownloadLoading(true);
if (downloadFile != null && downloadFile.exists()) {
startSave(downloadFile);
return;
}
doDownload(true, true);
}
private void doDownload(boolean showMsg, boolean save) {
call = JsonData.INSTANCE.downloadFile(fullUrl, mSavePath, (aBoolean, inputStream) -> {
File file = new File(mSavePath);
if (aBoolean && file.exists()) {
downloadFile = file;
if (save) {
startSave(downloadFile);
}
} else if (showMsg) {
runOnUiThread(() -> {
showDownloadLoading(false);
Toast.makeText(IdleWallActivity.this, getString(R.string.download_fail), Toast.LENGTH_SHORT).show();
});
}
return null;
});
}
private void showDownloadLoading(boolean show) {
vb.pbDownload.setVisibility(show ? View.VISIBLE : View.GONE);
vb.imageDownload.setVisibility(show ? View.GONE : View.VISIBLE);
}
private void showSetWallpaperLoading(boolean show) {
vb.pbSet.setVisibility(show ? View.VISIBLE : View.GONE);
vb.buttonSet.setVisibility(show ? View.GONE : View.VISIBLE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == permissionCode && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startSaveToAlbum();
}
}
private void startSave(File file) {
ObectManager.insertOrUpdateDownload(data);
Uri uri = JsonData.INSTANCE.saveToGallery(this, file);
runOnUiThread(() -> {
showDownloadLoading(false);
Toast.makeText(this, uri == null ? R.string.save_fail : R.string.save_success,
uri == null ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
});
}
@Override
public void onSelectType(int type) {
showSetWallpaperLoading(true);
if (downloadFile != null && downloadFile.exists()) {
new Thread(() -> setWallpaper(downloadFile, type)).start();
} else {
call = JsonData.INSTANCE.downloadFile(fullUrl, mSavePath, (aBoolean, inputStream) -> {
File file = new File(mSavePath);
if (aBoolean && file.exists()) {
downloadFile = file;
setWallpaper(downloadFile, type);
} else {
runOnUiThread(() -> {
showSetWallpaperLoading(false);
Toast.makeText(IdleWallActivity.this, R.string.set_fail, Toast.LENGTH_SHORT).show();
});
}
return null;
});
}
}
private void setWallpaper(File file, int type) {
try (FileInputStream fis = new FileInputStream(file)) {
switch (type) {
case Idledialog.type_home:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
wallpaperManager.setStream(fis, null, true, WallpaperManager.FLAG_SYSTEM);
} else {
wallpaperManager.setStream(fis);
}
break;
case Idledialog.type_lock:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
wallpaperManager.setStream(fis, null, true, WallpaperManager.FLAG_LOCK);
} else {
wallpaperManager.setStream(fis);
}
break;
case Idledialog.type_both:
wallpaperManager.setStream(fis);
break;
}
if (file.exists()) {
file.delete();
}
runOnUiThread(() -> {
showSetWallpaperLoading(false);
Toast.makeText(this, R.string.set_success, Toast.LENGTH_SHORT).show();
});
} catch (Exception e) {
runOnUiThread(() -> {
showSetWallpaperLoading(false);
Toast.makeText(this, R.string.set_fail, Toast.LENGTH_SHORT).show();
});
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (clockTimer != null) {
clockTimer.cancel();
clockTimer = null;
}
if (mainHandler != null) {
mainHandler.removeCallbacksAndMessages(null);
}
if (call != null) {
call.cancel();
}
if (dialogType != null && dialogType.isAdded()) {
dialogType.dismiss();
}
}
private void loadAd(){
AdsInsUtil.INSTANCE.loadAd(this, AdsInsUtil.Placement.TOP_ON_AD_THREE, new LoadListener() {
@Override
public void loadFailed(@NotNull String error) {
}
});
}
}

View File

@ -0,0 +1,171 @@
package com.idle.wall.idleactivity
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.widget.Toast
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import com.idle.wall.R
import com.idle.wall.ad.AdShowFailed
import com.idle.wall.ad.AdsInsUtil
import com.idle.wall.ad.LoadListener
import com.idle.wall.ad.ShowListener
import com.idle.wall.databinding.ActivityIdleFirstBinding
import com.tradplus.ads.base.bean.TPAdInfo
class SplashActivity : BaseActivity() {
private var countDownTimer: CountDownTimer? = null
private lateinit var vb: ActivityIdleFirstBinding
private val tickInterval = 100L
private val totalTime = 15_000L
/**
* tick 次数 15
*/
private val totalTicks = totalTime / tickInterval
/**
* tick 增加的进度 0.6666~
*/
private val normalStep = 100f / totalTicks
private val fastStep = normalStep * 4 // 加速倍率
private var currentStep = normalStep
// 进度控制
private var progress = 0f
// 广告状态
private var adAvailable = false
private var hasNavigated = false
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vb = ActivityIdleFirstBinding.inflate(layoutInflater)
setContentView(vb.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.isAppearanceLightStatusBars = true//状态栏文字颜色
// 1. 进来就 load 广告
loadSplashAd()
// 2. 启动进度条(只启动一次)
startProgressTimer()
}
private fun startProgressTimer() {
countDownTimer?.cancel()
countDownTimer = object : CountDownTimer(totalTime, tickInterval) {
override fun onTick(millisUntilFinished: Long) {
progress += currentStep
if (progress >= 100f) {
progress = 100f
vb.progressbar.progress = 100
cancel()
onProgressFinished()
} else {
vb.progressbar.progress = progress.toInt()
}
}
override fun onFinish() {
// 不使用
}
}.start()
}
// 进度走完后的统一出口
private fun onProgressFinished() {
if (hasNavigated) return
if (adAvailable) {
showSplashAd()
} else {
navigateToNext()
}
}
private fun loadSplashAd() {
AdsInsUtil.loadAd(
act = this,
adID = AdsInsUtil.Placement.TOP_ON_AD_ONE,
loadListener = object : LoadListener {
override fun loaded(ad: TPAdInfo) {
adAvailable = true
accelerateProgress()
}
override fun loadFailed(error: String) {
adAvailable = false
accelerateProgress()
}
}
)
}
// 广告 load 完 → 加速
private fun accelerateProgress() {
currentStep = fastStep
}
private fun showSplashAd() {
AdsInsUtil.showAd(
act = this,
adID = AdsInsUtil.Placement.TOP_ON_AD_ONE,
listener = object : ShowListener {
override fun onAdShown(ad: TPAdInfo?) {}
override fun onAdShowFailed(error: AdShowFailed?) {
navigateToNext()
}
override fun onAdClosed() {
navigateToNext()
}
}
)
}
private fun navigateToNext() {
if (hasNavigated) return
hasNavigated = true
navigateToMainActivity()
}
override fun shouldInterceptBackPress(): Boolean = true
override fun onInterceptBackPressed() {}
@SuppressLint("QueryPermissionsNeeded")
private fun navigateToMainActivity() {
try {
startActivity(Intent(this, IdleMainActivity::class.java))
finish()
} catch (e: Exception) {
Toast.makeText(this, "跳转失败", Toast.LENGTH_SHORT).show()
e.printStackTrace()
finish()
}
}
override fun onDestroy() {
super.onDestroy()
countDownTimer?.cancel()
countDownTimer = null
}
}

View File

@ -0,0 +1,10 @@
package com.idle.wall.idleadapter;
import com.idle.wall.idlebean.IdleData;
public interface OnItemclick {
void onItemClickAction(IdleData info);
default void onClickMore(String name){}
}

View File

@ -0,0 +1,9 @@
package com.idle.wall.idleadapter;
import com.idle.wall.idlebean.IdleData;
import java.util.List;
public interface OnLikelist {
void onLikeList(List<IdleData> dataList);
}

View File

@ -0,0 +1,6 @@
package com.idle.wall.idleadapter;
public interface OnSelecttype {
void onSelectType(int type);
}

View File

@ -0,0 +1,8 @@
package com.idle.wall.idleadapter;
import com.idle.wall.idlebean.IdleData;
public interface OnUpdatelike {
void onAdd(IdleData data);
void onRemove(IdleData data);
}

View File

@ -0,0 +1,10 @@
package com.idle.wall.idleadapter;
import com.idle.wall.idlebean.IdleData;
import java.util.List;
// 和OnLikelist接口逻辑完全对齐仅名称不同
public interface OnViewList {
void onViewList(List<IdleData> data);
}

View File

@ -0,0 +1,173 @@
package com.idle.wall.idlebean;
import androidx.annotation.Nullable;
import java.io.Serializable;
import io.objectbox.annotation.Entity;
import io.objectbox.annotation.Id;
@Entity
public class IdleData implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long id;
private String categoryName;
private String imId;
private String description;
private String fullUrl;
private String previewUrl1080;
private String previewUrl400;
private String previewUrl200;
private String authorName;
private String authorHeader;
private String authorHtml;
private boolean isLike;
private boolean isDownload;
private long downloadTime;
private long likeTime;
private long viewTime; // 浏览时间戳毫秒
public long getDownloadTime() {
return downloadTime;
}
public void setDownloadTime(long downloadTime) {
this.downloadTime = downloadTime;
}
public long getViewTime() {
return viewTime;
}
public void setViewTime(long viewTime) {
this.viewTime = viewTime;
}
// ========== 以下是你原有代码完全保留 ==========
public long getLikeTime() {
return likeTime;
}
public void setLikeTime(long likeTime) {
this.likeTime = likeTime;
}
public boolean isLike() {
return isLike;
}
public void setLike(boolean like) {
isLike = like;
}
public boolean isDownload() {
return isDownload;
}
public void setDownload(boolean download) {
isDownload = download;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getImId() {
return imId;
}
public void setImId(String imId) {
this.imId = imId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getFullUrl() {
return fullUrl;
}
public void setFullUrl(String fullUrl) {
this.fullUrl = fullUrl;
}
public String getPreviewUrl1080() {
return previewUrl1080;
}
public void setPreviewUrl1080(String previewUrl1080) {
this.previewUrl1080 = previewUrl1080;
}
public String getPreviewUrl400() {
return previewUrl400;
}
public void setPreviewUrl400(String previewUrl400) {
this.previewUrl400 = previewUrl400;
}
public String getPreviewUrl200() {
return previewUrl200;
}
public void setPreviewUrl200(String previewUrl200) {
this.previewUrl200 = previewUrl200;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
public String getAuthorHeader() {
return authorHeader;
}
public void setAuthorHeader(String authorHeader) {
this.authorHeader = authorHeader;
}
public String getAuthorHtml() {
return authorHtml;
}
public void setAuthorHtml(String authorHtml) {
this.authorHtml = authorHtml;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
IdleData pinkData = (IdleData) obj;
return imId != null && imId.equals(pinkData.imId);
}
@Override
public int hashCode() {
return imId != null ? imId.hashCode() : 0;
}
}

View File

@ -0,0 +1,170 @@
package com.idle.wall.idlefragment;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.idle.wall.R;
import com.idle.wall.idleactivity.IdleWallActivity;
import com.idle.wall.idleadapter.OnItemclick;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idleitem.ItemIdleList;
import com.idle.wall.idletool.Comutils;
import com.idle.wall.idletool.Itemhelper;
import com.idle.wall.idletool.ObectManager;
import java.util.ArrayList;
import java.util.List;
public class FragmentCategoryPage extends Fragment implements OnItemclick {
private RecyclerView recyclerView;
private ItemIdleList categoryAdapter;
private List<IdleData> categoryDataList;
private ProgressBar loadingView;
private String categoryName;
private TextView emptyView;
public static FragmentCategoryPage newInstance(String categoryName) {
FragmentCategoryPage fragment = new FragmentCategoryPage();
Bundle bundle = new Bundle();
bundle.putString("key_index", categoryName);
fragment.setArguments(bundle);
return fragment;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_category_page, container, false);
// 同步查找控件
recyclerView = view.findViewById(R.id.category_recycler);
loadingView = view.findViewById(R.id.loading_category);
if (recyclerView == null) {
View emptyView = new View(requireContext());
emptyView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
return emptyView;
}
parseCategoryName();
initCategoryLayout();
loadCategoryData(); // 同步加载数据
return view;
}
private void parseCategoryName() {
Bundle args = getArguments();
if (args != null) {
categoryName = args.getString("key_index");
}
}
private void initCategoryLayout() {
if (recyclerView == null) return;
// 同步初始化RecyclerView
GridLayoutManager gridLayoutManager = new GridLayoutManager(requireContext(), 2);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.setItemViewCacheSize(10);
if (recyclerView.getItemDecorationCount() == 0) {
int spacing = Comutils.dp2px(requireContext(), 2);
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.left = spacing / 2;
outRect.right = spacing / 2;
outRect.top = spacing / 2;
outRect.bottom = spacing / 2;
}
});
Itemhelper itemHelper = new Itemhelper(0, 0, 0);
recyclerView.addItemDecoration(itemHelper);
}
// 同步创建Adapter
categoryAdapter = new ItemIdleList(requireContext(), 0, false);
categoryAdapter.setInfoWallpaperListener(this);
recyclerView.setAdapter(categoryAdapter);
if (loadingView != null) {
loadingView.setVisibility(View.VISIBLE);
}
if (emptyView != null) {
emptyView.setVisibility(View.GONE);
}
recyclerView.setVisibility(View.GONE);
}
private void loadCategoryData() {
if (categoryName == null || categoryName.isEmpty()) {
if (loadingView != null) {
loadingView.setVisibility(View.GONE);
}
showEmptyView("暂无分类数据");
return;
}
List<IdleData> data = ObectManager.queryCategoryAll(categoryName);
categoryDataList = data != null ? data : new ArrayList<>();
// 同步过滤无效数据
categoryDataList.removeIf(idleData ->
idleData == null || idleData.getPreviewUrl400() == null || idleData.getPreviewUrl400().isEmpty()
);
// 同步更新UI
if (loadingView != null) {
loadingView.setVisibility(View.GONE);
}
if (categoryDataList.isEmpty()) {
showEmptyView("暂无" + categoryName + "分类内容");
recyclerView.setVisibility(View.GONE);
} else {
if (emptyView != null) {
emptyView.setVisibility(View.GONE);
}
recyclerView.setVisibility(View.VISIBLE);
categoryAdapter.setDataInfo(categoryDataList); // 同步设置数据
}
}
private void showEmptyView(String tipText) {
if (emptyView == null) return;
emptyView.setVisibility(View.VISIBLE);
emptyView.setText(tipText);
}
@Override
public void onItemClickAction(IdleData info) {
if (!isAdded() || info == null) return;
Intent intent = new Intent(requireActivity(), IdleWallActivity.class);
intent.putExtra(Comutils.key_info, info);
startActivity(intent);
}
@Override
public void onDestroyView() {
super.onDestroyView();
// 清理资源
categoryDataList = null;
categoryAdapter = null;
recyclerView = null;
loadingView = null;
emptyView = null;
}
}

View File

@ -0,0 +1,191 @@
package com.idle.wall.idlefragment;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.idle.wall.IdleApp;
import com.idle.wall.R;
import com.idle.wall.databinding.FragmentIdleMainBinding;
import com.idle.wall.idleactivity.IdleListActivity;
import com.idle.wall.idletool.Comutils;
import java.util.ArrayList;
import java.util.List;
public class FragmentIdleMain extends Fragment {
private FragmentIdleMainBinding vb;
private ProgressBar loadingPb;
private LinearLayout tabContainer;
private ViewPager2 contentViewPager;
private List<String> categoryNames = new ArrayList<>();
private int currentSelectedPos = 0;
private List<TextView> tabViewList = new ArrayList<>();
public static FragmentIdleMain newInstance(int type) {
FragmentIdleMain fragment = new FragmentIdleMain();
Bundle bundle = new Bundle();
bundle.putInt(Comutils.key_index, type);
fragment.setArguments(bundle);
return fragment;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
vb = FragmentIdleMainBinding.inflate(inflater, container, false);
loadingPb = vb.loadingPb;
tabContainer = vb.tabContainer;
contentViewPager = vb.contentViewPager;
return vb.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 初始化分类数据同步从App获取
categoryNames = IdleApp.getCategoryNames();
initContentViewPager();
initCategoryData();
setupViewPagerListener();
}
private void initContentViewPager() {
contentViewPager.setAdapter(new FragmentStateAdapter(this) {
@NonNull
@Override
public Fragment createFragment(int position) {
FragmentIdlePage fragment = new FragmentIdlePage();
Bundle bundle = new Bundle();
String cateName = (position < categoryNames.size()) ? categoryNames.get(position) : "";
bundle.putString(IdleListActivity.key_index, cateName);
fragment.setArguments(bundle);
return fragment;
}
@Override
public int getItemCount() {
return categoryNames.size();
}
});
contentViewPager.setOffscreenPageLimit(2);
contentViewPager.setUserInputEnabled(true);
}
private void initCategoryData() {
loadingPb.setVisibility(View.VISIBLE);
tabContainer.removeAllViews();
tabViewList.clear();
// 同步创建Tab主线程
createTabViews();
loadingPb.setVisibility(View.GONE);
updateTabSelection(currentSelectedPos);
contentViewPager.setCurrentItem(currentSelectedPos, false);
}
private void createTabViews() {
if (categoryNames.isEmpty() || getContext() == null) return;
Context context = getContext();
LinearLayout.LayoutParams tabParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.MATCH_PARENT
);
int marginRight = Comutils.dp2px(context, 8);
int marginLeft = Comutils.dp2px(context, 8);
tabParams.setMargins(marginLeft, 0, marginRight, 0);
for (int i = 0; i < categoryNames.size(); i++) {
String categoryName = categoryNames.get(i);
TextView tabView = new TextView(context);
tabView.setText(categoryName);
tabView.setTextSize(14);
tabView.setPadding(
Comutils.dp2px(context, 12),
Comutils.dp2px(context, 8),
Comutils.dp2px(context, 12),
Comutils.dp2px(context, 8)
);
tabView.setLayoutParams(tabParams);
tabView.setClickable(true);
tabView.setFocusable(true);
tabView.setTag(i);
tabView.setOnClickListener(v -> {
int position = (int) v.getTag();
if (currentSelectedPos != position) {
currentSelectedPos = position;
updateTabSelection(position);
contentViewPager.setCurrentItem(position, true);
}
});
tabContainer.addView(tabView);
tabViewList.add(tabView);
}
}
private void updateTabSelection(int selectedPosition) {
if (tabViewList.isEmpty() || selectedPosition >= tabViewList.size()) return;
for (int i = 0; i < tabViewList.size(); i++) {
TextView tabView = tabViewList.get(i);
if (i == selectedPosition) {
tabView.setTextColor(getResources().getColor(R.color.white));
tabView.setBackgroundResource(R.drawable.tab_selected_bg1);
} else {
tabView.setTextColor(getResources().getColor(R.color.sub_title_color));
tabView.setBackgroundResource(R.drawable.bg_idle_round);
}
}
}
private void setupViewPagerListener() {
contentViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (currentSelectedPos != position) {
currentSelectedPos = position;
updateTabSelection(position);
scrollToSelectedTab(position);
}
}
});
}
private void scrollToSelectedTab(int position) {
if (position < tabViewList.size()) {
TextView selectedTab = tabViewList.get(position);
tabContainer.scrollTo(selectedTab.getLeft() - Comutils.dp2px(getContext(), 8), 0);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (contentViewPager != null) {
contentViewPager.unregisterOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {});
}
tabViewList.clear();
vb = null;
loadingPb = null;
tabContainer = null;
contentViewPager = null;
}
}

View File

@ -0,0 +1,222 @@
package com.idle.wall.idlefragment;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.idle.wall.R;
import com.idle.wall.ItemPageAdapter;
import com.idle.wall.idleactivity.IdleLikeActivity;
import com.idle.wall.idleactivity.IdleMainActivity;
import com.idle.wall.idleactivity.IdleRecentActivity;
import com.idle.wall.idleadapter.OnLikelist;
import com.idle.wall.idleadapter.OnViewList; // 新增浏览数据监听接口
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idleitem.ItemIdlePage;
import com.idle.wall.idletool.ObectManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import io.objectbox.reactive.DataSubscription;
public class FragmentIdlePage extends Fragment {
private RecyclerView recyclerView;
private ItemPageAdapter adapter;
private List<ItemIdlePage> itemList;
private List<IdleData> likeDataList;
private List<IdleData> viewDataList;
private DataSubscription likeDataSubscription;
private DataSubscription viewDataSubscription;
private final ExecutorService dbExecutor = Executors.newSingleThreadExecutor();
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private boolean isRefreshing = false;
private static final long REFRESH_THROTTLE_MS = 500;
public FragmentIdlePage() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_idle_page, container, false);
recyclerView = view.findViewById(R.id.page_recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
likeDataList = new ArrayList<>();
viewDataList = new ArrayList<>(); // 初始化浏览数据
initData();
// 初始化适配器
adapter = new ItemPageAdapter(getContext(), itemList, item -> handleItemClick(item));
recyclerView.setAdapter(adapter);
// 隐藏加载中
View loadingView = view.findViewById(R.id.loading_page);
if (loadingView != null) {
loadingView.setVisibility(View.GONE);
}
return view;
}
@Override
public void onResume() {
super.onResume();
registerLikeDataListener();
registerViewDataListener();
queryLikeDataAsync();
queryViewDataAsync(); // 新增
}
@Override
public void onPause() {
super.onPause();
unregisterLikeDataListener();
unregisterViewDataListener(); // 新增
isRefreshing = false;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unregisterLikeDataListener();
unregisterViewDataListener();
dbExecutor.shutdown();
mainHandler.removeCallbacksAndMessages(null);
likeDataList = null;
viewDataList = null;
itemList = null;
adapter = null;
}
private void queryLikeDataAsync() {
dbExecutor.execute(() -> {
List<IdleData> data = ObectManager.getLikeList();
mainHandler.post(() -> {
likeDataList = data != null ? data : new ArrayList<>();
refreshLikeItemImage();
});
});
}
private void registerLikeDataListener() {
if (likeDataSubscription != null) return;
likeDataSubscription = ObectManager.setLikeDataListener(new OnLikelist() {
@Override
public void onLikeList(List<IdleData> data) {
if (isRefreshing) return;
isRefreshing = true;
likeDataList = data != null ? data : new ArrayList<>();
refreshLikeItemImage();
mainHandler.postDelayed(() -> isRefreshing = false, REFRESH_THROTTLE_MS);
}
});
}
private void unregisterLikeDataListener() {
if (likeDataSubscription != null) {
likeDataSubscription.cancel();
likeDataSubscription = null;
}
}
private void refreshLikeItemImage() {
if (itemList == null || itemList.isEmpty() || adapter == null) return;
ItemIdlePage likeItem = itemList.get(0);
String newImageUrl = getLikeImageUrl();
if (!newImageUrl.equals(likeItem.getImageUrl())) {
likeItem.setImageUrl(newImageUrl);
adapter.notifyItemChanged(0, "UPDATE_IMAGE");
}
}
private String getLikeImageUrl() {
if (!likeDataList.isEmpty()) {
IdleData first = likeDataList.get(0);
String url = first.getPreviewUrl400();
return (url != null && !url.isEmpty()) ? url : getDefaultImageUrl();
}
return getDefaultImageUrl();
}
private void queryViewDataAsync() {
dbExecutor.execute(() -> {
List<IdleData> data = ObectManager.getViewList();
mainHandler.post(() -> {
viewDataList = data != null ? data : new ArrayList<>();
refreshViewItemImage();
});
});
}
private void registerViewDataListener() {
if (viewDataSubscription != null) return;
viewDataSubscription = ObectManager.setViewDataListener(new OnViewList() {
@Override
public void onViewList(List<IdleData> data) {
if (isRefreshing) return;
isRefreshing = true;
viewDataList = data != null ? data : new ArrayList<>();
refreshViewItemImage();
mainHandler.postDelayed(() -> isRefreshing = false, REFRESH_THROTTLE_MS);
}
});
}
private void unregisterViewDataListener() {
if (viewDataSubscription != null) {
viewDataSubscription.cancel();
viewDataSubscription = null;
}
}
private void refreshViewItemImage() {
if (itemList == null || itemList.size() < 2 || adapter == null) return;
ItemIdlePage viewItem = itemList.get(1);
String newImageUrl = getViewImageUrl();
if (!newImageUrl.equals(viewItem.getImageUrl())) {
viewItem.setImageUrl(newImageUrl);
adapter.notifyItemChanged(1, "UPDATE_IMAGE");
}
}
private String getViewImageUrl() {
if (!viewDataList.isEmpty()) {
IdleData latestView = viewDataList.get(0);
String url = latestView.getPreviewUrl400();
return (url != null && !url.isEmpty()) ? url : getDefaultImageUrl();
}
return getDefaultImageUrl();
}
private String getDefaultImageUrl() {
return "local_placeholder";
}
private void initData() {
itemList = new ArrayList<>();
itemList.add(new ItemIdlePage(getDefaultImageUrl(), getString(R.string.like_page_title)));
itemList.add(new ItemIdlePage(getDefaultImageUrl(), getString(R.string.Recently)));
}
private void handleItemClick(ItemIdlePage item) {
if (getActivity() == null) return;
String itemName = item.getName();
if (getString(R.string.like_page_title).equals(itemName)) {
IdleLikeActivity.startLikeActivity((IdleMainActivity) getActivity());
} else if (getString(R.string.Recently).equals(itemName)) {
startActivity(new Intent(getActivity(), IdleRecentActivity.class));
}
}
}

View File

@ -0,0 +1,93 @@
package com.idle.wall.idlefragment;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.idle.wall.R;
import com.idle.wall.RecentAdapter;
import com.idle.wall.IdleWallpaper;
import com.idle.wall.RecentManager;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FragmentIdleRecent extends Fragment {
private static final String TAG = "RecentWallpaperFragment";
private static final int THREAD_POOL_SIZE = 2;
private GridView gvRecentWallpapers;
private TextView tvEmpty;
private ProgressBar pbLoading;
private RecentAdapter adapter;
private RecentManager recentManager;
private ExecutorService executorService;
private Handler mainHandler;
public static FragmentIdleRecent newInstance() {
return new FragmentIdleRecent();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_idle_recent, container, false);
gvRecentWallpapers = view.findViewById(R.id.gv_recent_wallpapers);
tvEmpty = view.findViewById(R.id.tv_empty);
pbLoading = view.findViewById(R.id.pb_loading);
executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
mainHandler = new Handler(Looper.getMainLooper());
recentManager = RecentManager.getInstance(requireContext());
adapter = new RecentAdapter(requireContext(), new ArrayList<>());
gvRecentWallpapers.setAdapter(adapter);
loadRecentWallpapers();
return view;
}
private void loadRecentWallpapers() {
pbLoading.setVisibility(View.VISIBLE);
tvEmpty.setVisibility(View.GONE);
gvRecentWallpapers.setVisibility(View.GONE);
executorService.execute(() -> {
List<IdleWallpaper> list = recentManager.getRecentWallpapers();
mainHandler.post(() -> {
pbLoading.setVisibility(View.GONE);
if (list.isEmpty()) {
tvEmpty.setVisibility(View.VISIBLE);
gvRecentWallpapers.setVisibility(View.GONE);
} else {
tvEmpty.setVisibility(View.GONE);
gvRecentWallpapers.setVisibility(View.VISIBLE);
adapter.updateData(list); // 触发预加载
}
});
});
}
public void refreshWallpaperList() {
loadRecentWallpapers();
}
@Override
public void onDestroy() {
super.onDestroy();
if (executorService != null && !executorService.isShutdown()) {
executorService.shutdown();
}
if (mainHandler != null) {
mainHandler.removeCallbacksAndMessages(null);
}
}
}

View File

@ -0,0 +1,77 @@
package com.idle.wall.idlefragment;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.idle.wall.databinding.FragmentIdleSettingBinding;
import com.idle.wall.idleactivity.IdleRateDialog;
public class FragmentIdleSetting extends Fragment {
private FragmentIdleSettingBinding vb;
public static FragmentIdleSetting newInstance() {
return new FragmentIdleSetting();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
vb = FragmentIdleSettingBinding.inflate(inflater, container, false);
initEdgeToEdge();
initVersionName();
initClickListeners();
return vb.getRoot();
}
private void initVersionName() {
try {
PackageInfo packageInfo = requireContext().getPackageManager()
.getPackageInfo(requireContext().getPackageName(), 0);
vb.tvVersion.setText("v" + packageInfo.versionName);
} catch (PackageManager.NameNotFoundException e) {
vb.tvVersion.setText("v1.0.0");
}
}
private void initEdgeToEdge() {
if (getActivity() != null) {
ViewCompat.setOnApplyWindowInsetsListener(vb.main, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}
private void initClickListeners() {
// 仅保留原有评分按钮点击事件
vb.tvRateUs.setOnClickListener(v -> {
if (getActivity() != null) {
new IdleRateDialog(getActivity()).show();
}
});
vb.llRateUs.setOnClickListener(v -> {
if (getActivity() != null) {
new IdleRateDialog(getActivity()).show();
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
vb = null;
}
}

View File

@ -0,0 +1,89 @@
package com.idle.wall.idlefragment;
import android.app.Dialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.idle.wall.databinding.IdleDialogBinding;
import com.idle.wall.idleadapter.OnSelecttype;
public class Idledialog extends DialogFragment {
private IdleDialogBinding vb;
private OnSelecttype listener;
public static final int type_home =0;
public static final int type_lock =1;
public static final int type_both =2;
public void setListener(OnSelecttype listener) {
this.listener = listener;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
vb = IdleDialogBinding.inflate(getLayoutInflater());
init();
return vb.getRoot();
}
@Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null) {
dialog.setCanceledOnTouchOutside(true);
Window window = dialog.getWindow();
if (window != null) {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
}
}
}
private void init(){
vb.layoutHome.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(listener!= null){
listener.onSelectType(type_home);
dismiss();
}
}
});
vb.layoutLock.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(listener!= null){
listener.onSelectType(type_lock);
dismiss();
}
}
});
vb.layoutBoth.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(listener!= null){
listener.onSelectType(type_both);
dismiss();
}
}
});
}
}

View File

@ -0,0 +1,165 @@
package com.idle.wall.idleitem;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.MultiTransformation;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.idle.wall.idleadapter.OnItemclick;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idletool.Comutils;
import com.idle.wall.idletool.ObectManager;
import com.idle.wall.R;
import java.util.ArrayList;
import java.util.List;
import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
public class ItemIdleLike extends RecyclerView.Adapter<ItemIdleLike.LikeVh> {
private static final MultiTransformation<Bitmap> TRANSFORMATION = new MultiTransformation<>(
new CenterCrop(),
new RoundedCornersTransformation(Comutils.dp2Px(12), 0)
);
private List<IdleData> mWallpaperList = new ArrayList<>();
private Context mContext;
private OnItemclick mOnItemClick;
private int mItemWidth;
public void setInfoWallpaperListener(OnItemclick onItemClick) {
this.mOnItemClick = onItemClick;
}
public ItemIdleLike(Context context) {
mContext = context;
setHasStableIds(true); // 开启稳定ID避免滑动闪烁
// 初始化Item宽度屏幕宽度/2 - 间距适配2列GridLayout
initItemWidth();
}
private void initItemWidth() {
if (mContext == null) return;
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
int screenWidth = dm.widthPixels;
int spacing = Comutils.dp2Px(30);
mItemWidth = (screenWidth - spacing) / 2;
}
public void setDataInfo(List<IdleData> dataInfo) {
if (dataInfo != null) {
this.mWallpaperList.clear();
this.mWallpaperList.addAll(dataInfo);
notifyDataSetChanged();
}
}
@NonNull
@Override
public LikeVh onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_idle_like, parent, false);
return new LikeVh(itemView);
}
@Override
public void onBindViewHolder(@NonNull LikeVh holder, int position) {
if (position < 0 || position >= mWallpaperList.size()) {
return;
}
IdleData info = mWallpaperList.get(position);
if (holder.spaceView != null) {
holder.spaceView.setVisibility(position == mWallpaperList.size() - 1 ? View.VISIBLE : View.GONE);
}
if (holder.layout != null && mItemWidth > 0) {
ViewGroup.LayoutParams params = holder.layout.getLayoutParams();
params.width = mItemWidth;
params.height = mItemWidth;
holder.layout.setLayoutParams(params);
}
// 初始化收藏按钮状态读取真实数据状态
if (holder.ivFavorite != null) {
holder.ivFavorite.setSelected(info.isLike());
holder.ivFavorite.setOnClickListener(v -> {
boolean newState = !holder.ivFavorite.isSelected();
holder.ivFavorite.setSelected(newState);
info.setLike(newState);
ObectManager.updateLike(info);
int adapterPosition = holder.getAdapterPosition();
if (adapterPosition != RecyclerView.NO_POSITION) {
mWallpaperList.remove(adapterPosition);
notifyItemRemoved(adapterPosition);
// 计算需要更新的item数量避免索引越界
int newSize = mWallpaperList.size();
if (adapterPosition < newSize) {
notifyItemRangeChanged(adapterPosition, newSize - adapterPosition);
}
}
});
}
String imgUrl = info.getPreviewUrl400();
if (imgUrl == null || imgUrl.isEmpty()) {
holder.ivWallpaper.setImageResource(R.mipmap.im_placeholder);
} else {
Glide.with(holder.ivWallpaper)
.load(imgUrl)
.placeholder(R.mipmap.im_placeholder)
.error(R.mipmap.im_placeholder)
.transform(TRANSFORMATION)
.dontAnimate()
.into(holder.ivWallpaper);
}
// 整个Item的点击事件
if (holder.layout != null) {
holder.layout.setOnClickListener(v -> {
if (mOnItemClick != null) {
mOnItemClick.onItemClickAction(info);
}
});
}
}
@Override
public int getItemCount() {
return mWallpaperList.size();
}
// 重写getItemId提供稳定ID
@Override
public long getItemId(int position) {
// 替换为Information的唯一标识字段比如id
return mWallpaperList.get(position).getId();
}
public class LikeVh extends RecyclerView.ViewHolder {
ImageView ivWallpaper;
RelativeLayout layout;
ImageView ivFavorite;
View spaceView;
public LikeVh(@NonNull View itemView) {
super(itemView);
ivWallpaper = itemView.findViewById(R.id.wallpaper);
layout = itemView.findViewById(R.id.layout);
ivFavorite = itemView.findViewById(R.id.image_favorite);
spaceView = itemView.findViewById(R.id.space_view);
}
}
}

View File

@ -0,0 +1,203 @@
package com.idle.wall.idleitem;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.recyclerview.widget.RecyclerView;
import com.idle.wall.R;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idleadapter.OnItemclick;
import com.idle.wall.idletool.Comutils;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class ItemIdleList extends RecyclerView.Adapter<ItemIdleList.ViewHolder> {
private Context mContext;
private List<IdleData> mDataList;
private OnItemclick mListener;
private OkHttpClient okHttpClient = new OkHttpClient();
private Handler mainHandler = new Handler(Looper.getMainLooper());
private int placeholderResId = R.mipmap.im_placeholder;
public ItemIdleList(Context context, int itemHeight, boolean isHorizontal) {
this.mContext = context;
}
public void setDataInfo(List<IdleData> dataList) {
this.mDataList = dataList;
notifyDataSetChanged();
}
public void setInfoWallpaperListener(OnItemclick listener) {
this.mListener = listener;
}
public static class SpaceItemDecoration extends RecyclerView.ItemDecoration {
private int spacing;
public SpaceItemDecoration(int spacing) {
this.spacing = spacing;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.left = spacing / 2;
outRect.right = spacing / 2;
outRect.bottom = spacing / 2;
if (parent.getChildLayoutPosition(view) < 2) {
outRect.top = spacing;
} else {
outRect.top = spacing / 2;
}
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_idle_list, parent, false);
ViewGroup.LayoutParams params = view.getLayoutParams();
int spacing = Comutils.dp2Px(10);
int itemWidth = (parent.getWidth() - spacing) / 2;
params.width = itemWidth;
params.height = itemWidth;
view.setLayoutParams(params);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (holder == null || position < 0 || position >= getItemCount()) {
return;
}
IdleData idleData = mDataList.get(position);
if (idleData == null) {
if (holder.ivWallpaper != null) {
holder.ivWallpaper.setImageResource(placeholderResId);
}
return;
}
if (holder.ivWallpaper == null) {
return;
}
holder.ivWallpaper.setImageResource(placeholderResId);
String realImageUrl = idleData.getPreviewUrl400();
if (realImageUrl != null && !realImageUrl.isEmpty() && realImageUrl.startsWith("http")) {
Request request = new Request.Builder().url(realImageUrl).build();
Call call = okHttpClient.newCall(request);
holder.currentCall = call;
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
mainHandler.post(() -> {
if (holder.ivWallpaper != null && !call.isCanceled()) {
holder.ivWallpaper.setImageResource(placeholderResId);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (call.isCanceled() || !response.isSuccessful() || response.body() == null) {
response.close();
mainHandler.post(() -> {
if (holder.ivWallpaper != null) {
holder.ivWallpaper.setImageResource(placeholderResId);
}
});
return;
}
InputStream inputStream = null;
try {
inputStream = response.body().byteStream();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
mainHandler.post(() -> {
if (holder.ivWallpaper != null && !call.isCanceled()) {
if (bitmap != null) {
holder.ivWallpaper.setImageBitmap(bitmap);
} else {
// Bitmap解码失败时显示占位图
holder.ivWallpaper.setImageResource(placeholderResId);
}
}
});
} catch (Exception e) {
e.printStackTrace();
mainHandler.post(() -> {
if (holder.ivWallpaper != null) {
holder.ivWallpaper.setImageResource(placeholderResId);
}
});
} finally {
if (inputStream != null) {
inputStream.close();
}
response.close();
}
}
});
} else {
// URL为空/非法时显示统一占位图替换系统默认图标
holder.ivWallpaper.setImageResource(placeholderResId);
}
holder.itemView.setOnClickListener(v -> {
if (mListener != null) {
mListener.onItemClickAction(idleData);
}
});
}
@Override
public void onViewRecycled(ViewHolder holder) {
super.onViewRecycled(holder);
if (holder.currentCall != null) {
holder.currentCall.cancel();
holder.currentCall = null;
}
if (holder.ivWallpaper != null) {
// 回收时重置为占位图避免复用Item显示旧图片
holder.ivWallpaper.setImageResource(placeholderResId);
// 释放Bitmap优化内存
holder.ivWallpaper.setImageBitmap(null);
}
}
@Override
public int getItemCount() {
return mDataList == null ? 0 : mDataList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView ivWallpaper;
Call currentCall;
public ViewHolder(View itemView) {
super(itemView);
ivWallpaper = itemView.findViewById(R.id.iv_wallpaper);
}
}
}

View File

@ -0,0 +1,194 @@
package com.idle.wall.idleitem;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey;
import com.idle.wall.R;
import com.idle.wall.IdleApp;
import com.idle.wall.databinding.ItemIdleMainBinding;
import com.idle.wall.idleadapter.OnItemclick;
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idletool.Comutils;
import com.idle.wall.idletool.JsonData;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ItemIdleMain extends ListAdapter<String, ItemIdleMain.MainViewHolder> {
private static final ExecutorService JSON_PARSE_POOL = Executors.newSingleThreadExecutor();
private static RequestOptions IMAGE_OPTIONS;
private final List<IdleData> categoryFirstDataCache = new java.util.ArrayList<>();
private OnItemclick listener;
private final Context appContext;
private static final DiffUtil.ItemCallback<String> DIFF_CALLBACK = new DiffUtil.ItemCallback<String>() {
@Override
public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
return oldItem.equals(newItem);
}
};
public ItemIdleMain() {
super(DIFF_CALLBACK);
this.appContext = IdleApp.mAppContext;
initImageOptions();
}
private void initImageOptions() {
if (IMAGE_OPTIONS == null && appContext != null) {
int cornerDp = 12;
int cornerPx = Comutils.dp2px(appContext, cornerDp);
IMAGE_OPTIONS = new RequestOptions()
.transform(new CenterCrop(), new RoundedCorners(cornerPx))
.placeholder(R.mipmap.im_placeholder)
.error(R.mipmap.im_placeholder)
.diskCacheStrategy(com.bumptech.glide.load.engine.DiskCacheStrategy.ALL)
.skipMemoryCache(false)
.timeout(10000);
}
}
public void submitCategoryData(List<String> newCategoryNames) {
if (newCategoryNames == null || newCategoryNames.isEmpty()) {
submitList(null);
categoryFirstDataCache.clear();
return;
}
submitList(newCategoryNames);
JSON_PARSE_POOL.execute(() -> {
parseCategoryDataAsync(newCategoryNames);
});
}
private void parseCategoryDataAsync(List<String> newCategoryNames) {
if (appContext == null) return;
synchronized (categoryFirstDataCache) {
categoryFirstDataCache.clear();
}
for (String categoryName : newCategoryNames) {
IdleData firstData = null;
try {
String jsonFileName = categoryName + ".json";
InputStream open = appContext.getAssets().open(jsonFileName);
String jsonStr = Comutils.getCovertStr(open);
if (!jsonStr.isEmpty()) {
List<IdleData> data = JsonData.INSTANCE.getData(jsonStr, categoryName);
if (!data.isEmpty()) {
Collections.shuffle(data);
firstData = data.get(0);
}
}
} catch (IOException e) {
e.printStackTrace();
firstData = null;
}
synchronized (categoryFirstDataCache) {
categoryFirstDataCache.add(firstData);
}
}
new Handler(Looper.getMainLooper()).post(() -> {
notifyItemRangeChanged(0, getItemCount());
});
}
public void setOnItemClickListener(OnItemclick listener) {
this.listener = listener;
}
@NonNull
@Override
public MainViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemIdleMainBinding binding = ItemIdleMainBinding.inflate(
LayoutInflater.from(parent.getContext()),
parent,
false
);
binding.ivCategory.setScaleType(ImageView.ScaleType.CENTER_CROP);
binding.ivCategory.setAdjustViewBounds(true);
return new MainViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull MainViewHolder holder, int position) {
String categoryName = getItem(position);
if (categoryName == null) return;
holder.binding.tvCategoryName.setText(categoryName);
// 修复核心创建final局部变量让lambda可以引用
final IdleData finalFirstData;
synchronized (categoryFirstDataCache) {
if (position < categoryFirstDataCache.size()) {
finalFirstData = categoryFirstDataCache.get(position);
} else {
finalFirstData = null;
}
}
if (finalFirstData != null && finalFirstData.getPreviewUrl400() != null) {
String imageUrl = finalFirstData.getPreviewUrl400();
Glide.with(holder.itemView.getContext())
.load(imageUrl)
.apply(IMAGE_OPTIONS)
.signature(new ObjectKey(imageUrl + System.currentTimeMillis() / 3600000))
.into(holder.binding.ivCategory);
} else {
holder.binding.ivCategory.setImageResource(R.mipmap.im_placeholder);
}
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
IdleData myData = new IdleData();
myData.setCategoryName(categoryName);
myData.setImId(categoryName + "_" + position);
// 引用final变量不再报错
if (finalFirstData != null) {
myData.setPreviewUrl400(finalFirstData.getPreviewUrl400());
}
listener.onItemClickAction(myData);
}
});
}
static class MainViewHolder extends RecyclerView.ViewHolder {
final ItemIdleMainBinding binding;
public MainViewHolder(@NonNull ItemIdleMainBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
public void release() {
JSON_PARSE_POOL.shutdown();
categoryFirstDataCache.clear();
listener = null;
}
}

View File

@ -0,0 +1,39 @@
// ItemPage.java
package com.idle.wall.idleitem;
public class ItemIdlePage {
private String imageUrl;
private String name;
private int imageResId;
public ItemIdlePage(int imageResId, String name) {
this.imageResId = imageResId;
this.name = name;
this.imageUrl = ""; // 空URL避免空指针
}
// 构造方法2兼容原有URL逻辑
public ItemIdlePage(String imageUrl, String name) {
this.imageUrl = imageUrl;
this.name = name;
this.imageResId = 0; // 默认无资源ID
}
public int getImageResId() {
return imageResId;
}
public void setImageResId(int imageResId) {
this.imageResId = imageResId;
}
public String getImageUrl() {
return imageUrl;
}
public String getName() {
return name;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
}

View File

@ -0,0 +1,208 @@
package com.idle.wall.idletool;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Debug;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import com.idle.wall.IdleApp;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
public class Comutils {
public static final String key_info = "key_info";
public static final String key_index = "key_index";
private static final String TAG = "Comutils";
public static String getCovertStr(InputStream stream) {
if (stream == null) {
Log.w(TAG, "getCovertStr: 输入流为null");
return "";
}
try (StringWriter writer = new StringWriter();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
char[] buffer = new char[1024];
int readLen;
while ((readLen = reader.read(buffer)) != -1) {
writer.write(buffer, 0, readLen);
}
return writer.toString();
} catch (IOException e) {
Log.e(TAG, "流转字符串失败", e);
return "";
}
}
// 单参数版本核心
public static int dp2px(int dp) {
Context context = IdleApp.mAppContext;
if (context == null) {
Log.w(TAG, "dp2px: 全局Context为null返回原始dp值");
return dp;
}
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
// 兼容旧方法名
public static int dp2Px(int dp) {
return dp2px(dp);
}
// 新增重载支持传Context解决所有参数不匹配问题
public static int dp2px(Context context, int dp) {
if (context == null) {
Log.w(TAG, "dp2px: Context为null降级使用全局上下文");
return dp2px(dp);
}
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
public static void initFull(Activity activity, boolean isLightStatusBar) {
if (activity == null) {
Log.w(TAG, "initFull: Activity为null跳过状态栏设置");
return;
}
Window window = activity.getWindow();
if (window == null) return;
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(android.graphics.Color.TRANSPARENT);
View decorView = window.getDecorView();
int uiFlags = decorView.getSystemUiVisibility();
if (isLightStatusBar) {
uiFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
uiFlags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
decorView.setSystemUiVisibility(uiFlags);
}
private static Bitmap getBitmap(Drawable drawable) {
if (drawable == null) {
Log.w(TAG, "getBitmap: Drawable为null");
return null;
}
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
return bitmap != null ? bitmap : Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
}
int width = drawable.getIntrinsicWidth() <= 0 ? 100 : drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight() <= 0 ? 100 : drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static String saveDrawableToFile(Drawable drawable, String saveFilePath) {
if (drawable == null || saveFilePath == null || saveFilePath.isEmpty()) {
Log.w(TAG, "saveDrawableToFile: Drawable或保存路径为空");
return null;
}
Bitmap bitmap = getBitmap(drawable);
if (bitmap == null) {
Log.e(TAG, "saveDrawableToFile: Bitmap转换失败");
return null;
}
File file = new File(saveFilePath);
File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
Log.e(TAG, "saveDrawableToFile: 创建目录失败:" + parentDir.getAbsolutePath());
bitmap.recycle();
return null;
}
try (FileOutputStream fos = new FileOutputStream(file)) {
boolean success = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
if (success) {
Log.d(TAG, "Drawable保存成功" + file.getAbsolutePath());
return file.getAbsolutePath();
} else {
Log.e(TAG, "Bitmap压缩失败");
return null;
}
} catch (IOException e) {
Log.e(TAG, "保存Drawable到文件失败", e);
return null;
} finally {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
}
public static void printUsedMemory(Context context) {
if (context == null) {
Log.w(TAG, "printUsedMemory: Context为null");
return;
}
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager == null) {
Log.w(TAG, "printUsedMemory: ActivityManager获取失败");
return;
}
int pid = android.os.Process.myPid();
Debug.MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(new int[]{pid});
if (memoryInfoArray == null || memoryInfoArray.length == 0) {
Log.w(TAG, "printUsedMemory: 未获取到内存信息");
return;
}
Debug.MemoryInfo memoryInfo = memoryInfoArray[0];
float totalPssMB = memoryInfo.getTotalPss() / 1024.0f;
float privateDirtyMB = memoryInfo.getTotalPrivateDirty() / 1024.0f;
float sharedDirtyMB = memoryInfo.getTotalSharedDirty() / 1024.0f;
Log.d("MemoryInfo", String.format("进程PID%d", pid));
Log.d("MemoryInfo", String.format("总PSS内存%.2f MB", totalPssMB));
Log.d("MemoryInfo", String.format("私有脏内存:%.2f MB", privateDirtyMB));
Log.d("MemoryInfo", String.format("共享脏内存:%.2f MB", sharedDirtyMB));
}
public static int[] getScreenWidthHeight(Context context) {
if (context == null) {
Log.w(TAG, "getScreenWidthHeight: Context为null");
return new int[]{0, 0};
}
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;
Log.d(TAG, String.format("屏幕尺寸:宽=%dpx高=%dpx", width, height));
return new int[]{width, height};
}
}

View File

@ -0,0 +1,56 @@
package com.idle.wall.idletool;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class Itemhelper extends RecyclerView.ItemDecoration {
private int ex_space = 0;
private int v_space = 0;
private int h_space = 0;
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int spanSize = 1;
int spanIndex = 0;
int spanCount = 1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager layoutManager1 = (GridLayoutManager) layoutManager;
GridLayoutManager.LayoutParams layoutParams = (GridLayoutManager.LayoutParams) view.getLayoutParams();
spanCount = layoutManager1.getSpanCount();
spanSize = layoutManager1.getSpanSizeLookup().getSpanSize(position);
spanIndex = layoutParams.getSpanIndex();
}
if (spanSize == spanCount) {
outRect.left = v_space + ex_space;
outRect.right = v_space + ex_space;
outRect.bottom = h_space;
} else {
int itemAllSpacing = (v_space * (spanCount + 1) + ex_space * 2) / spanCount;
int left = v_space * (spanIndex + 1) - itemAllSpacing * spanIndex + ex_space;
int right = itemAllSpacing - left;
outRect.left = left;
outRect.right = right;
outRect.bottom = h_space;
}
}
public Itemhelper(int v_space, int h_space, int ex_space) {
this.ex_space = ex_space;
this.h_space = h_space;
this.v_space = v_space;
}
}

View File

@ -0,0 +1,228 @@
package com.idle.wall.idletool
import android.Manifest
import android.app.Activity
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.util.Log
import androidx.core.app.ActivityCompat
import com.idle.wall.IdleApp
import com.idle.wall.idlebean.IdleData
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
object JsonData {
fun getData(jsonStr: String, category: String): MutableList<IdleData> {
val jsonArray = JSONArray(jsonStr)
var data = mutableListOf<IdleData>()
var get: JSONObject
var text_description: String
var links: JSONObject
var download: String
var urls: JSONObject
var regular: String
var small: String
var thumb: String
var users: JSONObject
var name: String
var header_large: String
var user_html: String
val host = "https://unsplash.com/photos/"
var bean: IdleData
var indexOf: Int
var substring: String
for (i in 0 until 300) {
get = jsonArray.getJSONObject(i)
text_description = get.getString("alt_description")
links = get.getJSONObject("links")
download = links.getString("download")
// val downloadLocation = links.getString("download_location")
// val html = links.getString("html")
urls = get.getJSONObject("urls")
// val full = urls.getString("full")
// val raw = urls.getString("raw")
regular = urls.getString("regular")
small = urls.getString("small")
thumb = urls.getString("thumb")
users = get.getJSONObject("user")
// val portfolio_url = users.getString("portfolio_url")
name = users.getString("name")
header_large = users.getString("header_large")
// val header_medium = users.getString("header_medium")
// val header_small = users.getString("header_small")
user_html = users.getString("authorHtml")
indexOf = download.indexOf("/download")
substring = download.substring(host.length, indexOf)
// Log.d(MyApp.TAG, "i=$i----------substring=$substring")
bean = IdleData().apply {
imId = substring
categoryName = category
description = text_description
fullUrl = download
previewUrl1080 = regular
previewUrl400 = small
previewUrl200 = thumb
authorName = name
authorHeader = header_large
authorHtml = user_html
}
data.add(bean)
}
return data
}
fun downloadFile(
url: String,
savePath: String,
result: (Boolean, input: InputStream?) -> Unit
): Call {
var client = OkHttpClient()
var request = Request.Builder().url(url).build()
var call = client.newCall(request)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
result.invoke(false, null)
}
override fun onResponse(call: Call, response: Response) {
response.body?.run {
val byteStream = byteStream()
val writeFile = writeFile(byteStream, savePath)
result.invoke(writeFile, byteStream)
} ?: run {
result.invoke(false, null)
}
}
})
return call
}
fun writeFile(input: InputStream, filePath: String): Boolean {
try {
val byte = ByteArray(4096)
val output = ByteArrayOutputStream()
var l: Int
while (input.read(byte).also { l = it } != -1) {
output.write(byte, 0, l)
}
val fileDe = File(filePath)
if (!fileDe.exists()) {
fileDe.createNewFile()
}
val fileOutputStream = FileOutputStream(filePath)
fileOutputStream.write(output.toByteArray())
output.close()
fileOutputStream.close()
return true
} catch (ex: Exception) {
Log.d("-----------", "---------ex=" + ex.message)
return false
}
}
fun saveToGallery(context: Context, photoFile: File): Uri? {
val displayName = "${System.currentTimeMillis()}.jpg"
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.Media.IS_PENDING, 1)
}
}
val contentResolver = context.contentResolver
val collectionUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val imageUri = contentResolver.insert(collectionUri, contentValues)
imageUri?.let { uri ->
try {
contentResolver.openOutputStream(uri)?.use { outputStream ->
val inputStream = FileInputStream(photoFile)
inputStream.copyTo(outputStream)
inputStream.close()
outputStream.close()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.clear()
contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
contentResolver.update(uri, contentValues, null, null)
} else {
}
return uri
} catch (e: IOException) {
Log.d(IdleApp.TAG, "-----------------e=${e.printStackTrace()}")
return null
}
} ?: run {
Log.d(IdleApp.TAG, "----------------false")
return null
}
}
fun requestPermission(context: Activity, requestCode: Int): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return true
}
return if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
context,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
requestCode
)
false
} else {
true
}
}
fun getSaveFilePath(context: Context, imId: String): String {
return "${context.cacheDir}/${imId}.jpg"
}
}

View File

@ -0,0 +1,200 @@
package com.idle.wall.idletool;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.idle.wall.IdleApp;
import com.idle.wall.idleadapter.OnLikelist;
import com.idle.wall.idleadapter.OnUpdatelike;
import com.idle.wall.idleadapter.OnViewList; // 新增导入浏览监听接口
import com.idle.wall.idlebean.IdleData;
import com.idle.wall.idlebean.IdleData_;
import com.idle.wall.idlebean.MyObjectBox;
import java.util.ArrayList;
import java.util.List;
import io.objectbox.Box;
import io.objectbox.BoxStore;
import io.objectbox.android.AndroidScheduler;
import io.objectbox.query.Query;
import io.objectbox.query.QueryBuilder;
import io.objectbox.reactive.DataObserver;
import io.objectbox.reactive.DataSubscription;
import io.objectbox.reactive.DataSubscriptionList;
public class ObectManager {
private static BoxStore boxStore;
private static Box<IdleData> LikeBox;
private static List<OnUpdatelike> list = new ArrayList<>();
public static void init(Context context) {
boxStore = MyObjectBox.builder().androidContext(context).build();
}
public static void addData(List<IdleData> dataList) {
synchronized (ObectManager.class) {
for (IdleData data : dataList) {
addData(data); // 复用原有单个添加逻辑
}
}
}
public static Box<IdleData> getObjectBoxLike() {
if (LikeBox == null) {
LikeBox = boxStore.boxFor(IdleData.class);
}
return LikeBox;
}
public static void addData(IdleData data) {
Box<IdleData> objectBoxLike = getObjectBoxLike();
String imId = data.getImId();
IdleData first = objectBoxLike.query()
.equal(IdleData_.imId, imId, QueryBuilder.StringOrder.CASE_SENSITIVE)
.build()
.findFirst();
if (first == null) {
objectBoxLike.put(data);
} else {
}
}
// ========== 你原有setLikeDataListener保留不变 ==========
public static DataSubscription setLikeDataListener(OnLikelist listener) {
Query<IdleData> build = getObjectBoxLike().query()
.equal(IdleData_.isLike, true)
.orderDesc(IdleData_.likeTime)
.build();
return build.subscribe(new DataSubscriptionList())
.on(AndroidScheduler.mainThread())
.observer(new DataObserver<List<IdleData>>() {
@Override
public void onData(@NonNull List<IdleData> data) {
listener.onLikeList(data);
}
});
}
// ========== 你原有updateLike保留不变 ==========
public static void updateLike(IdleData boxLike) {
Box<IdleData> objectBoxLike = getObjectBoxLike();
String imId = boxLike.getImId();
if (boxLike.isLike()) {
boxLike.setLikeTime(System.currentTimeMillis());
} else {
boxLike.setLikeTime(0);
}
objectBoxLike.put(boxLike);
for (OnUpdatelike listener : list) {
listener.onAdd(boxLike);
}
}
// ========== 你原有insertOrUpdateDownload保留不变 ==========
public static void insertOrUpdateDownload(IdleData boxLike) {
Box<IdleData> objectBoxLike = getObjectBoxLike();
String imId = boxLike.getImId();
IdleData first = objectBoxLike.query()
.equal(IdleData_.imId, imId, QueryBuilder.StringOrder.CASE_SENSITIVE)
.build()
.findFirst();
if (first == null) {
boxLike.setDownload(true);
objectBoxLike.put(boxLike);
} else {
first.setDownload(true);
objectBoxLike.put(first);
}
}
// ========== 你原有search保留不变 ==========
public static List<IdleData> search(String name) {
Box<IdleData> objectBoxLike = getObjectBoxLike();
List<IdleData> data = objectBoxLike.query()
.contains(IdleData_.description, name, QueryBuilder.StringOrder.CASE_SENSITIVE)
.build()
.find();
return data;
}
// ========== 你原有queryHome保留不变 ==========
public static List<IdleData> queryHome(String name) {
Box<IdleData> objectBoxLike = getObjectBoxLike();
List<IdleData> data = objectBoxLike.query()
.equal(IdleData_.categoryName, name, QueryBuilder.StringOrder.CASE_SENSITIVE)
.build()
.find(10, 1);
return data;
}
public static List<IdleData> queryCategoryAll(String name) {
Box<IdleData> objectBoxLike = getObjectBoxLike();
List<IdleData> data = objectBoxLike.query()
.equal(IdleData_.categoryName, name, QueryBuilder.StringOrder.CASE_SENSITIVE)
.build()
.find(0, 200);
return data;
}
public static List<IdleData> getLikeList() {
return getObjectBoxLike().query()
.equal(IdleData_.isLike, true)
.orderDesc(IdleData_.likeTime)
.build()
.find();
}
public static boolean queryIsLike(String imId) {
Box<IdleData> objectBoxLike = getObjectBoxLike();
IdleData first = objectBoxLike.query()
.equal(IdleData_.imId, imId, QueryBuilder.StringOrder.CASE_SENSITIVE)
.build()
.findFirst();
if (first != null && first.isLike()) {
return true;
} else {
return false;
}
}
public static List<IdleData> getViewList() {
return getObjectBoxLike().query()
.greater(IdleData_.viewTime, 0) // 仅查询有浏览记录的viewTime>0
.orderDesc(IdleData_.viewTime) // 最新浏览的排在前面
.build()
.find();
}
public static DataSubscription setViewDataListener(OnViewList listener) {
Query<IdleData> build = getObjectBoxLike().query()
.greater(IdleData_.viewTime, 0)
.orderDesc(IdleData_.viewTime)
.build();
return build.subscribe(new DataSubscriptionList())
.on(AndroidScheduler.mainThread())
.observer(new DataObserver<List<IdleData>>() {
@Override
public void onData(@NonNull List<IdleData> data) {
listener.onViewList(data);
}
});
}
public static void updateViewHistory(IdleData data) {
if (data == null) return;
IdleData existData = getObjectBoxLike().query()
.equal(IdleData_.imId, data.getImId(), QueryBuilder.StringOrder.CASE_SENSITIVE)
.build()
.findFirst();
if (existData != null) {
existData.setViewTime(System.currentTimeMillis());
getObjectBoxLike().put(existData);
} else {
data.setViewTime(System.currentTimeMillis());
addData(data); // 复用你原有addData方法避免重复添加
}
}
}

View File

@ -0,0 +1,34 @@
package com.idle.wall.idletool;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import com.idle.wall.IdleApp;
import com.idle.wall.R;
public class text extends androidx.appcompat.widget.AppCompatTextView {
public text(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(context,attrs);
}
private void initAttrs(Context context, AttributeSet attrs){
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomeTv);
boolean aBoolean = typedArray.getBoolean(R.styleable.CustomeTv_apply_font,false);
if(aBoolean){
setTypeface(IdleApp.defaultFont);
}
typedArray.recycle();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@color/MacaronPinkpro"/>
<padding
android:left="12dp"
android:top="4dp"
android:right="12dp"
android:bottom="4dp" />
</shape>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M747.2,283.2L54.5,283.2v692.7h692.7v-692.7zM656.8,373.6v512h-512v-512h512z"
android:fillColor="@color/white"/>
<path
android:pathData="M993.3,12.4v692.7h-287.2v-90.4h196.8v-512h-512v224.4h-90.4V12.4z"
android:fillColor="@color/white"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/white"
android:pathData="M776.7,1010.1h-479.9c-28.3,0 -51.3,-23 -51.3,-51.3v-891.1c0,-28.3 23,-51.3 51.3,-51.3h479.9c28.3,0 51.3,23 51.3,51.3v891.1c0,28.3 -23,51.3 -51.3,51.3v0zM536.6,958.8c19.1,0 34.2,-15.4 34.2,-34.2s-15.4,-34.2 -34.2,-34.2 -34.2,15.4 -34.2,34.2 15.4,34.2 34.2,34.2v0zM776.7,119h-479.9v719.7h479.6v-719.7h0.3z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M878.6,337.2a57.8,57.8 0,0 0,-81.7 0L574.6,559.5V173a57.8,57.8 0,0 0,-115.6 0v386.5L236.8,337.2a57.8,57.8 0,0 0,-81.7 0,57.8 57.8,0 0,0 0,81.8l318.7,318.7a57.6,57.6 0,0 0,40.9 16.9c0.7,0 1.5,-0.1 2.2,-0.1 0.7,0 1.5,0.1 2.2,0.1a57.6,57.6 0,0 0,40.9 -16.9L878.6,419a57.8,57.8 0,0 0,0 -81.8zM861,908.4H165.4a57.8,57.8 0,0 1,0 -115.6h695.6a57.8,57.8 0,0 1,0 115.6z"
android:fillColor="@color/white"/>
</vector>

View File

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M219.8,71c-12.3,0 -21.8,-9.6 -21.8,-21.8S208.9,27.3 219.8,27.3c12.3,0 21.8,9.6 21.8,21.8 0,12.3 -9.6,21.8 -21.8,21.8zM219.8,34.1c-8.2,0 -15,6.8 -15,15s6.8,15 15,15c8.2,0 15,-6.8 15,-15 0,-8.2 -6.8,-15 -15,-15z"
android:strokeAlpha="0.3"
android:fillColor="@color/sub_title_color"
android:fillAlpha="0.3"/>
<path
android:pathData="M935.3,1009L112,1009c-4.1,0 -6.8,2.7 -6.8,6.8s2.7,6.8 6.8,6.8h823.3c4.1,0 6.8,-2.7 6.8,-6.8s-2.7,-6.8 -6.8,-6.8zM84.7,1009h-54.6c-4.1,0 -6.8,2.7 -6.8,6.8s2.7,6.8 6.8,6.8h54.6c4.1,0 6.8,-2.7 6.8,-6.8s-4.1,-6.8 -6.8,-6.8zM994,1009h-27.3c-4.1,0 -6.8,2.7 -6.8,6.8s2.7,6.8 6.8,6.8h27.3c4.1,0 6.8,-2.7 6.8,-6.8s-2.7,-6.8 -6.8,-6.8zM244.4,428.7c4.1,0 6.8,-2.7 6.8,-6.8L251.2,210.3h400v109.2L764.6,319.5l4.1,4.1v379.6c0,4.1 2.7,6.8 6.8,6.8s6.8,-2.7 6.8,-6.8L782.3,318.1l-8.2,-8.2c0,-1.4 -2.7,-4.1 -4.1,-4.1l-107.9,-107.9 -1.4,-1.4h-423.3v225.3c0,4.1 2.7,6.8 6.8,6.8zM663.6,305.8v-86l86,86h-86zM244.4,518.8c4.1,0 6.8,-2.7 6.8,-6.8v-64.2c0,-4.1 -2.7,-6.8 -6.8,-6.8s-6.8,2.7 -6.8,6.8v64.2c0,4.1 2.7,6.8 6.8,6.8zM244.4,558.4c4.1,0 6.8,-2.7 6.8,-6.8L251.2,532.5c0,-4.1 -2.7,-6.8 -6.8,-6.8s-6.8,2.7 -6.8,6.8v19.1c0,4.1 2.7,6.8 6.8,6.8zM782.3,740c0,-4.1 -2.7,-6.8 -6.8,-6.8s-6.8,2.7 -6.8,6.8v84.7L251.2,824.7L251.2,570.7c0,-4.1 -2.7,-6.8 -6.8,-6.8s-6.8,2.7 -6.8,6.8v267.6h544.8v-98.3z"
android:fillColor="@color/sub_title_color"/>
<path
android:pathData="M460.1,434.2c-4.1,-1.4 -6.8,1.4 -8.2,5.5 -2.7,15 -12.3,27.3 -24.6,32.8 -24.6,9.6 -58.7,-8.2 -58.7,-9.6 -2.7,-1.4 -6.8,-1.4 -9.6,2.7 -1.4,2.7 -1.4,6.8 2.7,9.6 15,8.2 31.4,13.7 49.2,13.7 6.8,0 15,-1.4 21.8,-4.1 17.7,-8.2 30,-23.2 32.8,-42.3 0,-4.1 -1.4,-8.2 -5.5,-8.2zM584.4,490.2c5.5,2.7 10.9,2.7 17.7,2.7 16.4,-1.4 31.4,-6.8 45.1,-16.4 2.7,-2.7 4.1,-6.8 1.4,-9.6 -2.7,-2.7 -5.5,-4.1 -9.6,-1.4 -19.1,12.3 -35.5,16.4 -49.2,10.9 -21.8,-8.2 -31.4,-39.6 -31.4,-39.6 -1.4,-4.1 -5.5,-5.5 -8.2,-4.1 -4.1,1.4 -5.5,4.1 -4.1,8.2 0,2.7 9.6,38.2 38.2,49.2zM462.8,617.1c1.4,-1.4 36.9,-34.1 81.9,4.1 1.4,1.4 2.7,1.4 4.1,1.4 4.1,0 6.8,-2.7 6.8,-6.8 0,-2.7 -1.4,-4.1 -2.7,-5.5 -56,-45.1 -99.7,-2.7 -101,-2.7 -2.7,2.7 -2.7,6.8 0,9.6s8.2,2.7 10.9,0zM909.3,148.8c12.3,0 21.8,-9.6 21.8,-21.8s-9.6,-21.8 -21.8,-21.8c-12.3,0 -21.8,9.6 -21.8,21.8 1.4,12.3 10.9,21.8 21.8,21.8zM909.3,113.3c8.2,0 15,6.8 15,15s-6.8,15 -15,15c-8.2,0 -15,-6.8 -15,-15 1.4,-8.2 8.2,-15 15,-15zM73.7,296.3l13.7,8.2 -6.8,13.7c-1.4,2.7 0,5.5 2.7,6.8 2.7,1.4 5.5,0 6.8,-2.7l5.5,-12.3 12.3,8.2c1.4,0 1.4,1.4 2.7,1.4 2.7,0 5.5,-2.7 5.5,-5.5 0,-1.4 -1.4,-4.1 -2.7,-4.1l-13.7,-8.2 6.8,-13.7c1.4,-2.7 0,-5.5 -2.7,-6.8 -2.7,-1.4 -5.5,0 -6.8,2.7l-6.8,12.3 -10.9,-9.6c-2.7,-1.4 -5.5,-1.4 -6.8,1.4 -2.7,2.7 -1.4,6.8 1.4,8.2zM957.1,475.1l-19.1,-10.9 8.2,-20.5c1.4,-4.1 0,-6.8 -4.1,-9.6s-6.8,0 -9.6,4.1l-8.2,19.1 -17.7,-9.6c-2.7,-1.4 -6.8,0 -9.6,2.7 -1.4,2.7 0,6.8 2.7,9.6l19.1,10.9 -8.2,20.5c-1.4,4.1 0,6.8 4.1,9.6s6.8,0 9.6,-4.1l8.2,-19.1 17.7,9.6c2.7,1.4 6.8,0 9.6,-2.7s0,-8.2 -2.7,-9.6z"
android:fillColor="@color/sub_title_color"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M154.5,469.3H981.3a42.7,42.7 0,1 1,0 85.3H154.5l300.7,266.9a42.7,42.7 0,1 1,-57.1 63.4l-384,-341.3a42.7,42.7 0,0 1,0 -63.4l384,-341.3a42.7,42.7 0,1 1,57.1 63.4L154.5,469.3z"
android:fillColor="@color/black"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View 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">
<corners android:radius="20dp" />
<stroke android:width="1dp" android:color="@color/gray_100" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12dp"/> <!-- 圆角半径(确保足够大,肉眼可见) -->
<!-- 可选:加边框区分背景 -->
<stroke
android:width="1dp"/>
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M667.8,117.3C832.9,117.3 938.7,249.7 938.7,427.9c0,138.3 -125.1,290.5 -371.6,461.6a96.8,96.8 0,0 1,-110.2 0C210.4,718.4 85.3,566.1 85.3,427.9 85.3,249.7 191.1,117.3 356.2,117.3c59.6,0 100.1,20.8 155.8,68.1C567.7,138.2 608.2,117.3 667.8,117.3z"
android:fillColor="@color/favorite_true"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M667.8,117.3C832.9,117.3 938.7,249.7 938.7,427.9c0,138.3 -125.1,290.5 -371.6,461.6a96.8,96.8 0,0 1,-110.2 0C210.4,718.4 85.3,566.1 85.3,427.9 85.3,249.7 191.1,117.3 356.2,117.3c59.6,0 100.1,20.8 155.8,68.1C567.7,138.2 608.2,117.3 667.8,117.3zM667.8,180.5c-41.4,0 -70.3,15.2 -117,55 -2.2,1.8 -14.4,12.4 -17.9,15.4a32.3,32.3 0,0 1,-41.8 0c-3.5,-3 -15.8,-13.5 -17.9,-15.4 -46.7,-39.9 -75.5,-55 -117,-55C230.2,180.5 149.3,281.3 149.3,426.7 149.3,537.6 262.9,675.2 493.6,834.8a32.4,32.4 0,0 0,36.7 0C761.1,675.3 874.7,537.6 874.7,426.7c0,-145.4 -80.9,-246.2 -206.9,-246.2z"
android:fillColor="@color/white"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/white"
android:pathData="M426.9,683l170.8,0c25.6,0 42.7,-17.1 42.7,-42.7L640.3,512.3c0,-25.6 -17.1,-42.7 -42.7,-42.7l0,-42.7c0,-47 -38.4,-85.4 -85.4,-85.4s-85.4,38.4 -85.4,85.4l0,42.7c-25.6,0 -42.7,17.1 -42.7,42.7l0,128.1C384.2,666 401.3,683 426.9,683zM461,426.9c0,-29.9 21.3,-51.2 51.2,-51.2s51.2,21.3 51.2,51.2l0,42.7 -102.5,0L461,426.9 461,426.9zM725.7,42.7 L298.8,42.7c-47,0 -85.4,38.4 -85.4,85.4l0,768.4c0,47 38.4,85.4 85.4,85.4l426.9,0c47,0 85.4,-38.4 85.4,-85.4L811.1,128.1C811.1,81.1 772.7,42.7 725.7,42.7zM725.7,811.1 L298.8,811.1 298.8,213.4l426.9,0L725.7,811.1z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M271.9,728.1v-412.2c0,-30.7 18.9,-53.8 39.9,-53.8h412.2c21,0 39.9,23.6 39.9,54.3v220.2c0,2 0.5,4.1 1,5.6l14.8,-5.6v-220.2c0,-38.9 -25.1,-70.1 -56.3,-70.1h-412.2c-31.2,0 -56.3,31.2 -56.3,70.1v418.3l16.9,-6.7zM271.9,728.1v-412.2c0,-30.7 18.9,-53.8 39.9,-53.8h412.2c21,0 39.9,23.6 39.9,54.3v220.2c0,2 0.5,4.1 1,5.6l14.8,-5.6v-220.2c0,-38.9 -25.1,-70.1 -56.3,-70.1h-412.2c-31.2,0 -56.3,31.2 -56.3,70.1v418.3l16.9,-6.7zM958.5,800.8c-21.5,-16.9 -45.6,-29.7 -71.7,-38.4 -4.1,-1 -8.7,1.5 -9.7,5.6 -1,4.1 1,8.2 4.6,9.7 24.1,8.2 46.6,20.5 67.1,35.8 3.6,2.6 8.7,1.5 11.3,-2 2,-3.1 1.5,-7.7 -1.5,-10.8zM517.6,817.2c-37.9,0 -75.3,10.2 -108,30.2 -45.6,-61.4 -117.2,-97.3 -194,-97.3 -54.3,0 -107.5,18.4 -150,52.2 -3.1,3.1 -3.6,8.2 -0.5,11.3 2.6,3.1 7.2,3.6 10.2,1 39.9,-31.7 89.1,-48.6 140.3,-48.6 74.8,0 143.4,36.4 185.3,96.8 2.6,3.6 7.2,4.6 11.3,2 31.2,-21 68.1,-31.7 105.5,-31.7 35.8,0 69.6,9.7 99.8,27.6 3.6,2 8.2,1 10.8,-2 13.3,-17.9 29.2,-34.3 47.1,-47.6 3.6,-3.1 4.1,-8.2 1,-11.3 -2.6,-3.1 -7.2,-3.6 -10.8,-1.5 -17.4,12.8 -32.8,28.2 -46.1,45.1 -31.7,-16.9 -66.6,-26.1 -101.9,-26.1zM855.6,846.8c-41,-27.6 -96.3,-16.9 -123.9,23.6l-3.1,4.6c-2,3.6 -1,8.7 3.1,11.3 3.6,2 8.7,1 11.3,-3.1 8.2,-14.3 21.5,-25.1 36.9,-31.2 22.5,-8.7 47.6,-5.6 67.6,7.7 3.6,2.6 8.7,1.5 11.3,-2 1.5,-3.1 0.5,-8.2 -3.1,-10.8zM185.3,248.8c2,1.5 2,4.6 0.5,6.1m-45.1,205.3l-18.9,21c-1,1 -2.6,1 -3.6,0 -1,-1 -1,-2.6 0,-3.6l18.9,-21c1,-1 2.6,-1 3.6,0 1,1 1,2.6 0,3.6zM165.4,460.3l-18.9,21c-1,1 -2.6,1 -3.6,0 -1,-1 -1,-2.6 0,-3.6l18.9,-21c1,-1 2.6,-1 3.6,0 0.5,1 1,2.6 0,3.6zM813.1,146.4l-38.4,43c-2,2.6 -6.1,2.6 -8.2,0.5 -2.6,-2 -2.6,-6.1 -0.5,-8.2l38.4,-43c2,-2.6 6.1,-2.6 8.2,-0.5 2.6,2 2.6,5.6 0.5,8.2zM863.7,146.4l-38.4,43c-2,2.6 -6.1,2.6 -8.2,0.5 -2.6,-2 -2.6,-6.1 -0.5,-8.2l38.4,-43c2,-2.6 6.1,-2.6 8.2,-0.5 2.6,2 3.1,5.6 0.5,8.2z"
android:fillColor="@color/sub_title_color"/>
<path
android:pathData="M390.1,451.6c-17.9,0.5 -32.8,-13.8 -33.3,-31.2 -0.5,-17.9 13.8,-32.8 31.2,-33.3h1.5c17.9,0.5 31.7,15.4 31.2,33.3 0.5,16.9 -13.3,30.7 -30.7,31.2zM382.5,419.3c6.1,0 10.8,-4.6 10.8,-10.8 0,-6.1 -4.6,-10.8 -10.8,-10.8 -6.1,0 -10.8,4.6 -10.8,10.8 0,6.1 4.6,10.8 10.8,10.8zM656.4,451.6c-17.9,0.5 -32.8,-13.8 -33.3,-31.2 -0.5,-17.9 13.8,-32.8 31.2,-33.3h1.5c17.9,0.5 31.7,15.4 31.2,33.3 0.5,16.9 -13.3,30.7 -30.7,31.2zM648.2,419.3c6.1,0 10.8,-4.6 10.8,-10.8 0,-6.1 -4.6,-10.8 -10.8,-10.8s-10.8,4.6 -10.8,10.8c0,6.1 5.1,10.8 10.8,10.8zM596,515.1c-2,0 -4.1,-1 -5.6,-2.6 -11.8,-12.3 -23.6,-17.9 -35.3,-17.4 -11.3,0.5 -21,7.7 -26.1,14.3 -2.6,3.6 -7.7,4.1 -11.3,1.5 -0.5,-0.5 -1,-0.5 -1,-1 -14.3,-16.4 -26.6,-15.9 -36.4,-13.8 -8.2,2 -16.4,6.7 -24.6,14.3 -3.1,3.1 -8.2,2.6 -11.3,-0.5 -3.1,-3.1 -2.6,-8.2 0.5,-11.3 10.2,-9.2 21,-15.4 31.7,-17.9 17.4,-4.6 32.8,-0.5 46.6,12.3 6.7,-6.1 17.4,-12.8 31.7,-13.8 16.4,-1 32.3,6.7 47.6,22.5 3.1,3.1 3.1,8.2 0,11.3 -2.6,1 -4.6,2 -6.7,2zM765.4,798.2c-64.5,0 -116.7,-52.2 -116.7,-116.7 0,-64.5 52.2,-116.7 116.7,-116.7 64.5,0 116.7,52.2 116.7,116.7 0,64.5 -52.2,116.7 -116.7,116.7zM707.6,666.6c-8.7,0 -16.4,7.2 -15.9,16.4 0,8.7 7.2,15.9 15.9,15.9h118.3c8.7,0 16.4,-7.2 15.9,-16.4 0,-8.7 -7.2,-15.9 -15.9,-15.9h-118.3z"
android:fillColor="@color/sub_title_color"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_main_view"/>
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="16dp" />
<solid android:color="@color/color_main_view" />
</shape>

Some files were not shown because too many files have changed in this diff Show More