1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-09-19 23:21:34 +02:00

Add home tab sample

This commit is contained in:
Allan Wang 2023-06-20 02:45:53 -07:00
parent 38f0312610
commit d88bde4067
No known key found for this signature in database
GPG Key ID: C93E3F9C679D7A56
9 changed files with 165 additions and 37 deletions

View File

@ -19,6 +19,7 @@ package com.pitchedapps.frost
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.google.common.flogger.FluentLogger import com.google.common.flogger.FluentLogger
import com.pitchedapps.frost.components.FrostDataStore import com.pitchedapps.frost.components.FrostDataStore
import com.pitchedapps.frost.db.FrostDb import com.pitchedapps.frost.db.FrostDb
@ -26,12 +27,15 @@ import com.pitchedapps.frost.ext.FrostAccountId
import com.pitchedapps.frost.ext.idData import com.pitchedapps.frost.ext.idData
import com.pitchedapps.frost.ext.launchActivity import com.pitchedapps.frost.ext.launchActivity
import com.pitchedapps.frost.extension.FrostCoreExtension import com.pitchedapps.frost.extension.FrostCoreExtension
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.main.MainActivity 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 dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -43,22 +47,44 @@ import kotlinx.coroutines.withContext
* and will launch another activity without history after doing initialization work. * and will launch another activity without history after doing initialization work.
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class StartActivity : AppCompatActivity(), CoroutineScope by MainScope() { class StartActivity : AppCompatActivity() {
@Inject lateinit var frostDb: FrostDb @Inject lateinit var frostDb: FrostDb
@Inject lateinit var dataStore: FrostDataStore @Inject lateinit var dataStore: FrostDataStore
@Inject lateinit var frostCoreExtension: FrostCoreExtension @Inject lateinit var frostCoreExtension: FrostCoreExtension
@Inject lateinit var store: FrostWebStore
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
launch { lifecycleScope.launch {
val id = withContext(Dispatchers.IO) { getCurrentAccountId() } val id = withContext(Dispatchers.IO) { getCurrentAccountId() }
frostCoreExtension.install() // frostCoreExtension.install()
logger.atInfo().log("Starting Frost with id %s", id) logger.atInfo().log("Starting Frost with id %s", id)
// TODO load real tabs
store.dispatch(TabListAction.SetHomeTabs(data = listOf(FbItem.Feed, FbItem.Menu)))
// Test something scrollable
store.dispatch(
TabAction(
tabId = TabWebState.homeTabId(0),
TabAction.ContentAction.UpdateUrlAction(
"https://github.com/AllanWang/Frost-for-Facebook"
),
),
)
store.dispatch(
TabAction(
tabId = TabWebState.homeTabId(1),
TabAction.ContentAction.UpdateUrlAction("https://github.com/AllanWang/KAU"),
),
)
launchActivity<MainActivity>( launchActivity<MainActivity>(
intentBuilder = { intentBuilder = {
flags = flags =

View File

@ -22,8 +22,6 @@ import androidx.activity.compose.setContent
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import com.google.common.flogger.FluentLogger import com.google.common.flogger.FluentLogger
import com.pitchedapps.frost.compose.FrostTheme import com.pitchedapps.frost.compose.FrostTheme
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.tab
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
/** /**
@ -40,14 +38,12 @@ class MainActivity : ComponentActivity() {
logger.atInfo().log("onCreate main activity") logger.atInfo().log("onCreate main activity")
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
val tabs = FbItem.defaults().map { it.tab(this) } // TODO allow custom tabs
setContent { setContent {
FrostTheme { FrostTheme {
// MainScreen( // MainScreen(
// tabs = tabs, // tabs = tabs,
// ) // )
MainScreen2() MainScreenWebView()
} }
} }
} }

View File

@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.ContentAlpha import androidx.compose.material.ContentAlpha
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow import androidx.compose.material3.TabRow
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -44,23 +43,6 @@ import com.pitchedapps.frost.ext.GeckoContextId
import com.pitchedapps.frost.ext.components import com.pitchedapps.frost.ext.components
import mozilla.components.browser.state.helper.Target import mozilla.components.browser.state.helper.Target
@Composable
fun MainScreen2(modifier: Modifier = Modifier) {
val vm: MainScreenViewModel = viewModel()
Scaffold(
modifier = modifier,
topBar = { MainTopBar(modifier = modifier) },
) { paddingValues ->
vm.frostWebCompose.WebView(modifier = Modifier.padding(paddingValues))
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainTopBar(modifier: Modifier = Modifier) {
TopAppBar(title = { Text(text = "Title") })
}
/** /**
* Screen for MainActivity. * Screen for MainActivity.
* *

View File

@ -21,13 +21,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.pitchedapps.frost.compose.webview.FrostWebCompose
import com.pitchedapps.frost.ext.GeckoContextId import com.pitchedapps.frost.ext.GeckoContextId
import com.pitchedapps.frost.ext.WebTargetId
import com.pitchedapps.frost.ext.idData import com.pitchedapps.frost.ext.idData
import com.pitchedapps.frost.ext.toContextId import com.pitchedapps.frost.ext.toContextId
import com.pitchedapps.frost.extension.FrostCoreExtension import com.pitchedapps.frost.extension.FrostCoreExtension
import com.pitchedapps.frost.hilt.FrostComponents import com.pitchedapps.frost.hilt.FrostComponents
import com.pitchedapps.frost.web.state.FrostWebStore
import com.pitchedapps.frost.webview.FrostWebComposer import com.pitchedapps.frost.webview.FrostWebComposer
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -42,14 +41,13 @@ internal constructor(
@ApplicationContext context: Context, @ApplicationContext context: Context,
val components: FrostComponents, val components: FrostComponents,
val frostCoreExtension: FrostCoreExtension, val frostCoreExtension: FrostCoreExtension,
// sample: FrostWebEntrySample, val store: FrostWebStore,
frostWebComposer: FrostWebComposer, val frostWebComposer: FrostWebComposer,
// sample: FrostWebEntrySample,
) : ViewModel() { ) : ViewModel() {
val contextIdFlow: Flow<GeckoContextId?> = val contextIdFlow: Flow<GeckoContextId?> =
components.dataStore.account.idData.map { it?.toContextId() } components.dataStore.account.idData.map { it?.toContextId() }
var tabIndex: Int by mutableStateOf(0) var tabIndex: Int by mutableStateOf(0)
val frostWebCompose: FrostWebCompose = frostWebComposer.create(WebTargetId("test"))
} }

View File

@ -0,0 +1,65 @@
/*
* 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.main
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import com.pitchedapps.frost.web.state.FrostWebStore
import com.pitchedapps.frost.webview.FrostWebComposer
import mozilla.components.lib.state.ext.observeAsState
@Composable
fun MainScreenWebView(modifier: Modifier = Modifier) {
val vm: MainScreenViewModel = viewModel()
Scaffold(
modifier = modifier,
topBar = { MainTopBar(modifier = modifier) },
) { paddingValues ->
MainScreenWebContainer(
modifier = Modifier.padding(paddingValues),
store = vm.store,
frostWebComposer = vm.frostWebComposer,
)
}
}
@Composable
private fun MainScreenWebContainer(
modifier: Modifier,
store: FrostWebStore,
frostWebComposer: FrostWebComposer
) {
val homeTabs by store.observeAsState(initialValue = emptyList()) { it.homeTabs }
val homeTabComposables = remember(homeTabs) { homeTabs.map { frostWebComposer.create(it.id) } }
Box(modifier = modifier) { homeTabComposables.firstOrNull()?.WebView() }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainTopBar(modifier: Modifier = Modifier) {
TopAppBar(title = { Text(text = "Title") })
}

View File

@ -17,6 +17,7 @@
package com.pitchedapps.frost.web.state package com.pitchedapps.frost.web.state
import com.pitchedapps.frost.ext.WebTargetId import com.pitchedapps.frost.ext.WebTargetId
import com.pitchedapps.frost.facebook.FbItem
import mozilla.components.lib.state.Action import mozilla.components.lib.state.Action
/** /**
@ -35,6 +36,11 @@ sealed interface FrostWebAction : Action
*/ */
object InitAction : FrostWebAction object InitAction : FrostWebAction
/** Actions affecting multiple tabs */
sealed interface TabListAction : FrostWebAction {
data class SetHomeTabs(val data: List<FbItem>) : TabListAction
}
/** Action affecting a single tab */ /** Action affecting a single tab */
data class TabAction(val tabId: WebTargetId, val action: Action) : FrostWebAction { data class TabAction(val tabId: WebTargetId, val action: Action) : FrostWebAction {
sealed interface Action sealed interface Action

View File

@ -18,6 +18,7 @@ package com.pitchedapps.frost.web.state
import com.pitchedapps.frost.ext.WebTargetId import com.pitchedapps.frost.ext.WebTargetId
import com.pitchedapps.frost.web.state.reducer.ContentStateReducer import com.pitchedapps.frost.web.state.reducer.ContentStateReducer
import com.pitchedapps.frost.web.state.reducer.TabListReducer
/** /**
* See * See
@ -29,6 +30,7 @@ internal object FrostWebReducer {
fun reduce(state: FrostWebState, action: FrostWebAction): FrostWebState { fun reduce(state: FrostWebState, action: FrostWebAction): FrostWebState {
return when (action) { return when (action) {
is InitAction -> state is InitAction -> state
is TabListAction -> TabListReducer.reduce(state, action)
is TabAction -> is TabAction ->
state.updateTabState(action.tabId) { ContentStateReducer.reduce(it, action.action) } state.updateTabState(action.tabId) { ContentStateReducer.reduce(it, action.action) }
} }

View File

@ -16,6 +16,7 @@
*/ */
package com.pitchedapps.frost.web.state package com.pitchedapps.frost.web.state
import androidx.compose.ui.graphics.vector.ImageVector
import com.pitchedapps.frost.ext.FrostAccountId import com.pitchedapps.frost.ext.FrostAccountId
import com.pitchedapps.frost.ext.WebTargetId import com.pitchedapps.frost.ext.WebTargetId
import mozilla.components.lib.state.State import mozilla.components.lib.state.State
@ -60,14 +61,22 @@ data class AuthWebState(
data class TabWebState( data class TabWebState(
val id: WebTargetId, val id: WebTargetId,
val userId: AuthWebState.AuthUser, val userId: AuthWebState.AuthUser,
val baseUrl: String? = null, val baseUrl: String,
val url: String? = null, val url: String,
val icon: ImageVector? = null,
val title: String? = null, val title: String? = null,
val progress: Int = 100, val progress: Int = 100,
val loading: Boolean = false,
val canGoBack: Boolean = false, val canGoBack: Boolean = false,
val canGoForward: Boolean = false, val canGoForward: Boolean = false,
val transientState: TransientWebState = TransientWebState(), 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. * Transient web state.

View File

@ -0,0 +1,44 @@
/*
* 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.reducer
import com.pitchedapps.frost.facebook.FbItem
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
internal object TabListReducer {
fun reduce(state: FrostWebState, action: TabListAction): FrostWebState {
return when (action) {
is SetHomeTabs -> {
val tabs = action.data.mapIndexed { i, fbItem -> fbItem.toTab(i, state.auth) }
state.copy(homeTabs = tabs)
}
}
}
}
private fun FbItem.toTab(i: Int, auth: AuthWebState): TabWebState =
TabWebState(
id = TabWebState.homeTabId(i),
userId = auth.currentUser,
baseUrl = url,
url = url,
icon = icon,
)