mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-09-19 15:11:42 +02:00
Create session state and selected home tab
This commit is contained in:
parent
c010c67d33
commit
4a4fe97d08
@ -32,7 +32,7 @@ import com.pitchedapps.frost.main.MainActivity
|
||||
import com.pitchedapps.frost.web.state.FrostWebStore
|
||||
import com.pitchedapps.frost.web.state.TabAction
|
||||
import com.pitchedapps.frost.web.state.TabListAction
|
||||
import com.pitchedapps.frost.web.state.TabWebState
|
||||
import com.pitchedapps.frost.web.state.state.HomeTabSessionState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -72,7 +72,7 @@ class StartActivity : AppCompatActivity() {
|
||||
// Test something scrollable
|
||||
store.dispatch(
|
||||
TabAction(
|
||||
tabId = TabWebState.homeTabId(0),
|
||||
tabId = HomeTabSessionState.homeTabId(0),
|
||||
TabAction.ContentAction.UpdateUrlAction(
|
||||
"https://github.com/AllanWang/Frost-for-Facebook"
|
||||
),
|
||||
@ -80,7 +80,7 @@ class StartActivity : AppCompatActivity() {
|
||||
)
|
||||
store.dispatch(
|
||||
TabAction(
|
||||
tabId = TabWebState.homeTabId(1),
|
||||
tabId = HomeTabSessionState.homeTabId(1),
|
||||
TabAction.ContentAction.UpdateUrlAction("https://github.com/AllanWang/KAU"),
|
||||
),
|
||||
)
|
||||
|
@ -37,8 +37,8 @@ import com.pitchedapps.frost.web.state.FrostWebStore
|
||||
import com.pitchedapps.frost.web.state.TabAction
|
||||
import com.pitchedapps.frost.web.state.TabAction.ResponseAction.LoadUrlResponseAction
|
||||
import com.pitchedapps.frost.web.state.TabAction.ResponseAction.WebStepResponseAction
|
||||
import com.pitchedapps.frost.web.state.TabWebState
|
||||
import com.pitchedapps.frost.web.state.get
|
||||
import com.pitchedapps.frost.web.state.state.ContentState
|
||||
import com.pitchedapps.frost.webview.FrostChromeClient
|
||||
import com.pitchedapps.frost.webview.FrostWebViewClient
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -88,13 +88,14 @@ class FrostWebCompose(
|
||||
webView?.let { wv ->
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
|
||||
val canGoBack by store.observeAsState(initialValue = false) { it[tabId]?.canGoBack == true }
|
||||
val canGoBack by
|
||||
store.observeAsState(initialValue = false) { it[tabId]?.content?.canGoBack == true }
|
||||
|
||||
BackHandler(captureBackPresses && canGoBack) { wv.goBack() }
|
||||
|
||||
LaunchedEffect(wv, store) {
|
||||
fun storeFlow(action: suspend Flow<TabWebState>.() -> Unit) = launch {
|
||||
store.flow(lifecycleOwner).mapNotNull { it[tabId] }.action()
|
||||
fun storeFlow(action: suspend Flow<ContentState>.() -> Unit) = launch {
|
||||
store.flow(lifecycleOwner).mapNotNull { it[tabId]?.content }.action()
|
||||
}
|
||||
|
||||
storeFlow {
|
||||
@ -128,6 +129,8 @@ class FrostWebCompose(
|
||||
.apply {
|
||||
onCreated(this)
|
||||
|
||||
logger.atInfo().log("Created webview for %s", tabId)
|
||||
|
||||
this.layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
@ -141,7 +144,7 @@ class FrostWebCompose(
|
||||
webChromeClient = chromeClient
|
||||
webViewClient = client
|
||||
|
||||
val url = store.state[tabId]?.url
|
||||
val url = store.state[tabId]?.content?.url
|
||||
if (url != null) loadUrl(url)
|
||||
}
|
||||
.also { webView = it }
|
||||
@ -163,6 +166,7 @@ class FrostWebCompose(
|
||||
onRelease = { parentFrame ->
|
||||
val wv = parentFrame.children.first() as WebView
|
||||
onDispose(wv)
|
||||
logger.atInfo().log("Released webview for %s", tabId)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import androidx.compose.material.icons.filled.Store
|
||||
import androidx.compose.material.icons.filled.Today
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.ext.WebTargetId
|
||||
import com.pitchedapps.frost.main.MainTabItem
|
||||
import compose.icons.FontAwesomeIcons
|
||||
import compose.icons.fontawesomeicons.Solid
|
||||
@ -118,8 +119,9 @@ enum class FbItem(
|
||||
}
|
||||
|
||||
/** Converts [FbItem] to [MainTabItem]. */
|
||||
fun FbItem.tab(context: Context): MainTabItem =
|
||||
fun FbItem.tab(context: Context, id: WebTargetId): MainTabItem =
|
||||
MainTabItem(
|
||||
id = id,
|
||||
title = context.getString(titleId),
|
||||
icon = icon,
|
||||
url = url,
|
||||
|
@ -20,6 +20,7 @@ import android.webkit.CookieManager
|
||||
import com.google.common.flogger.FluentLogger
|
||||
import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.web.state.FrostLoggerMiddleware
|
||||
import com.pitchedapps.frost.web.state.FrostWebReducer
|
||||
import com.pitchedapps.frost.web.state.FrostWebStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@ -38,11 +39,9 @@ object FrostWebViewModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun frostWebStore(): FrostWebStore {
|
||||
fun frostWebStore(frostWebReducer: FrostWebReducer): FrostWebStore {
|
||||
val middleware = buildList { if (BuildConfig.DEBUG) add(FrostLoggerMiddleware()) }
|
||||
|
||||
val store = FrostWebStore(middleware = middleware)
|
||||
|
||||
return store
|
||||
return FrostWebStore(frostWebReducer = frostWebReducer, middleware = middleware)
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,10 @@ import androidx.activity.compose.setContent
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.google.common.flogger.FluentLogger
|
||||
import com.pitchedapps.frost.compose.FrostTheme
|
||||
import com.pitchedapps.frost.web.state.FrostWebStore
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import mozilla.components.lib.state.ext.observeAsState
|
||||
|
||||
/**
|
||||
* Main activity.
|
||||
@ -32,6 +35,8 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@Inject lateinit var store: FrostWebStore
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@ -43,7 +48,12 @@ class MainActivity : ComponentActivity() {
|
||||
// MainScreen(
|
||||
// tabs = tabs,
|
||||
// )
|
||||
MainScreenWebView()
|
||||
|
||||
val tabs =
|
||||
store.observeAsState(initialValue = null) { it.homeTabs.map { it.tab } }.value
|
||||
?: return@FrostTheme
|
||||
|
||||
MainScreenWebView(homeTabs = tabs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,12 @@
|
||||
package com.pitchedapps.frost.main
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.pitchedapps.frost.ext.WebTargetId
|
||||
|
||||
/** Data representation of a single main tab entry. */
|
||||
data class MainTabItem(val title: String, val icon: ImageVector, val url: String)
|
||||
data class MainTabItem(
|
||||
val id: WebTargetId,
|
||||
val title: String,
|
||||
val icon: ImageVector,
|
||||
val url: String
|
||||
)
|
||||
|
@ -23,6 +23,9 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@ -35,37 +38,81 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pitchedapps.frost.ext.WebTargetId
|
||||
import com.pitchedapps.frost.web.state.FrostWebStore
|
||||
import com.pitchedapps.frost.web.state.TabListAction.SelectHomeTab
|
||||
import com.pitchedapps.frost.webview.FrostWebComposer
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.lib.state.ext.observeAsState
|
||||
|
||||
@Composable
|
||||
fun MainScreenWebView(modifier: Modifier = Modifier) {
|
||||
fun MainScreenWebView(modifier: Modifier = Modifier, homeTabs: List<MainTabItem>) {
|
||||
val vm: MainScreenViewModel = viewModel()
|
||||
|
||||
val selectedHomeTab by vm.store.observeAsState(initialValue = null) { it.selectedHomeTab }
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = { MainTopBar(modifier = modifier) },
|
||||
bottomBar = {
|
||||
MainBottomBar(
|
||||
selectedTab = selectedHomeTab,
|
||||
items = homeTabs,
|
||||
onSelect = { vm.store.dispatch(SelectHomeTab(it)) },
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
MainScreenWebContainer(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
selectedTab = selectedHomeTab,
|
||||
items = homeTabs,
|
||||
store = vm.store,
|
||||
frostWebComposer = vm.frostWebComposer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainTopBar(modifier: Modifier = Modifier) {
|
||||
TopAppBar(modifier = modifier, title = { Text(text = "Title") })
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainBottomBar(
|
||||
modifier: Modifier = Modifier,
|
||||
selectedTab: WebTargetId?,
|
||||
items: List<MainTabItem>,
|
||||
onSelect: (WebTargetId) -> Unit
|
||||
) {
|
||||
NavigationBar(modifier = modifier) {
|
||||
items.forEach { item ->
|
||||
NavigationBarItem(
|
||||
icon = { Icon(item.icon, contentDescription = item.title) },
|
||||
selected = selectedTab == item.id,
|
||||
onClick = { onSelect(item.id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainScreenWebContainer(
|
||||
modifier: Modifier,
|
||||
selectedTab: WebTargetId?,
|
||||
items: List<MainTabItem>,
|
||||
store: FrostWebStore,
|
||||
frostWebComposer: FrostWebComposer
|
||||
) {
|
||||
val homeTabs by store.observeAsState(initialValue = emptyList()) { it.homeTabs }
|
||||
val homeTabComposables = remember(homeTabs) { homeTabs.map { frostWebComposer.create(it.id) } }
|
||||
val homeTabComposables = remember(items) { items.map { frostWebComposer.create(it.id) } }
|
||||
|
||||
PullRefresh(modifier = modifier, store = store) { homeTabComposables.firstOrNull()?.WebView() }
|
||||
PullRefresh(
|
||||
modifier = modifier,
|
||||
store = store,
|
||||
) {
|
||||
homeTabComposables.find { it.tabId == selectedTab }?.WebView()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@ -89,9 +136,3 @@ private fun PullRefresh(modifier: Modifier, store: FrostWebStore, content: @Comp
|
||||
PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainTopBar(modifier: Modifier = Modifier) {
|
||||
TopAppBar(title = { Text(text = "Title") })
|
||||
}
|
||||
|
@ -39,6 +39,8 @@ object InitAction : FrostWebAction
|
||||
/** Actions affecting multiple tabs */
|
||||
sealed interface TabListAction : FrostWebAction {
|
||||
data class SetHomeTabs(val data: List<FbItem>) : TabListAction
|
||||
|
||||
data class SelectHomeTab(val id: WebTargetId) : TabListAction
|
||||
}
|
||||
|
||||
/** Action affecting a single tab */
|
||||
|
@ -19,6 +19,10 @@ package com.pitchedapps.frost.web.state
|
||||
import com.pitchedapps.frost.ext.WebTargetId
|
||||
import com.pitchedapps.frost.web.state.reducer.ContentStateReducer
|
||||
import com.pitchedapps.frost.web.state.reducer.TabListReducer
|
||||
import com.pitchedapps.frost.web.state.state.FloatingTabSessionState
|
||||
import com.pitchedapps.frost.web.state.state.HomeTabSessionState
|
||||
import com.pitchedapps.frost.web.state.state.SessionState
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* See
|
||||
@ -26,25 +30,35 @@ import com.pitchedapps.frost.web.state.reducer.TabListReducer
|
||||
*
|
||||
* For firefox example
|
||||
*/
|
||||
internal object FrostWebReducer {
|
||||
class FrostWebReducer
|
||||
@Inject
|
||||
internal constructor(
|
||||
private val tabListReducer: TabListReducer,
|
||||
private val contentStateReducer: ContentStateReducer
|
||||
) {
|
||||
fun reduce(state: FrostWebState, action: FrostWebAction): FrostWebState {
|
||||
return when (action) {
|
||||
is InitAction -> state
|
||||
is TabListAction -> TabListReducer.reduce(state, action)
|
||||
is TabListAction -> tabListReducer.reduce(state, action)
|
||||
is TabAction ->
|
||||
state.updateTabState(action.tabId) { ContentStateReducer.reduce(it, action.action) }
|
||||
state.updateTabState(action.tabId) { session ->
|
||||
val newContent = contentStateReducer.reduce(session.content, action.action)
|
||||
session.createCopy(content = newContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("Unchecked_Cast")
|
||||
internal fun FrostWebState.updateTabState(
|
||||
tabId: WebTargetId,
|
||||
update: (TabWebState) -> TabWebState,
|
||||
update: (SessionState) -> SessionState,
|
||||
): FrostWebState {
|
||||
val floatingTabMatch = floatingTab?.takeIf { it.id == tabId }
|
||||
if (floatingTabMatch != null) return copy(floatingTab = update(floatingTabMatch))
|
||||
if (floatingTabMatch != null)
|
||||
return copy(floatingTab = update(floatingTabMatch) as FloatingTabSessionState)
|
||||
|
||||
val newHomeTabs = homeTabs.updateTabs(tabId, update)
|
||||
val newHomeTabs = homeTabs.updateTabs(tabId, update) as List<HomeTabSessionState>?
|
||||
if (newHomeTabs != null) return copy(homeTabs = newHomeTabs)
|
||||
return this
|
||||
}
|
||||
@ -55,12 +69,11 @@ internal fun FrostWebState.updateTabState(
|
||||
* @param tabId ID of the tab to change.
|
||||
* @param update Returns a new version of the tab state.
|
||||
*/
|
||||
internal fun List<TabWebState>.updateTabs(
|
||||
internal fun <T : SessionState> List<T>.updateTabs(
|
||||
tabId: WebTargetId,
|
||||
update: (TabWebState) -> TabWebState,
|
||||
): List<TabWebState>? {
|
||||
update: (T) -> T,
|
||||
): List<SessionState>? {
|
||||
val tabIndex = indexOfFirst { it.id == tabId }
|
||||
if (tabIndex == -1) return null
|
||||
|
||||
return subList(0, tabIndex) + update(get(tabIndex)) + subList(tabIndex + 1, size)
|
||||
}
|
||||
|
@ -16,9 +16,10 @@
|
||||
*/
|
||||
package com.pitchedapps.frost.web.state
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.pitchedapps.frost.ext.FrostAccountId
|
||||
import com.pitchedapps.frost.ext.WebTargetId
|
||||
import com.pitchedapps.frost.web.state.state.FloatingTabSessionState
|
||||
import com.pitchedapps.frost.web.state.state.HomeTabSessionState
|
||||
import mozilla.components.lib.state.State
|
||||
|
||||
/**
|
||||
@ -29,8 +30,9 @@ import mozilla.components.lib.state.State
|
||||
*/
|
||||
data class FrostWebState(
|
||||
val auth: AuthWebState = AuthWebState(),
|
||||
val homeTabs: List<TabWebState> = emptyList(),
|
||||
var floatingTab: TabWebState? = null,
|
||||
val selectedHomeTab: WebTargetId? = null,
|
||||
val homeTabs: List<HomeTabSessionState> = emptyList(),
|
||||
var floatingTab: FloatingTabSessionState? = null,
|
||||
) : State
|
||||
|
||||
/**
|
||||
@ -57,40 +59,3 @@ data class AuthWebState(
|
||||
object Unknown : AuthUser
|
||||
}
|
||||
}
|
||||
|
||||
data class TabWebState(
|
||||
val id: WebTargetId,
|
||||
val userId: AuthWebState.AuthUser,
|
||||
val baseUrl: String,
|
||||
val url: String,
|
||||
val icon: ImageVector? = null,
|
||||
val title: String? = null,
|
||||
val progress: Int = 100,
|
||||
val loading: Boolean = false,
|
||||
val canGoBack: Boolean = false,
|
||||
val canGoForward: Boolean = false,
|
||||
val transientState: TransientWebState = TransientWebState(),
|
||||
) {
|
||||
companion object {
|
||||
fun homeTabId(index: Int): WebTargetId = WebTargetId("home-tab--$index")
|
||||
|
||||
val FLOATING_TAB_ID = WebTargetId("floating-tab")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transient web state.
|
||||
*
|
||||
* While we typically don't want to store this, our webview is not a composable, and requires a
|
||||
* bridge to handle events.
|
||||
*
|
||||
* This state is not a list of pending actions, but rather a snapshot of the expected changes so
|
||||
* that conflicting events can be ignored.
|
||||
*
|
||||
* @param targetUrl url destination if nonnull
|
||||
* @param navStep pending steps. Positive = steps forward, negative = steps backward
|
||||
*/
|
||||
data class TransientWebState(
|
||||
val targetUrl: String? = null,
|
||||
val navStep: Int = 0,
|
||||
)
|
||||
|
@ -17,6 +17,7 @@
|
||||
package com.pitchedapps.frost.web.state
|
||||
|
||||
import com.pitchedapps.frost.ext.WebTargetId
|
||||
import com.pitchedapps.frost.web.state.state.SessionState
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.Store
|
||||
|
||||
@ -28,11 +29,12 @@ import mozilla.components.lib.state.Store
|
||||
*/
|
||||
class FrostWebStore(
|
||||
initialState: FrostWebState = FrostWebState(),
|
||||
frostWebReducer: FrostWebReducer,
|
||||
middleware: List<Middleware<FrostWebState, FrostWebAction>> = emptyList(),
|
||||
) :
|
||||
Store<FrostWebState, FrostWebAction>(
|
||||
initialState,
|
||||
FrostWebReducer::reduce,
|
||||
frostWebReducer::reduce,
|
||||
middleware,
|
||||
"FrostStore",
|
||||
) {
|
||||
@ -41,7 +43,7 @@ class FrostWebStore(
|
||||
}
|
||||
}
|
||||
|
||||
operator fun FrostWebState.get(tabId: WebTargetId): TabWebState? {
|
||||
operator fun FrostWebState.get(tabId: WebTargetId): SessionState? {
|
||||
if (floatingTab?.id == tabId) return floatingTab
|
||||
return homeTabs.find { it.id == tabId }
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import com.pitchedapps.frost.ext.WebTargetId
|
||||
import com.pitchedapps.frost.web.state.FrostWebState
|
||||
import com.pitchedapps.frost.web.state.FrostWebStore
|
||||
import com.pitchedapps.frost.web.state.TabWebState
|
||||
import com.pitchedapps.frost.web.state.state.SessionState
|
||||
import mozilla.components.lib.state.Store
|
||||
import mozilla.components.lib.state.ext.observeAsComposableState
|
||||
|
||||
@ -35,20 +35,20 @@ import mozilla.components.lib.state.ext.observeAsComposableState
|
||||
*/
|
||||
sealed class Target {
|
||||
/**
|
||||
* Looks up this target in the given [FrostWebStore] and returns the matching [TabWebState] if
|
||||
* Looks up this target in the given [FrostWebStore] and returns the matching [SessionState] if
|
||||
* available. Otherwise returns `null`.
|
||||
*
|
||||
* @param store to lookup this target in.
|
||||
*/
|
||||
fun lookupIn(store: FrostWebStore): TabWebState? = lookupIn(store.state)
|
||||
fun lookupIn(store: FrostWebStore): SessionState? = lookupIn(store.state)
|
||||
|
||||
/**
|
||||
* Looks up this target in the given [FrostWebState] and returns the matching [TabWebState] if
|
||||
* Looks up this target in the given [FrostWebState] and returns the matching [SessionState] if
|
||||
* available. Otherwise returns `null`.
|
||||
*
|
||||
* @param state to lookup this target in.
|
||||
*/
|
||||
abstract fun lookupIn(state: FrostWebState): TabWebState?
|
||||
abstract fun lookupIn(state: FrostWebState): SessionState?
|
||||
|
||||
/**
|
||||
* Observes this target and represents the mapped state (using [map]) via [State].
|
||||
@ -60,14 +60,14 @@ sealed class Target {
|
||||
* [LifecycleOwner] moves to the [Lifecycle.State.DESTROYED] state.
|
||||
*
|
||||
* @param store that should get observed
|
||||
* @param observe function that maps a [TabWebState] to the (sub) state that should get observed
|
||||
* @param observe function that maps a [SessionState] to the (sub) state that should get observed
|
||||
* for changes.
|
||||
*/
|
||||
@Composable
|
||||
fun <R> observeAsComposableStateFrom(
|
||||
store: FrostWebStore,
|
||||
observe: (TabWebState?) -> R,
|
||||
): State<TabWebState?> {
|
||||
observe: (SessionState?) -> R,
|
||||
): State<SessionState?> {
|
||||
return store.observeAsComposableState(
|
||||
map = { state -> lookupIn(state) },
|
||||
observe = { state -> observe(lookupIn(state)) },
|
||||
@ -75,13 +75,13 @@ sealed class Target {
|
||||
}
|
||||
|
||||
data class HomeTab(val id: WebTargetId) : Target() {
|
||||
override fun lookupIn(state: FrostWebState): TabWebState? {
|
||||
override fun lookupIn(state: FrostWebState): SessionState? {
|
||||
return state.homeTabs.find { it.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
object FloatingTab : Target() {
|
||||
override fun lookupIn(state: FrostWebState): TabWebState? {
|
||||
override fun lookupIn(state: FrostWebState): SessionState? {
|
||||
return state.floatingTab
|
||||
}
|
||||
}
|
||||
|
@ -28,11 +28,13 @@ import com.pitchedapps.frost.web.state.TabAction.UserAction
|
||||
import com.pitchedapps.frost.web.state.TabAction.UserAction.GoBackAction
|
||||
import com.pitchedapps.frost.web.state.TabAction.UserAction.GoForwardAction
|
||||
import com.pitchedapps.frost.web.state.TabAction.UserAction.LoadUrlAction
|
||||
import com.pitchedapps.frost.web.state.TabWebState
|
||||
import com.pitchedapps.frost.web.state.TransientWebState
|
||||
import com.pitchedapps.frost.web.state.state.ContentState
|
||||
import com.pitchedapps.frost.web.state.state.TransientWebState
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object ContentStateReducer {
|
||||
fun reduce(state: TabWebState, action: Action): TabWebState {
|
||||
internal class ContentStateReducer @Inject internal constructor() {
|
||||
|
||||
fun reduce(state: ContentState, action: Action): ContentState {
|
||||
return when (action) {
|
||||
is UpdateUrlAction -> state.copy(url = action.url)
|
||||
is UpdateProgressAction -> state.copy(progress = action.progress)
|
||||
|
@ -16,29 +16,42 @@
|
||||
*/
|
||||
package com.pitchedapps.frost.web.state.reducer
|
||||
|
||||
import android.content.Context
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.tab
|
||||
import com.pitchedapps.frost.web.state.AuthWebState
|
||||
import com.pitchedapps.frost.web.state.FrostWebState
|
||||
import com.pitchedapps.frost.web.state.TabListAction
|
||||
import com.pitchedapps.frost.web.state.TabListAction.SetHomeTabs
|
||||
import com.pitchedapps.frost.web.state.TabWebState
|
||||
import com.pitchedapps.frost.web.state.state.ContentState
|
||||
import com.pitchedapps.frost.web.state.state.HomeTabSessionState
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object TabListReducer {
|
||||
internal class TabListReducer
|
||||
@Inject
|
||||
internal constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
fun reduce(state: FrostWebState, action: TabListAction): FrostWebState {
|
||||
return when (action) {
|
||||
is SetHomeTabs -> {
|
||||
val tabs = action.data.mapIndexed { i, fbItem -> fbItem.toTab(i, state.auth) }
|
||||
val tabs =
|
||||
action.data.mapIndexed { i, fbItem -> fbItem.toHomeTabSession(context, i, state.auth) }
|
||||
state.copy(homeTabs = tabs)
|
||||
}
|
||||
is TabListAction.SelectHomeTab -> state.copy(selectedHomeTab = action.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FbItem.toTab(i: Int, auth: AuthWebState): TabWebState =
|
||||
TabWebState(
|
||||
id = TabWebState.homeTabId(i),
|
||||
private fun FbItem.toHomeTabSession(
|
||||
context: Context,
|
||||
i: Int,
|
||||
auth: AuthWebState
|
||||
): HomeTabSessionState =
|
||||
HomeTabSessionState(
|
||||
userId = auth.currentUser,
|
||||
baseUrl = url,
|
||||
url = url,
|
||||
icon = icon,
|
||||
content = ContentState(url = url),
|
||||
tab = tab(context, id = HomeTabSessionState.homeTabId(i)),
|
||||
)
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.pitchedapps.frost.web.state.state
|
||||
|
||||
import com.pitchedapps.frost.ext.WebTargetId
|
||||
import com.pitchedapps.frost.main.MainTabItem
|
||||
import com.pitchedapps.frost.web.state.AuthWebState.AuthUser
|
||||
|
||||
/** Data representation of single session. */
|
||||
interface SessionState {
|
||||
val id: WebTargetId
|
||||
val userId: AuthUser
|
||||
val content: ContentState
|
||||
|
||||
fun createCopy(
|
||||
id: WebTargetId = this.id,
|
||||
userId: AuthUser = this.userId,
|
||||
content: ContentState = this.content
|
||||
): SessionState
|
||||
}
|
||||
|
||||
/** Session for home screen, which includes nav bar data */
|
||||
data class HomeTabSessionState(
|
||||
override val userId: AuthUser,
|
||||
override val content: ContentState,
|
||||
val tab: MainTabItem,
|
||||
) : SessionState {
|
||||
|
||||
override val id: WebTargetId
|
||||
get() = tab.id
|
||||
|
||||
override fun createCopy(id: WebTargetId, userId: AuthUser, content: ContentState) =
|
||||
copy(userId = userId, content = content, tab = tab.copy(id = id))
|
||||
|
||||
companion object {
|
||||
fun homeTabId(index: Int): WebTargetId = WebTargetId("home-tab--$index")
|
||||
}
|
||||
}
|
||||
|
||||
data class FloatingTabSessionState(
|
||||
override val id: WebTargetId,
|
||||
override val userId: AuthUser,
|
||||
override val content: ContentState,
|
||||
) : SessionState {
|
||||
override fun createCopy(id: WebTargetId, userId: AuthUser, content: ContentState) =
|
||||
copy(id = id, userId = userId, content = content)
|
||||
}
|
||||
|
||||
/** Data relating to webview content */
|
||||
data class ContentState(
|
||||
val url: String,
|
||||
val title: String? = null,
|
||||
val progress: Int = 0,
|
||||
val loading: Boolean = false,
|
||||
val canGoBack: Boolean = false,
|
||||
val canGoForward: Boolean = false,
|
||||
val transientState: TransientWebState = TransientWebState(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Transient web state.
|
||||
*
|
||||
* While we typically don't want to store this, our webview is not a composable, and requires a
|
||||
* bridge to handle events.
|
||||
*
|
||||
* This state is not a list of pending actions, but rather a snapshot of the expected changes so
|
||||
* that conflicting events can be ignored.
|
||||
*
|
||||
* @param targetUrl url destination if nonnull
|
||||
* @param navStep pending steps. Positive = steps forward, negative = steps backward
|
||||
*/
|
||||
data class TransientWebState(
|
||||
val targetUrl: String? = null,
|
||||
val navStep: Int = 0,
|
||||
)
|
@ -55,7 +55,7 @@ class FrostChromeClient(private val tabId: WebTargetId, private val store: Frost
|
||||
override fun onProgressChanged(view: WebView, newProgress: Int) {
|
||||
super.onProgressChanged(view, newProgress)
|
||||
// TODO remove?
|
||||
if (store.state[tabId]?.progress == 100) return
|
||||
if (store.state[tabId]?.content?.progress == 100) return
|
||||
store.dispatch(UpdateProgressAction(newProgress))
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user