diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index 585731ef4..58a092632 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -20,11 +20,16 @@ import android.app.Activity import android.app.Application import android.os.Bundle import com.google.common.flogger.FluentLogger +import com.pitchedapps.frost.hilt.FrostComponents import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject +import javax.inject.Provider @HiltAndroidApp class FrostApp : Application() { + @Inject lateinit var componentsProvider: Provider + override fun onCreate() { super.onCreate() @@ -48,7 +53,7 @@ class FrostApp : Application() { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { logger.atFine().log("Activity %s created", activity.localClassName) } - } + }, ) } } diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/components/FrostDataStore.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/components/FrostDataStore.kt new file mode 100644 index 000000000..048bd516c --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/components/FrostDataStore.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.components + +import androidx.datastore.core.DataStore +import com.pitchedapps.frost.proto.Account +import com.pitchedapps.frost.proto.settings.Appearance +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton + +@Singleton +class FrostDataStore +@Inject +internal constructor( + private val accountProvider: Provider>, + private val appearanceProvider: Provider>, +) { + val account: DataStore + get() = accountProvider.get() + + val appearance: DataStore + get() = appearanceProvider.get() +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/FrostExtensions.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/FrostExtensions.kt new file mode 100644 index 000000000..44e22f90f --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/compose/FrostExtensions.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.platform.LocalContext +import com.pitchedapps.frost.ext.components +import com.pitchedapps.frost.extension.FrostCoreExtension + +@Composable +fun FrostCoreExtensionEffect() { + val components = LocalContext.current.components + + DisposableEffect(components.core.store) { + val feature = + FrostCoreExtension( + runtime = components.core.engine, + store = components.core.store, + converter = components.extensionModelConverter, + ) + + feature.start() + + onDispose { feature.stop() } + } +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/ext/ContextExt.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/ext/ContextExt.kt index 2637f9a54..47e883ad4 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/ext/ContextExt.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/ext/ContextExt.kt @@ -20,6 +20,8 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle +import com.pitchedapps.frost.FrostApp +import com.pitchedapps.frost.hilt.FrostComponents inline fun Context.launchActivity( clearStack: Boolean = false, @@ -38,3 +40,6 @@ inline fun Context.launchActivity( finish() } } + +val Context.components: FrostComponents + get() = (applicationContext as FrostApp).componentsProvider.get() diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/hilt/FrostComponents.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/hilt/FrostComponents.kt index 2366385ca..9aea568e3 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/hilt/FrostComponents.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/hilt/FrostComponents.kt @@ -17,7 +17,9 @@ package com.pitchedapps.frost.hilt import com.pitchedapps.frost.components.Core +import com.pitchedapps.frost.components.FrostDataStore import com.pitchedapps.frost.components.UseCases +import com.pitchedapps.frost.extension.ExtensionModelConverter import javax.inject.Inject import javax.inject.Singleton @@ -29,4 +31,11 @@ import javax.inject.Singleton * but with hilt */ @Singleton -class FrostComponents @Inject internal constructor(val core: Core, val useCases: UseCases) +class FrostComponents +@Inject +internal constructor( + val core: Core, + val useCases: UseCases, + val extensionModelConverter: ExtensionModelConverter, + val dataStore: FrostDataStore, +) diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/main/MainScreen.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/main/MainScreen.kt index 2293aced4..6587e4929 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/main/MainScreen.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/main/MainScreen.kt @@ -24,20 +24,19 @@ import androidx.compose.material.Icon import androidx.compose.material.Tab import androidx.compose.material.TabRow import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import com.pitchedapps.frost.components.UseCases +import com.pitchedapps.frost.compose.FrostCoreExtensionEffect import com.pitchedapps.frost.compose.FrostWeb -import com.pitchedapps.frost.extension.FrostCoreExtension +import com.pitchedapps.frost.ext.components import mozilla.components.browser.state.helper.Target -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.Engine @Composable fun MainScreen(modifier: Modifier = Modifier) { @@ -47,60 +46,58 @@ fun MainScreen(modifier: Modifier = Modifier) { if (contextId.isEmpty()) return // Not ready - DisposableEffect(vm.store) { - val feature = - FrostCoreExtension( - runtime = vm.engine, - store = vm.store, - converter = vm.extensionModelConverter, - ) + val tabs by vm.tabsFlow.collectAsState(initial = emptyList()) - feature.start() + if (tabs.isEmpty()) return // Not ready - onDispose { feature.stop() } - } + FrostCoreExtensionEffect() + + val onTabSelect = + remember(vm) { + { selectedIndex: Int -> + if (selectedIndex == vm.tabIndex) { + vm.components.useCases.homeTabs.reloadTab(selectedIndex) + // context.launchFloatingUrl(FACEBOOK_M_URL) + } else { + // Change? What if previous selected tab is not home tab + vm.components.useCases.homeTabs.selectHomeTab(selectedIndex) + vm.tabIndex = selectedIndex + } + } + } MainContainer( modifier = modifier, - engine = vm.engine, - store = vm.store, contextId = contextId, - useCases = vm.useCases, tabIndex = vm.tabIndex, - tabs = vm.tabs, - onTabSelect = { selectedIndex: Int -> - if (selectedIndex == vm.tabIndex) { - vm.useCases.homeTabs.reloadTab(selectedIndex) - // context.launchFloatingUrl(FACEBOOK_M_URL) - } else { - // Change? What if previous selected tab is not home tab - vm.useCases.homeTabs.selectHomeTab(selectedIndex) - vm.tabIndex = selectedIndex - } - }, + tabs = tabs, + onTabSelect = onTabSelect, ) } @Composable private fun MainContainer( - engine: Engine, - store: BrowserStore, contextId: String, - useCases: UseCases, tabIndex: Int, tabs: List, onTabSelect: (Int) -> Unit, modifier: Modifier = Modifier ) { + val components = LocalContext.current.components + LaunchedEffect(contextId) { - useCases.homeTabs.createHomeTabs(contextId, tabIndex, tabs.map { it.url }) + components.useCases.homeTabs.createHomeTabs(contextId, tabIndex, tabs.map { it.url }) } Column(modifier = modifier) { MainTabRow(selectedIndex = tabIndex, items = tabs, onTabSelect = onTabSelect) // For tab switching, must use SelectedTab // https://github.com/mozilla-mobile/android-components/issues/12798 - FrostWeb(engine = engine, store = store, target = Target.SelectedTab) + FrostWeb( + engine = components.core.engine, + store = components.core.store, + target = Target.SelectedTab + ) } } diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/main/MainScreenViewModel.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/main/MainScreenViewModel.kt index 74226fb7c..cf54cb4aa 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/main/MainScreenViewModel.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/main/MainScreenViewModel.kt @@ -20,21 +20,14 @@ import android.content.Context import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.datastore.core.DataStore import androidx.lifecycle.ViewModel -import com.pitchedapps.frost.components.UseCases -import com.pitchedapps.frost.extension.ExtensionModelConverter import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.hilt.FrostComponents -import com.pitchedapps.frost.proto.Account -import com.pitchedapps.frost.proto.settings.Appearance import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.Engine @HiltViewModel class MainScreenViewModel @@ -42,20 +35,17 @@ class MainScreenViewModel internal constructor( @ApplicationContext context: Context, val components: FrostComponents, - val engine: Engine, - val store: BrowserStore, - val useCases: UseCases, - val extensionModelConverter: ExtensionModelConverter, - accountDataStore: DataStore, - appearanceDataStore: DataStore, ) : ViewModel() { val tabsFlow: Flow> = - appearanceDataStore.data.map { appearance -> - appearance.mainTabsList.mapNotNull { FbItem.fromKey(it)?.tab(context) } - } + components.dataStore.appearance.data + .map { appearance -> + appearance.mainTabsList.mapNotNull { FbItem.fromKey(it) }.takeIf { it.isNotEmpty() } + ?: FbItem.defaults() + } + .map { items -> items.map { it.tab(context) } } - val contextIdFlow: Flow = accountDataStore.data.map { it.accountId } + val contextIdFlow: Flow = components.dataStore.account.data.map { it.accountId } var tabIndex: Int by mutableStateOf(0) }