Switch to room

pull/653/merge
Varun Patil 2023-08-21 12:32:52 -07:00
parent ec8c125b0e
commit 6dfe308268
19 changed files with 207 additions and 302 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -1,17 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\varun\.android\avd\Pixel_6_API_33_2.avd" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />

View File

@ -7,6 +7,7 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0" />
</component>
</project>

View File

@ -6,7 +6,7 @@
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.devtools.ksp' version '1.9.0-1.0.13'
}
android {
@ -22,28 +23,35 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
buildFeatures {
viewBinding true
}
}
def mediaVersion = "1.0.1"
dependencies {
def media_version = "1.1.1"
def room_version = "2.5.2"
implementation 'androidx.core:core-ktx:1.10.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
implementation 'androidx.exifinterface:exifinterface:1.3.6'
implementation "androidx.media3:media3-exoplayer:$mediaVersion"
implementation "androidx.media3:media3-ui:$mediaVersion"
implementation "androidx.media3:media3-exoplayer-hls:$mediaVersion"
implementation "androidx.media3:media3-exoplayer:$media_version"
implementation "androidx.media3:media3-ui:$media_version"
implementation "androidx.media3:media3-exoplayer-hls:$media_version"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
ksp "androidx.room:room-compiler:$room_version"
implementation "com.squareup.okhttp3:okhttp:4.10.0"
implementation "io.github.g00fy2:versioncompare:1.5.0"
}

View File

@ -386,7 +386,7 @@ import gallery.memories.databinding.ActivityMainBinding
fun refreshTimeline(force: Boolean = false) {
runOnUiThread {
// Check webview is loaded
if (binding?.webview?.url == null) return@runOnUiThread
if (binding.webview.url == null) return@runOnUiThread
// Schedule for resume if not active
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) || force) {

View File

@ -0,0 +1,48 @@
package gallery.memories.dao
import android.content.Context
import androidx.room.Database
import androidx.room.Room.databaseBuilder
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import gallery.memories.R
import gallery.memories.mapper.Photo
@Database(entities = [Photo::class], version = 8)
abstract class AppDatabase : RoomDatabase() {
abstract fun photoDao(): PhotoDao
companion object {
private val DATABASE_NAME = "memories_room"
@Volatile private var INSTANCE: AppDatabase? = null
fun get(context: Context): AppDatabase {
if (INSTANCE == null) {
synchronized(AppDatabase::class.java) {
val ctx = context.applicationContext
if (INSTANCE == null) {
INSTANCE = databaseBuilder(ctx, AppDatabase::class.java, DATABASE_NAME)
.fallbackToDestructiveMigration()
.addCallback(callbacks(ctx))
.build()
}
}
}
return INSTANCE!!
}
private fun callbacks(ctx: Context): Callback {
return object : Callback() {
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
// retrigger synchronization whenever database is destructed
ctx.getSharedPreferences(ctx.getString(R.string.preferences_key), 0).edit()
.remove(ctx.getString(R.string.preferences_last_sync_time))
.apply()
}
}
}
}
}

View File

@ -0,0 +1,44 @@
package gallery.memories.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import gallery.memories.mapper.Bucket
import gallery.memories.mapper.Day
import gallery.memories.mapper.Photo
@Dao
interface PhotoDao {
@Query("SELECT 1")
fun ping(): Int
@Query("SELECT * FROM photos WHERE dayid=:dayId AND bucket_id IN (:buckets)")
fun getPhotosByDay(dayId: Long, buckets: List<String>): List<Photo>
@Query("SELECT * FROM photos WHERE local_id IN (:fileIds)")
fun getPhotosByFileIds(fileIds: List<Long>): List<Photo>
@Query("SELECT * FROM photos WHERE auid IN (:auids)")
fun getPhotosByAUIDs(auids: List<Long>): List<Photo>
@Query("SELECT dayid, COUNT(local_id) AS count FROM photos WHERE bucket_id IN (:bucketIds) GROUP BY dayid")
fun getDays(bucketIds: List<String>): List<Day>
@Query("DELETE FROM photos WHERE local_id IN (:fileIds)")
fun deleteFileIds(fileIds: List<Long>)
@Query("UPDATE photos SET flag=1")
fun flagAll()
@Query("UPDATE photos SET flag=0 WHERE local_id=:fileId")
fun unflag(fileId: Long)
@Query("DELETE FROM photos WHERE flag=1")
fun deleteFlagged()
@Insert
fun insert(vararg photos: Photo)
@Query("SELECT bucket_id, bucket_name FROM photos GROUP BY bucket_id")
fun getBuckets(): List<Bucket>
}

View File

@ -0,0 +1,8 @@
package gallery.memories.mapper
import androidx.room.ColumnInfo
data class Bucket (
@ColumnInfo(name="bucket_id") val id: String,
@ColumnInfo(name="bucket_name") val name: String,
)

View File

@ -1,37 +1,8 @@
package gallery.memories.mapper
import android.database.Cursor
import org.json.JSONObject
import androidx.room.ColumnInfo
class Day {
val dayId: Long
val count: Long
companion object {
val FIELDS get(): Array<String> {
return arrayOf(
Photo.FIELD_DAY_ID,
"COUNT(${Photo.FIELD_LOCAL_ID})"
data class Day (
@ColumnInfo(name="dayid") val dayId: Long,
@ColumnInfo(name="count") val count: Long
)
}
fun unpack(cursor: Cursor): List<Day> {
val days = mutableListOf<Day>()
while (cursor.moveToNext()) {
days.add(Day(cursor))
}
return days
}
}
constructor(cursor: Cursor) {
dayId = cursor.getLong(0)
count = cursor.getLong(1)
}
val json get(): JSONObject {
return JSONObject()
.put(Fields.Day.DAYID, dayId)
.put(Fields.Day.COUNT, count)
}
}

View File

@ -1,80 +1,19 @@
package gallery.memories.mapper
import android.database.Cursor
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
class Photo {
val id: Long
val localId: Long
val auid: Long
val mtime: Long
val dateTaken: Long
val dayId: Long
val basename: String
val bucketId: Long
val bucketName: String
val flag: Int
companion object {
val FIELD_ID = "id"
val FIELD_LOCAL_ID = "local_id"
val FIELD_AUID = "auid"
val FIELD_MTIME = "mtime"
val FIELD_DATE_TAKEN = "date_taken"
val FIELD_DAY_ID = "dayid"
val FIELD_BASENAME = "basename"
val FIELD_BUCKET_ID = "bucket_id"
val FIELD_BUCKET_NAME = "bucket_name"
val FIELD_FLAG = "flag"
val FIELDS get(): Array<String> {
return arrayOf(
FIELD_ID,
FIELD_LOCAL_ID,
FIELD_AUID,
FIELD_MTIME,
FIELD_DATE_TAKEN,
FIELD_DAY_ID,
FIELD_BASENAME,
FIELD_BUCKET_ID,
FIELD_BUCKET_NAME,
FIELD_FLAG
@Entity(tableName="photos")
data class Photo (
@PrimaryKey(autoGenerate = true) val id: Int? = null,
@ColumnInfo(name="local_id") val localId: Long,
@ColumnInfo(name="auid") val auid: Long,
@ColumnInfo(name="mtime") val mtime: Long,
@ColumnInfo(name="date_taken") val dateTaken: Long,
@ColumnInfo(name="dayid") val dayId: Long,
@ColumnInfo(name="basename") val baseName: String,
@ColumnInfo(name="bucket_id") val bucketId: Long,
@ColumnInfo(name="bucket_name") val bucketName: String,
@ColumnInfo(name="flag") val flag: Int
)
}
val FIELDS_CREATE get(): String {
return """
$FIELD_ID INTEGER PRIMARY KEY AUTOINCREMENT,
$FIELD_LOCAL_ID INTEGER,
$FIELD_AUID INTEGER,
$FIELD_MTIME INTEGER,
$FIELD_DATE_TAKEN INTEGER,
$FIELD_DAY_ID INTEGER,
$FIELD_BASENAME TEXT,
$FIELD_BUCKET_ID INTEGER,
$FIELD_BUCKET_NAME TEXT,
$FIELD_FLAG INTEGER
""".trimIndent()
}
fun unpack(cursor: Cursor): List<Photo> {
val photos = mutableListOf<Photo>()
while (cursor.moveToNext()) {
photos.add(Photo(cursor))
}
return photos
}
}
constructor(cursor: Cursor) {
id = cursor.getLong(0)
localId = cursor.getLong(1)
auid = cursor.getLong(2)
mtime = cursor.getLong(3)
dateTaken = cursor.getLong(4)
dayId = cursor.getLong(5)
basename = cursor.getString(6)
bucketId = cursor.getLong(7)
bucketName = cursor.getString(8)
flag = cursor.getInt(9)
}
}

View File

@ -190,4 +190,19 @@ class SystemImage {
return crc.value
}
val photo get(): Photo {
val dateCache = utcDate
return Photo(
localId = fileId,
auid = auid,
mtime = mtime,
dateTaken = dateCache,
dayId = dateCache / 86400,
baseName = baseName,
bucketId = bucketId,
bucketName = bucketName,
flag = 0,
)
}
}

View File

@ -5,20 +5,21 @@ import gallery.memories.R
class ConfigService(private val mCtx: Context) {
companion object {
private var mEnabledBuckets: Set<String>? = null
private var mEnabledBuckets: List<String>? = null
}
var enabledBucketIds: Set<String>
var enabledBucketIds: List<String>
get() {
if (mEnabledBuckets != null) return mEnabledBuckets!!
mEnabledBuckets = mCtx.getSharedPreferences(mCtx.getString(R.string.preferences_key), 0)
.getStringSet(mCtx.getString(R.string.preferences_enabled_local_folders), null) ?: setOf()
.getStringSet(mCtx.getString(R.string.preferences_enabled_local_folders), null)?.toList()
?: listOf()
return mEnabledBuckets!!
}
set(value) {
mEnabledBuckets = value
mCtx.getSharedPreferences(mCtx.getString(R.string.preferences_key), 0).edit()
.putStringSet(mCtx.getString(R.string.preferences_enabled_local_folders), value)
.putStringSet(mCtx.getString(R.string.preferences_enabled_local_folders), value.toSet())
.apply()
}
}

View File

@ -1,137 +0,0 @@
package gallery.memories.service
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import gallery.memories.R
import gallery.memories.mapper.Day
import gallery.memories.mapper.Photo
import gallery.memories.mapper.SystemImage
class DbService(val context: Context) : SQLiteOpenHelper(context, "memories", null, 45) {
companion object {
val MEMORIES = "memories"
}
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE $MEMORIES (${Photo.FIELDS_CREATE})")
// Add index on local_id, dayid, and flag
db.execSQL("CREATE INDEX images_local_id ON $MEMORIES (${Photo.FIELD_LOCAL_ID})")
db.execSQL("CREATE INDEX images_auid ON $MEMORIES (${Photo.FIELD_AUID})")
db.execSQL("CREATE INDEX images_dayid ON $MEMORIES (${Photo.FIELD_DAY_ID})")
db.execSQL("CREATE INDEX images_flag ON $MEMORIES (${Photo.FIELD_FLAG})")
db.execSQL("CREATE INDEX images_bucket ON $MEMORIES (${Photo.FIELD_BUCKET_ID})")
db.execSQL("CREATE INDEX images_bucket_dayid ON $MEMORIES (${Photo.FIELD_BUCKET_ID}, ${Photo.FIELD_DAY_ID})")
}
override fun onUpgrade(database: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
database.execSQL("DROP TABLE IF EXISTS $MEMORIES")
// Reset sync time
context.getSharedPreferences(context.getString(R.string.preferences_key), 0).edit()
.remove(context.getString(R.string.preferences_last_sync_time))
.apply()
onCreate(database)
}
fun initialize(): DbService {
writableDatabase
return this
}
fun getPhotosByDay(dayId: Long, buckets: Set<String>): List<Photo> {
if (buckets.isEmpty()) return emptyList()
val bs = buckets.joinToString(",")
val where = "${Photo.FIELD_DAY_ID} = ? AND ${Photo.FIELD_BUCKET_ID} IN ($bs)"
readableDatabase.query(MEMORIES, Photo.FIELDS, where, arrayOf(
dayId.toString(),
), null, null, null).use { cursor ->
return Photo.unpack(cursor)
}
}
fun getPhotosBy(field: String, vals: List<Long>): List<Photo> {
if (vals.isEmpty()) return emptyList()
val vls = vals.joinToString(",")
readableDatabase.query(MEMORIES, Photo.FIELDS, "$field IN ($vls)", null,
null, null, null
).use { cursor ->
return Photo.unpack(cursor)
}
}
fun getPhotosByFileIds(fileIds: List<Long>): List<Photo> {
return getPhotosBy(Photo.FIELD_LOCAL_ID, fileIds)
}
fun getPhotosByAUIDs(auids: List<Long>): List<Photo> {
return getPhotosBy(Photo.FIELD_AUID, auids)
}
fun getDays(bucketIds: Set<String>): List<Day> {
if (bucketIds.isEmpty()) return emptyList()
val bs = bucketIds.joinToString(",")
val where = "${Photo.FIELD_BUCKET_ID} IN ($bs)"
readableDatabase.query(MEMORIES, Day.FIELDS, where, null,
Photo.FIELD_DAY_ID, null, null
).use { cursor ->
return Day.unpack(cursor)
}
}
fun deleteFileIds(fileIds: List<Long>) {
if (fileIds.isEmpty()) return
val ids = fileIds.joinToString(",")
writableDatabase.delete(MEMORIES, "${Photo.FIELD_LOCAL_ID} IN ($ids)", null)
}
fun flagAll() {
writableDatabase.execSQL("UPDATE $MEMORIES SET ${Photo.FIELD_FLAG} = 1")
}
fun unflag(fileId: Long) {
writableDatabase.execSQL("UPDATE $MEMORIES SET ${Photo.FIELD_FLAG} = 0 WHERE ${Photo.FIELD_LOCAL_ID} = $fileId")
}
fun deleteFlagged() {
writableDatabase.delete(MEMORIES, "${Photo.FIELD_FLAG} = 1", null)
}
fun insertImage(image: SystemImage) {
val dateTaken = image.utcDate
ContentValues().apply {
put(Photo.FIELD_LOCAL_ID, image.fileId)
put(Photo.FIELD_MTIME, image.mtime)
put(Photo.FIELD_DATE_TAKEN, dateTaken)
put(Photo.FIELD_DAY_ID, dateTaken / 86400)
put(Photo.FIELD_AUID, image.auid)
put(Photo.FIELD_BASENAME, image.baseName)
put(Photo.FIELD_BUCKET_ID, image.bucketId)
put(Photo.FIELD_BUCKET_NAME, image.bucketName)
}.let {
writableDatabase.insert(MEMORIES, null, it)
}
}
fun getBuckets(): Map<String, String> {
val ret = mutableMapOf<String, String>()
readableDatabase.query(MEMORIES, arrayOf(Photo.FIELD_BUCKET_ID, Photo.FIELD_BUCKET_NAME),
null, null, Photo.FIELD_BUCKET_ID, null, null
).use { cursor ->
while (cursor.moveToNext()) {
ret[cursor.getString(0)] = cursor.getString(1)
}
}
return ret
}
}

View File

@ -15,6 +15,7 @@ import androidx.exifinterface.media.ExifInterface
import androidx.media3.common.util.UnstableApi
import gallery.memories.MainActivity
import gallery.memories.R
import gallery.memories.dao.AppDatabase
import gallery.memories.mapper.Fields
import gallery.memories.mapper.Response
import gallery.memories.mapper.SystemImage
@ -26,9 +27,12 @@ import java.time.Instant
import java.util.concurrent.CountDownLatch
@UnstableApi class TimelineQuery(private val mCtx: MainActivity) {
private val mDbService = DbService(mCtx).initialize()
private val mConfigService = ConfigService(mCtx)
private val TAG = TimelineQuery::class.java.simpleName
private val mConfigService = ConfigService(mCtx)
// Database
private val mDb = AppDatabase.get(mCtx)
private val mPhotoDao = mDb.photoDao()
// Photo deletion events
var deleting = false
@ -50,6 +54,7 @@ import java.util.concurrent.CountDownLatch
}
fun initialize() {
mPhotoDao.ping()
if (syncDeltaDb() > 0) {
mCtx.refreshTimeline()
}
@ -103,8 +108,8 @@ import java.util.concurrent.CountDownLatch
@Throws(JSONException::class)
fun getByDayId(dayId: Long): JSONArray {
// Get the photos for the day from DB
val dbPhotos = mDbService.getPhotosByDay(dayId, mConfigService.enabledBucketIds)
val fileIds = dbPhotos.map { it.localId }.toMutableList()
val fileIds = mPhotoDao.getPhotosByDay(dayId, mConfigService.enabledBucketIds)
.map { it.localId }.toMutableList()
if (fileIds.isEmpty()) return JSONArray()
// Get latest metadata from system table
@ -117,19 +122,23 @@ import java.util.concurrent.CountDownLatch
}.let { JSONArray(it) }
// Remove files that were not found
mDbService.deleteFileIds(fileIds)
mPhotoDao.deleteFileIds(fileIds)
return photos
}
@Throws(JSONException::class)
fun getDays(): JSONArray {
return mDbService.getDays(mConfigService.enabledBucketIds).map { day -> day.json }.let { JSONArray(it) }
return mPhotoDao.getDays(mConfigService.enabledBucketIds).map {
JSONObject()
.put(Fields.Day.DAYID, it.dayId)
.put(Fields.Day.COUNT, it.count)
}.let { JSONArray(it) }
}
@Throws(Exception::class)
fun getImageInfo(id: Long): JSONObject {
val photos = mDbService.getPhotosByFileIds(listOf(id))
val photos = mPhotoDao.getPhotosByFileIds(listOf(id))
if (photos.isEmpty()) throw Exception("File not found in database")
// Get image from system table
@ -172,7 +181,7 @@ import java.util.concurrent.CountDownLatch
try {
// Get list of file IDs
val photos = mDbService.getPhotosByAUIDs(auids)
val photos = mPhotoDao.getPhotosByAUIDs(auids)
if (photos.isEmpty()) return Response.OK
val fileIds = photos.map { it.localId }
@ -206,7 +215,7 @@ import java.util.concurrent.CountDownLatch
}
// Delete from database
mDbService.deleteFileIds(fileIds)
mPhotoDao.deleteFileIds(fileIds)
} finally {
synchronized(this) { deleting = false }
}
@ -252,13 +261,13 @@ import java.util.concurrent.CountDownLatch
fun syncFullDb() {
// Flag all images for removal
mDbService.flagAll()
mPhotoDao.flagAll()
// Sync all files, marking them in the process
syncDb(0L)
// Clean up stale files
mDbService.deleteFlagged()
mPhotoDao.deleteFlagged()
}
@SuppressLint("SimpleDateFormat")
@ -267,38 +276,38 @@ import java.util.concurrent.CountDownLatch
val baseName = image.baseName
// Check if file with local_id and mtime already exists
val l = mDbService.getPhotosByFileIds(listOf(fileId))
val l = mPhotoDao.getPhotosByFileIds(listOf(fileId))
if (!l.isEmpty() && l[0].mtime == image.mtime) {
// File already exists, remove flag
mDbService.unflag(fileId)
mPhotoDao.unflag(fileId)
Log.v(TAG, "File already exists: $fileId / $baseName")
return
}
// Delete file with same local_id and insert new one
mDbService.deleteFileIds(listOf(fileId))
mDbService.insertImage(image)
mPhotoDao.deleteFileIds(listOf(fileId))
mPhotoDao.insert(image.photo)
Log.v(TAG, "Inserted file to local DB: $fileId / $baseName")
}
/** This is in timeline query because it calls the database service */
var localFolders: JSONArray
get() {
return mDbService.getBuckets().map {
return mPhotoDao.getBuckets().map {
JSONObject()
.put(Fields.Bucket.ID, it.key)
.put(Fields.Bucket.NAME, it.value)
.put(Fields.Bucket.ENABLED, mConfigService.enabledBucketIds.contains(it.key))
.put(Fields.Bucket.ID, it.id)
.put(Fields.Bucket.NAME, it.name)
.put(Fields.Bucket.ENABLED, mConfigService.enabledBucketIds.contains(it.id))
}.let { JSONArray(it) }
}
set(value) {
val enabledSet = mutableSetOf<String>()
val enabled = mutableListOf<String>()
for (i in 0 until value.length()) {
val obj = value.getJSONObject(i)
if (obj.getBoolean(Fields.Bucket.ENABLED)) {
enabledSet.add(obj.getString(Fields.Bucket.ID))
enabled.add(obj.getString(Fields.Bucket.ID))
}
}
mConfigService.enabledBucketIds = enabledSet
mConfigService.enabledBucketIds = enabled
}
}

View File

@ -1,11 +1,12 @@
buildscript {
ext.kotlin_version = '1.8.10'
ext.kotlin_version = '1.9.0'
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'com.android.application' version '8.1.0' apply false
id 'com.android.library' version '8.1.0' apply false
id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
}

View File

@ -19,3 +19,5 @@ android.useAndroidX=true
# 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
android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false

View File

@ -1,6 +1,6 @@
#Tue May 02 23:46:18 PDT 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME