Add a sync module to sync index [W.I.P]
This commit is contained in:
parent
88aec77169
commit
64ca44e4cd
@ -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"
|
||||
|
@ -10,10 +10,8 @@ import dagger.hilt.components.SingletonComponent
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface DataModule {
|
||||
|
||||
@Binds
|
||||
fun bindNetworkMonitor(
|
||||
networkMonitor: ConnectivityManagerNetworkMonitor
|
||||
): NetworkMonitor
|
||||
|
||||
}
|
@ -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>>
|
||||
|
||||
}
|
@ -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>
|
||||
|
||||
}
|
@ -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 = """
|
||||
|
@ -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"])
|
||||
|
@ -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
|
||||
|
@ -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
1
core/sync/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
59
core/sync/build.gradle.kts
Normal file
59
core/sync/build.gradle.kts
Normal 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)
|
||||
}
|
0
core/sync/consumer-rules.pro
Normal file
0
core/sync/consumer-rules.pro
Normal file
21
core/sync/proguard-rules.pro
vendored
Normal file
21
core/sync/proguard-rules.pro
vendored
Normal 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
|
@ -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)
|
||||
}
|
||||
}
|
4
core/sync/src/main/AndroidManifest.xml
Normal file
4
core/sync/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
17
core/sync/src/main/java/com/looker/sync/di/SyncModule.kt
Normal file
17
core/sync/src/main/java/com/looker/sync/di/SyncModule.kt
Normal 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
|
||||
}
|
@ -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 }
|
@ -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()
|
||||
}
|
27
core/sync/src/main/java/com/looker/sync/worker/SyncWorker.kt
Normal file
27
core/sync/src/main/java/com/looker/sync/worker/SyncWorker.kt
Normal 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")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
17
core/sync/src/test/java/com/looker/sync/ExampleUnitTest.kt
Normal file
17
core/sync/src/test/java/com/looker/sync/ExampleUnitTest.kt
Normal 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)
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ include(
|
||||
":core:database",
|
||||
":core:datastore",
|
||||
":core:model",
|
||||
":core:sync",
|
||||
":feature-settings",
|
||||
":installer"
|
||||
)
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user