Add account switch bottom sheet and update LocalPrefs
This commit is contained in:
@@ -11,21 +11,44 @@ import com.vitorpamplona.amethyst.service.model.Event
|
||||
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
|
||||
import nostr.postr.Persona
|
||||
import nostr.postr.toHex
|
||||
import nostr.postr.toNpub
|
||||
import java.util.Locale
|
||||
|
||||
data class AccountInfo(val npub: String, val current: Boolean, val displayName: String?, val profilePicture: String?)
|
||||
|
||||
class LocalPreferences(context: Context) {
|
||||
|
||||
private fun prefKeysForAccount(npub: String) = object {
|
||||
val NOSTR_PRIVKEY = "$npub/nostr_privkey"
|
||||
val NOSTR_PUBKEY = "$npub/nostr_pubkey"
|
||||
val DISPLAY_NAME = "$npub/display_name"
|
||||
val PROFILE_PICTURE_URL = "$npub/profile_picture"
|
||||
val FOLLOWING_CHANNELS = "$npub/following_channels"
|
||||
val HIDDEN_USERS = "$npub/hidden_users"
|
||||
val RELAYS = "$npub/relays"
|
||||
val DONT_TRANSLATE_FROM = "$npub/dontTranslateFrom"
|
||||
val LANGUAGE_PREFS = "$npub/languagePreferences"
|
||||
val TRANSLATE_TO = "$npub/translateTo"
|
||||
val ZAP_AMOUNTS = "$npub/zapAmounts"
|
||||
val LATEST_CONTACT_LIST = "$npub/latestContactList"
|
||||
val HIDE_DELETE_REQUEST_INFO = "$npub/hideDeleteRequestInfo"
|
||||
// val LAST_READ: (String) -> String = { route -> "$npub/last_read_route_$route" }
|
||||
}
|
||||
|
||||
private object PrefKeys {
|
||||
const val NOSTR_PRIVKEY = "nostr_privkey"
|
||||
const val NOSTR_PUBKEY = "nostr_pubkey"
|
||||
const val FOLLOWING_CHANNELS = "following_channels"
|
||||
const val HIDDEN_USERS = "hidden_users"
|
||||
const val RELAYS = "relays"
|
||||
const val DONT_TRANSLATE_FROM = "dontTranslateFrom"
|
||||
const val LANGUAGE_PREFS = "languagePreferences"
|
||||
const val TRANSLATE_TO = "translateTo"
|
||||
const val ZAP_AMOUNTS = "zapAmounts"
|
||||
const val LATEST_CONTACT_LIST = "latestContactList"
|
||||
const val HIDE_DELETE_REQUEST_INFO = "hideDeleteRequestInfo"
|
||||
const val CURRENT_ACCOUNT = "currentlyLoggedInAccount"
|
||||
|
||||
// val NOSTR_PRIVKEY = "nostr_privkey"
|
||||
// val NOSTR_PUBKEY = "nostr_pubkey"
|
||||
// val FOLLOWING_CHANNELS = "following_channels"
|
||||
// val HIDDEN_USERS = "hidden_users"
|
||||
// val RELAYS = "relays"
|
||||
// val DONT_TRANSLATE_FROM = "dontTranslateFrom"
|
||||
// val LANGUAGE_PREFS = "languagePreferences"
|
||||
// val TRANSLATE_TO = "translateTo"
|
||||
// val ZAP_AMOUNTS = "zapAmounts"
|
||||
// val LATEST_CONTACT_LIST = "latestContactList"
|
||||
// val HIDE_DELETE_REQUEST_INFO = "hideDeleteRequestInfo"
|
||||
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
||||
}
|
||||
|
||||
@@ -34,47 +57,88 @@ class LocalPreferences(context: Context) {
|
||||
|
||||
fun clearEncryptedStorage() {
|
||||
encryptedPreferences.edit().apply {
|
||||
encryptedPreferences.all.keys.forEach { remove(it) }
|
||||
encryptedPreferences.all.keys.forEach {
|
||||
remove(it)
|
||||
}
|
||||
// encryptedPreferences.all.keys.filter {
|
||||
// it.startsWith(npub)
|
||||
// }.forEach {
|
||||
// remove(it)
|
||||
// }
|
||||
}.apply()
|
||||
}
|
||||
|
||||
fun findAllLocalAccounts(): List<AccountInfo> {
|
||||
encryptedPreferences.apply {
|
||||
val currentAccount = getString(PrefKeys.CURRENT_ACCOUNT, null)
|
||||
return encryptedPreferences.all.keys.filter {
|
||||
it.endsWith("nostr_pubkey")
|
||||
}.map {
|
||||
val npub = it.substringBefore("/")
|
||||
val myPrefs = prefKeysForAccount(npub)
|
||||
AccountInfo(
|
||||
npub,
|
||||
npub == currentAccount,
|
||||
getString(myPrefs.DISPLAY_NAME, null),
|
||||
getString(myPrefs.PROFILE_PICTURE_URL, null)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveToEncryptedStorage(account: Account) {
|
||||
val npub = account.loggedIn.pubKey.toNpub()
|
||||
val myPrefs = prefKeysForAccount(npub)
|
||||
|
||||
encryptedPreferences.edit().apply {
|
||||
account.loggedIn.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHex()) }
|
||||
account.loggedIn.pubKey.let { putString(PrefKeys.NOSTR_PUBKEY, it.toHex()) }
|
||||
account.followingChannels.let { putStringSet(PrefKeys.FOLLOWING_CHANNELS, it) }
|
||||
account.hiddenUsers.let { putStringSet(PrefKeys.HIDDEN_USERS, it) }
|
||||
account.localRelays.let { putString(PrefKeys.RELAYS, gson.toJson(it)) }
|
||||
account.dontTranslateFrom.let { putStringSet(PrefKeys.DONT_TRANSLATE_FROM, it) }
|
||||
account.languagePreferences.let { putString(PrefKeys.LANGUAGE_PREFS, gson.toJson(it)) }
|
||||
account.translateTo.let { putString(PrefKeys.TRANSLATE_TO, it) }
|
||||
account.zapAmountChoices.let { putString(PrefKeys.ZAP_AMOUNTS, gson.toJson(it)) }
|
||||
account.backupContactList.let { putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(it)) }
|
||||
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, account.hideDeleteRequestInfo)
|
||||
putString(PrefKeys.CURRENT_ACCOUNT, npub)
|
||||
account.loggedIn.privKey?.let { putString(myPrefs.NOSTR_PRIVKEY, it.toHex()) }
|
||||
account.loggedIn.pubKey.let { putString(myPrefs.NOSTR_PUBKEY, it.toHex()) }
|
||||
putStringSet(myPrefs.FOLLOWING_CHANNELS, account.followingChannels)
|
||||
putStringSet(myPrefs.HIDDEN_USERS, account.hiddenUsers)
|
||||
putString(myPrefs.RELAYS, gson.toJson(account.localRelays))
|
||||
putStringSet(myPrefs.DONT_TRANSLATE_FROM, account.dontTranslateFrom)
|
||||
putString(myPrefs.LANGUAGE_PREFS, gson.toJson(account.languagePreferences))
|
||||
putString(myPrefs.TRANSLATE_TO, account.translateTo)
|
||||
putString(myPrefs.ZAP_AMOUNTS, gson.toJson(account.zapAmountChoices))
|
||||
putString(myPrefs.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList))
|
||||
putBoolean(myPrefs.HIDE_DELETE_REQUEST_INFO, account.hideDeleteRequestInfo)
|
||||
}.apply()
|
||||
}
|
||||
|
||||
fun saveCurrentAccountMetadata(account: Account) {
|
||||
val myPrefs = prefKeysForAccount(account.loggedIn.pubKey.toNpub())
|
||||
|
||||
encryptedPreferences.edit().apply {
|
||||
putString(myPrefs.DISPLAY_NAME, account.userProfile().toBestDisplayName())
|
||||
putString(myPrefs.PROFILE_PICTURE_URL, account.userProfile().profilePicture())
|
||||
}.apply()
|
||||
}
|
||||
|
||||
fun loadFromEncryptedStorage(): Account? {
|
||||
encryptedPreferences.apply {
|
||||
val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
|
||||
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null)
|
||||
val followingChannels = getStringSet(PrefKeys.FOLLOWING_CHANNELS, null) ?: setOf()
|
||||
val hiddenUsers = getStringSet(PrefKeys.HIDDEN_USERS, emptySet()) ?: setOf()
|
||||
val npub = getString(PrefKeys.CURRENT_ACCOUNT, null) ?: return null
|
||||
val myPrefs = prefKeysForAccount(npub)
|
||||
|
||||
val pubKey = getString(myPrefs.NOSTR_PUBKEY, null) ?: return null
|
||||
val privKey = getString(myPrefs.NOSTR_PRIVKEY, null)
|
||||
val followingChannels = getStringSet(myPrefs.FOLLOWING_CHANNELS, null) ?: setOf()
|
||||
val hiddenUsers = getStringSet(myPrefs.HIDDEN_USERS, emptySet()) ?: setOf()
|
||||
val localRelays = gson.fromJson(
|
||||
getString(PrefKeys.RELAYS, "[]"),
|
||||
getString(myPrefs.RELAYS, "[]"),
|
||||
object : TypeToken<Set<RelaySetupInfo>>() {}.type
|
||||
) ?: setOf<RelaySetupInfo>()
|
||||
|
||||
val dontTranslateFrom = getStringSet(PrefKeys.DONT_TRANSLATE_FROM, null) ?: setOf()
|
||||
val translateTo = getString(PrefKeys.TRANSLATE_TO, null) ?: Locale.getDefault().language
|
||||
val dontTranslateFrom = getStringSet(myPrefs.DONT_TRANSLATE_FROM, null) ?: setOf()
|
||||
val translateTo = getString(myPrefs.TRANSLATE_TO, null) ?: Locale.getDefault().language
|
||||
|
||||
val zapAmountChoices = gson.fromJson(
|
||||
getString(PrefKeys.ZAP_AMOUNTS, "[]"),
|
||||
getString(myPrefs.ZAP_AMOUNTS, "[]"),
|
||||
object : TypeToken<List<Long>>() {}.type
|
||||
) ?: listOf(500L, 1000L, 5000L)
|
||||
|
||||
val latestContactList = try {
|
||||
getString(PrefKeys.LATEST_CONTACT_LIST, null)?.let {
|
||||
getString(myPrefs.LATEST_CONTACT_LIST, null)?.let {
|
||||
Event.gson.fromJson(it, Event::class.java).getRefinedEvent(true) as ContactListEvent
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
@@ -83,32 +147,28 @@ class LocalPreferences(context: Context) {
|
||||
}
|
||||
|
||||
val languagePreferences = try {
|
||||
getString(PrefKeys.LANGUAGE_PREFS, null)?.let {
|
||||
getString(myPrefs.LANGUAGE_PREFS, null)?.let {
|
||||
gson.fromJson(it, object : TypeToken<Map<String, String>>() {}.type) as Map<String, String>
|
||||
} ?: mapOf<String, String>()
|
||||
} ?: mapOf()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
mapOf<String, String>()
|
||||
mapOf()
|
||||
}
|
||||
|
||||
val hideDeleteRequestInfo = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, false)
|
||||
val hideDeleteRequestInfo = getBoolean(myPrefs.HIDE_DELETE_REQUEST_INFO, false)
|
||||
|
||||
if (pubKey != null) {
|
||||
return Account(
|
||||
Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()),
|
||||
followingChannels,
|
||||
hiddenUsers,
|
||||
localRelays,
|
||||
dontTranslateFrom,
|
||||
languagePreferences,
|
||||
translateTo,
|
||||
zapAmountChoices,
|
||||
hideDeleteRequestInfo,
|
||||
latestContactList
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return Account(
|
||||
Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()),
|
||||
followingChannels,
|
||||
hiddenUsers,
|
||||
localRelays,
|
||||
dontTranslateFrom,
|
||||
languagePreferences,
|
||||
translateTo,
|
||||
zapAmountChoices,
|
||||
hideDeleteRequestInfo,
|
||||
latestContactList
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.vitorpamplona.amethyst.ui.navigation
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.ModalBottomSheetState
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Logout
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.RoboHashCache
|
||||
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun AccountSwitchBottomSheet(
|
||||
accountViewModel: AccountViewModel,
|
||||
sheetState: ModalBottomSheetState
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val localPrefs = LocalPreferences(context)
|
||||
val accounts = localPrefs.findAllLocalAccounts()
|
||||
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = accountState?.account ?: return
|
||||
|
||||
val accountUserState by account.userProfile().live().metadata.observeAsState()
|
||||
val accountUser = accountUserState?.user ?: return
|
||||
|
||||
LaunchedEffect(key1 = accountUser) {
|
||||
localPrefs.saveCurrentAccountMetadata(account)
|
||||
}
|
||||
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("Select Account", fontWeight = FontWeight.Bold)
|
||||
}
|
||||
accounts.forEach { acc ->
|
||||
val current = accountUser.pubkeyNpub() == acc.npub
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(32.dp, 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AsyncImageProxy(
|
||||
model = ResizeImage(acc.profilePicture, 64.dp),
|
||||
placeholder = BitmapPainter(RoboHashCache.get(context, acc.npub)),
|
||||
fallback = BitmapPainter(RoboHashCache.get(context, acc.npub)),
|
||||
error = BitmapPainter(RoboHashCache.get(context, acc.npub)),
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
modifier = Modifier
|
||||
.width(64.dp)
|
||||
.height(64.dp)
|
||||
.clip(shape = CircleShape)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column {
|
||||
acc.displayName?.let {
|
||||
Text(it)
|
||||
}
|
||||
Text(acc.npub.toShortenHex())
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
if (current) {
|
||||
Text("✓")
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
IconButton(onClick = { /*TODO*/ }) {
|
||||
Icon(imageVector = Icons.Default.Logout, "Logout")
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
TextButton(onClick = { coroutineScope.launch { sheetState.hide() } }) {
|
||||
Text("Add New Account")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,11 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ModalBottomSheetState
|
||||
import androidx.compose.material.ScaffoldState
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
@@ -56,10 +58,12 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun DrawerContent(
|
||||
navController: NavHostController,
|
||||
scaffoldState: ScaffoldState,
|
||||
sheetState: ModalBottomSheetState,
|
||||
accountViewModel: AccountViewModel,
|
||||
accountStateViewModel: AccountStateViewModel
|
||||
) {
|
||||
@@ -88,6 +92,7 @@ fun DrawerContent(
|
||||
account.userProfile(),
|
||||
navController,
|
||||
scaffoldState,
|
||||
sheetState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1F),
|
||||
@@ -214,15 +219,18 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ListContent(
|
||||
accountUser: User?,
|
||||
navController: NavHostController,
|
||||
scaffoldState: ScaffoldState,
|
||||
sheetState: ModalBottomSheetState,
|
||||
modifier: Modifier,
|
||||
accountViewModel: AccountStateViewModel,
|
||||
account: Account
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var backupDialogOpen by remember { mutableStateOf(false) }
|
||||
|
||||
Column(modifier = modifier.fillMaxHeight()) {
|
||||
@@ -260,11 +268,18 @@ fun ListContent(
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
IconRow(
|
||||
stringResource(R.string.log_out),
|
||||
R.drawable.ic_logout,
|
||||
MaterialTheme.colors.onBackground,
|
||||
onClick = { accountViewModel.logOff() }
|
||||
title = "Accounts",
|
||||
icon = R.drawable.manage_accounts,
|
||||
tint = MaterialTheme.colors.onBackground,
|
||||
onClick = { coroutineScope.launch { sheetState.show() } }
|
||||
)
|
||||
|
||||
// IconRow(
|
||||
// title = stringResource(R.string.log_out),
|
||||
// icon = R.drawable.ic_logout,
|
||||
// tint = MaterialTheme.colors.onBackground,
|
||||
// onClick = { accountViewModel.logOff() }
|
||||
// )
|
||||
}
|
||||
|
||||
if (backupDialogOpen) {
|
||||
|
||||
@@ -36,62 +36,95 @@ sealed class Route(
|
||||
val buildScreen: (AccountViewModel, AccountStateViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit
|
||||
) {
|
||||
object Home : Route(
|
||||
"Home",
|
||||
R.drawable.ic_home,
|
||||
hasNewItems = { acc, cache, ctx -> homeHasNewItems(acc, cache, ctx) },
|
||||
buildScreen = { acc, accSt, nav -> { _ -> HomeScreen(acc, nav) } }
|
||||
route = "Home",
|
||||
icon = R.drawable.ic_home,
|
||||
hasNewItems = { accountViewModel, cache, context ->
|
||||
homeHasNewItems(accountViewModel, cache, context)
|
||||
},
|
||||
buildScreen = { accountViewModel, _, navController ->
|
||||
{ HomeScreen(accountViewModel, navController) }
|
||||
}
|
||||
)
|
||||
|
||||
object Search : Route(
|
||||
"Search",
|
||||
R.drawable.ic_globe,
|
||||
buildScreen = { acc, accSt, nav -> { _ -> SearchScreen(acc, nav) } }
|
||||
route = "Search",
|
||||
icon = R.drawable.ic_globe,
|
||||
buildScreen = { accountViewModel, _, navController ->
|
||||
{ SearchScreen(accountViewModel, navController) }
|
||||
}
|
||||
)
|
||||
|
||||
object Notification : Route(
|
||||
"Notification",
|
||||
R.drawable.ic_notifications,
|
||||
hasNewItems = { acc, cache, ctx -> notificationHasNewItems(acc, cache, ctx) },
|
||||
buildScreen = { acc, accSt, nav -> { _ -> NotificationScreen(acc, nav) } }
|
||||
route = "Notification",
|
||||
icon = R.drawable.ic_notifications,
|
||||
hasNewItems = { accountViewModel, cache, context ->
|
||||
notificationHasNewItems(accountViewModel, cache, context)
|
||||
},
|
||||
buildScreen = { accountViewModel, _, navController ->
|
||||
{ NotificationScreen(accountViewModel, navController) }
|
||||
}
|
||||
)
|
||||
|
||||
object Message : Route(
|
||||
"Message",
|
||||
R.drawable.ic_dm,
|
||||
hasNewItems = { acc, cache, ctx -> messagesHasNewItems(acc, cache, ctx) },
|
||||
buildScreen = { acc, accSt, nav -> { _ -> ChatroomListScreen(acc, nav) } }
|
||||
route = "Message",
|
||||
icon = R.drawable.ic_dm,
|
||||
hasNewItems = { accountViewModel, cache, context ->
|
||||
messagesHasNewItems(accountViewModel, cache, context)
|
||||
},
|
||||
buildScreen = { accountViewModel, _, navController ->
|
||||
{ ChatroomListScreen(accountViewModel, navController) }
|
||||
}
|
||||
)
|
||||
|
||||
object Filters : Route(
|
||||
"Filters",
|
||||
R.drawable.ic_security,
|
||||
buildScreen = { acc, accSt, nav -> { _ -> FiltersScreen(acc, nav) } }
|
||||
route = "Filters",
|
||||
icon = R.drawable.ic_security,
|
||||
buildScreen = { accountViewModel, _, navController ->
|
||||
{ FiltersScreen(accountViewModel, navController) }
|
||||
}
|
||||
)
|
||||
|
||||
object Profile : Route(
|
||||
"User/{id}",
|
||||
R.drawable.ic_profile,
|
||||
route = "User/{id}",
|
||||
icon = R.drawable.ic_profile,
|
||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
||||
buildScreen = { acc, accSt, nav -> { ProfileScreen(it.arguments?.getString("id"), acc, nav) } }
|
||||
buildScreen = { accountViewModel, _, navController ->
|
||||
{ ProfileScreen(it.arguments?.getString("id"), accountViewModel, navController) }
|
||||
}
|
||||
)
|
||||
|
||||
object Note : Route(
|
||||
"Note/{id}",
|
||||
R.drawable.ic_moments,
|
||||
route = "Note/{id}",
|
||||
icon = R.drawable.ic_moments,
|
||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
||||
buildScreen = { acc, accSt, nav -> { ThreadScreen(it.arguments?.getString("id"), acc, nav) } }
|
||||
buildScreen = { accountViewModel, _, navController ->
|
||||
{ ThreadScreen(it.arguments?.getString("id"), accountViewModel, navController) }
|
||||
}
|
||||
)
|
||||
|
||||
object Room : Route(
|
||||
"Room/{id}",
|
||||
R.drawable.ic_moments,
|
||||
route = "Room/{id}",
|
||||
icon = R.drawable.ic_moments,
|
||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
||||
buildScreen = { acc, accSt, nav -> { ChatroomScreen(it.arguments?.getString("id"), acc, nav) } }
|
||||
buildScreen = { accountViewModel, _, navController ->
|
||||
{ ChatroomScreen(it.arguments?.getString("id"), accountViewModel, navController) }
|
||||
}
|
||||
)
|
||||
|
||||
object Channel : Route(
|
||||
"Channel/{id}",
|
||||
R.drawable.ic_moments,
|
||||
route = "Channel/{id}",
|
||||
icon = R.drawable.ic_moments,
|
||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
||||
buildScreen = { acc, accSt, nav -> { ChannelScreen(it.arguments?.getString("id"), acc, accSt, nav) } }
|
||||
buildScreen = { accountViewModel, accountStateViewModel, navController ->
|
||||
{
|
||||
ChannelScreen(
|
||||
it.arguments?.getString("id"),
|
||||
accountViewModel,
|
||||
accountStateViewModel,
|
||||
navController
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -124,18 +157,32 @@ private fun homeHasNewItems(account: Account, cache: NotificationCache, context:
|
||||
|
||||
HomeNewThreadFeedFilter.account = account
|
||||
|
||||
return (HomeNewThreadFeedFilter.feed().firstOrNull { it.createdAt() != null }?.createdAt() ?: 0) > lastTime
|
||||
return (
|
||||
HomeNewThreadFeedFilter.feed().firstOrNull { it.createdAt() != null }?.createdAt()
|
||||
?: 0
|
||||
) > lastTime
|
||||
}
|
||||
|
||||
private fun notificationHasNewItems(account: Account, cache: NotificationCache, context: Context): Boolean {
|
||||
private fun notificationHasNewItems(
|
||||
account: Account,
|
||||
cache: NotificationCache,
|
||||
context: Context
|
||||
): Boolean {
|
||||
val lastTime = cache.load("Notification", context)
|
||||
|
||||
NotificationFeedFilter.account = account
|
||||
|
||||
return (NotificationFeedFilter.feed().firstOrNull { it.createdAt() != null }?.createdAt() ?: 0) > lastTime
|
||||
return (
|
||||
NotificationFeedFilter.feed().firstOrNull { it.createdAt() != null }?.createdAt()
|
||||
?: 0
|
||||
) > lastTime
|
||||
}
|
||||
|
||||
private fun messagesHasNewItems(account: Account, cache: NotificationCache, context: Context): Boolean {
|
||||
private fun messagesHasNewItems(
|
||||
account: Account,
|
||||
cache: NotificationCache,
|
||||
context: Context
|
||||
): Boolean {
|
||||
ChatroomListKnownFeedFilter.account = account
|
||||
|
||||
val note = ChatroomListKnownFeedFilter.feed().firstOrNull {
|
||||
|
||||
@@ -78,7 +78,12 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
|
||||
|
||||
@Composable
|
||||
fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, navController: NavController) {
|
||||
fun ChannelScreen(
|
||||
channelId: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
accountStateViewModel: AccountStateViewModel,
|
||||
navController: NavController
|
||||
) {
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = accountState?.account
|
||||
|
||||
|
||||
@@ -7,9 +7,13 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material.DrawerValue
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ModalBottomSheetLayout
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.rememberDrawerState
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -19,6 +23,7 @@ import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.vitorpamplona.amethyst.buttons.NewChannelButton
|
||||
import com.vitorpamplona.amethyst.buttons.NewNoteButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.AccountSwitchBottomSheet
|
||||
import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
|
||||
import com.vitorpamplona.amethyst.ui.navigation.AppTopBar
|
||||
@@ -28,31 +33,44 @@ import com.vitorpamplona.amethyst.ui.navigation.currentRoute
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountState
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, startingPage: String? = null) {
|
||||
val navController = rememberNavController()
|
||||
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Hidden,
|
||||
confirmValueChange = { it != ModalBottomSheetValue.HalfExpanded },
|
||||
skipHalfExpanded = true
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.primaryVariant)
|
||||
.statusBarsPadding(),
|
||||
bottomBar = {
|
||||
AppBottomBar(navController, accountViewModel)
|
||||
},
|
||||
topBar = {
|
||||
AppTopBar(navController, scaffoldState, accountViewModel)
|
||||
},
|
||||
drawerContent = {
|
||||
DrawerContent(navController, scaffoldState, accountViewModel, accountStateViewModel)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingButton(navController, accountStateViewModel)
|
||||
},
|
||||
scaffoldState = scaffoldState
|
||||
ModalBottomSheetLayout(
|
||||
sheetState = sheetState,
|
||||
sheetContent = {
|
||||
AccountSwitchBottomSheet(accountViewModel = accountViewModel, sheetState = sheetState)
|
||||
}
|
||||
) {
|
||||
Column(modifier = Modifier.padding(bottom = it.calculateBottomPadding())) {
|
||||
AppNavigation(navController, accountViewModel, accountStateViewModel, startingPage)
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.primaryVariant)
|
||||
.statusBarsPadding(),
|
||||
bottomBar = {
|
||||
AppBottomBar(navController, accountViewModel)
|
||||
},
|
||||
topBar = {
|
||||
AppTopBar(navController, scaffoldState, accountViewModel)
|
||||
},
|
||||
drawerContent = {
|
||||
DrawerContent(navController, scaffoldState, sheetState, accountViewModel, accountStateViewModel)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingButton(navController, accountStateViewModel)
|
||||
},
|
||||
scaffoldState = scaffoldState
|
||||
) {
|
||||
Column(modifier = Modifier.padding(bottom = it.calculateBottomPadding())) {
|
||||
AppNavigation(navController, accountViewModel, accountStateViewModel, startingPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10.67,13.02C10.45,13.01 10.23,13 10,13c-2.42,0 -4.68,0.67 -6.61,1.82C2.51,15.34 2,16.32 2,17.35V20h9.26C10.47,18.87 10,17.49 10,16C10,14.93 10.25,13.93 10.67,13.02z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20.75,16c0,-0.22 -0.03,-0.42 -0.06,-0.63l1.14,-1.01l-1,-1.73l-1.45,0.49c-0.32,-0.27 -0.68,-0.48 -1.08,-0.63L18,11h-2l-0.3,1.49c-0.4,0.15 -0.76,0.36 -1.08,0.63l-1.45,-0.49l-1,1.73l1.14,1.01c-0.03,0.21 -0.06,0.41 -0.06,0.63s0.03,0.42 0.06,0.63l-1.14,1.01l1,1.73l1.45,-0.49c0.32,0.27 0.68,0.48 1.08,0.63L16,21h2l0.3,-1.49c0.4,-0.15 0.76,-0.36 1.08,-0.63l1.45,0.49l1,-1.73l-1.14,-1.01C20.72,16.42 20.75,16.22 20.75,16zM17,18c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2s2,0.9 2,2S18.1,18 17,18z"/>
|
||||
</vector>
|
||||
Reference in New Issue
Block a user