mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-09 20:42:34 +01:00
Add home tab sample
This commit is contained in:
parent
38f0312610
commit
d88bde4067
@ -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 =
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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"))
|
|
||||||
}
|
}
|
||||||
|
@ -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") })
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user