Merge pull request #3028 from PaulWoitaschek/common_modularlization
Fully modularlize common and app
41
AGENTS.md
@@ -21,14 +21,39 @@ Voice is a minimal, user‑focused audiobook player for Android, built for relia
|
||||
|
||||
Each module contains its own `build.gradle.kts`, `src/main/kotlin`, and `src/test/kotlin`:
|
||||
|
||||
* **app**: Main application
|
||||
* **common**: Shared utilities
|
||||
* **data**: Repositories and data layer
|
||||
* **playback**: Audio playback logic
|
||||
* **scanner**: File scanning and metadata extraction
|
||||
* **cover**: Cover art handling
|
||||
* **settings**: Configuration UI
|
||||
* …additional feature modules
|
||||
**Infrastructure**:
|
||||
|
||||
* `:app` - Main application entry point and DI setup
|
||||
* `:navigation` - Navigation framework
|
||||
* `:plugins` - Gradle build plugins
|
||||
* `:scripts` - Build and utility scripts
|
||||
|
||||
**Core Modules** (shared domain logic):
|
||||
|
||||
* `:core:common` - Legacy, to be removed
|
||||
* `:core:ui` - UI components and theming
|
||||
* `:core:data:api` & `:core:data:impl` - Data layer interfaces and implementations
|
||||
* `:core:playback` - Audio playback logic
|
||||
* `:core:scanner` - File scanning and metadata extraction
|
||||
* `:core:strings` - Localized strings
|
||||
* `:core:search` - Search functionality
|
||||
* `:core:documentfile` - File system abstractions
|
||||
* `:core:logging:core`, `:core:logging:crashlytics`, `:core:logging:debug` - Logging implementations
|
||||
* `:core:remoteconfig:core`, `:core:remoteconfig:firebase`, `:core:remoteconfig:noop` - Remote configuration
|
||||
* `:core:sleeptimer:api` & `:core:sleeptimer:impl` - Sleep timer core logic
|
||||
|
||||
**Feature Modules** (UI screens and features):
|
||||
|
||||
* `:features:playbackScreen` - Book playing interface
|
||||
* `:features:bookOverview` - Library/book list
|
||||
* `:features:sleepTimer` - Sleep timer functionality
|
||||
* `:features:settings` - App settings
|
||||
* `:features:folderPicker` - Folder selection
|
||||
* `:features:cover` - Cover art management
|
||||
* `:features:onboarding` - First-time user flow
|
||||
* `:features:bookmark` - Bookmark management
|
||||
* `:features:widget` - Home screen widget functionality
|
||||
* `:features:review:play` & `:features:review:noop` - App review prompts
|
||||
|
||||
## Build & Run
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.strings)
|
||||
implementation(projects.core.datastore)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.data.api)
|
||||
implementation(projects.core.data.impl)
|
||||
@@ -155,6 +155,8 @@ dependencies {
|
||||
implementation(projects.core.scanner)
|
||||
implementation(projects.features.playbackScreen)
|
||||
implementation(projects.navigation)
|
||||
implementation(projects.core.sleeptimer.api)
|
||||
implementation(projects.core.sleeptimer.impl)
|
||||
implementation(projects.features.sleepTimer)
|
||||
implementation(projects.features.settings)
|
||||
implementation(projects.features.folderPicker)
|
||||
@@ -164,6 +166,7 @@ dependencies {
|
||||
implementation(projects.core.documentfile)
|
||||
implementation(projects.features.onboarding)
|
||||
implementation(projects.features.bookmark)
|
||||
implementation(projects.features.widget)
|
||||
|
||||
implementation(libs.appCompat)
|
||||
implementation(libs.material)
|
||||
@@ -174,8 +177,6 @@ dependencies {
|
||||
|
||||
implementation(libs.serialization.json)
|
||||
|
||||
implementation(libs.materialDialog.core)
|
||||
implementation(libs.materialDialog.input)
|
||||
implementation(libs.coil)
|
||||
|
||||
if (includeProprietaryLibraries()) {
|
||||
|
||||
@@ -10,20 +10,20 @@ import io.kotest.matchers.longs.shouldBeGreaterThan
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.common.pref.FadeOutStore
|
||||
import voice.core.common.pref.SleepTimerPreferenceStore
|
||||
import voice.core.common.rootGraphAs
|
||||
import voice.core.common.sleepTimer.SleepTimerPreference
|
||||
import voice.core.data.BookContent
|
||||
import voice.core.data.BookId
|
||||
import voice.core.data.Chapter
|
||||
import voice.core.data.ChapterId
|
||||
import voice.core.data.repo.BookContentRepo
|
||||
import voice.core.data.repo.ChapterRepo
|
||||
import voice.core.data.sleeptimer.SleepTimerPreference
|
||||
import voice.core.data.store.CurrentBookStore
|
||||
import voice.core.data.store.FadeOutStore
|
||||
import voice.core.data.store.SleepTimerPreferenceStore
|
||||
import voice.core.playback.PlayerController
|
||||
import voice.core.playback.playstate.PlayStateManager
|
||||
import voice.core.playback.session.SleepTimer
|
||||
import voice.core.sleeptimer.SleepTimer
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package voice.app
|
||||
|
||||
import dev.zacsweers.metro.createGraphFactory
|
||||
import voice.app.injection.App
|
||||
import voice.app.injection.AppGraph
|
||||
import voice.app.di.App
|
||||
import voice.app.di.AppGraph
|
||||
|
||||
class TestApp : App() {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.DependencyGraph
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import voice.app.injection.AppGraph
|
||||
import voice.app.di.AppGraph
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@DependencyGraph(
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<application
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:name=".injection.App"
|
||||
android:name=".di.App"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:appCategory="audio"
|
||||
android:label="@string/app_name"
|
||||
@@ -37,10 +37,21 @@
|
||||
android:name="com.google.android.gms.car.application.theme"
|
||||
android:resource="@style/Theme.Material3.DayNight" />
|
||||
|
||||
<activity
|
||||
<activity-alias
|
||||
android:name=".features.MainActivity"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.app.features
|
||||
package voice.app
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
@@ -18,10 +18,11 @@ import androidx.navigation3.ui.NavDisplay
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Inject
|
||||
import voice.app.StartDestinationProvider
|
||||
import voice.core.common.compose.VoiceTheme
|
||||
import voice.app.navigation.NavEntryResolver
|
||||
import voice.app.navigation.StartDestinationProvider
|
||||
import voice.core.common.rootGraphAs
|
||||
import voice.core.logging.core.Logger
|
||||
import voice.core.ui.VoiceTheme
|
||||
import voice.features.review.ReviewFeature
|
||||
import voice.navigation.Destination
|
||||
import voice.navigation.NavigationCommand
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.app.injection
|
||||
package voice.app.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
@@ -8,7 +8,6 @@ import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.serialization.json.Json
|
||||
import voice.app.misc.AppInfoProviderImpl
|
||||
import voice.app.misc.MainActivityIntentProviderImpl
|
||||
@@ -48,10 +47,7 @@ object AndroidModule {
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun dispatcherProvider(): DispatcherProvider {
|
||||
return DispatcherProvider(
|
||||
main = Dispatchers.Main,
|
||||
io = Dispatchers.IO,
|
||||
)
|
||||
return DispatcherProvider()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.app.injection
|
||||
package voice.app.di
|
||||
|
||||
import android.app.Application
|
||||
import androidx.annotation.VisibleForTesting
|
||||
@@ -12,12 +12,12 @@ import dev.zacsweers.metro.createGraphFactory
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import voice.app.features.widget.TriggerWidgetOnChange
|
||||
import voice.core.common.DARK_THEME_SETTABLE
|
||||
import voice.core.common.pref.DarkThemeStore
|
||||
import voice.core.common.rootGraph
|
||||
import voice.core.data.store.DarkThemeStore
|
||||
import voice.core.scanner.MediaScanTrigger
|
||||
import voice.features.sleepTimer.AutoEnableSleepTimer
|
||||
import voice.core.sleeptimer.AutoEnableSleepTimer
|
||||
import voice.core.ui.DARK_THEME_SETTABLE
|
||||
import voice.features.widget.TriggerWidgetOnChange
|
||||
|
||||
open class App : Application() {
|
||||
|
||||
10
app/src/main/kotlin/voice/app/di/AppGraph.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package voice.app.di
|
||||
|
||||
import voice.app.features.widget.BaseWidgetProvider
|
||||
import voice.features.widget.WidgetGraph
|
||||
|
||||
interface AppGraph : WidgetGraph {
|
||||
|
||||
fun inject(target: App)
|
||||
override fun inject(target: BaseWidgetProvider)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.app.injection
|
||||
package voice.app.di
|
||||
|
||||
import android.app.Application
|
||||
import dev.zacsweers.metro.AppScope
|
||||
@@ -1,10 +0,0 @@
|
||||
package voice.app.injection
|
||||
|
||||
import voice.app.features.MainActivity
|
||||
import voice.app.features.widget.BaseWidgetProvider
|
||||
|
||||
interface AppGraph {
|
||||
|
||||
fun inject(target: App)
|
||||
fun inject(target: BaseWidgetProvider)
|
||||
}
|
||||
@@ -6,8 +6,5 @@ import voice.core.common.AppInfoProvider
|
||||
|
||||
@Inject
|
||||
class AppInfoProviderImpl : AppInfoProvider {
|
||||
|
||||
override val applicationID = BuildConfig.APPLICATION_ID
|
||||
|
||||
override val versionName: String = BuildConfig.VERSION_NAME
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package voice.app.misc
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import dev.zacsweers.metro.Inject
|
||||
import voice.app.features.MainActivity
|
||||
import voice.app.MainActivity
|
||||
import voice.core.playback.notification.MainActivityIntentProvider
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.app.features
|
||||
package voice.app.navigation
|
||||
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavEntry
|
||||
@@ -1,15 +1,15 @@
|
||||
package voice.app
|
||||
package voice.app.navigation
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.datastore.core.DataStore
|
||||
import dev.zacsweers.metro.Inject
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import voice.app.features.MainActivity.Companion.NI_GO_TO_BOOK
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.common.pref.OnboardingCompletedStore
|
||||
import voice.app.MainActivity
|
||||
import voice.core.data.BookId
|
||||
import voice.core.data.folders.AudiobookFolders
|
||||
import voice.core.data.store.CurrentBookStore
|
||||
import voice.core.data.store.OnboardingCompletedStore
|
||||
import voice.core.playback.PlayerController
|
||||
import voice.navigation.Destination
|
||||
|
||||
@@ -29,7 +29,7 @@ class StartDestinationProvider(
|
||||
return listOf(Destination.OnboardingWelcome)
|
||||
}
|
||||
|
||||
val goToBook = intent.getBooleanExtra(NI_GO_TO_BOOK, false)
|
||||
val goToBook = intent.getBooleanExtra(MainActivity.Companion.NI_GO_TO_BOOK, false)
|
||||
if (goToBook) {
|
||||
val bookId = runBlocking { currentBookStore.data.first() }
|
||||
if (bookId != null) {
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M12 8c1.1 0 2-0.9 2-2s-0.9-2-2-2-2 0.9-2 2 0.9 2 2 2zm0 2c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm0 6c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M23,12H17V10L20.39,6H17V4H23V6L19.62,10H23V12M15,16H9V14L12.39,10H9V8H15V10L11.62,14H15V16M7,20H1V18L4.39,14H1V12H7V14L3.62,18H7V20Z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/coverImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/content_cover" />
|
||||
|
||||
<voice.app.features.imagepicker.CropOverlay
|
||||
android:id="@+id/cropOverlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="colorAccentDark" format="reference" />
|
||||
</resources>
|
||||
@@ -1,11 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- copied private resources from appcompat -->
|
||||
<color name="copy_primary_text_disabled_material_dark">#4Dffffff</color>
|
||||
<color name="copy_primary_text_default_material_dark">#ffffffff</color>
|
||||
<color name="copy_background_material_dark">@color/material_grey_850</color>
|
||||
<color name="copy_secondary_text_disabled_material_dark">#36ffffff</color>
|
||||
<color name="copy_secondary_text_default_material_dark">#b3ffffff</color>
|
||||
|
||||
<color name="ic_shortcut_play_background">#FFFFFF</color>
|
||||
</resources>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
android:shortcutShortLabel="@string/play_current">
|
||||
<intent
|
||||
android:action="playCurrent"
|
||||
android:targetClass="voice.app.features.MainActivity"
|
||||
android:targetClass="voice.app.MainActivity"
|
||||
android:targetPackage="de.ph1b.audiobook" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
|
||||
@@ -11,6 +11,7 @@ import io.kotest.matchers.collections.shouldContainAll
|
||||
import io.kotest.matchers.collections.shouldContainExactly
|
||||
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
|
||||
import org.junit.Test
|
||||
import voice.app.navigation.NavEntryResolver
|
||||
import voice.navigation.Destination
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package voice.app.misc
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.junit.Test
|
||||
import voice.core.common.formatTime
|
||||
import voice.core.ui.formatTime
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class FormatTimeKtTest {
|
||||
|
||||
@@ -1,31 +1,10 @@
|
||||
plugins {
|
||||
id("voice.library")
|
||||
id("voice.compose")
|
||||
alias(libs.plugins.metro)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
androidResources {
|
||||
enable = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.strings)
|
||||
implementation(libs.appCompat)
|
||||
implementation(libs.material)
|
||||
api(libs.immutable)
|
||||
api(libs.datastore)
|
||||
implementation(libs.androidxCore)
|
||||
api(libs.navigation3.runtime)
|
||||
implementation(libs.lifecycle.viewmodel.compose)
|
||||
implementation(libs.serialization.json)
|
||||
implementation(libs.androidxCore)
|
||||
|
||||
testImplementation(kotlin("reflect"))
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.androidX.test.core)
|
||||
testImplementation(libs.androidX.test.junit)
|
||||
testImplementation(libs.androidX.test.runner)
|
||||
testImplementation(libs.robolectric)
|
||||
testImplementation(libs.bundles.testing.jvm)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package voice.core.common
|
||||
|
||||
interface AppInfoProvider {
|
||||
val applicationID: String
|
||||
val versionName: String
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package voice.core.common
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
data class DispatcherProvider(
|
||||
val main: CoroutineContext,
|
||||
val io: CoroutineContext,
|
||||
val main: CoroutineContext = Dispatchers.Main,
|
||||
val io: CoroutineContext = Dispatchers.IO,
|
||||
val mainImmediate: CoroutineContext = Dispatchers.Main.immediate,
|
||||
)
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun MainScope(dispatcherProvider: DispatcherProvider): CoroutineScope {
|
||||
return CoroutineScope(SupervisorJob() + dispatcherProvider.main)
|
||||
return CoroutineScope(SupervisorJob() + dispatcherProvider.mainImmediate)
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package voice.core.common.pref
|
||||
|
||||
import dev.zacsweers.metro.Qualifier
|
||||
|
||||
@Qualifier
|
||||
annotation class OnboardingCompletedStore
|
||||
|
||||
@Qualifier
|
||||
annotation class CurrentBookStore
|
||||
|
||||
@Qualifier
|
||||
annotation class AutoRewindAmountStore
|
||||
|
||||
@Qualifier
|
||||
annotation class SeekTimeStore
|
||||
|
||||
@Qualifier
|
||||
annotation class SleepTimerPreferenceStore
|
||||
|
||||
@Qualifier
|
||||
annotation class GridModeStore
|
||||
|
||||
@Qualifier
|
||||
annotation class DarkThemeStore
|
||||
|
||||
@Qualifier
|
||||
annotation class FadeOutStore
|
||||
@@ -11,6 +11,7 @@ kotlin {
|
||||
dependencies {
|
||||
api(projects.core.common)
|
||||
api(projects.core.documentfile)
|
||||
implementation(libs.metro.runtime)
|
||||
implementation(libs.appCompat)
|
||||
implementation(libs.androidxCore)
|
||||
implementation(libs.serialization.json)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package voice.core.common.grid
|
||||
package voice.core.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class GridMode {
|
||||
public enum class GridMode {
|
||||
LIST,
|
||||
GRID,
|
||||
FOLLOW_DEVICE,
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.common.sleepTimer
|
||||
package voice.core.data.sleeptimer
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import voice.core.common.serialization.LocalTimeSerializer
|
||||
@@ -7,7 +7,7 @@ import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@Serializable
|
||||
data class SleepTimerPreference(
|
||||
public data class SleepTimerPreference(
|
||||
/**
|
||||
* The custom sleep time duration
|
||||
*/
|
||||
@@ -22,8 +22,8 @@ data class SleepTimerPreference(
|
||||
val autoSleepEndTime: LocalTime,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val Default = SleepTimerPreference(
|
||||
public companion object {
|
||||
public val Default: SleepTimerPreference = SleepTimerPreference(
|
||||
autoSleepTimerEnabled = false,
|
||||
autoSleepStartTime = LocalTime.of(22, 0),
|
||||
autoSleepEndTime = LocalTime.of(6, 0),
|
||||
@@ -0,0 +1,33 @@
|
||||
package voice.core.data.store
|
||||
|
||||
import dev.zacsweers.metro.Qualifier
|
||||
|
||||
@Qualifier
|
||||
public annotation class OnboardingCompletedStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class CurrentBookStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class AutoRewindAmountStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class SeekTimeStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class SleepTimerPreferenceStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class GridModeStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class DarkThemeStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class FadeOutStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class AmountOfBatteryOptimizationRequestedStore
|
||||
|
||||
@Qualifier
|
||||
public annotation class ReviewDialogShownStore
|
||||
@@ -32,8 +32,8 @@ dependencies {
|
||||
api(projects.core.data.api)
|
||||
api(projects.core.common)
|
||||
api(projects.core.documentfile)
|
||||
implementation(projects.core.datastore)
|
||||
|
||||
implementation(libs.datastore)
|
||||
implementation(libs.androidxCore)
|
||||
implementation(libs.serialization.json)
|
||||
implementation(libs.coroutines.core)
|
||||
|
||||
@@ -9,7 +9,7 @@ import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import kotlinx.serialization.builtins.SetSerializer
|
||||
import voice.core.common.serialization.UriSerializer
|
||||
import voice.core.datastore.VoiceDataStoreFactory
|
||||
import voice.core.data.store.VoiceDataStoreFactory
|
||||
|
||||
@BindingContainer
|
||||
@ContributesTo(AppScope::class)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.datastore
|
||||
package voice.core.data.store
|
||||
|
||||
import androidx.datastore.core.Serializer
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
@@ -9,7 +9,7 @@ import kotlinx.serialization.json.encodeToStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class KotlinxDataStoreSerializer<T>(
|
||||
internal class KotlinxDataStoreSerializer<T>(
|
||||
override val defaultValue: T,
|
||||
private val json: Json,
|
||||
private val serializer: KSerializer<T>,
|
||||
@@ -1,10 +1,10 @@
|
||||
package voice.app.injection
|
||||
package voice.core.data.store
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import androidx.datastore.core.DataMigration
|
||||
|
||||
class PrefsDataMigration<T>(
|
||||
internal class PrefsDataMigration<T>(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val key: String,
|
||||
private val getFromSharedPreferences: () -> T,
|
||||
@@ -24,7 +24,7 @@ class PrefsDataMigration<T>(
|
||||
}
|
||||
}
|
||||
|
||||
fun booleanPrefsDataMigration(
|
||||
internal fun booleanPrefsDataMigration(
|
||||
sharedPreferences: SharedPreferences,
|
||||
key: String,
|
||||
): DataMigration<Boolean> {
|
||||
@@ -37,7 +37,7 @@ fun booleanPrefsDataMigration(
|
||||
)
|
||||
}
|
||||
|
||||
fun intPrefsDataMigration(
|
||||
internal fun intPrefsDataMigration(
|
||||
sharedPreferences: SharedPreferences,
|
||||
key: String,
|
||||
): DataMigration<Int> {
|
||||
@@ -1,5 +1,6 @@
|
||||
package voice.app.injection
|
||||
package voice.core.data.store
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.datastore.core.DataStore
|
||||
@@ -10,30 +11,23 @@ import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import kotlinx.serialization.builtins.nullable
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import voice.app.BuildConfig
|
||||
import voice.core.common.grid.GridMode
|
||||
import voice.core.common.pref.AutoRewindAmountStore
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.common.pref.DarkThemeStore
|
||||
import voice.core.common.pref.FadeOutStore
|
||||
import voice.core.common.pref.GridModeStore
|
||||
import voice.core.common.pref.OnboardingCompletedStore
|
||||
import voice.core.common.pref.SeekTimeStore
|
||||
import voice.core.common.pref.SleepTimerPreferenceStore
|
||||
import voice.core.common.sleepTimer.SleepTimerPreference
|
||||
import voice.core.data.BookId
|
||||
import voice.core.datastore.VoiceDataStoreFactory
|
||||
import voice.core.data.GridMode
|
||||
import voice.core.data.sleeptimer.SleepTimerPreference
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@BindingContainer
|
||||
@ContributesTo(AppScope::class)
|
||||
object PrefsModule {
|
||||
internal object StoreModule {
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun sharedPreferences(context: Context): SharedPreferences {
|
||||
return context.getSharedPreferences("${BuildConfig.APPLICATION_ID}_preferences", Context.MODE_PRIVATE)
|
||||
fun sharedPreferences(context: Application): SharedPreferences {
|
||||
return context.getSharedPreferences(
|
||||
"${context.packageName}_preferences",
|
||||
Context.MODE_PRIVATE,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -73,7 +67,7 @@ object PrefsModule {
|
||||
return factory.create(
|
||||
fileName = "fadeOut",
|
||||
defaultValue = 10.seconds,
|
||||
serializer = Duration.serializer(),
|
||||
serializer = Duration.Companion.serializer(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -96,9 +90,9 @@ object PrefsModule {
|
||||
@SleepTimerPreferenceStore
|
||||
fun sleepTimerPreference(factory: VoiceDataStoreFactory): DataStore<SleepTimerPreference> {
|
||||
return factory.create(
|
||||
serializer = SleepTimerPreference.serializer(),
|
||||
serializer = SleepTimerPreference.Companion.serializer(),
|
||||
fileName = "sleepTime3",
|
||||
defaultValue = SleepTimerPreference.Default,
|
||||
defaultValue = SleepTimerPreference.Companion.Default,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -146,4 +140,18 @@ object PrefsModule {
|
||||
defaultValue = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
@AmountOfBatteryOptimizationRequestedStore
|
||||
fun amountOfBatteryOptimizationsRequestedStore(factory: VoiceDataStoreFactory): DataStore<Int> {
|
||||
return factory.int("amountOfBatteryOptimizationsRequestedStore", 0)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
@ReviewDialogShownStore
|
||||
fun reviewDialogShown(factory: VoiceDataStoreFactory): DataStore<Boolean> {
|
||||
return factory.create(Boolean.serializer(), false, "reviewDialogShown")
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package voice.core.datastore
|
||||
package voice.core.data.store
|
||||
|
||||
import android.content.Context
|
||||
import android.app.Application
|
||||
import androidx.datastore.core.DataMigration
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
@@ -11,9 +11,9 @@ import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Inject
|
||||
class VoiceDataStoreFactory(
|
||||
internal class VoiceDataStoreFactory(
|
||||
private val json: Json,
|
||||
private val context: Context,
|
||||
private val context: Application,
|
||||
) {
|
||||
|
||||
fun <T> create(
|
||||
@@ -1,5 +1,6 @@
|
||||
package voice.app.injection
|
||||
package vocie.core.data.store
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
@@ -12,7 +13,9 @@ import kotlinx.serialization.json.Json
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import voice.core.datastore.VoiceDataStoreFactory
|
||||
import voice.core.data.store.VoiceDataStoreFactory
|
||||
import voice.core.data.store.booleanPrefsDataMigration
|
||||
import voice.core.data.store.intPrefsDataMigration
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DataMigrationTests {
|
||||
@@ -22,7 +25,7 @@ class DataMigrationTests {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val context = ApplicationProvider.getApplicationContext<Application>()
|
||||
sharedPreferences = context.getSharedPreferences("de.ph1b.audiobook_preferences", Context.MODE_PRIVATE)
|
||||
factory = VoiceDataStoreFactory(Json { ignoreUnknownKeys = true }, context)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.app.injection
|
||||
package vocie.core.data.store
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
@@ -18,12 +18,14 @@ import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import voice.core.common.grid.GridMode
|
||||
import voice.core.common.pref.AutoRewindAmountStore
|
||||
import voice.core.common.pref.DarkThemeStore
|
||||
import voice.core.common.pref.GridModeStore
|
||||
import voice.core.common.pref.SeekTimeStore
|
||||
import voice.core.datastore.VoiceDataStoreFactory
|
||||
import voice.core.common.AppInfoProvider
|
||||
import voice.core.data.GridMode
|
||||
import voice.core.data.store.AutoRewindAmountStore
|
||||
import voice.core.data.store.DarkThemeStore
|
||||
import voice.core.data.store.GridModeStore
|
||||
import voice.core.data.store.SeekTimeStore
|
||||
import voice.core.data.store.VoiceDataStoreFactory
|
||||
import voice.core.data.store.intPrefsDataMigration
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@DependencyGraph(
|
||||
@@ -46,6 +48,16 @@ interface MigrationTestGraph {
|
||||
@get:Provides
|
||||
val application: Application get() = ApplicationProvider.getApplicationContext()
|
||||
|
||||
@get:Provides
|
||||
val json: Json get() = Json.Default
|
||||
|
||||
@get:Provides
|
||||
val appInfoProvider: AppInfoProvider
|
||||
get() = object : AppInfoProvider {
|
||||
override val versionName: String
|
||||
get() = "1.2.3"
|
||||
}
|
||||
|
||||
val sharedPreferences: SharedPreferences
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import dev.zacsweers.metro.DependencyGraph
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import dev.zacsweers.metro.createGraphFactory
|
||||
import voice.core.common.pref.DarkThemeStore
|
||||
import voice.core.data.store.DarkThemeStore
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@DependencyGraph(
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
plugins {
|
||||
id("voice.library")
|
||||
alias(libs.plugins.metro)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(libs.serialization.json)
|
||||
api(libs.datastore)
|
||||
}
|
||||
@@ -4,6 +4,5 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.common)
|
||||
implementation(libs.androidxCore)
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.guava.asDeferred
|
||||
import kotlinx.coroutines.launch
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.data.BookId
|
||||
import voice.core.data.ChapterId
|
||||
import voice.core.data.repo.BookRepository
|
||||
import voice.core.data.store.CurrentBookStore
|
||||
import voice.core.logging.core.Logger
|
||||
import voice.core.playback.misc.Decibel
|
||||
import voice.core.playback.session.CustomCommand
|
||||
|
||||
@@ -11,14 +11,14 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import voice.core.common.pref.AutoRewindAmountStore
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.common.pref.SeekTimeStore
|
||||
import voice.core.data.BookContent
|
||||
import voice.core.data.BookId
|
||||
import voice.core.data.Chapter
|
||||
import voice.core.data.repo.BookRepository
|
||||
import voice.core.data.repo.ChapterRepo
|
||||
import voice.core.data.store.AutoRewindAmountStore
|
||||
import voice.core.data.store.CurrentBookStore
|
||||
import voice.core.data.store.SeekTimeStore
|
||||
import voice.core.logging.core.Logger
|
||||
import voice.core.playback.misc.Decibel
|
||||
import voice.core.playback.misc.VolumeGain
|
||||
|
||||
@@ -24,10 +24,10 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.guava.await
|
||||
import kotlinx.coroutines.guava.future
|
||||
import kotlinx.coroutines.launch
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.data.Book
|
||||
import voice.core.data.BookId
|
||||
import voice.core.data.repo.BookRepository
|
||||
import voice.core.data.store.CurrentBookStore
|
||||
import voice.core.logging.core.Logger
|
||||
import voice.core.playback.player.VoicePlayer
|
||||
import voice.core.playback.session.search.BookSearchHandler
|
||||
|
||||
@@ -9,7 +9,6 @@ import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
|
||||
import dev.zacsweers.metro.Inject
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.data.Book
|
||||
import voice.core.data.BookComparator
|
||||
import voice.core.data.BookContent
|
||||
@@ -18,6 +17,7 @@ import voice.core.data.Chapter
|
||||
import voice.core.data.repo.BookContentRepo
|
||||
import voice.core.data.repo.BookRepository
|
||||
import voice.core.data.repo.ChapterRepo
|
||||
import voice.core.data.store.CurrentBookStore
|
||||
import voice.core.data.toUri
|
||||
import java.io.File
|
||||
import voice.core.strings.R as StringsR
|
||||
|
||||
@@ -4,10 +4,10 @@ import android.provider.MediaStore
|
||||
import androidx.datastore.core.DataStore
|
||||
import dev.zacsweers.metro.Inject
|
||||
import kotlinx.coroutines.flow.first
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.data.Book
|
||||
import voice.core.data.BookId
|
||||
import voice.core.data.repo.BookRepository
|
||||
import voice.core.data.store.CurrentBookStore
|
||||
import voice.core.logging.core.Logger
|
||||
|
||||
@Inject
|
||||
|
||||
|
Before Width: | Height: | Size: 782 B After Width: | Height: | Size: 782 B |
|
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 496 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -5,6 +5,5 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.remoteconfig.core)
|
||||
implementation(projects.core.common)
|
||||
implementation(libs.firebase.remoteconfig)
|
||||
}
|
||||
|
||||
@@ -5,5 +5,4 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.remoteconfig.core)
|
||||
implementation(projects.core.common)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ kotlin {
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.data.api)
|
||||
implementation(projects.core.common)
|
||||
|
||||
implementation(libs.slf4j.noop)
|
||||
implementation(libs.jebml)
|
||||
|
||||
11
core/sleeptimer/api/build.gradle.kts
Normal file
@@ -0,0 +1,11 @@
|
||||
plugins {
|
||||
id("voice.library")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.androidX.test.core)
|
||||
testImplementation(libs.androidX.test.junit)
|
||||
testImplementation(libs.androidX.test.runner)
|
||||
testImplementation(libs.robolectric)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.playback.session
|
||||
package voice.core.sleeptimer
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlin.time.Duration
|
||||
@@ -7,4 +7,5 @@ interface SleepTimer {
|
||||
val leftSleepTimeFlow: Flow<Duration>
|
||||
fun sleepTimerActive(): Boolean
|
||||
fun setActive(enable: Boolean)
|
||||
fun setActive(sleepTime: Duration)
|
||||
}
|
||||
26
core/sleeptimer/impl/build.gradle.kts
Normal file
@@ -0,0 +1,26 @@
|
||||
plugins {
|
||||
id("voice.library")
|
||||
alias(libs.plugins.metro)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.sleeptimer.api)
|
||||
implementation(projects.core.data.api)
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.playback)
|
||||
implementation(projects.core.logging.core)
|
||||
|
||||
implementation(libs.datastore)
|
||||
implementation(libs.androidxCore)
|
||||
implementation(libs.seismic)
|
||||
implementation(libs.appCompat)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.mockk)
|
||||
testImplementation(libs.koTest.assert)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.androidX.test.core)
|
||||
testImplementation(libs.androidX.test.junit)
|
||||
testImplementation(libs.androidX.test.runner)
|
||||
testImplementation(libs.robolectric)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.features.sleepTimer
|
||||
package voice.core.sleeptimer
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import dev.zacsweers.metro.Inject
|
||||
@@ -7,15 +7,14 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import voice.core.common.DispatcherProvider
|
||||
import voice.core.common.MainScope
|
||||
import voice.core.common.pref.CurrentBookStore
|
||||
import voice.core.common.pref.SleepTimerPreferenceStore
|
||||
import voice.core.common.sleepTimer.SleepTimerPreference
|
||||
import voice.core.data.BookId
|
||||
import voice.core.data.repo.BookRepository
|
||||
import voice.core.data.repo.BookmarkRepo
|
||||
import voice.core.data.sleeptimer.SleepTimerPreference
|
||||
import voice.core.data.store.CurrentBookStore
|
||||
import voice.core.data.store.SleepTimerPreferenceStore
|
||||
import voice.core.playback.playstate.PlayStateManager
|
||||
import voice.core.playback.playstate.PlayStateManager.PlayState.Playing
|
||||
import voice.core.playback.session.SleepTimer
|
||||
import java.time.Clock
|
||||
|
||||
@Inject
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.features.sleepTimer
|
||||
package voice.core.sleeptimer
|
||||
|
||||
import java.time.LocalTime
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.features.sleepTimer
|
||||
package voice.core.sleeptimer
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.SensorManager
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.features.sleepTimer
|
||||
package voice.core.sleeptimer
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
@@ -14,9 +14,9 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import voice.core.common.pref.FadeOutStore
|
||||
import voice.core.common.pref.SleepTimerPreferenceStore
|
||||
import voice.core.common.sleepTimer.SleepTimerPreference
|
||||
import voice.core.data.sleeptimer.SleepTimerPreference
|
||||
import voice.core.data.store.FadeOutStore
|
||||
import voice.core.data.store.SleepTimerPreferenceStore
|
||||
import voice.core.logging.core.Logger
|
||||
import voice.core.playback.PlayerController
|
||||
import voice.core.playback.playstate.PlayStateManager
|
||||
@@ -24,12 +24,12 @@ import voice.core.playback.playstate.PlayStateManager.PlayState.Playing
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import voice.core.playback.session.SleepTimer as PlaybackSleepTimer
|
||||
import voice.core.sleeptimer.SleepTimer as SleepTimerApi
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
@Inject
|
||||
class SleepTimer(
|
||||
class SleepTimerImpl(
|
||||
private val playStateManager: PlayStateManager,
|
||||
private val shakeDetector: ShakeDetector,
|
||||
@SleepTimerPreferenceStore
|
||||
@@ -37,7 +37,7 @@ class SleepTimer(
|
||||
private val playerController: PlayerController,
|
||||
@FadeOutStore
|
||||
private val fadeOutStore: DataStore<Duration>,
|
||||
) : PlaybackSleepTimer {
|
||||
) : SleepTimerApi {
|
||||
|
||||
private val scope = MainScope()
|
||||
|
||||
@@ -68,7 +68,7 @@ class SleepTimer(
|
||||
}
|
||||
}
|
||||
|
||||
fun setActive(sleepTime: Duration) {
|
||||
override fun setActive(sleepTime: Duration) {
|
||||
Logger.i("Starting sleepTimer. Pause in $sleepTime.")
|
||||
leftSleepTime = sleepTime
|
||||
playerController.setVolume(1F)
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.features.sleepTimer
|
||||
package voice.core.sleeptimer
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import io.mockk.coEvery
|
||||
@@ -20,13 +20,12 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import voice.core.common.DispatcherProvider
|
||||
import voice.core.common.sleepTimer.SleepTimerPreference
|
||||
import voice.core.data.Book
|
||||
import voice.core.data.BookId
|
||||
import voice.core.data.repo.BookRepository
|
||||
import voice.core.data.repo.BookmarkRepo
|
||||
import voice.core.data.sleeptimer.SleepTimerPreference
|
||||
import voice.core.playback.playstate.PlayStateManager
|
||||
import voice.core.playback.session.SleepTimer
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
@@ -63,7 +62,7 @@ class AutoEnableSleepTimerTest {
|
||||
bookRepository = mockk(relaxUnitFun = true)
|
||||
currentBookStore = mockk<DataStore<BookId?>>(relaxUnitFun = true)
|
||||
|
||||
val dispatcherProvider = DispatcherProvider(main = testDispatcher, testDispatcher)
|
||||
val dispatcherProvider = DispatcherProvider(testDispatcher, testDispatcher, testDispatcher)
|
||||
autoEnableSleepTimer = AutoEnableSleepTimer(
|
||||
sleepTimerPreferenceStore = sleepTimerPreferenceStore,
|
||||
dispatcherProvider = dispatcherProvider,
|
||||
@@ -122,7 +121,7 @@ class AutoEnableSleepTimerTest {
|
||||
playStateFlow.value = PlayStateManager.PlayState.Playing
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any()) }
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any<Boolean>()) }
|
||||
coVerify(exactly = 0) { bookmarkRepo.addBookmarkAtBookPosition(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@@ -138,7 +137,7 @@ class AutoEnableSleepTimerTest {
|
||||
playStateFlow.value = PlayStateManager.PlayState.Playing
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any()) }
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any<Boolean>()) }
|
||||
coVerify(exactly = 0) { bookmarkRepo.addBookmarkAtBookPosition(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@@ -156,7 +155,7 @@ class AutoEnableSleepTimerTest {
|
||||
playStateFlow.value = PlayStateManager.PlayState.Playing
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any()) }
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any<Boolean>()) }
|
||||
coVerify(exactly = 0) { bookmarkRepo.addBookmarkAtBookPosition(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@@ -173,7 +172,7 @@ class AutoEnableSleepTimerTest {
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { sleepTimerPreferenceStore.data }
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any()) }
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any<Boolean>()) }
|
||||
coVerify(exactly = 0) { bookmarkRepo.addBookmarkAtBookPosition(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@@ -233,7 +232,7 @@ class AutoEnableSleepTimerTest {
|
||||
}
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any()) }
|
||||
coVerify(exactly = 0) { sleepTimer.setActive(any<Boolean>()) }
|
||||
coVerify(exactly = 0) { bookmarkRepo.addBookmarkAtBookPosition(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.features.sleepTimer
|
||||
package voice.core.sleeptimer
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.junit.Test
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.features.sleepTimer
|
||||
package voice.core.sleeptimer
|
||||
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
17
core/ui/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id("voice.library")
|
||||
id("voice.compose")
|
||||
alias(libs.plugins.metro)
|
||||
}
|
||||
|
||||
android {
|
||||
androidResources.enable = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(libs.datastore)
|
||||
implementation(libs.material)
|
||||
implementation(projects.core.data.api)
|
||||
implementation(projects.core.strings)
|
||||
implementation(libs.lifecycle.viewmodel.compose)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.common
|
||||
package voice.core.ui
|
||||
|
||||
import android.os.Build
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.common
|
||||
package voice.core.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.common
|
||||
package voice.core.ui
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.common.grid
|
||||
package voice.core.ui
|
||||
|
||||
import android.content.Context
|
||||
import dev.zacsweers.metro.Inject
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.common.compose
|
||||
package voice.core.ui
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import java.io.File
|
||||
@@ -1,6 +1,6 @@
|
||||
@file:Suppress("ktlint:standard:filename")
|
||||
|
||||
package voice.core.common.compose
|
||||
package voice.core.ui
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.ui.unit.Dp
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.common.compose
|
||||
package voice.core.ui
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||
@@ -15,7 +15,6 @@ import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import voice.core.common.R as CommonR
|
||||
import voice.core.strings.R as StringsR
|
||||
|
||||
@Composable
|
||||
@@ -53,7 +52,7 @@ fun PlayButton(
|
||||
private fun rememberPlayIconPainter(playing: Boolean): Painter {
|
||||
return rememberAnimatedVectorPainter(
|
||||
animatedImageVector = AnimatedImageVector.animatedVectorResource(
|
||||
id = CommonR.drawable.avd_pause_to_play,
|
||||
id = R.drawable.avd_pause_to_play,
|
||||
),
|
||||
atEnd = !playing,
|
||||
)
|
||||
@@ -1,9 +1,9 @@
|
||||
package voice.core.common.compose
|
||||
package voice.core.ui
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import voice.core.common.pref.DarkThemeStore
|
||||
import voice.core.data.store.DarkThemeStore
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
interface SharedGraph {
|
||||
@@ -1,4 +1,4 @@
|
||||
package voice.core.common.compose
|
||||
package voice.core.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -1,23 +1,17 @@
|
||||
package voice.core.common.compose
|
||||
package voice.core.ui
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import voice.core.common.DARK_THEME_SETTABLE
|
||||
import voice.core.common.rootGraphAs
|
||||
import androidx.compose.material3.MaterialTheme as Material3Theme
|
||||
|
||||
@Composable
|
||||
fun VoiceTheme(content: @Composable () -> Unit) {
|
||||
Material3Theme(
|
||||
MaterialTheme(
|
||||
colorScheme = if (isDarkTheme()) {
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
dynamicDarkColorScheme(LocalContext.current)
|
||||
@@ -35,15 +29,3 @@ fun VoiceTheme(content: @Composable () -> Unit) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun isDarkTheme(): Boolean {
|
||||
return if (DARK_THEME_SETTABLE) {
|
||||
val darkThemeFlow = remember {
|
||||
rootGraphAs<SharedGraph>().useDarkThemeStore.data
|
||||
}
|
||||
darkThemeFlow.collectAsState(initial = false, context = Dispatchers.Unconfined).value
|
||||
} else {
|
||||
isSystemInDarkTheme()
|
||||
}
|
||||
}
|
||||
20
core/ui/src/main/kotlin/voice/core/ui/isDarkTheme.kt
Normal file
@@ -0,0 +1,20 @@
|
||||
package voice.core.ui
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import voice.core.common.rootGraphAs
|
||||
|
||||
@Composable
|
||||
fun isDarkTheme(): Boolean {
|
||||
return if (DARK_THEME_SETTABLE) {
|
||||
val darkThemeFlow = remember {
|
||||
rootGraphAs<SharedGraph>().useDarkThemeStore.data
|
||||
}
|
||||
darkThemeFlow.collectAsState(initial = false, context = Dispatchers.Unconfined).value
|
||||
} else {
|
||||
isSystemInDarkTheme()
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 96 B After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 220 B After Width: | Height: | Size: 220 B |
|
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 76 B After Width: | Height: | Size: 76 B |
|
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 178 B |
|
Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 184 B |
|
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 88 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 274 B |
|
Before Width: | Height: | Size: 404 B After Width: | Height: | Size: 404 B |
|
Before Width: | Height: | Size: 86 B After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 334 B After Width: | Height: | Size: 334 B |
|
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 382 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 76 B After Width: | Height: | Size: 76 B |