This commit is contained in:
litingting 2025-09-09 13:50:16 +08:00
commit a5e90bbdb1
88 changed files with 14710 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

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

Binary file not shown.

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

@ -0,0 +1,79 @@
import java.util.Date
import java.text.SimpleDateFormat
val timestamp = SimpleDateFormat("MM_dd_HH_mm").format(Date())
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id ("kotlin-parcelize")
id("io.objectbox")
}
android {
namespace = "com.video.mobile.wallpaper"
compileSdk = 35
defaultConfig {
applicationId = "com.video.mobile.wallpaper"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
setProperty(
"archivesBaseName",
"Dynamic Cartoon Wallpaper_V" + versionName + "(${versionCode})_$timestamp"
)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures{
viewBinding = true
buildConfig = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation ("androidx.paging:paging-runtime-ktx:3.3.6")
implementation ("com.google.code.gson:gson:2.12.1")
implementation("com.github.bumptech.glide:glide:4.16.0")
// ViewModel + LiveData KTX
// implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.2")
// implementation ("androidx.lifecycle:lifecycle-livedata-ktx:2.9.2")
// Fragment KTX提供 viewModels 扩展)
implementation ("androidx.fragment:fragment-ktx:1.8.9")
implementation("androidx.media3:media3-exoplayer:1.8.0")
implementation ("androidx.media3:media3-ui:1.8.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:adapter-rxjava2:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}

View File

@ -0,0 +1,116 @@
{
"_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": "1:5861365936442707683",
"lastPropertyId": "6:8474641495170349359",
"name": "LikeWallpaper",
"properties": [
{
"id": "1:1896367441224580523",
"name": "objectId",
"type": 6,
"flags": 1
},
{
"id": "2:518404838438816658",
"name": "description",
"type": 9
},
{
"id": "3:8610300359262444087",
"name": "id",
"type": 6
},
{
"id": "4:1799690089661510919",
"name": "image",
"type": 9
},
{
"id": "5:3735709230659931937",
"name": "wallpapertype",
"type": 6
},
{
"id": "6:8474641495170349359",
"name": "thumbnail",
"type": 9
}
],
"relations": []
},
{
"id": "2:7214743200838441355",
"lastPropertyId": "10:5928432456802213941",
"name": "VideoWallpaper",
"properties": [
{
"id": "1:8404628459774998577",
"name": "objectId",
"type": 6,
"flags": 1
},
{
"id": "2:2199034792582915578",
"name": "category",
"type": 9
},
{
"id": "3:8906378192469633251",
"name": "description",
"type": 9
},
{
"id": "4:4298057389705852700",
"name": "downloads",
"type": 9
},
{
"id": "5:5956110026349494870",
"name": "id",
"type": 6
},
{
"id": "6:6267621209200928594",
"name": "image",
"type": 9
},
{
"id": "7:8054649528453649863",
"name": "pro",
"type": 5
},
{
"id": "8:6813789647380825500",
"name": "resolution",
"type": 9
},
{
"id": "9:3370332525900065977",
"name": "thumbnail",
"type": 9
},
{
"id": "10:5928432456802213941",
"name": "wallpapertype",
"type": 6
}
],
"relations": []
}
],
"lastEntityId": "2:7214743200838441355",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],
"retiredIndexUids": [],
"retiredPropertyUids": [],
"retiredRelationUids": [],
"version": 1
}

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

@ -0,0 +1,194 @@
# 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
# 保持 Gson TypeToken 不被混淆
-keep class com.google.gson.** { *; }
-keep class com.google.gson.reflect.TypeToken { *; }
# 保留与泛型相关的 TypeToken 使用,避免泛型信息丢失
-keep class * extends com.google.gson.reflect.TypeToken { *; }
# 保持 Gson 序列化相关的字段不被混淆
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# 保持 Gson 构造方法不被混淆
-keepclassmembers class com.google.gson.Gson {
public <init>(...);
}
#-------------------------Gson
# Gson 反射需要保留字段名
-keep class com.google.gson.stream.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
# 保留实体类字段(根据需要,你可以只针对自己的 model 包)
-keepclassmembers class com.video.mobile.wallpaper.database.VideoWallpaper {
<fields>;
}
#-------------------------Gson
#-------------------------Serializable
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-keep class com.video.mobile.wallpaper.database.VideoWallpaper implements java.io.Serializable { *; }
-keep class com.video.mobile.wallpaper.database.LikeWallpaper implements java.io.Serializable { *; }
#-------------------------Serializable
#-------------------------Glide
# Glide v4 混淆规则
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl { *; }
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** { **[] $VALUES; public *; }
#-------------------------Glide
# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
# OkHttp
-dontwarn okhttp3.**
-keep class okhttp3.** { *; }
# RxJava(如果用 RxJava2
-dontwarn io.reactivex.**
-keep class androidx.media3.** { *; }
-dontwarn androidx.media3.**
# 保留 Retrofit 接口
-keep interface com.video.mobile.wallpaper.network.VideoApi { *; }
-keep class com.video.mobile.wallpaper.database.LikeWallpaper { *; }
-keep class com.video.mobile.wallpaper.database.VideoWallpaper { *; }
# 保留 Retrofit 的所有类
-keep class retrofit2.** { *; }
# 保留 RxJava2 的所有类
-keep class io.reactivex.** { *; }
# 保留 CallAdapter Converter 相关类
-keep class retrofit2.adapter.** { *; }
-keep class retrofit2.converter.** { *; }
# 保留类型 Token类型适配器等相关类
-keepclassmembers class * {
@retrofit2.http.* <methods>;
}
# 保留 Retrofit OkHttp 的类和构造方法
-keep class retrofit2.** { *; }
-keep class okhttp3.** { *; }
# 保留 Retrofit 接口和方法
-keep class retrofit2.Retrofit { *; }
-keep class retrofit2.Call { *; }
-keep class retrofit2.http.** { *; }
# 保留所有使用 Retrofit 注解的字段方法和类
-keepclassmembers class * {
@retrofit2.http.* <methods>;
}
# 保留 Converter CallAdapter 相关类
-keep class retrofit2.converter.** { *; }
-keep class retrofit2.adapter.** { *; }
# 保留 `@GET`, `@POST` 等注解
-keepclassmembers class * {
@retrofit2.http.GET <methods>;
@retrofit2.http.POST <methods>;
@retrofit2.http.PUT <methods>;
@retrofit2.http.DELETE <methods>;
@retrofit2.http.PATCH <methods>;
@retrofit2.http.Headers <methods>;
@retrofit2.http.Query <methods>;
@retrofit2.http.Body <methods>;
@retrofit2.http.Path <methods>;
}
# 保留 OkHttp 相关类
-keep class okhttp3.Request { *; }
-keep class okhttp3.Response { *; }
-keep class okhttp3.OkHttpClient { *; }
# 保留 OkHttp 相关的拦截器
-keep class okhttp3.Interceptor { *; }
-keep class okhttp3.logging.HttpLoggingInterceptor { *; }
# 保留 OkHttp 的构造方法
-keepclassmembers class okhttp3.** {
public <init>(...);
}
# 保留 OkHttpClient 的链式构建方式
-keepclassmembers class okhttp3.OkHttpClient {
public okhttp3.OkHttpClient$Builder newBuilder(...);
}
# 保留 HttpLoggingInterceptor 相关类
-keep class okhttp3.logging.HttpLoggingInterceptor { *; }
# 保留 Call Request 方法
-keepclassmembers class okhttp3.Call {
public *;
}
-keepclassmembers class okhttp3.Request {
public *;
}
-keepclassmembers class okhttp3.Response {
public *;
}

View File

@ -0,0 +1,24 @@
package com.video.mobile.wallpaper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.video.mobile.wallpaper", appContext.packageName)
}
}

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.INTERNET" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission
android:name="android.permission.BIND_WALLPAPER"
tools:ignore="ProtectedPermissions" />
<application
android:name=".App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo"
android:supportsRtl="true"
android:theme="@style/Theme.DynamicCartoonWallpaper"
tools:targetApi="31">
<activity
android:name=".favorite.FavoriteActivity"
android:exported="false" />
<activity
android:name=".preview.DisplayVideoActivity"
android:exported="false" />
<activity
android:name=".SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".main.MainActivity"
android:screenOrientation="portrait" />
<service
android:name=".preview.VideoWallpaperService"
android:exported="true"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/video_wallpaper" />
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</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

View File

@ -0,0 +1,19 @@
package com.video.mobile.wallpaper
import android.app.Application
import com.video.mobile.wallpaper.database.DataBaseManager
class App : Application(){
companion object{
val TAG = "============"
lateinit var mApp :App
}
override fun onCreate() {
super.onCreate()
mApp = this
DataBaseManager.init(this)
Helper.loadJson(this)
}
}

View File

@ -0,0 +1,9 @@
package com.video.mobile.wallpaper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
class BaseViewHolder<T : ViewBinding>(
itemVb: T
) : RecyclerView.ViewHolder(itemVb.root)

View File

@ -0,0 +1,185 @@
package com.video.mobile.wallpaper
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Parcelable
import android.util.Log
import android.widget.Toast
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.video.mobile.wallpaper.database.DataBaseManager
import com.video.mobile.wallpaper.database.VideoWallpaper
import okhttp3.ResponseBody
import java.io.BufferedReader
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.io.OutputStream
import java.io.Reader
import java.io.StringWriter
import java.lang.reflect.Type
import java.nio.charset.StandardCharsets
import java.util.concurrent.Executors
object Helper {
val thumbnailStr: String = "https://neutrolabgames.com/LiveLoop/CpanelPix/VideoThumb/"
fun showMyLog(message: String) {
Log.d(App.TAG, message)
}
fun loadJson(context: Context) {
val name = arrayOf("Trending.json", "Explore.json", "Shift.json")
val cachedThreadPool = Executors.newFixedThreadPool(3)
for (i in 0..2) {
val task = i
cachedThreadPool.execute {
val gson: Gson = Gson()
var open = context.assets.open(name[task])
val type: Type =
object : TypeToken<List<VideoWallpaper?>?>() {
}.type
val covertStr: String = getJsonStr(open)
val data: List<VideoWallpaper> =
gson.fromJson(covertStr, type)
for (wallpaper in data) {
DataBaseManager.insertDb(wallpaper)
}
// Shared.INSTANCE.setDbinit("1")
}
}
}
fun showToast(context: Context,message: String){
Toast.makeText(context,message, Toast.LENGTH_SHORT).show()
}
fun getJsonStr(stream: InputStream): String {
var covertStr = ""
try {
val writer = StringWriter()
val buffer = CharArray(stream.available())
val reader: Reader = BufferedReader(InputStreamReader(stream, StandardCharsets.UTF_8))
var a = 0
while ((reader.read(buffer).also { a = it }) != -1) {
writer.write(buffer, 0, a)
}
covertStr = writer.toString()
} catch (e: IOException) {
return covertStr
}
return covertStr
}
@Suppress("DEPRECATION")
inline fun <reified T : java.io.Serializable> Intent.getSerializableExtraCompat(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializableExtra(key, T::class.java)
} else {
getSerializableExtra(key) as? T
}
}
fun writeFile(input: InputStream, filePath: String): Boolean {
try {
val byteArray = ByteArray(4096)
val output = ByteArrayOutputStream()
var bytesRead: Int
while ((input.read(byteArray).also { bytesRead = it }) != -1) {
output.write(byteArray, 0, bytesRead)
}
val file = File(filePath)
if (!file.exists()) {
file.createNewFile()
}
val fileOutputStream = FileOutputStream(filePath)
fileOutputStream.write(output.toByteArray())
output.close()
fileOutputStream.close()
return true
} catch (ex: Exception) {
showMyLog("---------ex=${ex.message}" )
return false
}
}
fun writeResponseBodyToDisk(body: ResponseBody, filePath: String): Boolean {
return try {
val file = File(filePath)
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
try {
val buffer = ByteArray(8 * 1024)
var bytesRead: Int
inputStream = body.byteStream()
outputStream = FileOutputStream(file)
var totalBytes: Long = 0
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
totalBytes += bytesRead
}
outputStream.flush()
showMyLog("downloaded=$totalBytes contentType = ${body.contentType()}")
val bytes = body.bytes()
showMyLog("bytes = ${String(bytes.take(100).toByteArray())}")
// 这里不能用 contentLength因为 chunked = -1
totalBytes > 0
} finally {
inputStream?.close()
outputStream?.close()
}
} catch (e: Exception) {
e.printStackTrace()
false
}
}
// fun writeResponseBodyToDisk(body: ResponseBody, filePath: String): Boolean {
// return try {
// val file = File(filePath)
// var inputStream: InputStream? = null
// var outputStream: OutputStream? = null
//
// try {
// val buffer = ByteArray(8 * 1024)
// var bytesRead: Int
// inputStream = body.byteStream()
// outputStream = FileOutputStream(file)
//
// while (inputStream.read(buffer).also { bytesRead = it } != -1) {
// outputStream.write(buffer, 0, bytesRead)
// }
// outputStream.flush()
//
// // ✅ 校验完整性
// val downloaded = file.length()
// val expected = body.contentLength()
// showMyLog("downloaded=$downloaded , expected=$expected")
//
// downloaded == expected || expected == -1L
// } finally {
// inputStream?.close()
// outputStream?.close()
// }
// } catch (e: Exception) {
// e.printStackTrace()
// false
// }
// }
}

View File

@ -0,0 +1,63 @@
package com.video.mobile.wallpaper
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.CountDownTimer
import android.widget.ProgressBar
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.video.mobile.wallpaper.main.MainActivity
class SplashActivity : AppCompatActivity() {
private var countDownTimer: CountDownTimer? = null
private val timer = 2000L
private lateinit var progressBar: ProgressBar
private lateinit var textProgress: TextView
override fun onCreate(savedInstanceState: Bundle?) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// installSplashScreen()
// }
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_splash)
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
}
progressBar = findViewById(R.id.progress_bar)
textProgress = findViewById(R.id.textview_progress)
countDownTimer = object : CountDownTimer(timer, 100) {
override fun onTick(millisUntilFinished: Long) {
val v: Float =
100 - millisUntilFinished.toFloat() / timer * 100
val v1 = v.toInt()
progressBar.progress = v1
textProgress.text = getString(R.string.splash_progress_text, v1)
}
override fun onFinish() {
launchMain()
}
}
countDownTimer?.start()
}
private fun launchMain() {
val max = 100
textProgress.text = getString(R.string.splash_progress_text, max)
progressBar.progress = max
startActivity(Intent(this@SplashActivity, MainActivity::class.java))
finish()
}
}

View File

@ -0,0 +1,143 @@
package com.video.mobile.wallpaper.database
import android.content.Context
import com.video.mobile.wallpaper.BuildConfig
import com.video.mobile.wallpaper.Helper
import io.objectbox.Box
import io.objectbox.BoxStore
import io.objectbox.android.AndroidScheduler
import io.objectbox.config.DebugFlags
import io.objectbox.query.Query
import io.objectbox.reactive.DataSubscription
import io.objectbox.reactive.DataSubscriptionList
object DataBaseManager {
val pageSize = 10
var boxStore: BoxStore? = null
var videoWallpaperBox: Box<VideoWallpaper>? = null
var likeWallpaperBox: Box<LikeWallpaper>? = null
val observer: DataSubscription? = null
fun init(context: Context?) {
boxStore = MyObjectBox.builder().androidContext(context)
.debugFlags(DebugFlags.LOG_QUERIES or DebugFlags.LOG_QUERY_PARAMETERS)
.build()
}
fun getDataBox(): Box<VideoWallpaper>? {
videoWallpaperBox = videoWallpaperBox ?: boxStore?.boxFor<VideoWallpaper>(VideoWallpaper::class.java)
return videoWallpaperBox
}
fun getLikeBox(): Box<LikeWallpaper>? {
likeWallpaperBox = likeWallpaperBox ?: boxStore?.boxFor<LikeWallpaper>(LikeWallpaper::class.java)
return likeWallpaperBox
}
fun setLikeUpdateListener(queryAllLike:(List<LikeWallpaper>)->Unit): DataSubscription {
val likeBox: Box<LikeWallpaper>? = getLikeBox()
val build: Query<LikeWallpaper> = likeBox!!.query()
.build()
return build.subscribe(DataSubscriptionList())
.on(AndroidScheduler.mainThread())
.observer { data ->
Helper.showMyLog("---OnLikeUpdateListener-------------" + data.size)
queryAllLike(data)
}
}
fun insertDb(resultData: VideoWallpaper) {
val objectBox: Box<VideoWallpaper>? = getDataBox()
val first: VideoWallpaper? = objectBox!!.query()
.equal(VideoWallpaper_.wallpapertype, resultData.wallpapertype)
.equal(VideoWallpaper_.id, resultData.id)
.build()
.findFirst()
if (first == null) {
// Common.logMsg("---insertDb------Wallpapertype------" + resultData.getWallpapertype() + "-------id=" + resultData.getId());
Helper.showMyLog("--------insertDb ${resultData.wallpapertype} + ${resultData.id}")
objectBox.put(resultData)
}
}
fun insertLike(favoriteData: LikeWallpaper) {
val likeBox: Box<LikeWallpaper>? = getLikeBox()
val first: LikeWallpaper? = likeBox!!.query()
.equal(LikeWallpaper_.id, favoriteData.id)
.build()
.findFirst()
if (first == null) {
Helper.showMyLog("---insertLike-----------------id=" + favoriteData.id)
likeBox.put(favoriteData)
} else {
Helper.showMyLog("---insertLike-----------------id=" + favoriteData.id)
}
}
fun deleteLike(id: Long,deleteResult:(b: Boolean)-> Unit) {
val likeBox: Box<LikeWallpaper>? = getLikeBox()
val first: LikeWallpaper? = likeBox!!.query()
.equal(LikeWallpaper_.id, id)
.build()
.findFirst()
if (first != null) {
val remove = likeBox.remove(first.objectId)
deleteResult(remove)
Helper.showMyLog("---deleteLike-----------------id=$id")
}
}
fun queryIsLike(id: Long): Boolean {
val likeBox: Box<LikeWallpaper>? = getLikeBox()
val first: LikeWallpaper? = likeBox!!.query()
.equal(LikeWallpaper_.id, id)
.build()
.findFirst()
return first != null
}
fun queryData(wallpaperType: Long, currentPage: Int): List<VideoWallpaper> {
val offset: Int = (currentPage - 1) * pageSize
val objectBoxLike: Box<VideoWallpaper>? = getDataBox()
val data: List<VideoWallpaper> = objectBoxLike!!.query()
.equal(VideoWallpaper_.wallpapertype, wallpaperType)
.build()
.find(offset.toLong(), pageSize.toLong())
return data
}
fun queryLike(currentPage: Int): List<LikeWallpaper> {
val offset: Int = (currentPage - 1) * pageSize
val likeBox: Box<LikeWallpaper>? = getLikeBox()
val data: List<LikeWallpaper> = likeBox!!.query()
.build()
.find(offset.toLong(), pageSize.toLong())
return data
}
fun queryAllData(wallpaperType: Long): List<VideoWallpaper> {
val objectBoxLike: Box<VideoWallpaper>? = getDataBox()
val data: List<VideoWallpaper> = objectBoxLike!!.query()
.equal(VideoWallpaper_.wallpapertype, wallpaperType)
.build()
.find()
return data
}
}

View File

@ -0,0 +1,23 @@
package com.video.mobile.wallpaper.database
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import java.io.Serializable
@Entity
data class LikeWallpaper(
@Id
var objectId: Long = 0,
var description: String? = null,
var id: Long = 0,
var image: String? = null,
//0 :trending 1:dnamic 2:shift 3:explore
var wallpapertype: Long = 0,
var thumbnail: String? = null
) : Serializable

View File

@ -0,0 +1,24 @@
package com.video.mobile.wallpaper.database
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import java.io.Serializable
@Entity
data class VideoWallpaper(
@Id
var objectId: Long = 0,
var category: String? = null,
var description: String? = null,
var downloads: String? = null,
var id: Long = 0,
var image: String? = null,
var pro: Int = 0,
var resolution: String? = null,
var thumbnail: String? = null,
//0 :trending 1:dnamic 2:shift 3:explore
var wallpapertype: Long = 0
): Serializable

View File

@ -0,0 +1,124 @@
package com.video.mobile.wallpaper.favorite
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.liveData
import androidx.paging.map
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.video.mobile.wallpaper.Helper
import com.video.mobile.wallpaper.R
import com.video.mobile.wallpaper.database.DataBaseManager
import com.video.mobile.wallpaper.database.LikeWallpaper
import com.video.mobile.wallpaper.page.LikePageSource
import com.video.mobile.wallpaper.preview.DisplayVideoActivity
import com.video.mobile.wallpaper.utils.RecyclerViewSpace
class FavoriteActivity : AppCompatActivity() {
private lateinit var imageBack: ImageView
private lateinit var recyclerView: RecyclerView
private lateinit var layoutEmpty: LinearLayout
private lateinit var favoriteAdapter: FavoriteAdapter
private val launcher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
val deleteSuccess = result.data?.getBooleanExtra(DisplayVideoActivity.IS_REFRESH, false) ?: false
if (deleteSuccess) {
favoriteAdapter.refresh()
Helper.showMyLog("--------- favoriteAdapter.refresh()")
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_favorite)
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
}
imageBack = findViewById(R.id.image_back)
imageBack.setOnClickListener { finish() }
recyclerView = findViewById(R.id.favorite_recyclerview)
layoutEmpty = findViewById(R.id.layout_empty)
init()
}
private fun init() {
favoriteAdapter = FavoriteAdapter(this@FavoriteActivity) { data, strImage ->
launcher.launch(Intent(this@FavoriteActivity, DisplayVideoActivity::class.java).apply {
putExtra(DisplayVideoActivity.KEY_LIKE_WALLPAPER, data)
putExtra(DisplayVideoActivity.THUMBNAIL_STRING, strImage)
putExtra(DisplayVideoActivity.KEY_TYPE, DisplayVideoActivity.SOURCE_LIKE)
})
}
recyclerView.run {
adapter = favoriteAdapter
layoutManager = GridLayoutManager(this@FavoriteActivity, 2)
addItemDecoration(RecyclerViewSpace(3, 3, 2))
}
getPagingData().observe(this@FavoriteActivity,
object : Observer<PagingData<LikeWallpaper>> {
override fun onChanged(value: PagingData<LikeWallpaper>) {
value.map { item ->
Helper.showMyLog("---------LikeWallpaper = ${item.id}") // 打印每条数据
item
}.let { favoriteAdapter.submitData(lifecycle, it) }
}
});
favoriteAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
checkEmpty()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
checkEmpty()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
checkEmpty()
}
private fun checkEmpty() {
Helper.showMyLog("---------checkEmpty") // 打印每条数据
layoutEmpty.isVisible = favoriteAdapter.itemCount == 0
recyclerView.isVisible = favoriteAdapter.itemCount != 0
}
})
}
private fun getPagingData(): LiveData<PagingData<LikeWallpaper>> {
return Pager<Int, LikeWallpaper>(
PagingConfig(DataBaseManager.pageSize)
) { LikePageSource() }.liveData
}
}

View File

@ -0,0 +1,91 @@
package com.video.mobile.wallpaper.favorite
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.video.mobile.wallpaper.Helper
import com.video.mobile.wallpaper.R
import com.video.mobile.wallpaper.database.LikeWallpaper
import com.video.mobile.wallpaper.database.VideoWallpaper
class FavoriteAdapter(var context: Context,var clickWallpaper:(wallpaper: LikeWallpaper,thumbnailStr: String)-> Unit) :
PagingDataAdapter<LikeWallpaper, FavoriteAdapter.FavoriteAdapterViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavoriteAdapterViewHolder {
Helper.showMyLog("-----onCreateViewHolder- ")
return FavoriteAdapterViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.favorite_adapter, parent, false)
)
}
override fun onBindViewHolder(holder: FavoriteAdapterViewHolder, position: Int) {
Helper.showMyLog("-----${position}-- ")
val likeWallpaper = getItem(position)
likeWallpaper?.run {
Helper.showMyLog("-----${position}--${Helper.thumbnailStr + thumbnail}")
val string = Helper.thumbnailStr + thumbnail
holder.imageFilterView.setOnClickListener {
clickWallpaper(likeWallpaper,string)
}
Glide.with(context)
.asDrawable()
.load(string)
.placeholder(R.drawable.thumbnail_placeholder)
.centerCrop()
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
Helper.showMyLog("---onLoadFailed--${e?.message}")
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
return false
}
})
.into(holder.imageFilterView)
} ?: Helper.showMyLog("-----erro-- ")
}
inner class FavoriteAdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageFilterView: ImageFilterView = itemView.findViewById(R.id.thumbnail)
}
}
val DIFF_CALLBACK: DiffUtil.ItemCallback<LikeWallpaper> =
object : DiffUtil.ItemCallback<LikeWallpaper>() {
override fun areItemsTheSame(oldItem: LikeWallpaper, newItem: LikeWallpaper): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: LikeWallpaper, newItem: LikeWallpaper): Boolean {
return oldItem.id == newItem.id
}
}

View File

@ -0,0 +1,12 @@
package com.video.mobile.wallpaper.main
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class FragmentAdapter(context: FragmentActivity, var list: List<Fragment>) :
FragmentStateAdapter(context) {
override fun getItemCount(): Int = list.size
override fun createFragment(position: Int): Fragment = list[position]
}

View File

@ -0,0 +1,91 @@
package com.video.mobile.wallpaper.main
import android.content.Intent
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.viewpager2.widget.ViewPager2
import com.video.mobile.wallpaper.R
import com.video.mobile.wallpaper.favorite.FavoriteActivity
class MainActivity : AppCompatActivity() {
private lateinit var tab1: TextView
private lateinit var tab2: TextView
private lateinit var tab3: TextView
private lateinit var tvList: List<TextView>
private lateinit var viewPager: ViewPager2
private lateinit var favoriteBtn: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
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
}
tab1 = findViewById(R.id.text_1_explore)
tab2 = findViewById(R.id.text_2_shift)
tab3 = findViewById(R.id.text_3_trending)
viewPager = findViewById(R.id.viewpager2)
favoriteBtn = findViewById(R.id.favorite_btn)
tvList = listOf(tab1, tab2, tab3)
setTabSelect(tab1)
setClick()
viewPager.run {
adapter = FragmentAdapter(
this@MainActivity, listOf(
MainFragment.newInstance(3),
MainFragment.newInstance(2),
MainFragment.newInstance(0)
)
)
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
when (position) {
0 -> setTabSelect(tab1)
1 -> setTabSelect(tab2)
2 -> setTabSelect(tab3)
}
}
})
}
}
private fun setClick() {
tab1.setOnClickListener {
setTabSelect(tab1)
viewPager.currentItem = 0
}
tab2.setOnClickListener {
setTabSelect(tab2)
viewPager.currentItem = 1
}
tab3.setOnClickListener {
setTabSelect(tab3)
viewPager.currentItem = 2
}
favoriteBtn.setOnClickListener {
startActivity(Intent(this@MainActivity, FavoriteActivity::class.java))
}
}
private fun setTabSelect(textView: TextView) {
tvList.forEach { it.isSelected = false }
textView.isSelected = true
}
}

View File

@ -0,0 +1,94 @@
package com.video.mobile.wallpaper.main
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.video.mobile.wallpaper.Helper
import com.video.mobile.wallpaper.R
import com.video.mobile.wallpaper.database.VideoWallpaper
class MainAdapter(var context: Context,var clickWallpaper:(wallpaper: VideoWallpaper,thumbnailStr: String)-> Unit) :
PagingDataAdapter<VideoWallpaper, MainAdapter.MainAdapterViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainAdapterViewHolder {
Helper.showMyLog("-----onCreateViewHolder- ")
return MainAdapterViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.main_adapter, parent, false)
)
}
override fun onBindViewHolder(holder: MainAdapterViewHolder, position: Int) {
Helper.showMyLog("-----${position}-- ")
val videoWallpaper = getItem(position)
videoWallpaper?.run {
if (position == 0 || position == 1) {
holder.viewPlaceholder.visibility = View.VISIBLE
} else {
holder.viewPlaceholder.visibility = View.GONE
}
Helper.showMyLog("-----${position}--${Helper.thumbnailStr + thumbnail}")
val string = Helper.thumbnailStr + thumbnail
holder.imageFilterView.setOnClickListener {
clickWallpaper(videoWallpaper,string)
}
Glide.with(context)
.asDrawable()
.load(string)
.placeholder(R.drawable.thumbnail_placeholder)
.centerCrop()
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
Helper.showMyLog("---onLoadFailed--${e?.message}")
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
return false
}
})
.into(holder.imageFilterView)
} ?: Helper.showMyLog("-----erro-- ")
}
inner class MainAdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageFilterView: ImageFilterView = itemView.findViewById(R.id.thumbnail)
val viewPlaceholder: View = itemView.findViewById(R.id.view_placeholder)
}
}
val DIFF_CALLBACK: DiffUtil.ItemCallback<VideoWallpaper> =
object : DiffUtil.ItemCallback<VideoWallpaper>() {
override fun areItemsTheSame(oldItem: VideoWallpaper, newItem: VideoWallpaper): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: VideoWallpaper, newItem: VideoWallpaper): Boolean {
return oldItem.id == newItem.id
}
}

View File

@ -0,0 +1,110 @@
package com.video.mobile.wallpaper.main
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.liveData
import androidx.paging.map
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.video.mobile.wallpaper.Helper
import com.video.mobile.wallpaper.R
import com.video.mobile.wallpaper.database.DataBaseManager
import com.video.mobile.wallpaper.database.VideoWallpaper
import com.video.mobile.wallpaper.page.AllWallpaperPageSource
import com.video.mobile.wallpaper.preview.DisplayVideoActivity
import com.video.mobile.wallpaper.utils.RecyclerViewSpace
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlin.properties.Delegates
private const val ARG_PARAM1 = "param1"
class MainFragment : Fragment() {
private var type by Delegates.notNull<Long>()
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
type = it.getLong(ARG_PARAM1)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_main, container, false)
recyclerView = view.findViewById(R.id.recycler_view)
onInit()
return view
}
private fun onInit(){
val mainAdapter = MainAdapter(requireActivity()){ data,strImage->
startActivity( Intent(requireActivity(), DisplayVideoActivity::class.java).apply {
putExtra(DisplayVideoActivity.KEY_VIDEO_WALLPAPER,data)
putExtra(DisplayVideoActivity.THUMBNAIL_STRING,strImage)
})
}
recyclerView.run {
adapter = mainAdapter
layoutManager = GridLayoutManager(requireContext(),2)
addItemDecoration(RecyclerViewSpace(3,3,2))
}
// viewModel.initType(type)
// lifecycleScope.launch {
// viewModel.videoPagingFlow.collectLatest { pagingData ->
// pagingData.map { item ->
// println("VideoWallpaper = ${item.id}") // 打印每条数据
// item
// }.let { mainAdapter.submitData(it) }
// }
// }
getPagingData(type).observe(getViewLifecycleOwner(),object :Observer<PagingData<VideoWallpaper>>{
override fun onChanged(value: PagingData<VideoWallpaper>) {
// mainAdapter.submitData(lifecycle, value)
value.map { item ->
Helper.showMyLog("---------VideoWallpaper = ${item.id}") // 打印每条数据
item
}.let { mainAdapter.submitData(lifecycle,it) }
}
});
}
fun getPagingData(wallpaperType: Long): LiveData<PagingData<VideoWallpaper>> {
return Pager<Int, VideoWallpaper>(
PagingConfig(DataBaseManager.pageSize)
) { AllWallpaperPageSource(wallpaperType) }.liveData
}
companion object {
@JvmStatic
fun newInstance(type: Long) =
MainFragment().apply {
arguments = Bundle().apply {
putLong(ARG_PARAM1, type)
}
}
}
}

View File

@ -0,0 +1,41 @@
//package com.video.mobile.wallpaper.main
//
//import androidx.lifecycle.LiveData
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import androidx.paging.Pager
//import androidx.paging.PagingConfig
//import androidx.paging.PagingData
//import androidx.paging.cachedIn
//import androidx.paging.liveData
//import com.video.mobile.wallpaper.database.DataBaseManager
//import com.video.mobile.wallpaper.database.VideoWallpaper
//import com.video.mobile.wallpaper.page.AllWallpaperPageSource
//import kotlinx.coroutines.flow.Flow
//import kotlin.properties.Delegates
//
//class MainViewModel : ViewModel() {
//
// fun getPagingData(wallpaperType: Long): LiveData<PagingData<VideoWallpaper>> {
//
// return Pager<Int, VideoWallpaper>(
// PagingConfig(DataBaseManager.pageSize)
// ) { AllWallpaperPageSource(wallpaperType) }.liveData
// }
//
//
// private var type by Delegates.notNull<Long>()
//
// fun initType(wallpaperType: Long){
// type = wallpaperType
// }
// val videoPagingFlow: Flow<PagingData<VideoWallpaper>> = Pager(
// config = PagingConfig(
// pageSize = DataBaseManager.pageSize, // 每页加载数量
// initialLoadSize = DataBaseManager.pageSize, // 首次加载数量
// enablePlaceholders = false // 是否占位符
// ),
// pagingSourceFactory = { AllWallpaperPageSource(type) }
// ).flow
// .cachedIn(viewModelScope)
//}

View File

@ -0,0 +1,37 @@
package com.video.mobile.wallpaper.network
import okhttp3.MediaType
import okhttp3.ResponseBody
import okio.Buffer
import okio.BufferedSource
import okio.ForwardingSource
import okio.buffer
import java.io.IOException
class CustomResponseBody(
var responseBody: ResponseBody,
var contentLength: String,
var progressListener: (mByte: Long,total: Long,complete: Boolean) -> Unit
) :
ResponseBody() {
private var bufferedSource: BufferedSource? = null
override fun contentLength(): Long = responseBody.contentLength()
override fun contentType(): MediaType? = responseBody.contentType()
override fun source(): BufferedSource =
bufferedSource ?: object : ForwardingSource(responseBody.source()) {
var totalBytesRead: Long = 0L
@Throws(IOException::class)
override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount)
totalBytesRead += if (bytesRead != -1L) bytesRead else 0
progressListener(totalBytesRead, contentLength.toLong(), bytesRead == -1L)
return bytesRead
}
}.buffer()
}

View File

@ -0,0 +1,18 @@
package com.video.mobile.wallpaper.network
import okhttp3.Interceptor
import okhttp3.Response
class HttpInterceptor(var progressListener: (mByte: Long, total: Long, complete: Boolean) -> Unit) :
Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalResponse = chain.proceed(chain.request())
var fileLength = originalResponse.header("File-Length")
if (fileLength == null) {
fileLength = "0"
}
return originalResponse.newBuilder()
.body(CustomResponseBody(originalResponse.body!!, fileLength, progressListener))
.build()
}
}

View File

@ -0,0 +1,256 @@
package com.video.mobile.wallpaper.network
import com.video.mobile.wallpaper.BuildConfig
import com.video.mobile.wallpaper.Helper
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Consumer
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import java.util.concurrent.TimeUnit
object RetrofitManager {
private var clientBuilder: OkHttpClient.Builder? = null
private const val BASE_URL = "https://neutrolabgames.com/"
private const val DEFAULT_TIMEOUT = 5L // 秒
val retrofit: Retrofit.Builder by lazy {
Retrofit.Builder()
.baseUrl("https://neutrolabgames.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
}
private fun createRetrofit(
progressListener: (bytesRead: Long, contentLength: Long, done: Boolean) -> Unit
): Retrofit {
// 日志拦截器
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG)
HttpLoggingInterceptor.Level.HEADERS
else
HttpLoggingInterceptor.Level.NONE
}
// OkHttpClient
val client = OkHttpClient.Builder()
// .addInterceptor(loggingInterceptor)
.addNetworkInterceptor(HttpInterceptor(progressListener))
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build()
}
@Synchronized
private fun getRetrofit(progressListener: (mByte: Long, total: Long, complete: Boolean) -> Unit): Retrofit? {
// retrofit = new Retrofit.Builder()
// .baseUrl(base_Host)
// .client(client)
// /**/ .addConverterFactory(ScalarsConverterFactory.create()) // 支持返回 String 类型
// * / .addConverterFactory(GsonConverterFactory.create()) */
// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// .build();
// builder = builder ?: Retrofit.Builder()
// .baseUrl(base_Host)
// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
val DEFAULT_TIMEOUT: Long = 5
val httpLoggingInterceptor =
HttpLoggingInterceptor().apply { setLevel(HttpLoggingInterceptor.Level.BODY) }
clientBuilder = clientBuilder ?: OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
clientBuilder?.apply {
addInterceptor(HttpInterceptor(progressListener))
}?.build()?.let {
return retrofit.client(it).build()
} ?: return null
}
fun getMp4(
id: Long,
image: String,
path: String,
progressListener: (mByte: Long, total: Long, complete: Boolean) -> Unit,
resultListener: (isOK: Boolean, savePath: String) -> Unit
): Disposable {
// val retrofit = createRetrofit(progressListener)
// val api = retrofit.create(VideoApi::class.java)
// Helper.showMyLog("----------getMp4----- ")
// return download(api.getMP4(id, "5eV6snEwfY7Yv6Ub", image, "DL8", "ViewLive"),path,resultListener)
val retrofit = createRetrofit(progressListener)
retrofit.let {
Helper.showMyLog("----------get Mp4----- ")
return it.create<VideoApi?>(VideoApi::class.java)
.getMP4(id, "5eV6snEwfY7Yv6Ub", image, "DL8", "ViewLive")
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(
{ body -> // onNext
val save = Helper.writeResponseBodyToDisk(body, path)
Helper.showMyLog("-----save =${save}")
},
{ error -> // onError
resultListener(false, error.message ?: "download error")
},
{ // onComplete
resultListener(true, path)
Helper.showMyLog( "Download completed")
},
{ disposable -> // onSubscribe
Helper.showMyLog("Subscribed, can cancel if needed")
// 例如:保存 disposable 用于取消下载
})
// .subscribe(object : Consumer<ResponseBody> {
// @Throws(Exception::class)
// override fun accept(body: ResponseBody) {
// val b: Boolean = Helper.writeFile(body.byteStream(), path)
// resultListener(b, path)
// }
// }, object : Consumer<Throwable> {
// @Throws(Exception::class)
// override fun accept(throwable: Throwable) {
// resultListener(false, throwable.message.toString())
// }
// })
}
}
fun getShiftMp4(
id: Long,
image: String,
path: String,
progressListener: (mByte: Long, total: Long, complete: Boolean) -> Unit,
resultListener: (isOK: Boolean, savePath: String) -> Unit
): Disposable {
//
// val retrofit = createRetrofit(progressListener)
// val api = retrofit.create(VideoApi::class.java)
// Helper.showMyLog("----------getShiftMp4----- ")
// return download(
// api.getShiftMP4(id, "5eV6snEwfY7Yv6Ub", image, "DL8", "ViewShiftLive"),
// path,
// resultListener
// )
val retrofit = createRetrofit(progressListener)
retrofit.let {
Helper.showMyLog("----------getShiftMp4----- ")
return it.create<VideoApi?>(VideoApi::class.java)
.getShiftMP4(id, "5eV6snEwfY7Yv6Ub", image, "DL8", "ViewShiftLive")
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(object : Consumer<ResponseBody> {
@Throws(Exception::class)
override fun accept(body: ResponseBody) {
val b: Boolean = Helper.writeResponseBodyToDisk(body, path)
resultListener(b, path)
}
}, object : Consumer<Throwable> {
@Throws(Exception::class)
override fun accept(throwable: Throwable) {
resultListener(false, throwable.message.toString())
}
})
}
}
private fun download(
apiCall: Observable<ResponseBody>,
path: String,
resultListener: (Boolean, String) -> Unit
): Disposable {
return apiCall
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(object : Consumer<ResponseBody> {
@Throws(Exception::class)
override fun accept(body: ResponseBody) {
val success = try {
Helper.writeFile(body.byteStream(), path)
} catch (e: Exception) {
e.printStackTrace()
false
}
resultListener(success, path)
}
}, object : Consumer<Throwable> {
@Throws(Exception::class)
override fun accept(throwable: Throwable) {
resultListener(false, throwable.message ?: "unknown error")
}
})
}
// public void getDnamicMp4(int id, String image,String path, OnVideoResultListener listener) {
// musicApi.getDMP4(id,"5eV6snEwfY7Yv6Ub",image,"DL8","ViewTimerLive","1" )
// .subscribeOn(Schedulers.io())
// .unsubscribeOn(Schedulers.io())
// .observeOn(Schedulers.io())
// .subscribe(new ObserverWrapper<>(new OnRequestListener<ResponseBody>() {
// @Override
// public void onFail(String errorMsg) {
// listener.onVideoResult(false,errorMsg);
// }
//
// @Override
// public void onSuccess(ResponseBody data) {
// boolean b = Common.writeFile(data.byteStream(), path);
// listener.onVideoResult(b,path);
// }
//
// }));
// }
// companion object {
// @Volatile
// private lateinit var REQUEST_MANAGER: RetrofitManager
//
// val instance: RetrofitManager
// get() {
// if (REQUEST_MANAGER == null) {
// synchronized(RetrofitManager::class.java) {
// if (REQUEST_MANAGER == null) {
// REQUEST_MANAGER = RetrofitManager()
// }
// }
// }
// return REQUEST_MANAGER
// }
// }
}

View File

@ -0,0 +1,41 @@
package com.video.mobile.wallpaper.network
import io.reactivex.Observable
import okhttp3.ResponseBody
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface VideoApi {
@POST("LiveLoop/AppData/jmywall.php")
@FormUrlEncoded
fun getMP4(
@Field("pi") pi: Long,
@Field("medium") medium: String?,
@Field("alpha") alpha: String?,
@Field("version") version: String?,
@Field("quality") quality: String?
): Observable<ResponseBody>
//shift
@POST("LiveLoop/AppData/jshiftwall.php")
@FormUrlEncoded
fun getShiftMP4(
@Field("pi") pi: Long,
@Field("medium") medium: String?,
@Field("alpha") alpha: String?,
@Field("version") version: String?,
@Field("quality") quality: String?
): Observable<ResponseBody> //
// //Dnamic
// @POST("LiveLoop/AppData/jdaytimewall.php")
// @FormUrlEncoded
// Observable<ResponseBody> getDMP4(@Field("pi") int pi,
// @Field("medium") String medium,
// @Field("alpha") String alpha,
// @Field("version") String version,
// @Field("quality") String quality,
// @Field("filenumber") String filenumber);
}

View File

@ -0,0 +1,36 @@
package com.video.mobile.wallpaper.page
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.video.mobile.wallpaper.Helper
import com.video.mobile.wallpaper.database.DataBaseManager
import com.video.mobile.wallpaper.database.VideoWallpaper
class AllWallpaperPageSource(var wallpaperType: Long) : PagingSource<Int, VideoWallpaper>() {
override fun getRefreshKey(state: PagingState<Int, VideoWallpaper>): Int? {
return null
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, VideoWallpaper> {
try {
val page = params.key ?: 1
page.let {
val data: List<VideoWallpaper> = DataBaseManager.queryData(wallpaperType, it)
Helper.showMyLog("--------load data ${data.size} ")
return LoadResult.Page(
data,
if (it > 1) it - 1 else null,
if (data.isEmpty()) null else it + 1
)
}
} catch (e: Exception) {
Helper.showMyLog("--------load ${e.message} ")
return LoadResult.Error(e)
}
}
}

View File

@ -0,0 +1,34 @@
package com.video.mobile.wallpaper.page
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.video.mobile.wallpaper.database.DataBaseManager
import com.video.mobile.wallpaper.database.LikeWallpaper
import com.video.mobile.wallpaper.database.VideoWallpaper
class LikePageSource() : PagingSource<Int, LikeWallpaper>() {
override fun getRefreshKey(state: PagingState<Int, LikeWallpaper>): Int? {
return null
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, LikeWallpaper> {
try {
val page = params.key ?: 1
page.let {
val data: List<LikeWallpaper> = DataBaseManager.queryLike(it)
return LoadResult.Page(
data,
if (it > 1) it - 1 else null,
if (data.isEmpty()) null else it + 1
)
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}

View File

@ -0,0 +1,502 @@
package com.video.mobile.wallpaper.preview
import android.app.WallpaperManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.activity.enableEdgeToEdge
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.video.mobile.wallpaper.App
import com.video.mobile.wallpaper.Helper
import com.video.mobile.wallpaper.Helper.getSerializableExtraCompat
import com.video.mobile.wallpaper.R
import com.video.mobile.wallpaper.database.DataBaseManager
import com.video.mobile.wallpaper.database.LikeWallpaper
import com.video.mobile.wallpaper.database.VideoWallpaper
import com.video.mobile.wallpaper.network.RetrofitManager
import com.video.mobile.wallpaper.utils.RecyclerViewSpace
import io.reactivex.disposables.Disposable
import java.io.File
import kotlin.properties.Delegates
class DisplayVideoActivity : AppCompatActivity() {
companion object {
val KEY_VIDEO_WALLPAPER = "video_wallpaper"
val KEY_LIKE_WALLPAPER = "like_wallpaper"
val KEY_TYPE = "source"
val SOURCE_MAIN = -1
val SOURCE_LIKE = -2
val THUMBNAIL_STRING = "thumbnail_string"
val IS_REFRESH = "is_refresh"
}
private var intentVideoWallpaper: VideoWallpaper? = null
private var intentLikeWallpaper: LikeWallpaper? = null
private var sourceType: Int = -1
private val loadingType_circle = 0
private val loadingType_download = 1
private val loadingType_retry = 2
private var exoPlayer: ExoPlayer? = null
private lateinit var downloadingProgressLayout: LinearLayout
private lateinit var backFrameLayout: FrameLayout
private lateinit var downloadProgressbar: ProgressBar
private lateinit var progressBar: ProgressBar
private lateinit var playerView: PlayerView
private lateinit var retryLinearLayout: LinearLayout
private lateinit var loadingLayout: RelativeLayout
private lateinit var retryTextView: TextView
private var wallpaperId by Delegates.notNull<Long>()
private var wallpaperType by Delegates.notNull<Long>()
private lateinit var thumbnailImageView: ImageView
private lateinit var cachePath: String
private lateinit var cacheFile: File
private var disposable: Disposable? = null
private lateinit var networkErrorStr: String
private lateinit var playErrorStr: String
private lateinit var thumbnailStr: String
private lateinit var textDescriptor: TextView
private lateinit var textBtnSet: TextView
private lateinit var frameFavorite: FrameLayout
private lateinit var imageFavorite: ImageView
private lateinit var textRetry: TextView
private lateinit var bottomLayout: LinearLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_display_video)
backFrameLayout = findViewById(R.id.back_btn)
bottomLayout = findViewById(R.id.layout_btn)
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)
val params = backFrameLayout.layoutParams as ViewGroup.MarginLayoutParams
params.topMargin = systemBars.top
backFrameLayout.layoutParams = params
val marginLayoutParams = bottomLayout.layoutParams as ViewGroup.MarginLayoutParams
marginLayoutParams.bottomMargin =
systemBars.bottom + RecyclerViewSpace.dpToPx(20f).toInt()
bottomLayout.layoutParams = marginLayoutParams
Helper.showMyLog("---systemBars.top--${systemBars.top} bottom = ${systemBars.bottom}")
insets
}
downloadingProgressLayout = findViewById(R.id.download_progress_layout)
downloadProgressbar = findViewById(R.id.horizontal_progressbar)
progressBar = findViewById(R.id.normal_progress)
playerView = findViewById(R.id.player_view)
retryLinearLayout = findViewById(R.id.retry_layout)
loadingLayout = findViewById(R.id.loading_bg_layout)
retryTextView = findViewById(R.id.tv_load_fail)
thumbnailImageView = findViewById(R.id.image_thumbnail)
textRetry = findViewById(R.id.tv_retry_button)
textDescriptor = findViewById(R.id.tv_video_describe)
textBtnSet = findViewById(R.id.text_btn_set)
frameFavorite = findViewById(R.id.framelayout_favorite)
imageFavorite = findViewById(R.id.im_favorite)
networkErrorStr = getString(R.string.download_fail_network)
playErrorStr = getString(R.string.download_fail_unknown)
getIntentData()
setAllClick()
initPlayerView()
if (cacheFile.exists()) {
playVideo(cacheFile)
} else {
getNetWorkVideo(cachePath)
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Helper.showMyLog("---handleOnBackPressed")
backPage()
}
})
}
private fun setAllClick() {
backFrameLayout.setOnClickListener {
backPage()
}
textBtnSet.setOnClickListener {
if (cacheFile.exists()) {
setVideoWallpaper()
} else {
Helper.showToast(this@DisplayVideoActivity, getString(R.string.cache_file_no_lost))
}
}
textRetry.setOnClickListener {
getNetWorkVideo(cachePath)
}
frameFavorite.setOnClickListener {
showCircleLoading(true, loadingType_circle)
val newStatus = !imageFavorite.isSelected
if (newStatus) {
intentVideoWallpaper?.let {
Helper.showMyLog("--- Video --insertLike ")
DataBaseManager.insertLike(LikeWallpaper().apply {
id = it.id
description = it.description
image = it.image
wallpapertype = it.wallpapertype
thumbnail = it.thumbnail
})
}
intentLikeWallpaper?.let {
Helper.showMyLog("---Like --insertLike ")
DataBaseManager.insertLike(it)
}
imageFavorite.isSelected = newStatus
showCircleLoading(false)
} else {
DataBaseManager.deleteLike(wallpaperId) { deleteOk ->
showCircleLoading(false)
if (deleteOk) {
imageFavorite.isSelected = newStatus
Helper.showToast(
this@DisplayVideoActivity,
getString(R.string.remove_favorite_ok)
)
} else {
Helper.showToast(
this@DisplayVideoActivity,
getString(R.string.remove_favorite_fail)
)
}
}
}
}
}
private fun backPage() {
val resultIntent = Intent()
resultIntent.putExtra(IS_REFRESH, !imageFavorite.isSelected)
setResult(RESULT_OK, resultIntent)
finish()
}
private fun getIntentData() {
sourceType = intent.getIntExtra(KEY_TYPE, SOURCE_MAIN)
when (sourceType) {
SOURCE_MAIN -> {
intentVideoWallpaper = intent.getSerializableExtraCompat(KEY_VIDEO_WALLPAPER)
Helper.showMyLog("---序列化 intentVideoWallpaper=${intentVideoWallpaper} ")
intentVideoWallpaper?.let {
wallpaperId = it.id
wallpaperType = it.wallpapertype
}
}
SOURCE_LIKE -> {
intentLikeWallpaper = intent.getSerializableExtraCompat(KEY_LIKE_WALLPAPER)
Helper.showMyLog("---序列化 intentLikeWallpaper=${intentLikeWallpaper} ")
intentLikeWallpaper?.let {
wallpaperId = it.id
wallpaperType = it.wallpapertype
}
}
}
textDescriptor.text = intentVideoWallpaper?.description ?: intentLikeWallpaper?.description
thumbnailStr = intent.getStringExtra(THUMBNAIL_STRING).toString()
cachePath = getCachePath(wallpaperId)
cacheFile = File(cachePath)
Helper.showMyLog("--thumbnailStr=${thumbnailStr} ")
Glide.with(this)
.asDrawable()
.load(thumbnailStr)
.placeholder(R.drawable.thumbnail_placeholder)
.centerCrop()
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
Helper.showMyLog("---onLoadFailed--${e?.message}")
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
return false
}
})
.into(thumbnailImageView)
imageFavorite.setSelected(DataBaseManager.queryIsLike(wallpaperId))
}
private fun initPlayerView() {
exoPlayer = ExoPlayer.Builder(this)
.build()
exoPlayer?.run {
repeatMode = ExoPlayer.REPEAT_MODE_ONE
// addListener(object : Player.Listener {
// override fun onPlaybackStateChanged(playbackState: Int) {
// if (playbackState == Player.STATE_READY) {
//// showCircleLoading(true, loadingType_circle)
// Helper.showMyLog("-------onPlaybackStateChanged-----")
// }
// }
// })
}
playerView.setPlayer(exoPlayer)
}
private fun setVideoWallpaper() {
WallpaperPathManager.video_wallpaper_path = cachePath
Helper.showMyLog("----------setVideo_path---cachePath=$cachePath")
val wallpaperManager = WallpaperManager.getInstance(this)
try {
wallpaperManager.clear()
Helper.showMyLog("----------clear=")
} catch (e: java.lang.Exception) {
Helper.showMyLog("---------e=" + e.message)
}
try {
val componentName =
ComponentName(this@DisplayVideoActivity, VideoWallpaperService::class.java)
val intent = Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER)
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, componentName)
startActivity(intent)
} catch (e: java.lang.Exception) {
Helper.showMyLog("---------e=" + e.message)
}
finish()
}
@OptIn(UnstableApi::class)
private fun playVideo(file: File) {
try {
val uri = Uri.fromFile(file)
// val uriFromFilePath: Uri = Com.getUriFromFilePath(this, filePath)
// val mediaItem = MediaItem.fromUri(uriFromFilePath)
// val mediaSource: ProgressiveMediaSource = createMediaSource(mediaItem)
val mediaItem = MediaItem.fromUri(uri)
exoPlayer?.setMediaItem(mediaItem)
exoPlayer?.addListener(object : Player.Listener {
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
Helper.showMyLog("-------------onPlayerError error=${error.message}")
// deleteFile(cacheFile)
showCircleLoading(true, loadingType_retry, playErrorStr)
}
override fun onRenderedFirstFrame() {
super.onRenderedFirstFrame()
//播放视频第一帧
showCircleLoading(false)
thumbnailImageView.visibility = View.GONE
}
})
exoPlayer?.prepare()
exoPlayer?.play()
} catch (e: Exception) {
Helper.showMyLog("playVideo ${e.message}")
// deleteFile(cacheFile)
showCircleLoading(true, loadingType_retry, playErrorStr)
}
}
private fun getNetWorkVideo(cachePath: String) {
showCircleLoading(true, loadingType_circle)
val imageStr = intentVideoWallpaper?.image ?: intentLikeWallpaper?.image
imageStr?.let {
if (wallpaperType == 2L) {
//shift
disposable = RetrofitManager
.getShiftMp4(
wallpaperId,
it,
cachePath,
{ mByte: Long, total: Long, complete: Boolean ->
runOnUiThread {
updateDownloadProgress(mByte, total, complete)
}
}) { ok: Boolean, path: String ->
runOnUiThread {
requestResult(ok, path)
}
}
} else {
disposable = RetrofitManager.getMp4(
wallpaperId,
it,
cachePath,
{ mByte: Long, total: Long, complete: Boolean ->
runOnUiThread {
updateDownloadProgress(mByte, total, complete)
}
}) { ok: Boolean, path: String ->
runOnUiThread {
requestResult(ok, path)
}
}
}
}
}
private fun requestResult(isOK: Boolean, path: String) {
Helper.showMyLog("------------requestResult-------path= ${path}")
val file = File(path)
if (file.exists() && isOK) {
playVideo(file)
} else {
showCircleLoading(true, loadingType_retry)
}
}
private fun showCircleLoading(
show: Boolean,
loadingType: Int? = 0,
retryString: String? = networkErrorStr
) {
if (show) {
loadingLayout.visibility = View.VISIBLE
when (loadingType) {
loadingType_circle -> {
progressBar.visibility = View.VISIBLE
retryLinearLayout.visibility = View.GONE
downloadingProgressLayout.visibility = View.GONE
}
loadingType_download -> {
progressBar.visibility = View.GONE
retryLinearLayout.visibility = View.GONE
downloadingProgressLayout.visibility = View.VISIBLE
}
loadingType_retry -> {
progressBar.visibility = View.GONE
retryLinearLayout.visibility = View.VISIBLE
downloadingProgressLayout.visibility = View.GONE
retryTextView.text = retryString
}
}
} else {
loadingLayout.visibility = View.GONE
}
}
private fun updateDownloadProgress(mByte: Long, total: Long, complete: Boolean) {
Helper.showMyLog("------------updateDownloadProgress---- ${mByte}")
if (total <= 0) {
} else {
val current = ((100 * mByte) / total).toInt()
if (downloadProgressbar.progress != current) {
runOnUiThread(object : Runnable {
override fun run() {
if (downloadingProgressLayout.visibility !== View.VISIBLE) {
showCircleLoading(true, loadingType_download)
Helper.showMyLog("------------showLoading------11-----")
} else {
Helper.showMyLog("------------showLoading------22-----")
}
downloadProgressbar.progress = current
}
})
}
}
}
/**
* 所有下载的视频保存位置
*/
private fun getCachePath(id: Long): String {
return "${App.mApp.cacheDir}/$id.mp4"
}
private fun deleteFile(file: File): Boolean {
return file.exists() && file.delete()
}
fun getUriFromFilePath(context: Context, filePath: String): Uri? {
val file = File(filePath)
// 使用 FileProvider 获取 URI
return FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file)
}
override fun onDestroy() {
super.onDestroy()
exoPlayer?.release()
// if (countDownTimer != null) {
// countDownTimer.cancel()
// countDownTimer = null
// }
disposable?.isDisposed?.let {
if (!it) {
Helper.showMyLog("----------- disposable?.dispose()---")
disposable?.dispose()
}
}
}
}

View File

@ -0,0 +1,112 @@
package com.video.mobile.wallpaper.preview
import android.net.Uri
import android.service.wallpaper.WallpaperService
import android.view.SurfaceHolder
import androidx.annotation.OptIn
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import com.video.mobile.wallpaper.Helper
import java.io.File
class VideoWallpaperService : WallpaperService() {
override fun onCreateEngine(): Engine? = VideoWallpaperEngine()
inner class VideoWallpaperEngine : Engine() {
private var exoPlayer: ExoPlayer? = null
@OptIn(UnstableApi::class)
private fun initExoPlay() {
exoPlayer = ExoPlayer.Builder(this@VideoWallpaperService).build()
exoPlayer?.repeatMode = ExoPlayer.REPEAT_MODE_ONE
update()
}
override fun onCreate(surfaceHolder: SurfaceHolder?) {
super.onCreate(surfaceHolder)
Helper.showMyLog("-----Engine-----onCreate")
initExoPlay()
}
@OptIn(UnstableApi::class)
override fun onSurfaceCreated(holder: SurfaceHolder?) {
super.onSurfaceCreated(holder)
val surface = holder?.surface
if (surface != null) {
exoPlayer?.setVideoSurface(surface)
exoPlayer?.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
}
}
override fun onVisibilityChanged(visible: Boolean) {
super.onVisibilityChanged(visible)
Helper.showMyLog("-------Engine---onVisibilityChanged visible=$visible")
if (visible) {
update()
exoPlayer?.play()
} else {
exoPlayer?.pause()
}
}
override fun onSurfaceDestroyed(holder: SurfaceHolder?) {
super.onSurfaceDestroyed(holder)
Helper.showMyLog("------Engine----onSurfaceDestroyed---")
exoPlayer?.release()
}
override fun onDestroy() {
super.onDestroy()
Helper.showMyLog("-------Engine---onDestroy---")
}
override fun onSurfaceChanged(
holder: SurfaceHolder?,
format: Int,
width: Int,
height: Int
) {
super.onSurfaceChanged(holder, format, width, height)
}
private fun update() {
val file = File(WallpaperPathManager.video_wallpaper_path)
if (file.isFile && file.exists()) {
val uri = Uri.fromFile(file)
val mediaItem = MediaItem.fromUri(uri)
exoPlayer?.run {
setMediaItem(mediaItem)
addListener(object : Player.Listener {
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
Helper.showMyLog("-----------update--onPlayerError")
}
override fun onRenderedFirstFrame() {
super.onRenderedFirstFrame()
//播放视频第一帧
Helper.showMyLog("-----------update--播放视频第一帧")
}
})
prepare()
playWhenReady = true
}
}
}
}
}

View File

@ -0,0 +1,52 @@
package com.video.mobile.wallpaper.preview
import android.content.Context
import android.content.SharedPreferences
import com.video.mobile.wallpaper.App
import androidx.core.content.edit
object WallpaperPathManager {
val KEY_CURRENT_WAPPPAPER_PATH = "video_wallpaper_path"
val DB_INIT_DATAT = "init_db_data"
private var shared: SharedPreferences? = null
var video_wallpaper_path: String
get() = queryString(
KEY_CURRENT_WAPPPAPER_PATH,
""
)
set(value) {
saveString(KEY_CURRENT_WAPPPAPER_PATH, value)
}
var dbinit: String
get() = queryString(
DB_INIT_DATAT,
"0"
)
set(value) {
saveString(DB_INIT_DATAT, value)
}
private fun getShared(): SharedPreferences {
if (shared == null) {
shared = App.mApp.getSharedPreferences("", Context.MODE_PRIVATE)
}
return shared!!
}
fun saveString(key: String, value: String) {
getShared().edit {
putString(key, value)
}
}
fun queryString(key: String, defaultValue: String): String {
return getShared()
.getString(key, defaultValue).orEmpty()
}
}

View File

@ -0,0 +1,72 @@
package com.video.mobile.wallpaper.utils
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.video.mobile.wallpaper.App
import kotlin.math.roundToInt
class RecyclerViewSpace(v: Int, h: Int, ex: Int) : ItemDecoration() {
private val v: Int = dpToPx(v.toFloat()).roundToInt()
private val h: Int = dpToPx(h.toFloat()).roundToInt()
private val ex: Int = dpToPx(ex.toFloat()).roundToInt()
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
var spanCount = 1
var spanSize = 1
var spanIndex = 0
val childAdapterPosition = parent.getChildAdapterPosition(view)
val layoutManager = parent.layoutManager
if (layoutManager is StaggeredGridLayoutManager) {
val layoutParams = view.layoutParams as StaggeredGridLayoutManager.LayoutParams
spanCount = layoutManager.spanCount
if (layoutParams.isFullSpan) {
spanSize = spanCount
}
spanIndex = layoutParams.spanIndex
} else if (layoutManager is GridLayoutManager) {
val gridLayoutManager = layoutManager
val layoutParams = view.layoutParams as GridLayoutManager.LayoutParams
spanCount = gridLayoutManager.spanCount
spanSize = gridLayoutManager.spanSizeLookup.getSpanSize(childAdapterPosition)
spanIndex = layoutParams.spanIndex
} else if (layoutManager is LinearLayoutManager) {
outRect.left = v
outRect.right = v
outRect.bottom = h
}
if (spanSize == spanCount) {
outRect.left = v + ex
outRect.right = v + ex
outRect.bottom = h
} else {
val itemAllSpacing = (v * (spanCount + 1) + ex * 2) / spanCount
val left = v * (spanIndex + 1) - itemAllSpacing * spanIndex + ex
val right = itemAllSpacing - left
outRect.left = left
outRect.right = right
outRect.bottom = h
}
}
companion object {
fun dpToPx(dpValue: Float): Float {
val density: Float = App.mApp.resources.displayMetrics.density
return density * dpValue + 0.5f
}
}
}

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="M395.2,513.6l323.1,-312.4c19.1,-18.4 19.1,-48.3 0,-66.7 -19.1,-18.4 -49.9,-18.4 -69,0L291.8,480.3c-19.1,18.4 -19.1,48.3 0,66.7l357.6,345.7c9.5,9.2 22,13.8 34.5,13.8 12.5,0 25,-4.6 34.5,-13.8 19.1,-18.4 19.1,-48.2 0,-66.7L395.2,513.6z"
android:fillColor="@color/black"/>
</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/main_sub_color"/>
</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">
<solid android:color="@color/main_sub_color" />
<corners android:radius="9dp" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/icon_favorite_selected" android:state_selected="true" />
<item android:drawable="@drawable/icon_favorite_unselected" android:state_selected="false" />
</selector>

View File

@ -0,0 +1,39 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72.7dp"
android:height="64dp"
android:viewportWidth="1163"
android:viewportHeight="1024">
<path
android:pathData="M1103.8,383.2h-15a15,15 0,0 0,0 30h15v15a15,15 0,0 0,30 0v-15h15a15,15 0,0 0,0 -30h-15v-15a15,15 0,0 0,-30 0v15z"
android:fillColor="#F4F4F4"/>
<path
android:pathData="M0,151.7a25.3,25.3 0,1 0,50.6 0,25.3 25.3,0 0,0 -50.6,0z"
android:fillColor="#F4F4F4"/>
<path
android:pathData="M970,763.9L839.8,455.1H310.6L180.4,763.9H177V1024h796.4V763.9z"
android:fillColor="#F8F8F8"/>
<path
android:pathData="M973.4,1024H177V763.9h3.4L310.6,455.1h529.1L970,763.9h2.5l0.9,7.3V1024h-0zM191.9,1009.4h766.6l-0.4,-235.3 -128.3,-304.3H320.6L191.9,774.9v234.5z"
android:fillColor="#CCCCCC"/>
<path
android:pathData="M309.8,657.4L189.6,1005.9l15.5,5.5 120.2,-348.5L309.8,657.4zM828,657.4L948.1,1005.9 932.7,1011.4 812.5,662.9 828,657.4z"
android:fillColor="#CCCCCC"/>
<path
android:pathData="M492.3,758.5H177v265.5h796.4V758.5H658.2a83,83 0,1 1,-165.9 0z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M973.4,1024H177V758.5h315l0.6,6.9c3.5,42.7 39.8,76.1 82.7,76.1 42.9,0 79.2,-33.4 82.7,-76.1l0.6,-6.9H973.4v265.5h-0zM191.9,1009.1h766.6V773.4H671.9c-7.2,47 -48.5,83 -96.7,83 -48.3,0 -89.5,-35.9 -96.8,-83H191.9V1009.1z"
android:fillColor="#CCCCCC"/>
<path
android:pathData="M303.4,455.1h531v227.6H303.4z"
android:fillColor="#F4F4F4"/>
<path
android:pathData="M834.4,682.7L303.4,682.7L303.4,455.1h531v227.6zM318.3,668L819.5,668L819.5,469.7L318.3,469.7v198.3z"
android:fillColor="#CCCCCC"/>
<path
android:pathData="M506.3,593.1c11,-5.9 21.9,-12.1 32.7,-18.4 4.3,-2.7 5.8,-8.6 3.4,-13.3 -2.4,-4.7 -7.8,-6.5 -12.2,-4.1 -10.3,6.1 -21.1,12.1 -32.2,18.1a9.9,9.9 0,0 0,-5 8.3,10.2 10.2,0 0,0 4.1,8.8 8.6,8.6 0,0 0,9.1 0.6zM570.3,555.1a764,764 0,0 0,32.3 -19.9,9 9,0 0,0 2.6,-12.8 9.6,9.6 0,0 0,-13.1 -2.5,754.3 754.3,0 0,1 -31.5,19.4 9.1,9.1 0,0 0,-3 12.5,9.6 9.6,0 0,0 12.7,3.3zM635.2,516.8a519,519 0,0 0,31.5 -21.8c4.1,-3.1 4.5,-8.4 0.8,-11.9 -3.7,-3.5 -10,-3.7 -14.1,-0.7a500.2,500.2 0,0 1,-30.3 20.9c-4.1,2.9 -4.8,8 -1.5,11.5 3.2,3.6 9.3,4.4 13.7,1.8zM684.7,477.2c7.9,-10.1 15.2,-20.8 22,-32 2.4,-4.5 1.4,-10.4 -2.3,-13.5 -3.7,-3.1 -8.7,-2.2 -11.5,1.9 -6.3,10.5 -13.2,20.5 -20.6,30a11.3,11.3 0,0 0,0.5 14.1c3.5,3.7 8.8,3.5 11.9,-0.6zM734.8,399.8c-1.9,4.2 -6.3,6 -9.9,3.9 -3.7,-2.1 -5.3,-7.2 -3.6,-11.6 4.3,-10.5 7.6,-21 9.9,-31.7 1.1,-4.6 5.2,-7.3 9.1,-6.2 3.9,1.2 6.4,5.9 5.5,10.6 -2.6,12 -6.3,23.8 -10.9,35zM745.9,332.3a80.2,80.2 0,0 0,-11.3 -37.8c-2.4,-4 -7.2,-5 -10.7,-2.2 -3.5,2.8 -4.3,8.3 -1.9,12.2 5,8.3 8,17.9 8.6,28.8 0.4,4.7 4,8.2 8.1,7.9 4.1,-0.3 7.3,-4.3 7.2,-9zM703,263.3a123.8,123.8 0,0 0,-35.6 -10.3,9.1 9.1,0 0,0 -8.1,2.8 7.2,7.2 0,0 0,-1.3 7.8c1.1,2.6 3.7,4.5 6.8,4.9 10.5,1.4 20.7,4.3 30.2,8.7 2.7,1.4 6.1,1.4 8.8,-0.1a7.6,7.6 0,0 0,4.1 -7.1,7.8 7.8,0 0,0 -4.9,-6.6zM621.6,253c-15.3,3.1 -28.3,9.8 -37.3,19.8a11.6,11.6 0,0 0,-2.5 10c0.7,3.6 3.1,6.5 6.1,7.6 3.1,1.1 6.4,0.1 8.7,-2.4 6.2,-6.8 16,-11.9 28.1,-14.4 4.9,-1 8.1,-6.4 7.3,-12.1 -0.9,-5.7 -5.5,-9.5 -10.4,-8.5zM581.9,315.4c3.3,11.3 10.4,23.1 21.3,35.5a9.3,9.3 0,0 0,13.1 0.8,9.4 9.4,0 0,0 0.8,-13.2c-9.2,-10.4 -14.9,-20 -17.4,-28.3a9.3,9.3 0,1 0,-15.6 -3.8,9.4 9.4,0 0,0 -2.2,9.1zM623.1,372.6a118.4,118.4 0,0 0,35.4 18.9c3.1,1 6.4,0.1 8.8,-2.5a11.3,11.3 0,0 0,2.5 -9.9c-0.8,-3.6 -3.2,-6.4 -6.3,-7.4a101.3,101.3 0,0 1,-30.2 -16.2c-4.1,-2.8 -9.5,-1.5 -12.2,3.1 -2.7,4.6 -1.7,10.8 2.1,14.1zM704.2,391.9a8.3,8.3 0,0 1,-7.5 -4.3,10.1 10.1,0 0,1 -0.4,-9.3 8.5,8.5 0,0 1,7.2 -5c10.6,-0.6 21.1,-2.7 31.3,-6.1a7.9,7.9 0,0 1,8.4 2c2.3,2.3 3.2,5.8 2.5,9.1a9,9 0,0 1,-6 6.8c-11.5,4 -23.4,6.3 -35.5,6.9zM785.3,378c11.4,-6 22.4,-12.8 32.9,-20.2a9.1,9.1 0,0 0,1.8 -12.6,9.5 9.5,0 0,0 -12.8,-2.4 276.6,276.6 0,0 1,-30.8 18.9,9.2 9.2,0 0,0 -5.2,7.9 9.2,9.2 0,0 0,4.5 8.3c2.9,1.7 6.6,1.8 9.6,0.1zM838.7,326.9a498.7,498.7 0,0 0,30.8 -22.6c3.8,-3.2 3.7,-8.2 -0.4,-11.3a12.7,12.7 0,0 0,-14.5 0c-9.5,7.5 -19.4,14.8 -29.6,21.7 -4.1,3 -4.4,7.9 -0.7,11.2 3.7,3.3 10.1,3.7 14.4,1zM887.1,287.6c7.5,-10.3 14.7,-20.9 21.6,-31.7 2.6,-4.4 1.8,-10.5 -1.9,-13.7 -3.7,-3.2 -8.9,-2.5 -11.8,1.7 -6.7,10.5 -13.7,20.7 -21,30.7 -2.8,4.2 -2.3,10.3 1.2,13.8 3.5,3.5 8.7,3.1 11.8,-0.8zM939.8,223.1a748.9,748.9 0,0 0,19.7 -31.9,9.5 9.5,0 0,0 0.1,-9.4 9.1,9.1 0,0 0,-7.8 -4.8,9.1 9.1,0 0,0 -8,4.6 740,740 0,0 1,-19.3 31.2,9.5 9.5,0 0,0 -0.9,9.5 9.1,9.1 0,0 0,7.8 5.3,9.1 9.1,0 0,0 8.2,-4.5zM976,159.6c3.2,-6.7 6.3,-13.5 9.4,-20.3 1.7,-4.4 0.1,-9.6 -3.8,-11.8 -3.9,-2.2 -8.6,-0.6 -10.7,3.6a795.9,795.9 0,0 1,-9.1 19.9c-2.1,4.4 -0.6,10 3.3,12.4 3.9,2.4 8.8,0.7 11,-3.7z"
android:fillColor="#CCCCCC"/>
<path
android:pathData="M651.1,88.5C626.7,88.5 606.8,68.6 606.8,44.2S626.7,0 651.1,0 695.3,19.8 695.3,44.2 675.5,88.5 651.1,88.5zM651.1,16.3A28,28 0,0 0,623.1 44.2c0,15.4 12.5,28 28,28A28,28 0,0 0,679.1 44.2,28 28,0 0,0 651.1,16.3z"
android:fillColor="#D8D8D8"/>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</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="M910.9,364.8l-1.3,-10.1a237,237 0,0 0,-6.2 -28,201.4 201.4,0 0,0 -42.6,-80 46.1,46.1 0,0 0,-3.8 -4.5,212 212,0 0,0 -160,-71.7c-75.7,0 -146.7,29.8 -185.1,74.1 -38.4,-44.3 -109.6,-74.1 -185.1,-74.1a212,212 0,0 0,-160 71.7,46.1 46.1,0 0,0 -3.8,4.5 201.4,201.4 0,0 0,-42.6 80,237 237,0 0,0 -6.2,28l-1.3,10.1A186.4,186.4 0,0 0,112 385.9a205.3,205.3 0,0 0,1.1 21.3c16,194.1 368,423.2 382.9,433l16,10.4 16,-10.4c15,-9.6 367,-238.7 382.9,-433a205.3,205.3 0,0 0,1.1 -21.3,186.4 186.4,0 0,0 -1.1,-21.1z"
android:fillColor="@color/favorite_red"/>
</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="M908.8,214.4c-9.6,-12.8 -19.2,-22.4 -28.8,-32 -112,-115.2 -230.4,-105.6 -342.4,-16 -9.6,6.4 -19.2,16 -28.8,25.6 -9.6,-9.6 -19.2,-16 -28.8,-25.6 -112,-86.4 -230.4,-99.2 -342.4,16 -9.6,9.6 -19.2,19.2 -25.6,32 -134.4,195.2 -60.8,387.2 137.6,560 44.8,38.4 89.6,73.6 137.6,102.4 16,9.6 32,19.2 44.8,28.8 9.6,6.4 12.8,9.6 19.2,9.6 3.2,3.2 6.4,3.2 12.8,6.4 3.2,3.2 9.6,3.2 16,6.4 25.6,6.4 64,3.2 89.6,-12.8 3.2,0 9.6,-3.2 16,-9.6 12.8,-6.4 28.8,-16 44.8,-28.8 48,-28.8 92.8,-64 137.6,-102.4 201.6,-176 275.2,-368 140.8,-560zM736,732.8c-41.6,35.2 -86.4,70.4 -131.2,99.2 -16,9.6 -28.8,19.2 -44.8,25.6 -6.4,3.2 -12.8,6.4 -16,9.6 -6.4,3.2 -16,6.4 -25.6,9.6h-28.8s-3.2,0 -3.2,-3.2c-3.2,0 -6.4,-3.2 -9.6,-3.2 -3.2,-3.2 -9.6,-6.4 -16,-9.6 -12.8,-6.4 -28.8,-16 -44.8,-25.6 -44.8,-28.8 -89.6,-60.8 -131.2,-99.2C105.6,576 41.6,412.8 153.6,246.4c6.4,-9.6 16,-16 22.4,-25.6 89.6,-96 182.4,-86.4 275.2,-12.8 9.6,6.4 16,12.8 22.4,19.2l28.8,32 6.4,6.4 16,-16c12.8,-12.8 25.6,-25.6 41.6,-38.4C659.2,137.6 752,128 841.6,224c6.4,9.6 16,16 22.4,25.6 118.4,156.8 54.4,323.2 -128,483.2z"
android:fillColor="@color/white"/>
</vector>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 外层:渐变填充 -->
<item>
<shape android:shape="rectangle">
<gradient
android:startColor="#FF5722"
android:endColor="#03A9F4"
android:angle="0" />
<corners android:radius="16dp" />
</shape>
</item>
<!-- 内层:中间透明(留出边框) -->
<item
android:left="4dp"
android:top="4dp"
android:right="4dp"
android:bottom="4dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="12dp" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<gradient
android:endColor="@color/main_FF7FED"
android:startColor="@color/main_536DFF" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/main_tab_selected"/>
<item android:drawable="@drawable/main_tab_unselect"/>
</selector>

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="8dp" />
<solid android:color="@color/main_sub_color" />
</shape>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="5dp" />
<solid android:color="@color/main_sub_color" /> <!-- 背景颜色 -->
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="20dp" />
<gradient
android:angle="0"
android:endColor="@color/main_FF7FED"
android:startColor="@color/main_536DFF" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,25 @@
<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="M972.8,0a51.2,51.2 0,0 1,51.2 51.2v921.6a51.2,51.2 0,0 1,-51.2 51.2H51.2a51.2,51.2 0,0 1,-51.2 -51.2V51.2a51.2,51.2 0,0 1,51.2 -51.2h921.6z"
android:fillColor="#BEBEBE"/>
<path
android:pathData="M755.2,448L1024,716.8v256a51.2,51.2 0,0 1,-51.2 51.2H51.2a51.2,51.2 0,0 1,-51.2 -51.2v-113.2l294.4,-294.4 171.8,171.8L755.2,448z"
android:strokeAlpha="0.694"
android:fillColor="#FFFFFF"
android:fillAlpha="0.694"/>
<path
android:pathData="M332.8,332.8m-102.4,0a102.4,102.4 0,1 0,204.8 0,102.4 102.4,0 1,0 -204.8,0Z"
android:strokeAlpha="0.694"
android:fillColor="#FFFFFF"
android:fillAlpha="0.694"/>
<path
android:pathData="M972.8,0a51.2,51.2 0,0 1,51.2 51.2v921.6a51.2,51.2 0,0 1,-51.2 51.2L51.2,1024a51.2,51.2 0,0 1,-51.2 -51.2L0,51.2a51.2,51.2 0,0 1,51.2 -51.2h921.6zM870.4,153.6L153.6,153.6v716.8h716.8L870.4,153.6z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M972.8,0a51.2,51.2 0,0 1,51.2 51.2v921.6a51.2,51.2 0,0 1,-51.2 51.2L51.2,1024a51.2,51.2 0,0 1,-51.2 -51.2L0,51.2a51.2,51.2 0,0 1,51.2 -51.2h921.6zM972.8,25.6L51.2,25.6a25.6,25.6 0,0 0,-25.4 22.6L25.6,51.2v921.6a25.6,25.6 0,0 0,22.6 25.4L51.2,998.4h921.6a25.6,25.6 0,0 0,25.4 -22.6L998.4,972.8L998.4,51.2a25.6,25.6 0,0 0,-22.6 -25.4L972.8,25.6z"
android:fillColor="#D9D9D9"/>
</vector>

View File

@ -0,0 +1,190 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".preview.DisplayVideoActivity">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@color/white"
app:resize_mode="zoom"
app:show_buffering="never"
app:show_shuffle_button="false"
app:show_subtitle_button="false"
app:show_vr_button="false"
app:use_controller="false" />
<ImageView
android:id="@+id/image_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:id="@+id/loading_bg_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white_BFFFFFFF"
android:clickable="true"
android:focusable="true"
android:visibility="visible">
<LinearLayout
android:id="@+id/download_progress_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:lineSpacingExtra="4dp"
android:text="@string/wait_patiently"
android:textColor="@color/black"
android:textSize="16sp" />
<ProgressBar
android:id="@+id/horizontal_progressbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="270dp"
android:layout_height="4dp"
android:layout_marginTop="10dp"
android:max="100"
android:progress="100"
android:progressDrawable="@drawable/splash_progress_drawable" />
</LinearLayout>
<ProgressBar
android:id="@+id/normal_progress"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:indeterminateTint="@color/main_536DFF"
android:visibility="visible" />
<LinearLayout
android:id="@+id/retry_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:id="@+id/tv_load_fail"
android:layout_width="320dp"
android:layout_height="wrap_content"
android:gravity="center"
android:lineSpacingExtra="4dp"
android:text="@string/download_fail_network"
android:textColor="@color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_retry_button"
android:layout_width="120dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:background="@drawable/main_gradient_border"
android:gravity="center"
android:text="@string/retry"
android:textColor="@color/white"
android:textSize="17sp" />
</LinearLayout>
</RelativeLayout>
<TextView
android:id="@+id/tv_video_describe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="12dp"
android:padding="9dp"
android:text="Eternal hues dance in the void's embrace 🎗️"
android:textColor="@color/black"
android:textSize="15sp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:background="@drawable/common_set_wallpaper_bg"
app:layout_constraintBottom_toTopOf="@id/layout_btn"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<LinearLayout
android:id="@+id/layout_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="25dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/text_btn_set"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/common_set_wallpaper_bg"
android:gravity="center"
android:paddingStart="11dp"
android:paddingTop="9dp"
android:paddingEnd="11dp"
android:paddingBottom="9dp"
android:text="@string/set_wallpaper"
android:textColor="@color/black"
android:textSize="17sp" />
<FrameLayout
android:id="@+id/framelayout_favorite"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginStart="9dp"
android:layout_marginEnd="12dp"
android:background="@drawable/common_back_button_bg">
<ImageView
android:id="@+id/im_favorite"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_gravity="center"
android:src="@drawable/display_selector_favorite" />
</FrameLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/back_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="20dp"
android:background="@drawable/common_back_button_bg"
android:padding="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/common_back_button" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".favorite.FavoriteActivity">
<ImageView
android:id="@+id/image_back"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_marginStart="5dp"
android:layout_marginTop="10dp"
android:padding="10dp"
android:src="@drawable/common_back_button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/favorite"
android:textSize="17sp"
app:layout_constraintBottom_toBottomOf="@id/image_back"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/image_back" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/favorite_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
app:layout_constraintTop_toBottomOf="@id/image_back" />
<LinearLayout
android:id="@+id/layout_empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="115dp"
android:layout_height="115dp"
android:src="@drawable/empty_favorite" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="20dp"
android:text="@string/empty_text"
android:textColor="@color/gray"
android:textSize="15sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<TextView
android:id="@+id/text_view_name"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_marginStart="16dp"
android:gravity="center"
android:text="@string/app_name"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="45dp"
app:layout_constraintTop_toTopOf="@id/text_view_name"
android:padding="5dp"
android:id="@+id/favorite_btn"
android:src="@drawable/icon_favorite_selected"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/text_1_explore"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginStart="15dp"
android:layout_marginTop="20dp"
android:background="@drawable/main_tab_selector"
android:gravity="center"
android:text="@string/tab1_explore"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/text_2_shift"
app:layout_constraintTop_toBottomOf="@id/text_view_name" />
<TextView
android:id="@+id/text_2_shift"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginStart="10dp"
android:background="@drawable/main_tab_selector"
android:gravity="center"
android:text="@string/tab2_shift"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="@id/text_1_explore"
app:layout_constraintRight_toLeftOf="@id/text_3_trending"
app:layout_constraintTop_toTopOf="@id/text_1_explore" />
<TextView
android:id="@+id/text_3_trending"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="15dp"
android:background="@drawable/main_tab_selector"
android:gravity="center"
android:text="@string/tab3_trending"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="@id/text_2_shift"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/text_1_explore" />
<androidx.viewpager2.widget.ViewPager2
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/viewpager2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_1_explore" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SplashActivity">
<ImageView
android:id="@+id/image_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="200dp"
android:src="@mipmap/logo"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@string/app_name"
android:textColor="@color/black"
android:textSize="17sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_logo" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="18dp"
android:layout_marginStart="26dp"
android:layout_marginEnd="26dp"
android:layout_marginBottom="55dp"
android:max="100"
android:progress="1"
android:progressDrawable="@drawable/splash_progress_drawable"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/textview_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1%"
android:textColor="@color/white"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@id/progress_bar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/progress_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@mipmap/logo"
app:roundPercent="0.1" />
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainFragment">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler_view" />
</FrameLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="wrap_content"
android:layout_height="20dp"
android:id="@+id/view_placeholder"/>
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@mipmap/logo"
app:roundPercent="0.1" />
</LinearLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.DynamicCartoonWallpaper" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="main_sub_color">#70D7D6D6</color>
<color name="main_D4E7FF">#D4E7FF</color>
<color name="main_FFEFFC">#FFEFFC</color>
<color name="main_536DFF">#536DFF</color>
<color name="main_FF7FED">#FF7FED</color>
<color name="white_BFFFFFFF">#BFFFFFFF</color>
<color name="favorite_red">#CA1919</color>
<color name="gray">#C8C6C6</color>
</resources>

View File

@ -0,0 +1,17 @@
<resources>
<string name="app_name">Dynamic Cartoon Wallpaper</string>
<string name="splash_progress_text">%d%%</string>
<string name="tab1_explore">Explore</string>
<string name="tab2_shift">Shift</string>
<string name="tab3_trending">Trending</string>
<string name="download_fail_network">Loading failed, please check the network and try again</string>
<string name="download_fail_unknown">Some unknown errors occurred during playing, please try again later</string>
<string name="retry">Retry</string>
<string name="wait_patiently">Downloading video, please wait patiently</string>
<string name="set_wallpaper">Set Wallpaper</string>
<string name="favorite">Favorite</string>
<string name="empty_text">Go collect your favorite wallpapers</string>
<string name="remove_favorite_ok">Successfully canceled the collection</string>
<string name="remove_favorite_fail">Failed to cancel collection</string>
<string name="cache_file_no_lost">The resource is lost, please re-enter this page</string>
</resources>

View File

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.DynamicCartoonWallpaper" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.DynamicCartoonWallpaper" parent="Base.Theme.DynamicCartoonWallpaper" />
<!-- <style name="Theme.MyApp.Splash" parent="Theme.SplashScreen">-->
<!-- <item name="windowSplashScreenBackground">@color/white</item>-->
<!-- <item name="windowSplashScreenAnimatedIcon">@mipmap/logo</item>-->
<!-- <item name="postSplashScreenTheme">@style/Theme.DynamicCartoonWallpaper</item>-->
<!-- </style>-->
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 允许访问应用的内部存储文件 -->
<files-path name="internal_files" path="." />
<cache-path name="inner_app_cache" path="." />
<!-- 允许访问应用的外部存储文件(如果有的话) -->
<external-path name="external_files" path="." />
</paths>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_name"
android:thumbnail="@mipmap/logo">
</wallpaper>

View File

@ -0,0 +1,17 @@
package com.video.mobile.wallpaper
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

10
build.gradle.kts Normal file
View File

@ -0,0 +1,10 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
}
buildscript {
dependencies {
classpath("io.objectbox:objectbox-gradle-plugin:4.0.3")
}
}

23
gradle.properties Normal file
View File

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

26
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,26 @@
[versions]
agp = "8.10.1"
kotlin = "2.0.21"
coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
appcompat = "1.7.1"
material = "1.12.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Aug 13 18:04:51 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

6
keystore.properties Normal file
View File

@ -0,0 +1,6 @@
app_name=Dynamic Cartoon Wallpaper
package_name=com.video.mobile.wallpaper
keystoreFile=app/DynamicCartoonWallpaper.jks
key_alias=DynamicCartoonWallpaperkey0
key_store_password=DynamicCartoonWallpaper
key_password=DynamicCartoonWallpaper

24
settings.gradle.kts Normal file
View File

@ -0,0 +1,24 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Dynamic Cartoon Wallpaper"
include(":app")