Skip to content

Commit

Permalink
Implement sync with dropbox on Android and desktop
Browse files Browse the repository at this point in the history
  • Loading branch information
prof18 committed Jul 7, 2024
1 parent 1d3cf44 commit 5a42ccf
Show file tree
Hide file tree
Showing 102 changed files with 3,264 additions and 239 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ render.experimental.xml
# Keystore files
*.jks
*.keystore
keystore.properties

# Google Services (e.g. APIs or Firebase)
google-services.json
Expand Down
14 changes: 14 additions & 0 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ if (localProperties.exists()) {
localProperties.inputStream().use { local.load(it) }
}

val dropboxAppKey: String = local.getProperty("dropbox_key")
android {
namespace = "com.prof18.feedflow.android"
compileSdk = libs.versions.android.compile.sdk.get().toInt()
Expand All @@ -26,6 +27,12 @@ android {
targetSdk = libs.versions.android.target.sdk.get().toInt()
versionCode = getVersionCode()
versionName = getVersionName()

addManifestPlaceholders(
mapOf(
"dropboxKey" to dropboxAppKey,
),
)
}

buildFeatures {
Expand Down Expand Up @@ -55,6 +62,7 @@ android {
getByName("debug") {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
buildConfigField("String", "DROPBOX_APP_KEY", "\"$dropboxAppKey\"")
}

getByName("release") {
Expand All @@ -65,6 +73,7 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
buildConfigField("String", "DROPBOX_APP_KEY", "\"$dropboxAppKey\"")
}
}
}
Expand Down Expand Up @@ -102,6 +111,11 @@ dependencies {
implementation(libs.koin.core)
implementation(libs.koin.android)
implementation(libs.koin.compose)
implementation(libs.koin.workmanager)

implementation(libs.dropbox.core.android)
implementation(libs.workmanager)
implementation(libs.androidx.lifecycle.process)

debugImplementation(compose.uiTooling)

Expand Down
39 changes: 38 additions & 1 deletion androidApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

Expand Down Expand Up @@ -29,8 +30,44 @@
</intent-filter>
</activity>

<activity android:name=".accounts.dropbox.DropboxSyncActivity" />

<activity
android:name="com.dropbox.core.android.AuthActivity"
android:exported="true"
android:configChanges="orientation|keyboard"
android:launchMode="singleTask">
<!-- Your activity starting authorization flow should also configured with android:launchMode="singleTask".
If that activity is configured with android:taskAffinity, this AuthActivity should also configured
with the same android:taskAffinity so the auth result can be correctly passed back. -->
<intent-filter>
<data android:scheme="db-${dropboxKey}" />

<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- Additional intent-filter required as a workaround for Apps using targetSdk=33 until the fix in the Dropbox app is available to all users. https://github.com/dropbox/dropbox-sdk-java/issues/406 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@ package com.prof18.feedflow.android

import android.app.Application
import android.content.Context
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.prof18.feedflow.android.readermode.ReaderModeViewModel
import com.prof18.feedflow.core.utils.AppEnvironment
import com.prof18.feedflow.shared.di.getWith
import com.prof18.feedflow.shared.di.initKoin
import com.prof18.feedflow.shared.domain.feedsync.FeedSyncRepository
import com.prof18.feedflow.shared.ui.utils.coilImageLoader
import com.prof18.feedflow.shared.utils.enableKmpCrashlytics
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.androidx.workmanager.koin.workManagerFactory
import org.koin.dsl.module

class FeedFlowApp : Application() {

private val feedSyncRepo by inject<FeedSyncRepository>()

override fun onCreate() {
super.onCreate()

Expand All @@ -28,8 +37,11 @@ class FeedFlowApp : Application() {
enableKmpCrashlytics()
}

com.prof18.feedflow.shared.di.initKoin(
initKoin(
appEnvironment = appEnvironment,
platformSetup = {
workManagerFactory()
},
modules = listOf(
module {
single<Context> { this@FeedFlowApp }
Expand All @@ -55,5 +67,16 @@ class FeedFlowApp : Application() {
},
),
)

with(ProcessLifecycleOwner.get()) {
lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
feedSyncRepo.enqueueBackup()
}
},
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@ import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
Expand All @@ -28,6 +35,7 @@ import coil3.ImageLoader
import coil3.annotation.ExperimentalCoilApi
import coil3.compose.setSingletonImageLoaderFactory
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.prof18.feedflow.android.accounts.AccountsScreen
import com.prof18.feedflow.android.addfeed.AddFeedScreen
import com.prof18.feedflow.android.feedsourcelist.FeedSourceListScreen
import com.prof18.feedflow.android.home.HomeScreen
Expand All @@ -38,12 +46,19 @@ import com.prof18.feedflow.android.settings.SettingsScreen
import com.prof18.feedflow.android.settings.about.AboutScreen
import com.prof18.feedflow.android.settings.about.LicensesScreen
import com.prof18.feedflow.android.settings.importexport.ImportExportScreen
import com.prof18.feedflow.shared.domain.feedsync.FeedSyncMessageQueue
import com.prof18.feedflow.shared.domain.model.SyncResult
import com.prof18.feedflow.shared.ui.utils.LocalFeedFlowStrings
import com.prof18.feedflow.shared.ui.utils.ProvideFeedFlowStrings
import com.prof18.feedflow.shared.ui.utils.rememberFeedFlowStrings
import org.koin.android.ext.android.inject
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.getKoin

class MainActivity : ComponentActivity() {

private val messageQueue by inject<FeedSyncMessageQueue>()

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalCoilApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -66,6 +81,18 @@ class MainActivity : ComponentActivity() {
val readerModeViewModel: ReaderModeViewModel = koinViewModel()

val windowSize = calculateWindowSizeClass(this@MainActivity)
val snackbarHostState = remember { SnackbarHostState() }

val errorMessage = LocalFeedFlowStrings.current.errorAccountSync
LaunchedEffect(Unit) {
messageQueue.messageQueue.collect { message ->
if (message is SyncResult.Error) {
snackbarHostState.showSnackbar(
message = errorMessage,
)
}
}
}

FeedFlowTheme {
val navController = rememberNavController()
Expand All @@ -80,6 +107,15 @@ class MainActivity : ComponentActivity() {
navController = navController,
readerModeViewModel = readerModeViewModel,
)

Column(
modifier = Modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Bottom,
) {
SnackbarHost(snackbarHostState)
}
}
}
}
Expand Down Expand Up @@ -139,6 +175,9 @@ class MainActivity : ComponentActivity() {
navigateToImportExport = {
navController.navigate(Screen.ImportExport.name)
},
navigateToAccounts = {
navController.navigate(Screen.Accounts.name)
},
)
}

Expand Down Expand Up @@ -210,6 +249,14 @@ class MainActivity : ComponentActivity() {
},
)
}

composable(Screen.Accounts.name) {
AccountsScreen(
navigateBack = {
navController.popBackStack()
},
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ sealed class Screen(val name: String) {
data object ImportExport : Screen("import_export")
data object ReaderMode : Screen("reader_mode")
data object Search : Screen("search")
data object Accounts : Screen("accounts")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.prof18.feedflow.android.accounts

import android.content.Intent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.prof18.feedflow.android.accounts.dropbox.DropboxSyncActivity
import com.prof18.feedflow.shared.presentation.AccountsViewModel
import com.prof18.feedflow.shared.ui.accounts.AccountsContent
import org.koin.androidx.compose.koinViewModel

@Composable
internal fun AccountsScreen(
navigateBack: () -> Unit,
) {
val context = LocalContext.current
val viewModel = koinViewModel<AccountsViewModel>()

val syncAccount by viewModel.accountsState.collectAsStateWithLifecycle()

LaunchedEffect(Unit) {
viewModel.restoreAccounts()
}

AccountsContent(
syncAccount = syncAccount,
onBackClick = navigateBack,
onDropboxCLick = {
context.startActivity(
Intent(context, DropboxSyncActivity::class.java),
)
},
)
}
Loading

0 comments on commit 5a42ccf

Please sign in to comment.