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 {
|
object Lifecycle {
|
||||||
private const val lifecycleVersion = "2.5.1"
|
private const val lifecycleVersion = "2.5.1"
|
||||||
const val runtime = "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
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 viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||||
|
|
||||||
const val fragment = "androidx.fragment:fragment-ktx:1.5.5"
|
const val fragment = "androidx.fragment:fragment-ktx:1.5.5"
|
||||||
|
@ -10,10 +10,8 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
interface DataModule {
|
interface DataModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindNetworkMonitor(
|
fun bindNetworkMonitor(
|
||||||
networkMonitor: ConnectivityManagerNetworkMonitor
|
networkMonitor: ConnectivityManagerNetworkMonitor
|
||||||
): NetworkMonitor
|
): 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>>
|
fun getAppsFromAuthor(authorName: String): Flow<List<AppEntity>>
|
||||||
|
|
||||||
@Query(value = "SELECT * FROM apps WHERE packageName = :packageName")
|
@Query(value = "SELECT * FROM apps WHERE packageName = :packageName")
|
||||||
fun getApp(packageName: String): List<AppEntity>
|
fun getApp(packageName: String): Flow<List<AppEntity>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
value = """
|
value = """
|
||||||
|
@ -2,12 +2,12 @@ package com.looker.core.database.model
|
|||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import com.looker.core.model.new.App
|
import com.looker.core.model.newer.App
|
||||||
import com.looker.core.model.new.Author
|
import com.looker.core.model.newer.Author
|
||||||
import com.looker.core.model.new.Donate
|
import com.looker.core.model.newer.Donate
|
||||||
import com.looker.core.model.new.Localized
|
import com.looker.core.model.newer.Localized
|
||||||
import com.looker.core.model.new.Metadata
|
import com.looker.core.model.newer.Metadata
|
||||||
import com.looker.core.model.new.toPackageName
|
import com.looker.core.model.newer.toPackageName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Entity(tableName = "apps", primaryKeys = ["repoId", "packageName"])
|
@Entity(tableName = "apps", primaryKeys = ["repoId", "packageName"])
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.looker.core.database.model
|
package com.looker.core.database.model
|
||||||
|
|
||||||
import com.looker.core.model.new.Package
|
import com.looker.core.model.newer.Package
|
||||||
import com.looker.core.model.new.Permission
|
import com.looker.core.model.newer.Permission
|
||||||
import com.looker.core.model.new.toPackageName
|
import com.looker.core.model.newer.toPackageName
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -2,7 +2,7 @@ package com.looker.core.database.model
|
|||||||
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.looker.core.model.new.Repo
|
import com.looker.core.model.newer.Repo
|
||||||
|
|
||||||
@Entity(tableName = "repos")
|
@Entity(tableName = "repos")
|
||||||
data class RepoEntity(
|
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:database",
|
||||||
":core:datastore",
|
":core:datastore",
|
||||||
":core:model",
|
":core:model",
|
||||||
|
":core:sync",
|
||||||
":feature-settings",
|
":feature-settings",
|
||||||
":installer"
|
":installer"
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user