V1.0.0(1) 完成版

This commit is contained in:
lihongwei 2025-05-14 16:06:25 +08:00
parent 8cea6d978d
commit 8142923eeb
61 changed files with 14226 additions and 35 deletions

BIN
app/LumaWallpaper.jks Normal file

Binary file not shown.

View File

@ -1,8 +1,13 @@
import java.text.SimpleDateFormat
import java.util.Date
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("com.google.devtools.ksp")
id("kotlin-parcelize")
}
val timestamp: String = SimpleDateFormat("MM_dd_HH_mm").format(Date())
android {
namespace = "com.wallpaper.lumawallpaper"
compileSdk = 35
@ -12,14 +17,21 @@ android {
minSdk = 23
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionName = "1.0.0"
setProperty(
"archivesBaseName",
"Luma Wallpaper_V" + versionName + "(${versionCode})_$timestamp"
)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
viewBinding = true
}
buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@ -45,4 +57,18 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation("com.github.bumptech.glide:glide:4.16.0")
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
implementation("androidx.room:room-runtime:2.7.1")
ksp("androidx.room:room-compiler:2.7.1")
implementation("androidx.room:room-ktx:2.7.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.0")
implementation ("com.squareup.okhttp3:okhttp:4.12.0")
implementation ("androidx.activity:activity-ktx:1.10.1")
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.9.0")
}

View File

@ -18,4 +18,17 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile
-keepclassmembers class com.wallpaper.lumawallpaper.App {
public static final java.lang.String DB_NAME;
public static final int DB_VERSION;
}
-keepclassmembers class * {
@androidx.room.Query <methods>;
}
-keep class com.wallpaper.lumawallpaper.database.AppDatabase { *; }
-keep class com.wallpaper.lumawallpaper.database.LumaEntity { *; }
-keep class com.wallpaper.lumawallpaper.database.LumaDao { *; }

View File

@ -2,7 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@ -13,7 +19,16 @@
android:theme="@style/Theme.LumaWallpaper"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".ui.activity.LumaActivity"
android:exported="false" />
<activity
android:name=".ui.activity.CategoryActivity"
android:exported="false" />
<activity
android:name=".ui.activity.MainActivity"
android:exported="false" />
<activity
android:name=".ui.activity.SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
package com.wallpaper.lumawallpaper
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.wallpaper.lumawallpaper.database.AppDatabase
import com.wallpaper.lumawallpaper.utils.JsonUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class App : Application() {
companion object {
@Volatile
private lateinit var instance: App
const val DB_VERSION = 1
const val DB_NAME = "luma_database"
private const val PREF_NAME = "luma_preferences"
private const val INIT_DATABASE = "InitDatabase"
@JvmStatic
fun getContext(): Context {
return instance.applicationContext
}
}
override fun onCreate() {
super.onCreate()
instance = this
val preferences: SharedPreferences = getSharedPreferences(PREF_NAME, MODE_PRIVATE)
val init = preferences.getBoolean(INIT_DATABASE, false)
if (!init) {
initDatabase()
preferences.edit() { putBoolean(INIT_DATABASE, true) }
}
}
private fun initDatabase() {
val lumaDao = AppDatabase.getDatabase(this).LumaDao()
val lumaEntityList = JsonUtils.parseJson("wallpaper.json")
CoroutineScope(Dispatchers.IO).launch {
lumaDao.insertAll(lumaEntityList)
}
}
}

View File

@ -1,20 +0,0 @@
package com.wallpaper.lumawallpaper
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
class MainActivity : AppCompatActivity() {
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
}
}
}

View File

@ -0,0 +1,6 @@
package com.wallpaper.lumawallpaper.call
data class DownloadCall(
val isSuccessful: Boolean,
val message: String
)

View File

@ -0,0 +1,30 @@
package com.wallpaper.lumawallpaper.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.wallpaper.lumawallpaper.App
@Database(entities = [LumaEntity::class], version = App.DB_VERSION, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun LumaDao(): LumaDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
App.DB_NAME
).build()
INSTANCE = instance
instance
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.wallpaper.lumawallpaper.database
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
interface LumaDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(lumaEntityList: List<LumaEntity>)
@Update
suspend fun update(lumaEntity: LumaEntity)
@Query("SELECT * FROM LumaEntity WHERE isFavorite = 1")
fun getFavoriteLumaEntities(): Flow<List<LumaEntity>>
@Query("SELECT * FROM LumaEntity WHERE id IN (SELECT MAX(id) FROM LumaEntity GROUP BY name)")
fun getUniqueLumaEntities(): Flow<List<LumaEntity>>
@Query("SELECT * FROM LumaEntity WHERE name = :name ORDER BY id DESC")
fun getLumaEntitiesByName(name: String): Flow<List<LumaEntity>>
@Query("SELECT isFavorite FROM LumaEntity WHERE source = :imagePath AND name = :name ")
fun getFavoriteStatus(imagePath: String, name: String): Flow<Boolean>
@Query("SELECT * FROM lumaentity WHERE id IN (SELECT MIN(id) FROM lumaentity GROUP BY name)")
suspend fun getMinIdLumaEntities(): List<LumaEntity>
@Query("SELECT * FROM lumaentity WHERE id IN (:lumaEntityIds) LIMIT :pageSize OFFSET (:page - 1) * :pageSize")
suspend fun getLumaEntitiesFromLumaMinIdPaged(
page: Int,
pageSize: Int,
lumaEntityIds: List<Int>
): List<LumaEntity>
@Query("SELECT COUNT(*) FROM lumaentity")
suspend fun getTotalLumaEntities(): Int
@Query("SELECT * FROM lumaentity WHERE name = :name LIMIT :pageSize OFFSET (:page - 1) * :pageSize")
suspend fun getLumaEntitiesByNamePaged(
name: String,
page: Int,
pageSize: Int
): List<LumaEntity>
@Query("SELECT * FROM lumaentity WHERE isFavorite = 1 LIMIT :pageSize OFFSET (:page - 1) * :pageSize")
suspend fun getFavoriteLumaEntitiesPaged(page: Int, pageSize: Int): List<LumaEntity>
@Query("SELECT * FROM lumaentity WHERE isFavorite = 1")
fun getFavoriteMagicEntities(): Flow<List<LumaEntity>>
@Query("SELECT COUNT(*) FROM lumaentity WHERE name = :name")
suspend fun getTotalLumaEntitiesByName(name: String): Int
}

View File

@ -0,0 +1,17 @@
package com.wallpaper.lumawallpaper.database
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Parcelize
@Entity
data class LumaEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String,
val original: String,
val previewThumb: String,
val source: String,
var isFavorite: Boolean
) : Parcelable

View File

@ -0,0 +1,91 @@
package com.wallpaper.lumawallpaper.ui.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.wallpaper.lumawallpaper.R
import com.wallpaper.lumawallpaper.databinding.ActivityCategoryBinding
import com.wallpaper.lumawallpaper.ui.adapter.LumaAdapter
import com.wallpaper.lumawallpaper.ui.viewmodel.LumaViewModel
import com.wallpaper.lumawallpaper.utils.ItemDecoration
class CategoryActivity : AppCompatActivity() {
private lateinit var binding: ActivityCategoryBinding
private lateinit var adapter: LumaAdapter
private lateinit var lumaViewModel: LumaViewModel
private lateinit var name: String
private val layoutManager by lazy { GridLayoutManager(this, 2) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCategoryBinding.inflate(layoutInflater)
setContentView(binding.root)
enableEdgeToEdge()
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
}
initData()
initEvent()
}
private fun initData() {
name = intent.getStringExtra("name").toString()
lumaViewModel = ViewModelProvider(this)[LumaViewModel::class.java]
binding.recyclerView.layoutManager = layoutManager
adapter = LumaAdapter(
lumaViewModel,
this,
mutableListOf(),
this,
1
)
binding.recyclerView.adapter = adapter
val itemDecoration = ItemDecoration(20, 15, 20)
binding.recyclerView.addItemDecoration(itemDecoration)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val isLoading = lumaViewModel.isLoading.value ?: false
val hasMore = lumaViewModel.hasMore.value ?: true
val threshold = 5
if (!isLoading && hasMore && (visibleItemCount + firstVisibleItemPosition + threshold) >= totalItemCount && firstVisibleItemPosition >= 0) {
lumaViewModel.loadNextPage(name)
}
}
})
lumaViewModel.lumaListLiveData.observe(this) { newList ->
val isFirstLoad = adapter.itemCount == 0
adapter.updateData(newList, !isFirstLoad)
}
lumaViewModel.hasMore.observe(this) { hasMore ->
adapter.setHasMoreData(hasMore)
}
lumaViewModel.loadInitialDataByName(name)
}
private fun initEvent() {
binding.back.setOnClickListener {
finish()
}
}
}

View File

@ -0,0 +1,208 @@
package com.wallpaper.lumawallpaper.ui.activity
import android.annotation.SuppressLint
import android.app.WallpaperManager
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.wallpaper.lumawallpaper.R
import com.wallpaper.lumawallpaper.databinding.ActivityLumaBinding
import com.wallpaper.lumawallpaper.database.LumaEntity
import com.wallpaper.lumawallpaper.ui.viewmodel.LumaViewModel
import com.wallpaper.lumawallpaper.utils.WallpaperUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LumaActivity : AppCompatActivity() {
private lateinit var lumaBinding: ActivityLumaBinding
private lateinit var imageSourceUrl: String
private lateinit var originalImageUrl: String
private lateinit var lumaEntity: LumaEntity
private lateinit var imageTitle: String
private lateinit var loadedBitmap: Bitmap
private lateinit var utilHelper: WallpaperUtil
private lateinit var wallpaperService: WallpaperManager
private lateinit var viewModel: LumaViewModel
@RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
lumaBinding = ActivityLumaBinding.inflate(layoutInflater)
setContentView(lumaBinding.root)
configureWindowInsets()
initializeComponents()
registerEventListeners()
}
private fun configureWindowInsets() {
ViewCompat.setOnApplyWindowInsetsListener(lumaBinding.main) { v, insets ->
val navigationBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
v.setPadding(0, 0, 0, navigationBars.bottom)
insets
}
}
private fun initializeComponents() {
val receivedData: LumaEntity? = intent.getParcelableExtra("lumaEntity")
if (receivedData == null) {
finish()
return
}
lumaEntity = receivedData
imageSourceUrl = lumaEntity.source
originalImageUrl = lumaEntity.original
imageTitle = lumaEntity.name
viewModel = ViewModelProvider(this)[LumaViewModel::class.java]
utilHelper = WallpaperUtil(lumaBinding.progressBar, lumaBinding.view)
wallpaperService = WallpaperManager.getInstance(this)
}
@RequiresApi(Build.VERSION_CODES.N)
private fun registerEventListeners() {
showProgress()
lumaBinding.back.setOnClickListener { finishActivity() }
lumaBinding.like.setOnClickListener {
toggleFavoriteStatus()
refreshFavoriteIcon()
}
lumaBinding.set.setOnClickListener { showWallpaperDialog() }
lumaBinding.downPicture.setOnClickListener {
showProgress()
utilHelper.downloadWallpaper(this, originalImageUrl)
}
loadImageContent()
monitorFavoriteChanges()
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == WallpaperUtil.PERMISSION_REQUEST_CODE && grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
utilHelper.downloadWallpaper(this, originalImageUrl)
} else {
Toast.makeText(this, "Permission denied for storage", Toast.LENGTH_SHORT).show()
}
}
@SuppressLint("InflateParams")
@RequiresApi(Build.VERSION_CODES.N)
private fun showWallpaperDialog() {
val bottomDialog = BottomSheetDialog(this)
val dialogView = LayoutInflater.from(this).inflate(R.layout.set_dialog, null)
dialogView.findViewById<View>(R.id.both).setOnClickListener {
configureWallpaper(loadedBitmap, WallpaperManager.FLAG_SYSTEM or WallpaperManager.FLAG_LOCK)
bottomDialog.dismiss()
}
dialogView.findViewById<View>(R.id.lock).setOnClickListener {
configureWallpaper(loadedBitmap, WallpaperManager.FLAG_LOCK)
bottomDialog.dismiss()
}
dialogView.findViewById<View>(R.id.desktop).setOnClickListener {
configureWallpaper(loadedBitmap, WallpaperManager.FLAG_SYSTEM)
bottomDialog.dismiss()
}
bottomDialog.setContentView(dialogView)
bottomDialog.show()
}
@RequiresApi(Build.VERSION_CODES.N)
private fun configureWallpaper(bitmap: Bitmap, flags: Int) {
showProgress()
lifecycleScope.launch {
try {
setWallpaperImage(bitmap, flags)
withContext(Dispatchers.Main) {
hideProgress()
Toast.makeText(this@LumaActivity, "Wallpaper applied", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
hideProgress()
Toast.makeText(this@LumaActivity, "Failed to apply wallpaper", Toast.LENGTH_SHORT).show()
}
}
}
}
@RequiresApi(Build.VERSION_CODES.N)
private suspend fun setWallpaperImage(bitmap: Bitmap, flags: Int) {
withContext(Dispatchers.IO) {
wallpaperService.setBitmap(bitmap, null, true, flags)
}
}
private fun loadImageContent() {
Glide.with(this)
.asBitmap()
.load(imageSourceUrl)
.error(R.mipmap.placeholder)
.override(1080, 1920)
.centerInside()
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
lumaBinding.imageView.setImageBitmap(resource)
loadedBitmap = resource
hideProgress()
}
override fun onLoadCleared(placeholder: Drawable?) {
lumaBinding.imageView.setImageDrawable(placeholder)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
lumaBinding.imageView.setImageDrawable(errorDrawable)
hideProgress()
}
})
}
private fun monitorFavoriteChanges() {
lifecycleScope.launch {
viewModel.getFavoriteStatus(imageSourceUrl, imageTitle).collect { refreshFavoriteIcon() }
}
}
private fun toggleFavoriteStatus() {
lumaEntity.isFavorite = !lumaEntity.isFavorite
viewModel.update(lumaEntity)
}
private fun refreshFavoriteIcon() {
lumaBinding.like.setImageResource(
if (lumaEntity.isFavorite) R.drawable.like else R.drawable.un_like
)
}
private fun showProgress() {
lumaBinding.progressBar.visibility = View.VISIBLE
lumaBinding.view.visibility = View.VISIBLE
}
private fun hideProgress() {
lumaBinding.progressBar.visibility = View.GONE
lumaBinding.view.visibility = View.GONE
}
private fun finishActivity() {
finish()
}
}

View File

@ -0,0 +1,112 @@
package com.wallpaper.lumawallpaper.ui.activity
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.google.android.material.tabs.TabLayoutMediator
import com.wallpaper.lumawallpaper.R
import com.wallpaper.lumawallpaper.databinding.ActivityMainBinding
import com.wallpaper.lumawallpaper.databinding.MainTabBinding
import com.wallpaper.lumawallpaper.ui.adapter.MainAdapter
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.getRoot())
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v: View, insets: WindowInsetsCompat ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
initData()
initEvent()
}
private fun initData() {
val adapter = MainAdapter(this)
binding.mainViewpager2.setAdapter(adapter)
}
private fun initEvent() {
TabLayoutMediator(
binding.mainTabLayout,
binding.mainViewpager2
) { tab: TabLayout.Tab, position: Int ->
val mainTabBinding: MainTabBinding =
MainTabBinding.inflate(LayoutInflater.from(this))
tab.setCustomView(mainTabBinding.getRoot())
setTab(mainTabBinding, position)
}.attach()
binding.mainTabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
updateTab(tab, true)
}
override fun onTabUnselected(tab: TabLayout.Tab) {
updateTab(tab, false)
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
fun updateTab(tab: TabLayout.Tab, isSelected: Boolean) {
if (tab.customView != null) {
val mainTabBinding: MainTabBinding =
MainTabBinding.bind(tab.customView!!)
val iconResId = getIconResource(tab.position, isSelected)
mainTabBinding.image.setImageResource(iconResId)
val textColor = if (isSelected) R.color.white else R.color.black
mainTabBinding.text.setTextColor(
ContextCompat.getColor(
this@MainActivity,
textColor
)
)
}
}
})
}
@SuppressLint("SetTextI18n")
private fun setTab(mainTabBinding: MainTabBinding, position: Int) {
var iconResId = getIconResource(position, false)
var textColorResId = R.color.black
when (position) {
0 -> {
mainTabBinding.text.text = "Home"
iconResId = R.drawable.home
textColorResId = R.color.white
}
1 -> mainTabBinding.text.text = "Collection"
}
mainTabBinding.image.setImageResource(iconResId)
mainTabBinding.text.setTextColor(ContextCompat.getColor(this, textColorResId))
}
private fun getIconResource(position: Int, isSelected: Boolean): Int {
if (position == 1) {
return if (isSelected) R.drawable.collection else R.drawable.un_collection
}
return if (isSelected) R.drawable.home else R.drawable.un_home
}
}

View File

@ -0,0 +1,75 @@
package com.wallpaper.lumawallpaper.ui.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.wallpaper.lumawallpaper.R
import com.wallpaper.lumawallpaper.databinding.ActivitySplashBinding
@SuppressLint("CustomSplashScreen")
class SplashActivity : AppCompatActivity() {
private lateinit var binding: ActivitySplashBinding
private lateinit var countDownTimer: CountDownTimer
companion object {
private const val TOTAL_TIME: Long = 1000
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
binding = ActivitySplashBinding.inflate(layoutInflater)
setContentView(binding.getRoot())
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main)
) { v: View, insets: WindowInsetsCompat ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
Glide.with(this)
.load(R.mipmap.placeholder)
.transform(CenterCrop(), RoundedCorners(32))
.into(binding.image)
countDownTimer = object : CountDownTimer(TOTAL_TIME, 50) {
override fun onTick(millisUntilFinished: Long) {
val percentage = (100 - millisUntilFinished.toFloat() / TOTAL_TIME * 100).toInt()
binding.progressBar.progress = percentage
}
override fun onFinish() {
startMain()
}
}.start()
countDownTimer.start()
}
private fun startMain() {
binding.progressBar.progress = 100
val intent = Intent(
this@SplashActivity,
MainActivity::class.java
)
startActivity(intent)
finish()
}
override fun onDestroy() {
super.onDestroy()
countDownTimer.cancel()
}
}

View File

@ -0,0 +1,136 @@
package com.wallpaper.lumawallpaper.ui.adapter
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.wallpaper.lumawallpaper.R
import com.wallpaper.lumawallpaper.database.LumaEntity
import com.wallpaper.lumawallpaper.ui.activity.LumaActivity
import com.wallpaper.lumawallpaper.ui.activity.CategoryActivity
import com.wallpaper.lumawallpaper.ui.viewmodel.LumaViewModel
class LumaAdapter(
private val lumaViewModel: LumaViewModel,
private val context: Context,
private var lumaList: MutableList<LumaEntity> = mutableListOf(),
private val activity: Activity,
private val type: Int
) :
RecyclerView.Adapter<LumaAdapter.ViewHolder>() {
private var isLoadingMore = false
private var hasMoreData = true
fun updateData(newList: List<LumaEntity>, isLoadMore: Boolean = false) {
if (isLoadMore) {
val previousSize = lumaList.size
lumaList.addAll(newList)
notifyItemRangeInserted(previousSize, newList.size)
isLoadingMore = false
} else {
lumaList.clear()
lumaList.addAll(newList)
notifyDataSetChanged()
}
}
fun setLoadingMore(loading: Boolean) {
isLoadingMore = loading
}
fun setHasMoreData(hasMore: Boolean) {
hasMoreData = hasMore
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View =
LayoutInflater.from(context).inflate(R.layout.item_luma, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val lumaEntity: LumaEntity = lumaList[position]
holder.bind(lumaEntity)
}
override fun getItemCount(): Int {
return lumaList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val imageView: ImageView = itemView.findViewById(R.id.item_image_view)
private val favorite: ImageView = itemView.findViewById(R.id.item_favorite)
private val title: TextView = itemView.findViewById(R.id.item_title)
fun bind(lumaEntity: LumaEntity) {
val imagePath: String = lumaEntity.previewThumb
loadImage(imagePath)
if (type == 0) {
title.text = lumaEntity.name
favorite.visibility = View.GONE
} else {
title.visibility = View.GONE
setFavoriteButton(lumaEntity)
}
setClickListeners(lumaEntity)
}
private fun loadImage(imagePath: String) {
Glide.with(context)
.load(imagePath)
.thumbnail(
Glide.with(context)
.load(imagePath)
.override(100, 100)
.transform(CenterCrop(), RoundedCorners(32))
)
.transform(CenterCrop(), RoundedCorners(32))
.error(R.mipmap.placeholder)
.placeholder(R.mipmap.placeholder)
.into(imageView)
}
private fun setFavoriteButton(lumaEntity: LumaEntity) {
favorite.setImageResource(if (lumaEntity.isFavorite) R.drawable.like else R.drawable.un_like)
}
private fun setClickListeners(lumaEntity: LumaEntity) {
imageView.setOnClickListener {
val intent: Intent
if (type == 0) {
intent =
Intent(activity, CategoryActivity::class.java)
intent.putExtra("name", lumaEntity.name)
} else {
intent = Intent(activity, LumaActivity::class.java)
intent.putExtra("lumaEntity", lumaEntity)
}
activity.startActivity(intent)
}
favorite.setOnClickListener { toggleFavorite(lumaEntity) }
}
private fun toggleFavorite(lumaEntity: LumaEntity) {
val newStatus: Boolean = !lumaEntity.isFavorite
lumaEntity.isFavorite = newStatus
updateImageInDatabase(lumaEntity)
notifyItemChanged(adapterPosition)
}
private fun updateImageInDatabase(lumaEntity: LumaEntity) {
lumaViewModel.update(lumaEntity)
}
}
}

View File

@ -0,0 +1,24 @@
package com.wallpaper.lumawallpaper.ui.adapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.wallpaper.lumawallpaper.ui.fragment.CategoryFragment
import com.wallpaper.lumawallpaper.ui.fragment.FavoriteFragment
class MainAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
private val fragmentList: MutableList<Fragment> = ArrayList()
init {
fragmentList.add(CategoryFragment())
fragmentList.add(FavoriteFragment())
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
override fun getItemCount(): Int {
return fragmentList.size
}
}

View File

@ -0,0 +1,79 @@
package com.wallpaper.lumawallpaper.ui.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.wallpaper.lumawallpaper.databinding.FragmentCategoryBinding
import com.wallpaper.lumawallpaper.ui.adapter.LumaAdapter
import com.wallpaper.lumawallpaper.ui.viewmodel.LumaViewModel
import com.wallpaper.lumawallpaper.utils.ItemDecoration
class CategoryFragment : Fragment() {
private lateinit var binding: FragmentCategoryBinding
private lateinit var adapter: LumaAdapter
private lateinit var lumaViewModel: LumaViewModel
private val layoutManager by lazy { GridLayoutManager(context, 2) }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCategoryBinding.inflate(inflater, container, false)
initData()
initEvent()
return binding.getRoot()
}
private fun initData() {
lumaViewModel = ViewModelProvider(this)[LumaViewModel::class.java]
binding.recyclerView.layoutManager = layoutManager
adapter = LumaAdapter(
lumaViewModel,
requireContext(),
mutableListOf(),
requireActivity(),
0
)
binding.recyclerView.adapter = adapter
val itemDecoration = ItemDecoration(20, 15, 20)
binding.recyclerView.addItemDecoration(itemDecoration)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val isLoading = lumaViewModel.isLoading.value ?: false
val hasMore = lumaViewModel.hasMore.value ?: true
val threshold = 5
if (!isLoading && hasMore && (visibleItemCount + firstVisibleItemPosition + threshold) >= totalItemCount && firstVisibleItemPosition >= 0) {
lumaViewModel.loadNextPage()
}
}
})
lumaViewModel.lumaListLiveData.observe(viewLifecycleOwner) { newList ->
val isFirstLoad = adapter.itemCount == 0
adapter.updateData(newList, !isFirstLoad)
}
}
private fun initEvent() {
loadCategory()
}
private fun loadCategory() {
lumaViewModel.loadInitialData()
}
}

View File

@ -0,0 +1,98 @@
package com.wallpaper.lumawallpaper.ui.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.wallpaper.lumawallpaper.databinding.FragmentFavoriteBinding
import com.wallpaper.lumawallpaper.ui.adapter.LumaAdapter
import com.wallpaper.lumawallpaper.ui.viewmodel.LumaViewModel
import com.wallpaper.lumawallpaper.utils.ItemDecoration
import kotlinx.coroutines.launch
class FavoriteFragment : Fragment() {
private lateinit var binding: FragmentFavoriteBinding
private lateinit var adapter: LumaAdapter
private lateinit var lumaViewModel: LumaViewModel
private val layoutManager by lazy { GridLayoutManager(context, 2) }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFavoriteBinding.inflate(inflater, container, false)
initData()
return binding.getRoot()
}
private fun initData() {
lumaViewModel = ViewModelProvider(this)[LumaViewModel::class.java]
binding.recyclerView.layoutManager = layoutManager
adapter = LumaAdapter(
lumaViewModel,
requireContext(),
mutableListOf(),
requireActivity(),
1
)
binding.recyclerView.adapter = adapter
val itemDecoration = ItemDecoration(20, 15, 20)
binding.recyclerView.addItemDecoration(itemDecoration)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val isLoading = lumaViewModel.isLoading.value ?: false
val hasMore = lumaViewModel.hasMore.value ?: true
val threshold = 5
if (!isLoading && hasMore && (visibleItemCount + firstVisibleItemPosition + threshold) >= totalItemCount && firstVisibleItemPosition >= 0) {
lumaViewModel.loadNextPage(isFavorite = true)
}
}
})
lumaViewModel.lumaListLiveData.observe(viewLifecycleOwner) { newList ->
val isFirstLoad = adapter.itemCount == 0
adapter.updateData(newList, !isFirstLoad)
if (newList.isEmpty() && isFirstLoad) {
binding.tip.visibility = View.VISIBLE
} else {
binding.tip.visibility = View.GONE
}
}
lumaViewModel.hasMore.observe(viewLifecycleOwner) { hasMore ->
adapter.setHasMoreData(hasMore)
}
lumaViewModel.loadInitialFavoriteData()
loadFavorite()
}
private fun loadFavorite() {
lifecycleScope.launch {
lumaViewModel.favoriteMagicEntities.collect { lumaEntity ->
if (lumaEntity.isEmpty()) {
binding.tip.visibility = View.VISIBLE
} else {
binding.tip.visibility = View.GONE
}
adapter.updateData(lumaEntity)
}
}
}
}

View File

@ -0,0 +1,128 @@
package com.wallpaper.lumawallpaper.ui.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.wallpaper.lumawallpaper.database.AppDatabase
import com.wallpaper.lumawallpaper.database.LumaDao
import com.wallpaper.lumawallpaper.database.LumaEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LumaViewModel(application: Application) : AndroidViewModel(application) {
private val lumaDao: LumaDao = AppDatabase.getDatabase(application).LumaDao()
private val _lumaListLiveData = MutableLiveData<MutableList<LumaEntity>>()
val lumaListLiveData: LiveData<MutableList<LumaEntity>> = _lumaListLiveData
val favoriteMagicEntities: Flow<List<LumaEntity>> = lumaDao.getFavoriteLumaEntities()
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
private val _hasMore = MutableLiveData<Boolean>(true)
val hasMore: LiveData<Boolean> = _hasMore
private var currentPage = 1
private val pageSize = 20
private var currentQueryName: String? = null
private var isCurrentlyLoadingFavorites = false
init {
_lumaListLiveData.value = mutableListOf()
}
fun loadInitialData() {
isCurrentlyLoadingFavorites = false
currentPage = 1
_hasMore.value = true
loadMagicEntitiesPaged(currentPage, pageSize)
}
fun loadInitialFavoriteData() {
isCurrentlyLoadingFavorites = true
currentPage = 1
_hasMore.value = true
loadFavoriteMagicEntitiesPaged(currentPage, pageSize)
}
fun loadInitialDataByName(name: String) {
currentQueryName = name
currentPage = 1
_hasMore.value = true
loadMagicEntitiesByNamePaged(name, currentPage, pageSize)
}
fun loadNextPage(name: String? = null, isFavorite: Boolean = false) {
if (_isLoading.value == true || _hasMore.value == false) {
return
}
currentPage++
_isLoading.value = true
if (isFavorite) {
loadFavoriteMagicEntitiesPaged(currentPage, pageSize)
} else if (!name.isNullOrEmpty()) {
loadMagicEntitiesByNamePaged(name, currentPage, pageSize)
} else {
loadMagicEntitiesPaged(currentPage, pageSize)
}
}
private fun loadMagicEntitiesPaged(page: Int, size: Int) {
viewModelScope.launch(Dispatchers.IO) {
val newData = getLumaEntitiesFromUniqueLumaPaged(page = page, pageSize = size)
updateLiveData(newData)
}
}
private suspend fun getLumaEntitiesFromUniqueLumaPaged(page: Int, pageSize: Int): List<LumaEntity> {
val minIdLumaEntities = lumaDao.getMinIdLumaEntities()
val lumaEntityIds = minIdLumaEntities.map { it.id }
return lumaDao.getLumaEntitiesFromLumaMinIdPaged(page, pageSize, lumaEntityIds)
}
private fun loadMagicEntitiesByNamePaged(name: String, page: Int, size: Int) {
viewModelScope.launch(Dispatchers.IO) {
val newData =
lumaDao.getLumaEntitiesByNamePaged(name = name, page = page, pageSize = size)
updateLiveData(newData)
}
}
private fun loadFavoriteMagicEntitiesPaged(page: Int, size: Int) {
viewModelScope.launch(Dispatchers.IO) {
val newData = lumaDao.getFavoriteLumaEntitiesPaged(page = page, pageSize = size)
updateLiveData(newData)
}
}
private suspend fun updateLiveData(newData: List<LumaEntity>) {
withContext(Dispatchers.Main) {
val currentList = _lumaListLiveData.value ?: mutableListOf()
currentList.addAll(newData)
_lumaListLiveData.value = currentList
_isLoading.value = false
if (newData.size < pageSize) {
_hasMore.value = false
}
}
}
fun getFavoriteStatus(imagePath: String, name: String): Flow<Boolean> {
return lumaDao.getFavoriteStatus(imagePath, name)
}
fun update(lumaEntity: LumaEntity) {
viewModelScope.launch {
lumaDao.update(lumaEntity)
}
}
}

View File

@ -0,0 +1,76 @@
package com.wallpaper.lumawallpaper.utils;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.wallpaper.lumawallpaper.App;
public class ItemDecoration extends RecyclerView.ItemDecoration {
private final int verticalSpacing;
private final int horizontalSpacing;
private final int extraSpacing;
public ItemDecoration(int verticalSpacingDp, int horizontalSpacingDp, int extraSpacingDp) {
this.verticalSpacing = Math.round(dpToPx(verticalSpacingDp));
this.horizontalSpacing = Math.round(dpToPx(horizontalSpacingDp));
this.extraSpacing = Math.round(dpToPx(extraSpacingDp));
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int spanCount = 1;
int spanSize = 1;
int spanIndex = 0;
int position = parent.getChildAdapterPosition(view);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
spanCount = staggeredGridLayoutManager.getSpanCount();
spanSize = layoutParams.isFullSpan() ? spanCount : 1;
spanIndex = layoutParams.getSpanIndex();
} else if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
GridLayoutManager.LayoutParams layoutParams = (GridLayoutManager.LayoutParams) view.getLayoutParams();
spanCount = gridLayoutManager.getSpanCount();
spanSize = gridLayoutManager.getSpanSizeLookup().getSpanSize(position);
spanIndex = layoutParams.getSpanIndex();
} else if (layoutManager instanceof LinearLayoutManager) {
outRect.left = horizontalSpacing;
outRect.right = horizontalSpacing;
outRect.bottom = verticalSpacing;
return;
}
if (spanSize == spanCount) {
outRect.left = horizontalSpacing + extraSpacing;
outRect.right = horizontalSpacing + extraSpacing;
} else {
int totalSpacing = (horizontalSpacing * (spanCount + 1) + extraSpacing * 2) / spanCount;
int leftSpacing = horizontalSpacing * (spanIndex + 1) - totalSpacing * spanIndex + extraSpacing;
int rightSpacing = totalSpacing - leftSpacing;
outRect.left = leftSpacing;
outRect.right = rightSpacing;
}
outRect.bottom = verticalSpacing;
if (position < spanCount) {
outRect.top = verticalSpacing;
}
}
public static float dpToPx(float dpValue) {
float density = App.getContext().getResources().getDisplayMetrics().density;
return dpValue * density + 0.5f;
}
}

View File

@ -0,0 +1,71 @@
package com.wallpaper.lumawallpaper.utils
import com.wallpaper.lumawallpaper.App
import com.wallpaper.lumawallpaper.database.LumaEntity
import org.json.JSONArray
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
object JsonUtils {
private fun loadJSONFromAsset(fileName: String): String {
val jsonString = StringBuilder()
try {
App.getContext().assets.open(fileName).use { inputStream ->
BufferedReader(InputStreamReader(inputStream)).use { reader ->
reader.forEachLine { line ->
jsonString.append(line)
}
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return jsonString.toString()
}
fun parseJson(fileName: String): List<LumaEntity> {
val audioDataList = mutableListOf<LumaEntity>()
try {
val jsonString = loadJSONFromAsset(fileName)
if (jsonString.isEmpty()) {
throw IllegalArgumentException("JSON file is empty or invalid.")
}
val jsonArray = JSONArray(jsonString)
for (i in 0 until jsonArray.length()) {
val categoryObject = jsonArray.getJSONObject(i)
val name = categoryObject.getString("name")
val listArray = categoryObject.getJSONArray("data")
for (j in 0 until listArray.length()) {
val itemObject = listArray.getJSONObject(j)
val original = itemObject.getString("original")
val previewThumb = itemObject.getString("previewThumb")
val source = itemObject.getString("source")
audioDataList.add(
LumaEntity(
name = name,
original = original,
previewThumb = previewThumb,
source = source,
isFavorite = false
)
)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return audioDataList
}
}

View File

@ -0,0 +1,105 @@
package com.wallpaper.lumawallpaper.utils
import android.Manifest
import android.app.Activity
import android.content.ContentValues
import android.content.pm.PackageManager
import android.os.Build
import android.provider.MediaStore
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.wallpaper.lumawallpaper.call.DownloadCall
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
import java.util.concurrent.TimeUnit
class WallpaperUtil(
private val loadingBar: ProgressBar,
private val overlayView: View
) {
companion object {
const val PERMISSION_REQUEST_CODE = 456
}
fun downloadWallpaper(context: Activity, imageUrl: String) {
if (ContextCompat.checkSelfPermission(
context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
) {
startDownload(context, imageUrl)
} else {
ActivityCompat.requestPermissions(
context, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE)
}
}
private fun startDownload(context: Activity, url: String) {
CoroutineScope(Dispatchers.Main).launch {
try {
showLoading()
val result = withContext(Dispatchers.IO) { downloadToGallery(context, url) }
Toast.makeText(context, result.message, Toast.LENGTH_SHORT).show()
} finally {
hideLoading()
}
}
}
private fun downloadToGallery(context: Activity, url: String): DownloadCall {
val fileName = "wallpaper_${System.currentTimeMillis()}.jpg"
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.Media.IS_PENDING, 1)
}
}
val collectionUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val imageUri = context.contentResolver.insert(collectionUri, contentValues)
?: return DownloadCall(false, "Failed to initialize storage")
return try {
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val request = Request.Builder().url(url).build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
response.body?.byteStream()?.use { inputStream ->
context.contentResolver.openOutputStream(imageUri)?.use { outputStream ->
inputStream.copyTo(outputStream)
} ?: throw IOException("Failed to open output stream")
} ?: throw IOException("Empty response body")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.clear()
contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
context.contentResolver.update(imageUri, contentValues, null, null)
}
DownloadCall(true, "Image saved to gallery")
} catch (e: Exception) {
context.contentResolver.delete(imageUri, null, null)
DownloadCall(false, "Failed to save image: ${e.localizedMessage}")
}
}
private fun showLoading() {
loadingBar.visibility = View.VISIBLE
overlayView.visibility = View.VISIBLE
}
private fun hideLoading() {
loadingBar.visibility = View.GONE
overlayView.visibility = View.GONE
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/white"
android:pathData="M707.5,62.1c-17.4,0 -33.8,6.5 -46.1,18.2L279.6,431.9c-18,17.2 -28.3,40.6 -28.3,65.4s10.2,48.3 28.6,65.9l381.4,350.4c24.6,23.5 67.6,23.5 92.2,0 12.3,-11.7 19.1,-27.3 19.1,-43.8s-6.8,-32.2 -19.1,-43.9l-278,-262.6c-18.5,-17.7 -28.7,-41 -28.7,-65.9s10.2,-48.2 28.6,-65.8l278.1,-263.6c12.3,-11.7 19,-27.3 19,-43.8s-6.8,-32.2 -19.1,-43.9a66.3,66.3 0,0 0,-46.1 -18.2zM707.5,993.8a132.7,132.7 0,0 1,-92.2 -36.4l-52.3,-49.8 -328.5,-300.1c-31.3,-29.8 -48.2,-68.7 -48.2,-110.2s16.9,-80.4 47.7,-109.7L615.7,36a132.6,132.6 0,0 1,91.7 -36c34.8,0 67.6,12.9 92.1,36.4 24.6,23.4 38.2,54.6 38.2,87.7s-13.6,64.4 -38.2,87.8l-278.1,263.6a30.1,30.1 0,0 0,0.1 43.8l278,262.6c24.7,23.6 38.3,54.8 38.3,87.9s-13.6,64.3 -38.2,87.7a132.9,132.9 0,0 1,-92.1 36.4z"/>
</vector>

View File

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#009688" />
<corners android:radius="100dp" />
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/white"
android:pathData="M914.2,189.4L577.3,15.8a143.7,143.7 0,0 0,-131 0L109.4,190.4A135.3,135.3 0,0 0,33.4 314.7v393.5a143.6,143.6 0,0 0,76 125.2l339,174.6a137,137 0,0 0,65.5 15.9,143.1 143.1,0 0,0 65.5,-15.9l334.8,-173.6a140.8,140.8 0,0 0,76 -125.2L990.2,313.6a139.9,139.9 0,0 0,-76 -124.3zM480.1,77.9a71.4,71.4 0,0 1,31.7 -8.4,64 64,0 0,1 31.7,7.4l336.9,173.6a95.8,95.8 0,0 1,13.7 8.4l-383.4,197.8L127.4,262a30.3,30.3 0,0 1,13.7 -8.4zM143.2,770.1a69.3,69.3 0,0 1,-39.1 -63.1L104.1,327.2l141.5,72.6v158.9a34.3,34.3 0,0 0,19.1 31.5l25.5,12.7a10.1,10.1 0,0 0,7.4 2.1,17.1 17.1,0 0,0 17.9,-17.9L315.6,435.6l159.3,81v426.1zM880.4,770.1l-333.7,173.6v-427l372.8,-189.4v379.8a69.3,69.3 0,0 1,-39.1 63.1z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M768,768q0,-14.9 -10.9,-25.7t-25.7,-10.9 -25.7,10.9 -10.9,25.7 10.9,25.7 25.7,10.9 25.7,-10.9 10.9,-25.7zM914.3,768q0,-14.9 -10.9,-25.7t-25.7,-10.9 -25.7,10.9 -10.9,25.7 10.9,25.7 25.7,10.9 25.7,-10.9 10.9,-25.7zM987.4,640v182.9q0,22.9 -16,38.9t-38.9,16L91.4,877.7q-22.9,0 -38.9,-16t-16,-38.9v-182.9q0,-22.9 16,-38.9t38.9,-16h265.7l77.1,77.7q33.1,32 77.7,32t77.7,-32l77.7,-77.7h265.1q22.9,0 38.9,16t16,38.9zM801.7,314.9q9.7,23.4 -8,40l-256,256q-10.3,10.9 -25.7,10.9t-25.7,-10.9L230.3,354.9q-17.7,-16.6 -8,-40 9.7,-22.3 33.7,-22.3h146.3L402.3,36.6q0,-14.9 10.9,-25.7t25.7,-10.9h146.3q14.9,0 25.7,10.9t10.9,25.7v256h146.3q24,0 33.7,22.3z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/white"
android:pathData="M875.2,572.5V874.9q0,16.4 -12,28.4T834.9,915.2H592.9V673.3H431.7v241.9H189.8q-16.4,0 -28.4,-12t-12,-28.4V572.5q0,-0.6 0.3,-1.9t0.3,-1.9l362.3,-298.6 362.3,298.6q0.6,1.3 0.6,3.8zM1015.7,529l-39.1,46.6q-5,5.7 -13.2,6.9h-1.9q-8.2,0 -13.2,-4.4L512.3,214.6 76.3,578.1q-7.5,5 -15.1,4.4 -8.2,-1.3 -13.2,-6.9l-39.1,-46.6q-5,-6.3 -4.4,-14.8t6.9,-13.5l453,-377.4q20.1,-16.4 47.9,-16.4t47.9,16.4L713.8,251.8V129q0,-8.8 5.7,-14.5t14.5,-5.7h121q8.8,0 14.5,5.7t5.7,14.5v257.1l138,114.7q6.3,5 6.9,13.5t-4.4,14.8z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M725.3,192c-89.6,0 -168.5,44.8 -213.3,115.2C467.2,236.8 388.3,192 298.7,192 157.9,192 42.7,307.2 42.7,448c0,253.9 469.3,512 469.3,512s469.3,-256 469.3,-512c0,-140.8 -115.2,-256 -256,-256z"
android:fillColor="#F44336"/>
</vector>

View File

@ -0,0 +1,30 @@
<!-- res/drawable/seekbar_progress_drawable.xml -->
<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="#D3D3D3" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="5dp" />
<solid android:color="#FFD700" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="5dp" />
<gradient
android:startColor="#4891FF"
android:endColor="#6CE89E"
android:angle="0" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,9 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="45"
android:endColor="#8BC34A"
android:startColor="#E8ECEF"
android:type="linear"
android:useLevel="false" />
<corners android:radius="16sp" />
</shape>

View File

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="12dp" />
<solid android:color="#33FFFFFF" />
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M783.9,401.3a128,128 0,0 1,128 128v256a128,128 0,0 1,-128 128h-256a128,128 0,0 1,-128 -128v-256a128,128 0,0 1,128 -128zM603.6,112.1a106.7,106.7 0,0 1,106.6 102l0.1,4.6v64a42.7,42.7 0,0 1,-85.2 3.2l-0.1,-3.2v-64a21.3,21.3 0,0 0,-18.9 -21.2l-2.5,-0.1L553.8,197.4l-12.3,65.4c-4.4,29.2 -27.9,52.1 -57.5,54l-3.9,0.1h-142c-30,0 -54.6,-21.7 -60.4,-49l-0.6,-3.6 -12.6,-66.9h-44.8a21.3,21.3 0,0 0,-21.2 18.8l-0.1,2.5v586.3a21.3,21.3 0,0 0,18.8 21.2l2.5,0.1h72.9a42.7,42.7 0,0 1,3.2 85.2l-3.2,0.1h-72.9a106.7,106.7 0,0 1,-106.6 -102l-0.1,-4.6L112.9,218.8a106.7,106.7 0,0 1,102 -106.6l4.6,-0.1h384zM783.9,486.6h-256a42.7,42.7 0,0 0,-42.7 42.7v256a42.7,42.7 0,0 0,42.7 42.7h91.6v-0.4c3.1,-113 94.1,-204 207,-207.3L826.5,529.3a42.7,42.7 0,0 0,-42.7 -42.7zM704.9,827.9h79a42.7,42.7 0,0 0,42.7 -42.7v-79.7a128,128 0,0 0,-121.7 122.3zM596.5,545.5a53.3,53.3 0,1 1,0 106.7,53.3 53.3,0 0,1 0,-106.7zM467,197.4h-115.8l6.4,34.2h102.9l6.4,-34.2z"
android:fillColor="@color/black"/>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="@color/gray" />
<corners android:radius="6dp" />
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/black"
android:pathData="M914.2,189.4L577.3,15.8a143.7,143.7 0,0 0,-131 0L109.4,190.4A135.3,135.3 0,0 0,33.4 314.7v393.5a143.6,143.6 0,0 0,76 125.2l339,174.6a137,137 0,0 0,65.5 15.9,143.1 143.1,0 0,0 65.5,-15.9l334.8,-173.6a140.8,140.8 0,0 0,76 -125.2L990.2,313.6a139.9,139.9 0,0 0,-76 -124.3zM480.1,77.9a71.4,71.4 0,0 1,31.7 -8.4,64 64,0 0,1 31.7,7.4l336.9,173.6a95.8,95.8 0,0 1,13.7 8.4l-383.4,197.8L127.4,262a30.3,30.3 0,0 1,13.7 -8.4zM143.2,770.1a69.3,69.3 0,0 1,-39.1 -63.1L104.1,327.2l141.5,72.6v158.9a34.3,34.3 0,0 0,19.1 31.5l25.5,12.7a10.1,10.1 0,0 0,7.4 2.1,17.1 17.1,0 0,0 17.9,-17.9L315.6,435.6l159.3,81v426.1zM880.4,770.1l-333.7,173.6v-427l372.8,-189.4v379.8a69.3,69.3 0,0 1,-39.1 63.1z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/black"
android:pathData="M875.2,572.5V874.9q0,16.4 -12,28.4T834.9,915.2H592.9V673.3H431.7v241.9H189.8q-16.4,0 -28.4,-12t-12,-28.4V572.5q0,-0.6 0.3,-1.9t0.3,-1.9l362.3,-298.6 362.3,298.6q0.6,1.3 0.6,3.8zM1015.7,529l-39.1,46.6q-5,5.7 -13.2,6.9h-1.9q-8.2,0 -13.2,-4.4L512.3,214.6 76.3,578.1q-7.5,5 -15.1,4.4 -8.2,-1.3 -13.2,-6.9l-39.1,-46.6q-5,-6.3 -4.4,-14.8t6.9,-13.5l453,-377.4q20.1,-16.4 47.9,-16.4t47.9,16.4L713.8,251.8V129q0,-8.8 5.7,-14.5t14.5,-5.7h121q8.8,0 14.5,5.7t5.7,14.5v257.1l138,114.7q6.3,5 6.9,13.5t-4.4,14.8z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M725.3,192c-89.6,0 -168.5,44.8 -213.3,115.2C467.2,236.8 388.3,192 298.7,192 157.9,192 42.7,307.2 42.7,448c0,253.9 469.3,512 469.3,512s469.3,-256 469.3,-512c0,-140.8 -115.2,-256 -256,-256z"
android:fillColor="@color/gray"/>
</vector>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/soft_black"
tools:context=".ui.activity.CategoryActivity">
<ImageView
android:id="@+id/back"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="25dp"
android:src="@drawable/back"
app:layout_constraintBottom_toBottomOf="@+id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/title" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:gravity="center"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="25dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,101 @@
<?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"
android:background="@color/soft_black"
tools:context=".ui.activity.LumaActivity">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/back"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:src="@drawable/back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/background_container"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
android:layout_marginBottom="32dp"
android:background="@drawable/rounded_rectangle_transparent"
android:gravity="center"
android:orientation="horizontal"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:id="@+id/like"
android:layout_width="wrap_content"
android:layout_height="34dp"
android:layout_weight="1"
android:src="@drawable/un_like" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/down_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1">
<ProgressBar
android:id="@+id/down_progress"
android:layout_width="24dp"
android:layout_height="24dp"
android:indeterminateTint="@color/black"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="34dp"
android:layout_height="34dp"
android:src="@drawable/download"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/set"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_weight="1"
android:src="@drawable/set" />
</LinearLayout>
<ImageView
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray"
android:focusable="true"
android:visibility="gone" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,15 +5,38 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
android:background="@color/soft_black"
tools:context=".ui.activity.MainActivity">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="24sp"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/main_viewpager2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/main_tab_layout"
app:layout_constraintTop_toBottomOf="@+id/title" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/main_tab_layout"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginBottom="25dp"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:tabIndicatorHeight="0dp"
app:tabRippleColor="@android:color/transparent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,50 @@
<?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"
android:background="@color/soft_black"
tools:context=".ui.activity.SplashActivity">
<ImageView
android:id="@+id/image"
android:layout_width="150dp"
android:layout_height="150dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.388" />
<TextView
android:id="@+id/splash_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:gravity="center"
android:text="@string/app_name"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="5dp"
android:layout_marginStart="53dp"
android:layout_marginEnd="53dp"
android:layout_marginBottom="80dp"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/progress_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,17 @@
<?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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.fragment.CategoryFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="25dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
</FrameLayout>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.FavoriteFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="25dp" />
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Aww, you havent liked any wallpapers yet! Cmon, cutie, spread some love! 🥰💖"
android:textColor="@color/white"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,32 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="256dp">
<ImageView
android:id="@+id/item_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/item_favorite"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:src="@drawable/un_like"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/rounded_rectangle_gradient">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#10FFFFFF"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/circle_background"
android:elevation="4dp"
android:src="@drawable/home"
android:scaleType="centerInside"
app:layout_constraintBottom_toTopOf="@+id/text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="8dp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:text="@string/app_name"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image"
app:layout_constraintVertical_bias="0.1" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:background="@drawable/rounded_rectangle_transparent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text=""
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/both"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:background="@drawable/set_background"
android:padding="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Both"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/lock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:layout_marginBottom="20dp"
android:background="@drawable/set_background"
android:padding="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/both">
<TextView
android:id="@+id/lock_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lock"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/desktop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:layout_marginBottom="20dp"
android:background="@drawable/set_background"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/lock">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Desktop"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +0,0 @@
<?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: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -2,4 +2,6 @@
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="soft_black">#0D1B2A</color>
<color name="gray">#9C979D</color>
</resources>

View File

@ -2,4 +2,6 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
}

6
keystore.properties Normal file
View File

@ -0,0 +1,6 @@
app_name=Luma Wallpaper
package_name=com.wallpaper.lumawallpaper
keystoreFile=app/LumaWallpaper.jks
key_alias=LumaWallpaperkey0
key_store_password=LumaWallpaper
key_password=LumaWallpaper