From 3bc1dc2a9f0ccee7f44abc9bccb05737b6c9570a Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Mon, 18 Mar 2024 21:37:32 +0530 Subject: [PATCH 01/11] Update version name; --- app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7cf31851..dc822e65 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,7 +18,7 @@ android { defaultConfig { applicationId = "com.kafka.user" versionCode = 53 - versionName = libs.versions.versionname.toString() + versionName = "0.13.0" val properties = Properties() properties.load(project.rootProject.file("local.properties").inputStream()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 32160df4..fbdb2922 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,8 +36,6 @@ androidx-baselineprofile = "1.2.3" profileinstaller = "1.3.1" review = "2.0.1" -versionname = "0.13.0" - [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } From 4da2e82a23b13ee0c1d08233114c793174f0ead4 Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Tue, 19 Mar 2024 19:49:44 +0530 Subject: [PATCH 02/11] Release 0.14.0 --- app/build.gradle.kts | 4 ++-- build.gradle.kts | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dc822e65..0b7cbdd0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { defaultConfig { applicationId = "com.kafka.user" - versionCode = 53 - versionName = "0.13.0" + versionCode = 54 + versionName = "0.14.0" val properties = Properties() properties.load(project.rootProject.file("local.properties").inputStream()) diff --git a/build.gradle.kts b/build.gradle.kts index 57b5bce8..75162aa7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ + import com.android.build.gradle.BaseExtension -import com.diffplug.gradle.spotless.SpotlessExtension import org.jetbrains.kotlin.gradle.plugin.KaptExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask @@ -141,9 +141,3 @@ fun Project.configureAndroidProject() { } } } - -tasks.register("appVersionName") { - doLast { - println(libs.versions.versionname) - } -} From 7c7a0c18f5fc1aaeced52bff8fa88bc1a21f5a8d Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Fri, 22 Mar 2024 21:30:56 +0530 Subject: [PATCH 03/11] Fix a terrible bug with wrong google server ID; --- app/build.gradle.kts | 6 +++--- app/src/main/java/com/kafka/user/injection/AppModule.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0b7cbdd0..3a0d0c80 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { defaultConfig { applicationId = "com.kafka.user" - versionCode = 54 - versionName = "0.14.0" + versionCode = 55 + versionName = "0.15.0" val properties = Properties() properties.load(project.rootProject.file("local.properties").inputStream()) @@ -26,7 +26,7 @@ android { buildConfigField( "String", "GOOGLE_SERVER_CLIENT_ID", - properties["PIPELESS_AUTH_TOKEN"]?.toString() ?: System.getenv("PIPELESS_AUTH_TOKEN") + properties["GOOGLE_SERVER_CLIENT_ID"]?.toString() ?: System.getenv("GOOGLE_SERVER_CLIENT_ID") ) buildConfigField( "String", diff --git a/app/src/main/java/com/kafka/user/injection/AppModule.kt b/app/src/main/java/com/kafka/user/injection/AppModule.kt index 355d052e..b859e8e2 100644 --- a/app/src/main/java/com/kafka/user/injection/AppModule.kt +++ b/app/src/main/java/com/kafka/user/injection/AppModule.kt @@ -110,7 +110,7 @@ class AppModule { @Provides - fun provideGoogleClientIdProvider() = object : SecretsProvider { + fun provideSecretsProvider() = object : SecretsProvider { override val googleServerClientId: String = BuildConfig.GOOGLE_SERVER_CLIENT_ID override val pipelessAuthToken: String = BuildConfig.PIPELESS_AUTH_TOKEN } From 5139279a698b762ff8c09fc45dd8a03c09d60775 Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Tue, 9 Apr 2024 20:56:49 +0530 Subject: [PATCH 04/11] Updated deps; --- data/models/src/main/java/com/kafka/data/model/MediaType.kt | 4 ++-- gradle/libs.versions.toml | 4 ++-- .../src/main/java/org/kafka/ui/components/MessageBox.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/models/src/main/java/com/kafka/data/model/MediaType.kt b/data/models/src/main/java/com/kafka/data/model/MediaType.kt index f9c7443f..df9e15c9 100644 --- a/data/models/src/main/java/com/kafka/data/model/MediaType.kt +++ b/data/models/src/main/java/com/kafka/data/model/MediaType.kt @@ -1,6 +1,6 @@ package com.kafka.data.model sealed class MediaType { - object Text : MediaType() - object Audio : MediaType() + data object Text : MediaType() + data object Audio : MediaType() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fbdb2922..740732a2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ androidxlifecycle = "2.7.0" navigation = "2.7.7" coil = "2.6.0" compose-alpha = "2024.01.00-alpha01" -compose-bom = "2024.02.02" +compose-bom = "2024.04.00" constraintlayout = "1.1.0-alpha13" composecompiler = "1.5.10" coroutines = "1.8.0" @@ -127,7 +127,7 @@ fetch-okhttp = "androidx.tonyodev.fetch2okhttp:xfetch2okhttp:3.1.6" dagger-dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" } -google-bom = "com.google.firebase:firebase-bom:32.7.4" +google-bom = "com.google.firebase:firebase-bom:32.8.0" google-analytics = { module = "com.google.firebase:firebase-analytics" } google-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } google-dynamic_links = { module = "com.google.firebase:firebase-dynamic-links" } diff --git a/ui/components/src/main/java/org/kafka/ui/components/MessageBox.kt b/ui/components/src/main/java/org/kafka/ui/components/MessageBox.kt index 99abbd79..72768e07 100644 --- a/ui/components/src/main/java/org/kafka/ui/components/MessageBox.kt +++ b/ui/components/src/main/java/org/kafka/ui/components/MessageBox.kt @@ -25,7 +25,7 @@ fun MessageBox( modifier: Modifier = Modifier, icon: ImageVector? = null, onClick: () -> Unit = {}, - onIconClick: () -> Unit = {} + onIconClick: () -> Unit = onClick ) { Surface( modifier = modifier, From c751d504cd10c690b6ed560b936e02934b93241b Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Thu, 18 Apr 2024 22:32:37 +0530 Subject: [PATCH 05/11] Update kotlinx.serialization to 1.6.2; --- .../kafka/data/model/StringListSerializer.kt | 31 ++++++------------- .../kafka/data/model/SubjectListSerializer.kt | 27 ---------------- .../com/kafka/data/model/item/Metadata.kt | 5 ++- .../data/feature/item/ItemDetailMapper.kt | 14 +++++---- .../kafka/domain/observers/ObserveHomepage.kt | 2 -- gradle/libs.versions.toml | 14 ++++----- 6 files changed, 26 insertions(+), 67 deletions(-) delete mode 100644 data/models/src/main/java/com/kafka/data/model/SubjectListSerializer.kt diff --git a/data/models/src/main/java/com/kafka/data/model/StringListSerializer.kt b/data/models/src/main/java/com/kafka/data/model/StringListSerializer.kt index 247ce870..b20b193c 100644 --- a/data/models/src/main/java/com/kafka/data/model/StringListSerializer.kt +++ b/data/models/src/main/java/com/kafka/data/model/StringListSerializer.kt @@ -1,27 +1,14 @@ package com.kafka.data.model -import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder - -object StringListSerializer : KSerializer> { - override val descriptor: SerialDescriptor = String.serializer().descriptor - - override fun deserialize(decoder: Decoder): List { - val surrogate = try { - decoder.decodeSerializableValue(ListSerializer(String.serializer())) - .joinToString(",") - } catch (ex: Exception) { - decoder.decodeSerializableValue(String.serializer()) - } - return surrogate.split(",") - } - - override fun serialize(encoder: Encoder, value: List) { - val surrogate = value.joinToString(",") - encoder.encodeSerializableValue(String.serializer(), surrogate) - } +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonTransformingSerializer + +object StringListSerializer : + JsonTransformingSerializer>(ListSerializer(String.serializer())) { + // If response is not an array, then it is a single object that should be wrapped into the array + override fun transformDeserialize(element: JsonElement): JsonElement = + if (element !is JsonArray) JsonArray(listOf(element)) else element } diff --git a/data/models/src/main/java/com/kafka/data/model/SubjectListSerializer.kt b/data/models/src/main/java/com/kafka/data/model/SubjectListSerializer.kt deleted file mode 100644 index bcc85e0f..00000000 --- a/data/models/src/main/java/com/kafka/data/model/SubjectListSerializer.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.kafka.data.model - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder - -object SubjectListSerializer : KSerializer> { - override val descriptor: SerialDescriptor = String.serializer().descriptor - - override fun deserialize(decoder: Decoder): List { - val surrogate = try { - decoder.decodeSerializableValue(ListSerializer(String.serializer())) - .joinToString(";") - } catch (ex: Exception) { - decoder.decodeSerializableValue(String.serializer()) - } - return surrogate.split(";") - } - - override fun serialize(encoder: Encoder, value: List) { - val surrogate = value.joinToString(";") - encoder.encodeSerializableValue(String.serializer(), surrogate) - } -} diff --git a/data/models/src/main/java/com/kafka/data/model/item/Metadata.kt b/data/models/src/main/java/com/kafka/data/model/item/Metadata.kt index e76b1d07..a424ce0c 100644 --- a/data/models/src/main/java/com/kafka/data/model/item/Metadata.kt +++ b/data/models/src/main/java/com/kafka/data/model/item/Metadata.kt @@ -1,7 +1,6 @@ package com.kafka.data.model.item import com.kafka.data.model.StringListSerializer -import com.kafka.data.model.SubjectListSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -22,7 +21,7 @@ data class Metadata( @Serializable(with = StringListSerializer::class) val description: List? = null, @SerialName("subject") - @Serializable(with = SubjectListSerializer::class) + @Serializable(with = StringListSerializer::class) val subject: List? = null, @SerialName("identifier") val identifier: String, @@ -43,6 +42,6 @@ data class Metadata( @SerialName("year") val year: String? = null, @SerialName("language") - @Serializable(with = SubjectListSerializer::class) + @Serializable(with = StringListSerializer::class) val languages: List? = null, ) diff --git a/data/repo/src/main/java/com/kafka/data/feature/item/ItemDetailMapper.kt b/data/repo/src/main/java/com/kafka/data/feature/item/ItemDetailMapper.kt index 281d1368..689643af 100644 --- a/data/repo/src/main/java/com/kafka/data/feature/item/ItemDetailMapper.kt +++ b/data/repo/src/main/java/com/kafka/data/feature/item/ItemDetailMapper.kt @@ -20,7 +20,7 @@ class ItemDetailMapper @Inject constructor( return ItemDetail( itemId = from.metadata.identifier, - language = from.metadata.languages?.joinToString(), + language = from.metadata.languages?.map { it.split(";") }?.flatten()?.joinToString(), title = from.metadata.title?.dismissUpperCase(), description = from.metadata.description?.joinToString()?.format() ?: "", creator = from.metadata.creator?.take(5)?.joinToString()?.sanitizeForRoom(), @@ -30,11 +30,13 @@ class ItemDetailMapper @Inject constructor( coverImage = from.findCoverImage(), metadata = from.metadata.collection, primaryFile = from.files.primaryFile(from.metadata.mediatype)?.fileId, - subject = from.metadata.subject - ?.subList(0, from.metadata.subject!!.size.coerceAtMost(12)) - ?.map { it.substring(0, it.length.coerceAtMost(50)) } - ?.map { it.trim() } - ?.filter { it.isNotEmpty() }, + subject = run { + val subjects = from.metadata.subject?.map { it.split(";") }?.flatten() + subjects?.subList(0, subjects.size.coerceAtMost(12)) + ?.map { it.substring(0, it.length.coerceAtMost(50)) } + ?.map { it.trim() } + ?.filter { it.isNotEmpty() } + }, rating = itemDao.getOrNull(from.metadata.identifier)?.rating, ).also { insertFiles(from, it) diff --git a/domain/src/main/java/org/kafka/domain/observers/ObserveHomepage.kt b/domain/src/main/java/org/kafka/domain/observers/ObserveHomepage.kt index 15c2258f..a0707b72 100644 --- a/domain/src/main/java/org/kafka/domain/observers/ObserveHomepage.kt +++ b/domain/src/main/java/org/kafka/domain/observers/ObserveHomepage.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import org.kafka.base.CoroutineDispatchers -import org.kafka.base.debug import org.kafka.base.domain.SubjectInteractor import javax.inject.Inject @@ -23,7 +22,6 @@ class ObserveHomepage @Inject constructor( observeRecentItems.execute(Unit), homepageRepository.observeHomepageCollection(), ) { recentItems, collection -> - debug { "ObserveHomepage: collection=$collection" } val collectionWithRecentItems = collection.mapNotNull { when (it) { is HomepageCollection.RecentItems -> { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 740732a2..e8d17891 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] accompanist = "0.34.0" -agp = "8.3.0" +agp = "8.3.2" androidxhilt = "1.2.0" androidxlifecycle = "2.7.0" navigation = "2.7.7" @@ -8,11 +8,11 @@ coil = "2.6.0" compose-alpha = "2024.01.00-alpha01" compose-bom = "2024.04.00" constraintlayout = "1.1.0-alpha13" -composecompiler = "1.5.10" +composecompiler = "1.5.12" coroutines = "1.8.0" dagger = "2.51" icons = "1.0.0" -kotlin = "1.9.22" +kotlin = "1.9.23" kotlin-immutable = "0.3.7" material3 = "1.2.1" mixpanel = "7.0.0" @@ -21,18 +21,18 @@ paging = "3.2.1" retrofit = "2.9.0" room = "2.6.1" threetenbp = "1.5.2" -serialization = "1.5.0" +serialization = "1.6.3" exoplayer = "2.19.1" pdfviewer = "3.2.0-beta.1" compileSdk = "34" minSdk = "24" targetSdk = "33" -core-ktx = "1.12.0" +core-ktx = "1.13.0" androidx-test-ext-junit = "1.1.5" espresso-core = "3.5.1" uiautomator = "2.3.0" -benchmark-macro-junit4 = "1.2.3" -androidx-baselineprofile = "1.2.3" +benchmark-macro-junit4 = "1.2.4" +androidx-baselineprofile = "1.2.4" profileinstaller = "1.3.1" review = "2.0.1" From 092b2eedd8d6f4a936bade8ebb0977113408da03 Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Sat, 20 Apr 2024 20:30:49 +0530 Subject: [PATCH 06/11] Show login prompt on favorites screen; --- .gitignore | 1 + .idea/kotlinc.xml | 2 +- .../java/com/kafka/user/home/AppNavigation.kt | 1 + .../org/kafka/ui/components/MessageBox.kt | 20 ++++-- .../main/java/org/kafka/homepage/Homepage.kt | 2 +- .../java/org/kafka/library/LibraryScreen.kt | 6 +- .../library/favorites/FavoriteViewModel.kt | 23 +++++-- .../org/kafka/library/favorites/Favorites.kt | 64 +++++++++++-------- ui/library/src/main/res/values/strings.xml | 1 + 9 files changed, 80 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index ff8a3939..007a38e8 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ captures/ .idea/dictionaries .idea/libraries .idea/caches +.idea/kotlinc.xml # Keystore files # Uncomment the following line if you do not want to check your keystore files in. diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 8d81632f..fe63bb67 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/app/src/main/java/com/kafka/user/home/AppNavigation.kt b/app/src/main/java/com/kafka/user/home/AppNavigation.kt index 55a6632a..8bc158c2 100644 --- a/app/src/main/java/com/kafka/user/home/AppNavigation.kt +++ b/app/src/main/java/com/kafka/user/home/AppNavigation.kt @@ -141,6 +141,7 @@ private fun NavGraphBuilder.addLibraryRoot() { addPlayer(RootScreen.Library) addWebView(RootScreen.Library) addOnlineReader(RootScreen.Library) + addLogin(RootScreen.Library) } } diff --git a/ui/components/src/main/java/org/kafka/ui/components/MessageBox.kt b/ui/components/src/main/java/org/kafka/ui/components/MessageBox.kt index 72768e07..2aebd465 100644 --- a/ui/components/src/main/java/org/kafka/ui/components/MessageBox.kt +++ b/ui/components/src/main/java/org/kafka/ui/components/MessageBox.kt @@ -23,7 +23,8 @@ import ui.common.theme.theme.Dimens fun MessageBox( text: String, modifier: Modifier = Modifier, - icon: ImageVector? = null, + leadingIcon: ImageVector? = null, + trailingIcon: ImageVector? = null, onClick: () -> Unit = {}, onIconClick: () -> Unit = onClick ) { @@ -41,18 +42,27 @@ fun MessageBox( .fillMaxWidth() .clickable { onClick() } .padding(Dimens.Spacing16), - horizontalArrangement = Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.spacedBy(Dimens.Spacing12), verticalAlignment = Alignment.CenterVertically ) { + if (leadingIcon != null) { + IconResource( + imageVector = leadingIcon, + modifier = Modifier.size(Dimens.Spacing20), + tint = MaterialTheme.colorScheme.primary + ) + } + Text( text = text, style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.primary + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.weight(1f) ) - if (icon != null) { + if (trailingIcon != null) { IconResource( - imageVector = icon, + imageVector = trailingIcon, modifier = Modifier.size(Dimens.Spacing20), tint = MaterialTheme.colorScheme.primary, onClick = onIconClick diff --git a/ui/homepage/src/main/java/org/kafka/homepage/Homepage.kt b/ui/homepage/src/main/java/org/kafka/homepage/Homepage.kt index f8a67701..dd8a2106 100644 --- a/ui/homepage/src/main/java/org/kafka/homepage/Homepage.kt +++ b/ui/homepage/src/main/java/org/kafka/homepage/Homepage.kt @@ -163,7 +163,7 @@ private fun HomepageFeedItems( item(key = "search_prompt") { MessageBox( text = stringResource(R.string.find_many_more_on_the_search_page), - icon = Icons.ArrowForward, + trailingIcon = Icons.ArrowForward, onClick = { goToSearch() }, modifier = Modifier .padding(Dimens.Gutter) diff --git a/ui/library/src/main/java/org/kafka/library/LibraryScreen.kt b/ui/library/src/main/java/org/kafka/library/LibraryScreen.kt index 8976344b..f95bd8c3 100644 --- a/ui/library/src/main/java/org/kafka/library/LibraryScreen.kt +++ b/ui/library/src/main/java/org/kafka/library/LibraryScreen.kt @@ -19,7 +19,7 @@ import org.kafka.ui.components.scaffoldPadding fun LibraryScreen() { Scaffold { padding -> ProvideScaffoldPadding(padding = padding) { - val pagerState = rememberPagerState(pageCount = { LibraryTab.values().size }) + val pagerState = rememberPagerState(pageCount = { LibraryTab.entries.size }) Column( modifier = Modifier @@ -28,12 +28,12 @@ fun LibraryScreen() { ) { Tabs( pagerState = pagerState, - tabs = LibraryTab.values().map { it.name }.toPersistentList(), + tabs = LibraryTab.entries.map { it.name }.toPersistentList(), modifier = Modifier.fillMaxWidth() ) HorizontalPager(modifier = Modifier.fillMaxSize(), state = pagerState) { page -> - when (LibraryTab.values()[page]) { + when (LibraryTab.entries[page]) { LibraryTab.Favorites -> Favorites() LibraryTab.Downloads -> Downloads() } diff --git a/ui/library/src/main/java/org/kafka/library/favorites/FavoriteViewModel.kt b/ui/library/src/main/java/org/kafka/library/favorites/FavoriteViewModel.kt index 08b6125f..960ea800 100644 --- a/ui/library/src/main/java/org/kafka/library/favorites/FavoriteViewModel.kt +++ b/ui/library/src/main/java/org/kafka/library/favorites/FavoriteViewModel.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import org.kafka.analytics.logger.Analytics import org.kafka.base.extensions.stateInDefault +import org.kafka.domain.observers.ObserveUser import org.kafka.domain.observers.library.ObserveFavorites import org.kafka.navigation.Navigator import org.kafka.navigation.Screen @@ -22,6 +23,7 @@ import javax.inject.Inject class FavoriteViewModel @Inject constructor( observeFavorites: ObserveFavorites, preferencesStore: PreferencesStore, + observeUser: ObserveUser, private val analytics: Analytics, private val navigator: Navigator, ) : ViewModel() { @@ -34,13 +36,23 @@ class FavoriteViewModel @Inject constructor( val state: StateFlow = combine( observeFavorites.flow, layoutType.map { LayoutType.valueOf(it) }, - ) { favorites, layout -> - FavoriteViewState(favoriteItems = favorites, layoutType = layout) + observeUser.flow + ) { favorites, layout, user -> + FavoriteViewState( + favoriteItems = favorites, + layoutType = layout, + isUserLoggedIn = user != null + ) }.stateInDefault( scope = viewModelScope, initialValue = FavoriteViewState(), ) + init { + observeFavorites(Unit) + observeUser(ObserveUser.Params()) + } + fun updateLayoutType(layoutType: LayoutType) { this.layoutType.value = layoutType.name } @@ -50,13 +62,14 @@ class FavoriteViewModel @Inject constructor( navigator.navigate(Screen.ItemDetail.createRoute(navigator.currentRoot.value, itemId)) } - init { - observeFavorites(Unit) + fun goToLogin() { + navigator.navigate(Screen.Login.createRoute(navigator.currentRoot.value)) } } @Immutable data class FavoriteViewState( val favoriteItems: List? = null, - val layoutType: LayoutType = LayoutType.List + val layoutType: LayoutType = LayoutType.List, + val isUserLoggedIn: Boolean = false ) diff --git a/ui/library/src/main/java/org/kafka/library/favorites/Favorites.kt b/ui/library/src/main/java/org/kafka/library/favorites/Favorites.kt index 8009b13a..16d6ef1f 100644 --- a/ui/library/src/main/java/org/kafka/library/favorites/Favorites.kt +++ b/ui/library/src/main/java/org/kafka/library/favorites/Favorites.kt @@ -1,6 +1,7 @@ package org.kafka.library.favorites import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -17,10 +18,12 @@ import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.kafka.data.entities.Item +import org.kafka.common.image.Icons import org.kafka.common.plus import org.kafka.common.snackbar.UiMessage import org.kafka.common.widgets.FullScreenMessage import org.kafka.favorites.R +import org.kafka.ui.components.MessageBox import org.kafka.ui.components.bottomScaffoldPadding import org.kafka.ui.components.item.Item import org.kafka.ui.components.item.LayoutType @@ -31,32 +34,43 @@ import ui.common.theme.theme.Dimens internal fun Favorites(favoriteViewModel: FavoriteViewModel = hiltViewModel()) { val favoriteViewState by favoriteViewModel.state.collectAsStateWithLifecycle() - favoriteViewState.favoriteItems?.let { items -> - if (items.isEmpty()) { - FullScreenMessage(UiMessage(stringResource(id = R.string.no_favorites_items_message))) - } else { - when (favoriteViewState.layoutType) { - LayoutType.List -> FavoriteItemList( - favoriteItems = items, - openItemDetail = favoriteViewModel::openItemDetail, - header = { - LayoutType( - layoutType = favoriteViewState.layoutType, - changeViewType = favoriteViewModel::updateLayoutType - ) - } - ) + Column { + if (!favoriteViewState.isUserLoggedIn) { + MessageBox( + text = stringResource(R.string.log_in_to_sync_favorite), + trailingIcon = Icons.ArrowForward, + modifier = Modifier.padding(Dimens.Spacing16), + onClick = favoriteViewModel::goToLogin + ) + } + + favoriteViewState.favoriteItems?.let { items -> + if (items.isEmpty()) { + FullScreenMessage(UiMessage(stringResource(id = R.string.no_favorites_items_message))) + } else { + when (favoriteViewState.layoutType) { + LayoutType.List -> FavoriteItemList( + favoriteItems = items, + openItemDetail = favoriteViewModel::openItemDetail, + header = { + LayoutType( + layoutType = favoriteViewState.layoutType, + changeViewType = favoriteViewModel::updateLayoutType + ) + } + ) - LayoutType.Grid -> FavoriteItemGrid( - favoriteItems = items, - openItemDetail = favoriteViewModel::openItemDetail, - header = { - LayoutType( - layoutType = favoriteViewState.layoutType, - changeViewType = favoriteViewModel::updateLayoutType - ) - } - ) + LayoutType.Grid -> FavoriteItemGrid( + favoriteItems = items, + openItemDetail = favoriteViewModel::openItemDetail, + header = { + LayoutType( + layoutType = favoriteViewState.layoutType, + changeViewType = favoriteViewModel::updateLayoutType + ) + } + ) + } } } } diff --git a/ui/library/src/main/res/values/strings.xml b/ui/library/src/main/res/values/strings.xml index 1e1e6f63..602f7cf4 100644 --- a/ui/library/src/main/res/values/strings.xml +++ b/ui/library/src/main/res/values/strings.xml @@ -5,4 +5,5 @@ No downloaded items Your downloaded items will appear here Downloads are saved on your device. They can be opened or shared freely using any files app. + Log in to sync your favorite items to your account. From c4455ba45135487c662141143c054d0d8de650fd Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Sun, 21 Apr 2024 12:50:39 +0530 Subject: [PATCH 07/11] Fix compile errors --- .../src/main/java/org/kafka/base/Combine.kt | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 base/domain/src/main/java/org/kafka/base/Combine.kt diff --git a/base/domain/src/main/java/org/kafka/base/Combine.kt b/base/domain/src/main/java/org/kafka/base/Combine.kt new file mode 100644 index 00000000..5ef62a65 --- /dev/null +++ b/base/domain/src/main/java/org/kafka/base/Combine.kt @@ -0,0 +1,133 @@ +@file:Suppress("UNCHECKED_CAST") + +package org.kafka.base + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + transform: suspend (T1, T2, T3, T4, T5, T6) -> R, +): Flow = combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> -> + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + ) +} + +fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + flow7: Flow, + transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R, +): Flow = combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> -> + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + args[6] as T7, + ) +} + +fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + flow7: Flow, + flow8: Flow, + transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R, +): Flow = combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { args: Array<*> -> + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + args[6] as T7, + args[7] as T8, + ) +} + +fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + flow7: Flow, + flow8: Flow, + flow9: Flow, + transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R, +): Flow = + combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9) { args: Array<*> -> + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + args[6] as T7, + args[7] as T8, + args[8] as T9, + ) + } + +fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + flow7: Flow, + flow8: Flow, + flow9: Flow, + flow10: Flow, + transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> R, +): Flow = combine( + flow, + flow2, + flow3, + flow4, + flow5, + flow6, + flow7, + flow8, + flow9, + flow10 +) { args: Array<*> -> + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + args[6] as T7, + args[7] as T8, + args[8] as T9, + args[9] as T10, + ) +} From 5464b86ddeaa977eabf8537d47d9f0e94492490e Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Sat, 20 Apr 2024 20:01:43 +0530 Subject: [PATCH 08/11] Initial updates for continue listening feature --- .../user/initializer/AudioItemInitializer.kt | 34 +++++++++++++++++++ .../initializer/AudioProgressInitializer.kt | 23 +++++++++---- .../com/kafka/user/injection/AppModule.kt | 10 ++++++ .../java/com/kafka/data/dao/RecentTextDao.kt | 3 ++ .../java/com/kafka/data/db/KafkaDatabase.kt | 3 +- .../com/kafka/data/entities/RecentItem.kt | 5 +-- 6 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/kafka/user/initializer/AudioItemInitializer.kt diff --git a/app/src/main/java/com/kafka/user/initializer/AudioItemInitializer.kt b/app/src/main/java/com/kafka/user/initializer/AudioItemInitializer.kt new file mode 100644 index 00000000..f80c0351 --- /dev/null +++ b/app/src/main/java/com/kafka/user/initializer/AudioItemInitializer.kt @@ -0,0 +1,34 @@ +package com.kafka.user.initializer + +import android.app.Application +import com.sarahang.playback.core.PlaybackConnection +import com.sarahang.playback.core.fileId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import org.kafka.base.AppInitializer +import org.kafka.base.CoroutineDispatchers +import org.kafka.base.ProcessLifetime +import org.kafka.domain.interactors.UpdateRecentItem +import org.kafka.domain.observers.ObserveRecentItems +import javax.inject.Inject + +class AudioItemInitializer @Inject constructor( + private val playbackConnection: PlaybackConnection, + private val dispatchers: CoroutineDispatchers, + private val updateRecentItem: UpdateRecentItem, + private val observeRecentItems: ObserveRecentItems, + @ProcessLifetime private val coroutineScope: CoroutineScope, +) : AppInitializer { + + override fun init(application: Application) { + coroutineScope.launch(dispatchers.io) { + playbackConnection.nowPlaying + .collectLatest { nowPlaying -> + nowPlaying.fileId?.let { fileId -> + + } + } + } + } +} diff --git a/app/src/main/java/com/kafka/user/initializer/AudioProgressInitializer.kt b/app/src/main/java/com/kafka/user/initializer/AudioProgressInitializer.kt index 073a87c2..b65ad122 100644 --- a/app/src/main/java/com/kafka/user/initializer/AudioProgressInitializer.kt +++ b/app/src/main/java/com/kafka/user/initializer/AudioProgressInitializer.kt @@ -1,17 +1,17 @@ package com.kafka.user.initializer import android.app.Application -import org.kafka.base.AppInitializer import com.kafka.data.dao.RecentAudioDao import com.kafka.data.entities.RecentAudioItem -import org.kafka.base.ProcessLifetime import com.sarahang.playback.core.PlaybackConnection +import com.sarahang.playback.core.album import com.sarahang.playback.core.fileId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch +import org.kafka.base.AppInitializer import org.kafka.base.CoroutineDispatchers +import org.kafka.base.ProcessLifetime import org.kafka.base.debug import javax.inject.Inject @@ -21,19 +21,28 @@ class AudioProgressInitializer @Inject constructor( private val recentAudioDao: RecentAudioDao, @ProcessLifetime private val coroutineScope: CoroutineScope, ) : AppInitializer { + override fun init(application: Application) { coroutineScope.launch(dispatchers.io) { - playbackConnection.playbackProgress - .filter { it.position % 5L == 0L && it.position != 0L } + playbackConnection.nowPlaying +// .filter { it.position % 5L == 0L && it.position != 0L } .collectLatest { timestamp -> debug { "Updating progress for $timestamp" } + + val albumId = playbackConnection.nowPlaying.value.album + playbackConnection.nowPlaying.value.fileId?.let { fileId -> val audioItem = recentAudioDao.get(fileId) if (audioItem == null) { - val audio = RecentAudioItem(fileId, timestamp.position, timestamp.total) + val audio = RecentAudioItem( + fileId = fileId, + albumId = albumId, + currentTimestamp = 0, + duration = 0 + ) recentAudioDao.insert(audio) } else { - recentAudioDao.updateTimestamp(fileId, timestamp.position) + recentAudioDao.updateTimestamp(fileId, 0) } } } diff --git a/app/src/main/java/com/kafka/user/injection/AppModule.kt b/app/src/main/java/com/kafka/user/injection/AppModule.kt index b859e8e2..ebfe7f40 100644 --- a/app/src/main/java/com/kafka/user/injection/AppModule.kt +++ b/app/src/main/java/com/kafka/user/injection/AppModule.kt @@ -17,6 +17,8 @@ import com.kafka.recommendations.topic.FirebaseTopicsImpl import com.kafka.recommendations.topic.FirebaseTopicsInitializer import com.kafka.user.BuildConfig import com.kafka.user.deeplink.FirebaseDynamicDeepLinkHandler +import com.kafka.user.initializer.AudioItemInitializer +import com.kafka.user.initializer.AudioProgressInitializer import com.kafka.user.initializer.FirebaseInitializer import com.kafka.user.initializer.LoggerInitializer import com.kafka.user.initializer.ReaderProgressInitializer @@ -155,6 +157,14 @@ abstract class AppModuleBinds { @IntoSet abstract fun provideFirebaseTopicsInitializer(bind: FirebaseTopicsInitializer): AppInitializer + @Binds + @IntoSet + abstract fun provideAudioProgressInitializer(bind: AudioProgressInitializer): AppInitializer + + @Binds + @IntoSet + abstract fun provideAudioItemInitializer(bind: AudioItemInitializer): AppInitializer + @Singleton @Binds abstract fun provideNotificationManager(bind: NotificationManagerImpl): NotificationManager diff --git a/data/database/src/main/java/com/kafka/data/dao/RecentTextDao.kt b/data/database/src/main/java/com/kafka/data/dao/RecentTextDao.kt index 45e44612..30bd6ea6 100644 --- a/data/database/src/main/java/com/kafka/data/dao/RecentTextDao.kt +++ b/data/database/src/main/java/com/kafka/data/dao/RecentTextDao.kt @@ -23,6 +23,9 @@ abstract class RecentAudioDao : EntityDao { @Query("select * from recent_audio where fileId = :fileId") abstract suspend fun get(fileId: String): RecentAudioItem? + @Query("select * from recent_audio where albumId = :albumId") + abstract suspend fun getByAlbumId(albumId: String): RecentAudioItem? + @Query("select * from recent_audio where fileId = :fileId") abstract fun observe(fileId: String): Flow diff --git a/data/database/src/main/java/com/kafka/data/db/KafkaDatabase.kt b/data/database/src/main/java/com/kafka/data/db/KafkaDatabase.kt index cc6db2a3..4b8d7eea 100644 --- a/data/database/src/main/java/com/kafka/data/db/KafkaDatabase.kt +++ b/data/database/src/main/java/com/kafka/data/db/KafkaDatabase.kt @@ -43,10 +43,11 @@ interface KafkaDatabase { RecentAudioItem::class, DownloadRequest::class, ], - version = 4, + version = 5, exportSchema = true, autoMigrations = [ AutoMigration(from = 3, to = 4, spec = KafkaRoomDatabase.UserRemovalMigration::class), + AutoMigration(from = 4, to = 5), ], ) @TypeConverters(AppTypeConverters::class) diff --git a/data/database/src/main/java/com/kafka/data/entities/RecentItem.kt b/data/database/src/main/java/com/kafka/data/entities/RecentItem.kt index 35fcc435..4cd142d3 100644 --- a/data/database/src/main/java/com/kafka/data/entities/RecentItem.kt +++ b/data/database/src/main/java/com/kafka/data/entities/RecentItem.kt @@ -31,9 +31,9 @@ data class RecentItem( ) companion object { - fun fromItem(item: ItemDetail): RecentItem { + fun fromItem(item: ItemDetail, fileId: String = item.files!!.first()): RecentItem { return RecentItem( - fileId = item.files!!.first(), + fileId = fileId, itemId = item.itemId, title = item.title.orEmpty(), coverUrl = item.coverImage.orEmpty(), @@ -74,6 +74,7 @@ data class RecentTextItem( @Entity(tableName = "recent_audio") data class RecentAudioItem( @PrimaryKey val fileId: String, + val albumId: String?, val currentTimestamp: Long, val duration: Long, ) : BaseEntity { From 99427fc4b4dd706b8bc3601331b78c0fea127d4d Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Mon, 22 Apr 2024 23:29:14 +0530 Subject: [PATCH 09/11] Resume album from last played audio; --- .../initializer/AudioProgressInitializer.kt | 32 +++++++++--------- .../main/java/com/kafka/data/dao/FileDao.kt | 1 - .../dao/{RecentTextDao.kt => RecentsDao.kt} | 14 +++----- .../java/com/kafka/data/db/KafkaDatabase.kt | 5 ++- .../com/kafka/data/entities/RecentItem.kt | 8 ++--- domain/build.gradle | 7 ++-- .../kafka/domain/interactors/ResumeAlbum.kt | 26 +++++++++++++++ .../interactors/UpdateCurrentTimestamp.kt | 21 ------------ .../interactors/recent/IsResumableAudio.kt | 28 ++++++++++++++++ .../java/org/kafka/item/detail/ItemDetail.kt | 3 +- .../kafka/item/detail/ItemDetailActions.kt | 11 +++---- .../kafka/item/detail/ItemDetailViewModel.kt | 33 +++++++++++++++---- .../kafka/item/detail/ItemDetailViewState.kt | 3 +- .../org/kafka/item/files/FilesViewModel.kt | 1 + ui/item/src/main/res/values/strings.xml | 1 + 15 files changed, 123 insertions(+), 71 deletions(-) rename data/database/src/main/java/com/kafka/data/dao/{RecentTextDao.kt => RecentsDao.kt} (61%) create mode 100644 domain/src/main/java/org/kafka/domain/interactors/ResumeAlbum.kt delete mode 100644 domain/src/main/java/org/kafka/domain/interactors/UpdateCurrentTimestamp.kt create mode 100644 domain/src/main/java/org/kafka/domain/interactors/recent/IsResumableAudio.kt diff --git a/app/src/main/java/com/kafka/user/initializer/AudioProgressInitializer.kt b/app/src/main/java/com/kafka/user/initializer/AudioProgressInitializer.kt index b65ad122..3748fd66 100644 --- a/app/src/main/java/com/kafka/user/initializer/AudioProgressInitializer.kt +++ b/app/src/main/java/com/kafka/user/initializer/AudioProgressInitializer.kt @@ -4,7 +4,7 @@ import android.app.Application import com.kafka.data.dao.RecentAudioDao import com.kafka.data.entities.RecentAudioItem import com.sarahang.playback.core.PlaybackConnection -import com.sarahang.playback.core.album +import com.sarahang.playback.core.albumId import com.sarahang.playback.core.fileId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest @@ -15,6 +15,12 @@ import org.kafka.base.ProcessLifetime import org.kafka.base.debug import javax.inject.Inject +/** + * An [AppInitializer] that updates the the currently playing audio. + * + * It listens to the [PlaybackConnection.nowPlaying] and updates the recent audio item + * so that it can be played from last item when the user plays the album again. + */ class AudioProgressInitializer @Inject constructor( private val playbackConnection: PlaybackConnection, private val dispatchers: CoroutineDispatchers, @@ -25,24 +31,18 @@ class AudioProgressInitializer @Inject constructor( override fun init(application: Application) { coroutineScope.launch(dispatchers.io) { playbackConnection.nowPlaying -// .filter { it.position % 5L == 0L && it.position != 0L } .collectLatest { timestamp -> debug { "Updating progress for $timestamp" } - val albumId = playbackConnection.nowPlaying.value.album - - playbackConnection.nowPlaying.value.fileId?.let { fileId -> - val audioItem = recentAudioDao.get(fileId) - if (audioItem == null) { - val audio = RecentAudioItem( - fileId = fileId, - albumId = albumId, - currentTimestamp = 0, - duration = 0 - ) - recentAudioDao.insert(audio) - } else { - recentAudioDao.updateTimestamp(fileId, 0) + playbackConnection.nowPlaying.value.albumId?.let { albumId -> + playbackConnection.nowPlaying.value.fileId?.let { fileId -> + val audioItem = recentAudioDao.getByAlbumId(albumId) + if (audioItem == null) { + val audio = RecentAudioItem(fileId = fileId, albumId = albumId) + recentAudioDao.insert(audio) + } else { + recentAudioDao.updateNowPlaying(albumId = albumId, fileId = fileId) + } } } } diff --git a/data/database/src/main/java/com/kafka/data/dao/FileDao.kt b/data/database/src/main/java/com/kafka/data/dao/FileDao.kt index e9ae17a1..d10cb3a8 100644 --- a/data/database/src/main/java/com/kafka/data/dao/FileDao.kt +++ b/data/database/src/main/java/com/kafka/data/dao/FileDao.kt @@ -24,7 +24,6 @@ abstract class FileDao : EntityDao { .filter { it.key.contains("mp3", true) } .filterKeys { it.contains("mp3", true) } .values.flatten() - .sortedBy { it.name } } @Query("select * from File where fileId = :fileId") diff --git a/data/database/src/main/java/com/kafka/data/dao/RecentTextDao.kt b/data/database/src/main/java/com/kafka/data/dao/RecentsDao.kt similarity index 61% rename from data/database/src/main/java/com/kafka/data/dao/RecentTextDao.kt rename to data/database/src/main/java/com/kafka/data/dao/RecentsDao.kt index 30bd6ea6..cb6b1245 100644 --- a/data/database/src/main/java/com/kafka/data/dao/RecentTextDao.kt +++ b/data/database/src/main/java/com/kafka/data/dao/RecentsDao.kt @@ -20,18 +20,12 @@ abstract class RecentTextDao : EntityDao { @Dao abstract class RecentAudioDao : EntityDao { - @Query("select * from recent_audio where fileId = :fileId") - abstract suspend fun get(fileId: String): RecentAudioItem? - @Query("select * from recent_audio where albumId = :albumId") abstract suspend fun getByAlbumId(albumId: String): RecentAudioItem? - @Query("select * from recent_audio where fileId = :fileId") - abstract fun observe(fileId: String): Flow - - @Query("select * from recent_audio where fileId IN (:fileIds)") - abstract fun observe(fileIds: List): Flow> + @Query("select * from recent_audio where albumId = :albumId") + abstract fun observeByAlbumId(albumId: String): Flow - @Query("update recent_audio set currentTimestamp = :timestamp where fileId = :fileId") - abstract suspend fun updateTimestamp(fileId: String, timestamp: Long) + @Query("update recent_audio set fileId = :fileId where albumId = :albumId") + abstract suspend fun updateNowPlaying(albumId: String, fileId: String) } diff --git a/data/database/src/main/java/com/kafka/data/db/KafkaDatabase.kt b/data/database/src/main/java/com/kafka/data/db/KafkaDatabase.kt index 4b8d7eea..3d8ca309 100644 --- a/data/database/src/main/java/com/kafka/data/db/KafkaDatabase.kt +++ b/data/database/src/main/java/com/kafka/data/db/KafkaDatabase.kt @@ -3,6 +3,7 @@ package com.kafka.data.db import androidx.room.AutoMigration import androidx.room.Database import androidx.room.DeleteTable +import androidx.room.RenameColumn import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.room.migration.AutoMigrationSpec @@ -47,11 +48,13 @@ interface KafkaDatabase { exportSchema = true, autoMigrations = [ AutoMigration(from = 3, to = 4, spec = KafkaRoomDatabase.UserRemovalMigration::class), - AutoMigration(from = 4, to = 5), + AutoMigration(from = 4, to = 5, spec = KafkaRoomDatabase.RecentAudioMigration::class), ], ) @TypeConverters(AppTypeConverters::class) abstract class KafkaRoomDatabase : RoomDatabase(), KafkaDatabase { @DeleteTable(tableName = "user") class UserRemovalMigration : AutoMigrationSpec + @RenameColumn(tableName = "recent_audio", fromColumnName = "fileId", toColumnName = "albumId") + class RecentAudioMigration : AutoMigrationSpec } diff --git a/data/database/src/main/java/com/kafka/data/entities/RecentItem.kt b/data/database/src/main/java/com/kafka/data/entities/RecentItem.kt index 4cd142d3..d202914c 100644 --- a/data/database/src/main/java/com/kafka/data/entities/RecentItem.kt +++ b/data/database/src/main/java/com/kafka/data/entities/RecentItem.kt @@ -73,10 +73,10 @@ data class RecentTextItem( @Immutable @Entity(tableName = "recent_audio") data class RecentAudioItem( - @PrimaryKey val fileId: String, - val albumId: String?, - val currentTimestamp: Long, - val duration: Long, + @PrimaryKey val albumId: String, + val fileId: String, + val currentTimestamp: Long = 0, + val duration: Long = 0, ) : BaseEntity { val progress: Int get() = (currentTimestamp * 100 / duration).toInt() diff --git a/domain/build.gradle b/domain/build.gradle index aafe731c..ecd817fe 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -17,16 +17,19 @@ android { dependencies { api project(':core:remote-config') api project(':data:repo') - implementation libs.kotlin.coroutines.android - implementation platform(libs.google.bom) implementation project(':base:domain') implementation project(':core:analytics') implementation project(':core:downloader') + implementation project(':core-playback') + + implementation platform(libs.google.bom) + implementation libs.firestore.ktx implementation libs.fetch implementation libs.google.auth implementation libs.google.coroutines implementation libs.google.firestore implementation libs.google.playservices.auth + implementation libs.kotlin.coroutines.android } diff --git a/domain/src/main/java/org/kafka/domain/interactors/ResumeAlbum.kt b/domain/src/main/java/org/kafka/domain/interactors/ResumeAlbum.kt new file mode 100644 index 00000000..173a77b1 --- /dev/null +++ b/domain/src/main/java/org/kafka/domain/interactors/ResumeAlbum.kt @@ -0,0 +1,26 @@ +package org.kafka.domain.interactors + +import com.kafka.data.dao.RecentAudioDao +import com.sarahang.playback.core.PlaybackConnection +import com.sarahang.playback.core.apis.AudioDataSource +import org.kafka.base.domain.Interactor +import javax.inject.Inject + +class ResumeAlbum @Inject constructor( + private val playbackConnection: PlaybackConnection, + private val recentAudioDao: RecentAudioDao, + private val audioDataSource: AudioDataSource +) : Interactor() { + + override suspend fun doWork(params: String) { + val audio = recentAudioDao.getByAlbumId(params) + val files = audioDataSource.findAudiosByItemId(params) + + val index = files + .map { it.id } + .indexOf(audio?.fileId) + .coerceAtLeast(0) + + playbackConnection.playAlbum(params, index) + } +} diff --git a/domain/src/main/java/org/kafka/domain/interactors/UpdateCurrentTimestamp.kt b/domain/src/main/java/org/kafka/domain/interactors/UpdateCurrentTimestamp.kt deleted file mode 100644 index 32bf25d5..00000000 --- a/domain/src/main/java/org/kafka/domain/interactors/UpdateCurrentTimestamp.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.kafka.domain.interactors - -import com.kafka.data.dao.RecentAudioDao -import kotlinx.coroutines.withContext -import org.kafka.base.CoroutineDispatchers -import org.kafka.base.domain.Interactor -import javax.inject.Inject - -class UpdateCurrentTimestamp @Inject constructor( - private val dispatchers: CoroutineDispatchers, - private val recentAudioDao: RecentAudioDao, -) : Interactor() { - - override suspend fun doWork(params: Params) { - withContext(dispatchers.io) { - recentAudioDao.updateTimestamp(params.fileId, params.timestamp) - } - } - - data class Params(val fileId: String, val timestamp: Long) -} diff --git a/domain/src/main/java/org/kafka/domain/interactors/recent/IsResumableAudio.kt b/domain/src/main/java/org/kafka/domain/interactors/recent/IsResumableAudio.kt new file mode 100644 index 00000000..25307ede --- /dev/null +++ b/domain/src/main/java/org/kafka/domain/interactors/recent/IsResumableAudio.kt @@ -0,0 +1,28 @@ +package org.kafka.domain.interactors.recent + +import com.kafka.data.dao.RecentAudioDao +import com.sarahang.playback.core.apis.AudioDataSource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import org.kafka.base.CoroutineDispatchers +import org.kafka.base.domain.SubjectInteractor +import javax.inject.Inject + +class IsResumableAudio @Inject constructor( + private val dispatchers: CoroutineDispatchers, + private val recentAudioDao: RecentAudioDao, + private val audioDataSource: AudioDataSource +) : SubjectInteractor() { + + override fun createObservable(params: Params): Flow { + return recentAudioDao.observeByAlbumId(params.albumId).map { recentAudio -> + val files = audioDataSource.findAudiosByItemId(params.albumId) + + files.map { it.id }.indexOf(recentAudio?.fileId) > 0 + }.flowOn(dispatchers.io) + } + + data class Params(val albumId: String) + +} diff --git a/ui/item/src/main/java/org/kafka/item/detail/ItemDetail.kt b/ui/item/src/main/java/org/kafka/item/detail/ItemDetail.kt index 05387095..65793a08 100644 --- a/ui/item/src/main/java/org/kafka/item/detail/ItemDetail.kt +++ b/ui/item/src/main/java/org/kafka/item/detail/ItemDetail.kt @@ -134,7 +134,6 @@ private fun ItemDetail( modifier: Modifier = Modifier, lazyGridState: LazyGridState = rememberLazyGridState() ) { - Box(modifier.fillMaxSize()) { InfiniteProgressBar( show = state.isFullScreenLoading, @@ -159,7 +158,7 @@ private fun ItemDetail( item(span = { GridItemSpan(GridItemSpan) }) { ItemDetailActions( itemId = state.itemDetail!!.itemId, - isAudio = state.itemDetail.isAudio, + ctaText = state.ctaText.orEmpty(), onPrimaryAction = onPrimaryAction, openFiles = openFiles, isFavorite = state.isFavorite, diff --git a/ui/item/src/main/java/org/kafka/item/detail/ItemDetailActions.kt b/ui/item/src/main/java/org/kafka/item/detail/ItemDetailActions.kt index 8748ae01..eca56271 100644 --- a/ui/item/src/main/java/org/kafka/item/detail/ItemDetailActions.kt +++ b/ui/item/src/main/java/org/kafka/item/detail/ItemDetailActions.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -35,13 +34,13 @@ import ui.common.theme.theme.Dimens @Composable fun ItemDetailActions( itemId: String, - isAudio: Boolean, + ctaText: String, onPrimaryAction: (String) -> Unit, openFiles: (String) -> Unit, isFavorite: Boolean, toggleFavorite: () -> Unit ) { - BoxWithConstraints(Modifier.fillMaxWidth()) { + Box(Modifier.fillMaxWidth()) { Row( Modifier .widthIn(max = WIDE_LAYOUT_MIN_WIDTH) @@ -69,9 +68,9 @@ fun ItemDetailActions( } FloatingButton( - text = stringResource(if (isAudio) R.string.play else R.string.read), + text = ctaText, modifier = Modifier.weight(0.8f), - onClickLabel = stringResource(if (isAudio) R.string.play else R.string.read), + onClickLabel = ctaText, onClicked = { onPrimaryAction(itemId) } ) } @@ -139,7 +138,7 @@ private fun ActionsPreview() { AppTheme { ItemDetailActions( itemId = "123", - isAudio = false, + ctaText = "Read", onPrimaryAction = {}, openFiles = {}, isFavorite = false, diff --git a/ui/item/src/main/java/org/kafka/item/detail/ItemDetailViewModel.kt b/ui/item/src/main/java/org/kafka/item/detail/ItemDetailViewModel.kt index 769d39e8..588faae1 100644 --- a/ui/item/src/main/java/org/kafka/item/detail/ItemDetailViewModel.kt +++ b/ui/item/src/main/java/org/kafka/item/detail/ItemDetailViewModel.kt @@ -1,11 +1,13 @@ package org.kafka.item.detail import android.app.Activity +import android.app.Application import android.content.Context import android.content.ContextWrapper import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.kafka.data.entities.ItemDetail import com.kafka.data.feature.item.DownloadStatus import com.kafka.data.model.ArchiveQuery import com.kafka.data.model.SearchFilter.Creator @@ -14,25 +16,26 @@ import com.kafka.data.model.booksByAuthor import com.kafka.remote.config.RemoteConfig import com.kafka.remote.config.isOnlineReaderEnabled import com.kafka.remote.config.isShareEnabled -import com.sarahang.playback.core.PlaybackConnection import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.kafka.analytics.AppReviewManager import org.kafka.analytics.logger.Analytics +import org.kafka.base.combine import org.kafka.base.extensions.stateInDefault import org.kafka.common.ObservableLoadingCounter import org.kafka.common.collectStatus import org.kafka.common.shareText import org.kafka.common.snackbar.SnackbarManager import org.kafka.common.snackbar.UiMessage +import org.kafka.domain.interactors.ResumeAlbum import org.kafka.domain.interactors.UpdateFavorite import org.kafka.domain.interactors.UpdateItemDetail import org.kafka.domain.interactors.UpdateItems import org.kafka.domain.interactors.recent.AddRecentItem +import org.kafka.domain.interactors.recent.IsResumableAudio import org.kafka.domain.interactors.recommendation.PostRecommendationEvent import org.kafka.domain.interactors.recommendation.PostRecommendationEvent.RecommendationEvent import org.kafka.domain.observers.ObserveCreatorItems @@ -56,6 +59,7 @@ import javax.inject.Inject class ItemDetailViewModel @Inject constructor( observeItemDetail: ObserveItemDetail, observeDownloadByItemId: ObserveDownloadByItemId, + isResumableAudio: IsResumableAudio, private val updateItemDetail: UpdateItemDetail, private val observeCreatorItems: ObserveCreatorItems, private val updateItems: UpdateItems, @@ -63,12 +67,13 @@ class ItemDetailViewModel @Inject constructor( private val observeFavoriteStatus: ObserveFavoriteStatus, private val updateFavorite: UpdateFavorite, private val postRecommendationEvent: PostRecommendationEvent, - private val playbackConnection: PlaybackConnection, + private val resumeAlbum: ResumeAlbum, private val navigator: Navigator, private val remoteConfig: RemoteConfig, private val snackbarManager: SnackbarManager, private val analytics: Analytics, private val appReviewManager: AppReviewManager, + private val application: Application, savedStateHandle: SavedStateHandle ) : ViewModel() { private val itemId: String = checkNotNull(savedStateHandle["itemId"]) @@ -81,14 +86,16 @@ class ItemDetailViewModel @Inject constructor( observeCreatorItems.flow, observeFavoriteStatus.flow, loadingState.observable, - observeDownloadByItemId.flow - ) { itemDetail, itemsByCreator, isFavorite, isLoading, downloadItem -> + observeDownloadByItemId.flow, + isResumableAudio.flow + ) { itemDetail, itemsByCreator, isFavorite, isLoading, downloadItem, isResumableAudio -> ItemDetailViewState( itemDetail = itemDetail, itemsByCreator = itemsByCreator, isFavorite = isFavorite, isLoading = isLoading, - downloadItem = downloadItem + downloadItem = downloadItem, + ctaText = itemDetail?.let { ctaText(itemDetail, isResumableAudio) }.orEmpty() ) }.stateInDefault( scope = viewModelScope, @@ -98,6 +105,7 @@ class ItemDetailViewModel @Inject constructor( init { observeItemDetail(ObserveItemDetail.Param(itemId)) observeFavoriteStatus(ObserveFavoriteStatus.Params(itemId)) + isResumableAudio(IsResumableAudio.Params(itemId)) observeDownloadByItemId( ObserveDownloadByItemId.Params( itemId = itemId, @@ -122,7 +130,7 @@ class ItemDetailViewModel @Inject constructor( if (state.value.itemDetail!!.isAudio) { addRecentItem(itemId) analytics.log { playItem(itemId) } - playbackConnection.playAlbum(itemId) + viewModelScope.launch { resumeAlbum(itemId).collect() } } else { openReader(itemId) } @@ -228,6 +236,17 @@ class ItemDetailViewModel @Inject constructor( is ContextWrapper -> baseContext.getActivity() else -> null } + + private fun ctaText(itemDetail: ItemDetail, isResumableAudio: Boolean) = + if (itemDetail.isAudio) { + if (isResumableAudio) { + application.getString(R.string.resume) + } else { + application.getString(R.string.play) + } + } else { + application.getString(R.string.read) + } } private const val itemOpenThresholdForAppReview = 20 diff --git a/ui/item/src/main/java/org/kafka/item/detail/ItemDetailViewState.kt b/ui/item/src/main/java/org/kafka/item/detail/ItemDetailViewState.kt index 41fe0deb..cca6d97e 100644 --- a/ui/item/src/main/java/org/kafka/item/detail/ItemDetailViewState.kt +++ b/ui/item/src/main/java/org/kafka/item/detail/ItemDetailViewState.kt @@ -13,7 +13,8 @@ data class ItemDetailViewState( val itemDetail: ItemDetail? = null, val itemsByCreator: ImmutableList? = null, val isLoading: Boolean = false, - val downloadItem: ItemWithDownload? = null + val downloadItem: ItemWithDownload? = null, + val ctaText: String? = null, ) { val hasItemsByCreator get() = !itemsByCreator.isNullOrEmpty() diff --git a/ui/item/src/main/java/org/kafka/item/files/FilesViewModel.kt b/ui/item/src/main/java/org/kafka/item/files/FilesViewModel.kt index a38d0b0b..e395c232 100644 --- a/ui/item/src/main/java/org/kafka/item/files/FilesViewModel.kt +++ b/ui/item/src/main/java/org/kafka/item/files/FilesViewModel.kt @@ -95,6 +95,7 @@ fun File.asAudio() = Audio( title = title, artist = creator, album = itemTitle, + albumId = itemId, duration = duration, playbackUrl = playbackUrl.orEmpty(), coverImage = coverImage diff --git a/ui/item/src/main/res/values/strings.xml b/ui/item/src/main/res/values/strings.xml index fffeff1b..f822c701 100644 --- a/ui/item/src/main/res/values/strings.xml +++ b/ui/item/src/main/res/values/strings.xml @@ -2,6 +2,7 @@ File type is not supported Play + Resume Read More by author "\n Check out %1$s on Kafka\n \n %2$s\n " From 043f4961c9ecfc2a36b57bb8bcfd37a031a3e0ba Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Mon, 22 Apr 2024 23:38:09 +0530 Subject: [PATCH 10/11] Resume album from last played audio; --- .../user/initializer/AudioItemInitializer.kt | 34 -- .../com/kafka/user/injection/AppModule.kt | 5 - .../5.json | 512 ++++++++++++++++++ .../kafka/domain/interactors/ResumeAlbum.kt | 4 + .../interactors/recent/IsResumableAudio.kt | 10 +- 5 files changed, 523 insertions(+), 42 deletions(-) delete mode 100644 app/src/main/java/com/kafka/user/initializer/AudioItemInitializer.kt create mode 100644 data/database/schemas/com.kafka.data.db.KafkaRoomDatabase/5.json diff --git a/app/src/main/java/com/kafka/user/initializer/AudioItemInitializer.kt b/app/src/main/java/com/kafka/user/initializer/AudioItemInitializer.kt deleted file mode 100644 index f80c0351..00000000 --- a/app/src/main/java/com/kafka/user/initializer/AudioItemInitializer.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.kafka.user.initializer - -import android.app.Application -import com.sarahang.playback.core.PlaybackConnection -import com.sarahang.playback.core.fileId -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import org.kafka.base.AppInitializer -import org.kafka.base.CoroutineDispatchers -import org.kafka.base.ProcessLifetime -import org.kafka.domain.interactors.UpdateRecentItem -import org.kafka.domain.observers.ObserveRecentItems -import javax.inject.Inject - -class AudioItemInitializer @Inject constructor( - private val playbackConnection: PlaybackConnection, - private val dispatchers: CoroutineDispatchers, - private val updateRecentItem: UpdateRecentItem, - private val observeRecentItems: ObserveRecentItems, - @ProcessLifetime private val coroutineScope: CoroutineScope, -) : AppInitializer { - - override fun init(application: Application) { - coroutineScope.launch(dispatchers.io) { - playbackConnection.nowPlaying - .collectLatest { nowPlaying -> - nowPlaying.fileId?.let { fileId -> - - } - } - } - } -} diff --git a/app/src/main/java/com/kafka/user/injection/AppModule.kt b/app/src/main/java/com/kafka/user/injection/AppModule.kt index ebfe7f40..a59e6fa7 100644 --- a/app/src/main/java/com/kafka/user/injection/AppModule.kt +++ b/app/src/main/java/com/kafka/user/injection/AppModule.kt @@ -17,7 +17,6 @@ import com.kafka.recommendations.topic.FirebaseTopicsImpl import com.kafka.recommendations.topic.FirebaseTopicsInitializer import com.kafka.user.BuildConfig import com.kafka.user.deeplink.FirebaseDynamicDeepLinkHandler -import com.kafka.user.initializer.AudioItemInitializer import com.kafka.user.initializer.AudioProgressInitializer import com.kafka.user.initializer.FirebaseInitializer import com.kafka.user.initializer.LoggerInitializer @@ -161,10 +160,6 @@ abstract class AppModuleBinds { @IntoSet abstract fun provideAudioProgressInitializer(bind: AudioProgressInitializer): AppInitializer - @Binds - @IntoSet - abstract fun provideAudioItemInitializer(bind: AudioItemInitializer): AppInitializer - @Singleton @Binds abstract fun provideNotificationManager(bind: NotificationManagerImpl): NotificationManager diff --git a/data/database/schemas/com.kafka.data.db.KafkaRoomDatabase/5.json b/data/database/schemas/com.kafka.data.db.KafkaRoomDatabase/5.json new file mode 100644 index 00000000..9793fc17 --- /dev/null +++ b/data/database/schemas/com.kafka.data.db.KafkaRoomDatabase/5.json @@ -0,0 +1,512 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "069fe8a01d037697ddf32ddb517545ac", + "entities": [ + { + "tableName": "ItemDetail", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` TEXT NOT NULL, `language` TEXT, `title` TEXT, `description` TEXT, `creator` TEXT, `collection` TEXT, `mediaType` TEXT, `coverImage` TEXT, `files` TEXT, `metadata` TEXT, `primaryFile` TEXT, `subject` TEXT, `rating` REAL, PRIMARY KEY(`itemId`))", + "fields": [ + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "creator", + "columnName": "creator", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "collection", + "columnName": "collection", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mediaType", + "columnName": "mediaType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverImage", + "columnName": "coverImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "files", + "columnName": "files", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "metadata", + "columnName": "metadata", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "primaryFile", + "columnName": "primaryFile", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "rating", + "columnName": "rating", + "affinity": "REAL", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "itemId" + ] + }, + "indices": [ + { + "name": "index_ItemDetail_itemId", + "unique": true, + "columnNames": [ + "itemId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ItemDetail_itemId` ON `${TABLE_NAME}` (`itemId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "File", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`fileId` TEXT NOT NULL, `itemId` TEXT NOT NULL, `itemTitle` TEXT, `size` INTEGER, `name` TEXT NOT NULL, `title` TEXT NOT NULL, `extension` TEXT, `creator` TEXT, `time` TEXT, `format` TEXT NOT NULL, `playbackUrl` TEXT, `readerUrl` TEXT, `downloadUrl` TEXT, `coverImage` TEXT, `localUri` TEXT, PRIMARY KEY(`fileId`))", + "fields": [ + { + "fieldPath": "fileId", + "columnName": "fileId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itemTitle", + "columnName": "itemTitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extension", + "columnName": "extension", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "creator", + "columnName": "creator", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playbackUrl", + "columnName": "playbackUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "readerUrl", + "columnName": "readerUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadUrl", + "columnName": "downloadUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverImage", + "columnName": "coverImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localUri", + "columnName": "localUri", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "fileId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Item", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` TEXT NOT NULL, `language` TEXT, `title` TEXT, `description` TEXT, `mediaType` TEXT, `coverImage` TEXT, `collection` TEXT, `genre` TEXT, `subject` TEXT, `uploader` TEXT, `position` INTEGER NOT NULL, `rating` REAL, `creator_id` TEXT, `creator_name` TEXT, PRIMARY KEY(`itemId`))", + "fields": [ + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mediaType", + "columnName": "mediaType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverImage", + "columnName": "coverImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "collection", + "columnName": "collection", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "rating", + "columnName": "rating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "creator.id", + "columnName": "creator_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "creator.name", + "columnName": "creator_name", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "itemId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "queue_meta_data", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `currentSeekPos` INTEGER NOT NULL, `currentSongId` TEXT, `isPlaying` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currentSeekPos", + "columnName": "currentSeekPos", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currentSongId", + "columnName": "currentSongId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isPlaying", + "columnName": "isPlaying", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recent_search", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `search_term` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "searchTerm", + "columnName": "search_term", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_recent_search_search_term", + "unique": true, + "columnNames": [ + "search_term" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_search_search_term` ON `${TABLE_NAME}` (`search_term`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "recent_text", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`fileId` TEXT NOT NULL, `currentPage` INTEGER NOT NULL, `localUri` TEXT NOT NULL, `type` TEXT NOT NULL, `pages` TEXT NOT NULL, PRIMARY KEY(`fileId`))", + "fields": [ + { + "fieldPath": "fileId", + "columnName": "fileId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentPage", + "columnName": "currentPage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUri", + "columnName": "localUri", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pages", + "columnName": "pages", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "fileId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recent_audio", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`albumId` TEXT NOT NULL, `fileId` TEXT NOT NULL, `currentTimestamp` INTEGER NOT NULL, `duration` INTEGER NOT NULL, PRIMARY KEY(`albumId`))", + "fields": [ + { + "fieldPath": "albumId", + "columnName": "albumId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fileId", + "columnName": "fileId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentTimestamp", + "columnName": "currentTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "albumId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "download_requests", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `entity_type` TEXT NOT NULL, `request_id` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entityType", + "columnName": "entity_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "requestId", + "columnName": "request_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '069fe8a01d037697ddf32ddb517545ac')" + ] + } +} \ No newline at end of file diff --git a/domain/src/main/java/org/kafka/domain/interactors/ResumeAlbum.kt b/domain/src/main/java/org/kafka/domain/interactors/ResumeAlbum.kt index 173a77b1..2bbc0f1f 100644 --- a/domain/src/main/java/org/kafka/domain/interactors/ResumeAlbum.kt +++ b/domain/src/main/java/org/kafka/domain/interactors/ResumeAlbum.kt @@ -6,6 +6,10 @@ import com.sarahang.playback.core.apis.AudioDataSource import org.kafka.base.domain.Interactor import javax.inject.Inject +/** + * Resumes an album from the last played audio + * or start from the beginning if last played audio is not found or in case of an error. + * */ class ResumeAlbum @Inject constructor( private val playbackConnection: PlaybackConnection, private val recentAudioDao: RecentAudioDao, diff --git a/domain/src/main/java/org/kafka/domain/interactors/recent/IsResumableAudio.kt b/domain/src/main/java/org/kafka/domain/interactors/recent/IsResumableAudio.kt index 25307ede..bf778143 100644 --- a/domain/src/main/java/org/kafka/domain/interactors/recent/IsResumableAudio.kt +++ b/domain/src/main/java/org/kafka/domain/interactors/recent/IsResumableAudio.kt @@ -9,6 +9,10 @@ import org.kafka.base.CoroutineDispatchers import org.kafka.base.domain.SubjectInteractor import javax.inject.Inject +/** + * Checks if the item has been played before. + * It does not explicitly check if the item is an audio item but if it exists in recent audios then it must be an audio. + * */ class IsResumableAudio @Inject constructor( private val dispatchers: CoroutineDispatchers, private val recentAudioDao: RecentAudioDao, @@ -16,13 +20,13 @@ class IsResumableAudio @Inject constructor( ) : SubjectInteractor() { override fun createObservable(params: Params): Flow { - return recentAudioDao.observeByAlbumId(params.albumId).map { recentAudio -> - val files = audioDataSource.findAudiosByItemId(params.albumId) + return recentAudioDao.observeByAlbumId(params.itemId).map { recentAudio -> + val files = audioDataSource.findAudiosByItemId(params.itemId) files.map { it.id }.indexOf(recentAudio?.fileId) > 0 }.flowOn(dispatchers.io) } - data class Params(val albumId: String) + data class Params(val itemId: String) } From 87852b97f236593ab0629c6591ff05bac7ffbcc3 Mon Sep 17 00:00:00 2001 From: vipulkumar Date: Tue, 23 Apr 2024 11:53:10 +0530 Subject: [PATCH 11/11] Release 0.16.0 --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3a0d0c80..b9d5c119 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { defaultConfig { applicationId = "com.kafka.user" - versionCode = 55 - versionName = "0.15.0" + versionCode = 56 + versionName = "0.16.0" val properties = Properties() properties.load(project.rootProject.file("local.properties").inputStream())