diff --git a/build.gradle.kts b/build.gradle.kts index 40bbacb3..0fbaac48 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,35 +26,6 @@ tasks.register("clean", Delete::class) { delete(layout.buildDirectory.get()) } -// todo: delete -//allprojects { -// apply { -// plugin(rootProject.libs.plugins.detekt.get().pluginId) -// } -// -// dependencies { -// detektPlugins(rootProject.libs.io.gitlab.arturbosch.detekt.formatting) { -// exclude(group = "org.slf4j", module = "slf4j-nop") -// } -// detektPlugins(rootProject.libs.detekt.compose.rules) -// } -// -// detekt { -// source.setFrom( -// files( -// "src", -// DEFAULT_SRC_DIR_JAVA, -// DEFAULT_TEST_SRC_DIR_JAVA, -// DEFAULT_SRC_DIR_KOTLIN, -// DEFAULT_TEST_SRC_DIR_KOTLIN, -// ), -// ) -// config.setFrom(rootProject.files("config/detekt/detekt.yml")) -// parallel = true -// autoCorrect = true -// } -//} - subprojects { tasks.withType().configureEach { kotlinOptions { diff --git a/i18n/src/commonMain/kotlin/com/prof18/feedflow/i18n/Locales.kt b/i18n/src/commonMain/kotlin/com/prof18/feedflow/i18n/Locales.kt index d3c91aba..4b6907e7 100644 --- a/i18n/src/commonMain/kotlin/com/prof18/feedflow/i18n/Locales.kt +++ b/i18n/src/commonMain/kotlin/com/prof18/feedflow/i18n/Locales.kt @@ -16,4 +16,4 @@ expect fun String.format(vararg args: Any): String @Suppress("UnusedPrivateProperty") // This is a trick to be sure that KSP re-generates the strings when there's no code updates -private const val StringsVersion = 37 +private const val StringsVersion = 38 diff --git a/i18n/src/commonMain/resources/locale/values/strings.xml b/i18n/src/commonMain/resources/locale/values/strings.xml index a693d440..4bca7460 100644 --- a/i18n/src/commonMain/resources/locale/values/strings.xml +++ b/i18n/src/commonMain/resources/locale/values/strings.xml @@ -93,7 +93,7 @@ ℹ The reader mode tries to get the content of the article. There might be some glitches or unwanted characters. Feed Accounts - Connect an account to sync feed subscriptions, categories, read and bookmarked articles between your devices.\n\nThe functionality is in Beta and more services will be supported in the future. + Connect an account to sync feed subscriptions, categories, read and bookmarked articles between your devices.\n\nThe functionality is in Beta, issues may occur (for example, read articles might not be fully synced). More services will be supported in the future. Behaviour Help Connect diff --git a/iosApp/Source/Accounts/Dropbox/DropboxSyncScreen.swift b/iosApp/Source/Accounts/Dropbox/DropboxSyncScreen.swift index 095b29a1..7f92e981 100644 --- a/iosApp/Source/Accounts/Dropbox/DropboxSyncScreen.swift +++ b/iosApp/Source/Accounts/Dropbox/DropboxSyncScreen.swift @@ -58,6 +58,22 @@ struct DropboxSyncScreen: View { self.appState.emitGenericError() } } + .task { + do { + let stream = asyncSequence(for: viewModel.syncMessageQueue) + for try await message in stream where message.isError() { + self.appState.snackbarQueue.append( + SnackbarData( + title: feedFlowStrings.errorAccountSync, + subtitle: nil, + showBanner: true + ) + ) + } + } catch { + self.appState.emitGenericError() + } + } } } diff --git a/iosApp/Source/App/FeedFlowApp.swift b/iosApp/Source/App/FeedFlowApp.swift index a02c7311..6320d2c6 100644 --- a/iosApp/Source/App/FeedFlowApp.swift +++ b/iosApp/Source/App/FeedFlowApp.swift @@ -67,7 +67,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { return true } - func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { + func application( + _ application: UIApplication, + handleEventsForBackgroundURLSession identifier: String, + completionHandler: @escaping () -> Void + ) { DropboxClientsManager.handleEventsForBackgroundURLSession( with: identifier, creationInfos: [], diff --git a/iosApp/Source/Home/HomeScreen.swift b/iosApp/Source/Home/HomeScreen.swift index 984c729a..f9e5c959 100644 --- a/iosApp/Source/Home/HomeScreen.swift +++ b/iosApp/Source/Home/HomeScreen.swift @@ -177,26 +177,10 @@ struct HomeScreen: View { self.appState.emitGenericError() } } - .task { - do { - let stream = asyncSequence(for: homeViewModel.syncMessageQueue) - for try await message in stream where message.isError() { - self.appState.snackbarQueue.append( - SnackbarData( - title: feedFlowStrings.errorAccountSync, - subtitle: nil, - showBanner: true - ) - ) - } - } catch { - self.appState.emitGenericError() - } - } .onChange(of: scenePhase) { newScenePhase in switch newScenePhase { case .background: - homeViewModel.enqueueBackup() + homeViewModel.enqueueBackup(lastVisibleIndex: Int32(indexHolder.getLastReadIndex())) default: break } diff --git a/shared/src/androidMain/kotlin/com/prof18/feedflow/shared/di/KoinAndroid.kt b/shared/src/androidMain/kotlin/com/prof18/feedflow/shared/di/KoinAndroid.kt index d8ef67be..c1bfb9cb 100644 --- a/shared/src/androidMain/kotlin/com/prof18/feedflow/shared/di/KoinAndroid.kt +++ b/shared/src/androidMain/kotlin/com/prof18/feedflow/shared/di/KoinAndroid.kt @@ -80,6 +80,7 @@ internal actual fun getPlatformModule(appEnvironment: AppEnvironment): Module = feedSyncRepository = get(), dateFormatter = get(), feedRetrieverRepository = get(), + feedSyncMessageQueue = get(), ) } diff --git a/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/di/Koin.kt b/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/di/Koin.kt index 80034758..f89ef48f 100644 --- a/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/di/Koin.kt +++ b/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/di/Koin.kt @@ -143,7 +143,6 @@ private val coreModule = module { feedManagerRepository = get(), settingsRepository = get(), feedSyncRepository = get(), - feedSyncMessageQueue = get(), ) } diff --git a/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/domain/feed/retriever/FeedRetrieverRepository.kt b/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/domain/feed/retriever/FeedRetrieverRepository.kt index 73b07140..40620ac6 100644 --- a/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/domain/feed/retriever/FeedRetrieverRepository.kt +++ b/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/domain/feed/retriever/FeedRetrieverRepository.kt @@ -76,6 +76,7 @@ internal class FeedRetrieverRepository( val currentFeedFilter: StateFlow = currentFeedFilterMutableState.asStateFlow() private var currentPage: Int = 0 + private var isFeedSyncDone = true private val knownUrlSuffix = listOf( "", @@ -200,12 +201,15 @@ internal class FeedRetrieverRepository( getFeeds() } + isFeedSyncDone = false parseFeeds( feedSourceUrls = feedSourceUrls, forceRefresh = forceRefresh, ) feedSyncRepository.syncFeedItems() + isFeedSyncDone = true + updateRefreshCount() getFeeds() } @@ -398,7 +402,7 @@ internal class FeedRetrieverRepository( val refreshedFeedCount = oldUpdate.refreshedFeedCount + 1 val totalFeedCount = oldUpdate.totalFeedCount - if (feedToUpdate.isEmpty()) { + if (feedToUpdate.isEmpty() && isFeedSyncDone) { FinishedFeedUpdateStatus } else { InProgressFeedUpdateStatus( diff --git a/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/presentation/HomeViewModel.kt b/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/presentation/HomeViewModel.kt index e77f3ac6..d7f401ae 100644 --- a/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/presentation/HomeViewModel.kt +++ b/shared/src/commonMain/kotlin/com/prof18/feedflow/shared/presentation/HomeViewModel.kt @@ -9,7 +9,6 @@ import com.prof18.feedflow.core.model.FeedItemId import com.prof18.feedflow.core.model.NavDrawerState import com.prof18.feedflow.shared.domain.feed.manager.FeedManagerRepository import com.prof18.feedflow.shared.domain.feed.retriever.FeedRetrieverRepository -import com.prof18.feedflow.shared.domain.feedsync.FeedSyncMessageQueue import com.prof18.feedflow.shared.domain.feedsync.FeedSyncRepository import com.prof18.feedflow.shared.domain.model.FeedUpdateStatus import com.prof18.feedflow.shared.domain.settings.SettingsRepository @@ -35,7 +34,6 @@ class HomeViewModel internal constructor( private val feedManagerRepository: FeedManagerRepository, private val settingsRepository: SettingsRepository, private val feedSyncRepository: FeedSyncRepository, - feedSyncMessageQueue: FeedSyncMessageQueue, ) : BaseViewModel() { // Loading @@ -66,9 +64,6 @@ class HomeViewModel internal constructor( @NativeCoroutinesState val currentFeedFilter = feedRetrieverRepository.currentFeedFilter - @NativeCoroutines - val syncMessageQueue = feedSyncMessageQueue.messageQueue - init { scope.launch { feedRetrieverRepository.updateFeedFilter(FeedFilter.Timeline) @@ -248,8 +243,10 @@ class HomeViewModel internal constructor( } } - fun enqueueBackup() { + // Used on iOS + fun enqueueBackup(lastVisibleIndex: Int) { scope.launch { + markAsReadOnScroll(lastVisibleIndex) feedSyncRepository.enqueueBackup() } } diff --git a/shared/src/commonMobileMain/kotlin/com/prof18/feedflow/shared/presentation/DropboxSyncViewModel.kt b/shared/src/commonMobileMain/kotlin/com/prof18/feedflow/shared/presentation/DropboxSyncViewModel.kt index b5369325..97a785e2 100644 --- a/shared/src/commonMobileMain/kotlin/com/prof18/feedflow/shared/presentation/DropboxSyncViewModel.kt +++ b/shared/src/commonMobileMain/kotlin/com/prof18/feedflow/shared/presentation/DropboxSyncViewModel.kt @@ -11,6 +11,7 @@ import com.prof18.feedflow.feedsync.dropbox.DropboxStringCredentials import com.prof18.feedflow.feedsync.dropbox.getDxCredentialsAsString import com.prof18.feedflow.shared.domain.DateFormatter import com.prof18.feedflow.shared.domain.feed.retriever.FeedRetrieverRepository +import com.prof18.feedflow.shared.domain.feedsync.FeedSyncMessageQueue import com.prof18.feedflow.shared.domain.feedsync.FeedSyncRepository import com.rickclephas.kmp.nativecoroutines.NativeCoroutines import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState @@ -30,6 +31,7 @@ class DropboxSyncViewModel internal constructor( private val feedSyncRepository: FeedSyncRepository, private val dateFormatter: DateFormatter, private val feedRetrieverRepository: FeedRetrieverRepository, + feedSyncMessageQueue: FeedSyncMessageQueue, ) : BaseViewModel() { private val dropboxSyncUiMutableState = MutableStateFlow( @@ -44,6 +46,9 @@ class DropboxSyncViewModel internal constructor( @NativeCoroutines val dropboxSyncMessageState: SharedFlow = dropboxSyncMessageMutableState.asSharedFlow() + @NativeCoroutines + val syncMessageQueue = feedSyncMessageQueue.messageQueue + init { restoreDropboxAuth() } diff --git a/shared/src/iosMain/kotlin/com/prof18/feedflow/shared/di/KoinIOS.kt b/shared/src/iosMain/kotlin/com/prof18/feedflow/shared/di/KoinIOS.kt index 79ec2fe6..6bd33403 100644 --- a/shared/src/iosMain/kotlin/com/prof18/feedflow/shared/di/KoinIOS.kt +++ b/shared/src/iosMain/kotlin/com/prof18/feedflow/shared/di/KoinIOS.kt @@ -12,6 +12,7 @@ import com.prof18.feedflow.i18n.feedFlowStrings import com.prof18.feedflow.shared.domain.HtmlParser import com.prof18.feedflow.shared.domain.browser.BrowserSettingsRepository import com.prof18.feedflow.shared.domain.feedsync.FeedSyncIosWorker +import com.prof18.feedflow.shared.domain.feedsync.FeedSyncMessageQueue import com.prof18.feedflow.shared.domain.feedsync.FeedSyncRepository import com.prof18.feedflow.shared.domain.feedsync.FeedSyncWorker import com.prof18.feedflow.shared.domain.opml.OpmlFeedHandler @@ -108,6 +109,7 @@ internal actual fun getPlatformModule(appEnvironment: AppEnvironment): Module = feedSyncRepository = get(), dateFormatter = get(), feedRetrieverRepository = get(), + feedSyncMessageQueue = get(), ) } }