Add a sync module to sync index [W.I.P]

This commit is contained in:
LooKeR 2023-01-28 01:22:31 +05:30
parent 88aec77169
commit 64ca44e4cd
No known key found for this signature in database
GPG Key ID: 52C4E65FEDDD5BD4
21 changed files with 366 additions and 14 deletions

View File

@ -66,6 +66,7 @@ object Kotlin {
object Lifecycle {
private const val lifecycleVersion = "2.5.1"
const val runtime = "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
const val livedata = "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
const val viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
const val fragment = "androidx.fragment:fragment-ktx:1.5.5"

View File

@ -10,10 +10,8 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface DataModule {
@Binds
fun bindNetworkMonitor(
networkMonitor: ConnectivityManagerNetworkMonitor
): NetworkMonitor
}

View File

@ -0,0 +1,19 @@
package com.looker.core.data.fdroid.repository
import com.looker.core.model.newer.App
import com.looker.core.model.newer.Author
import com.looker.core.model.newer.Package
import com.looker.core.model.newer.PackageName
import kotlinx.coroutines.flow.Flow
interface AppRepository {
fun getApps(): Flow<List<App>>
fun getApp(packageName: PackageName): Flow<List<App>>
fun getAppFromAuthor(author: Author): Flow<List<App>>
fun getPackages(packageName: PackageName): Flow<List<Package>>
}

View File

@ -0,0 +1,12 @@
package com.looker.core.data.fdroid.repository
import com.looker.core.model.newer.Repo
import kotlinx.coroutines.flow.Flow
interface RepoRepository {
fun getRepos() : Flow<List<Repo>>
fun getRepo(id: Long) : Flow<Repo>
}

View File

@ -24,7 +24,7 @@ interface AppDao {
fun getAppsFromAuthor(authorName: String): Flow<List<AppEntity>>
@Query(value = "SELECT * FROM apps WHERE packageName = :packageName")
fun getApp(packageName: String): List<AppEntity>
fun getApp(packageName: String): Flow<List<AppEntity>>
@Query(
value = """

View File

@ -2,12 +2,12 @@ package com.looker.core.database.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.looker.core.model.new.App
import com.looker.core.model.new.Author
import com.looker.core.model.new.Donate
import com.looker.core.model.new.Localized
import com.looker.core.model.new.Metadata
import com.looker.core.model.new.toPackageName
import com.looker.core.model.newer.App
import com.looker.core.model.newer.Author
import com.looker.core.model.newer.Donate
import com.looker.core.model.newer.Localized
import com.looker.core.model.newer.Metadata
import com.looker.core.model.newer.toPackageName
import kotlinx.serialization.Serializable
@Entity(tableName = "apps", primaryKeys = ["repoId", "packageName"])

View File

@ -1,8 +1,8 @@
package com.looker.core.database.model
import com.looker.core.model.new.Package
import com.looker.core.model.new.Permission
import com.looker.core.model.new.toPackageName
import com.looker.core.model.newer.Package
import com.looker.core.model.newer.Permission
import com.looker.core.model.newer.toPackageName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable

View File

@ -2,7 +2,7 @@ package com.looker.core.database.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.looker.core.model.new.Repo
import com.looker.core.model.newer.Repo
@Entity(tableName = "repos")
data class RepoEntity(

1
core/sync/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,59 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
id(Hilt.plugin)
}
android {
compileSdk = Android.compileSdk
namespace = "com.looker.sync"
defaultConfig {
minSdk = Android.minSdk
targetSdk = Android.compileSdk
testInstrumentationRunner = Test.jUnitRunner
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
buildConfig = false
aidl = false
renderScript = false
shaders = false
resValues = false
}
}
dependencies {
implementation(project(Modules.coreCommon))
implementation(project(Modules.coreData))
implementation(project(Modules.coreDatastore))
implementation(project(Modules.coreModel))
implementation(Core.core)
implementation(Lifecycle.livedata)
implementation(Work.manager)
implementation(Hilt.android)
implementation(Hilt.work)
kapt(Hilt.compiler)
testImplementation(Test.jUnit)
androidTestImplementation(Test.androidJUnit)
androidTestImplementation(Test.espresso)
}

View File

21
core/sync/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# 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

View File

@ -0,0 +1,24 @@
package com.looker.sync
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.looker.sync.test", appContext.packageName)
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,17 @@
package com.looker.sync.di
import com.looker.core.data.utils.SyncStatusMonitor
import com.looker.sync.status.WorkManagerSyncStatusMonitor
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface SyncModule {
@Binds
fun bindsSyncStatusMonitor(
syncStatusMonitor: WorkManagerSyncStatusMonitor
): SyncStatusMonitor
}

View File

@ -0,0 +1,28 @@
package com.looker.sync.status
import android.content.Context
import androidx.lifecycle.Transformations
import androidx.lifecycle.asFlow
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.looker.core.data.utils.SyncStatusMonitor
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import javax.inject.Inject
internal const val SyncWorkName = "sync_work"
class WorkManagerSyncStatusMonitor @Inject constructor(
@ApplicationContext context: Context
) : SyncStatusMonitor {
override val isSyncing: Flow<Boolean> =
Transformations.map(
WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData(SyncWorkName),
MutableList<WorkInfo>::anyRunning
)
.asFlow()
.conflate()
}
private val List<WorkInfo>.anyRunning get() = any { it.state == WorkInfo.State.RUNNING }

View File

@ -0,0 +1,61 @@
package com.looker.sync.worker
import android.content.Context
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import kotlin.reflect.KClass
@EntryPoint
@InstallIn(SingletonComponent::class)
interface HiltWorkerFactoryEntryPoint {
fun hiltWorkerFactory(): HiltWorkerFactory
}
private const val WORKER_CLASS_NAME = "RouterWorkerDelegateClassName"
/**
* Adds metadata to a WorkRequest to identify what [CoroutineWorker] the [DelegatingWorker] should
* delegate to
*/
internal fun KClass<out CoroutineWorker>.delegatedData() =
Data.Builder()
.putString(WORKER_CLASS_NAME, qualifiedName)
.build()
/**
* A worker that delegates sync to another [CoroutineWorker] constructed with a [HiltWorkerFactory].
*
* This allows for creating and using [CoroutineWorker] instances with extended arguments
* without having to provide a custom WorkManager configuration that the app module needs to utilize.
*
* In other words, it allows for custom workers in a library module without having to own
* configuration of the WorkManager singleton.
*/
class DelegatingWorker(
appContext: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(appContext, workerParams) {
private val workerClassName =
workerParams.inputData.getString(WORKER_CLASS_NAME) ?: ""
private val delegateWorker =
EntryPointAccessors.fromApplication<HiltWorkerFactoryEntryPoint>(appContext)
.hiltWorkerFactory()
.createWorker(appContext, workerClassName, workerParams)
as? CoroutineWorker
?: throw IllegalArgumentException("Unable to find appropriate worker")
override suspend fun getForegroundInfo(): ForegroundInfo =
delegateWorker.getForegroundInfo()
override suspend fun doWork(): Result =
delegateWorker.doWork()
}

View File

@ -0,0 +1,27 @@
package com.looker.sync.worker
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import com.looker.core.datastore.UserPreferencesRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
@HiltWorker
class SyncWorker @AssistedInject constructor(
@Assisted private val appContext: Context,
@Assisted workerParams: WorkerParameters,
private val userPreferencesRepository: UserPreferencesRepository
): CoroutineWorker(appContext, workerParams) {
override suspend fun getForegroundInfo(): ForegroundInfo = appContext.syncForegroundInfo()
override suspend fun doWork(): Result {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,62 @@
package com.looker.sync.worker
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.work.Constraints
import androidx.work.ForegroundInfo
import androidx.work.NetworkType
import com.looker.core.datastore.UserPreferences
import com.looker.core.datastore.model.ProxyType
import com.looker.core.common.R as CommonR
private const val SyncNotificationId = 0
private const val SyncNotificationChannelID = "SyncNotificationChannel"
// All sync work needs an internet connectionS
val SyncConstraints
get() = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
/**
* Foreground information for sync on lower API levels when sync workers are being
* run with a foreground service
*/
fun Context.syncForegroundInfo() = ForegroundInfo(
SyncNotificationId,
syncWorkNotification(),
)
/**
* Notification displayed on lower API levels when sync workers are being
* run with a foreground service
*/
private fun Context.syncWorkNotification(): Notification {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
SyncNotificationChannelID,
getString(CommonR.string.sync_repositories),
NotificationManager.IMPORTANCE_DEFAULT,
).apply {
description = getString(CommonR.string.sync_repositories)
}
// Register the channel with the system
val notificationManager: NotificationManager? =
getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
notificationManager?.createNotificationChannel(channel)
}
return NotificationCompat.Builder(
this,
SyncNotificationChannelID,
)
.setSmallIcon(CommonR.drawable.ic_sync,)
.setContentTitle(getString(CommonR.string.syncing))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
}

View File

@ -0,0 +1,17 @@
package com.looker.sync
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)
}
}

View File

@ -22,6 +22,7 @@ include(
":core:database",
":core:datastore",
":core:model",
":core:sync",
":feature-settings",
":installer"
)
)