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 com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
|
||||||
import nostr.postr.Persona
|
import nostr.postr.Persona
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
import nostr.postr.toNpub
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
data class AccountInfo(val npub: String, val current: Boolean, val displayName: String?, val profilePicture: String?)
|
||||||
|
|
||||||
class LocalPreferences(context: Context) {
|
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 {
|
private object PrefKeys {
|
||||||
const val NOSTR_PRIVKEY = "nostr_privkey"
|
const val CURRENT_ACCOUNT = "currentlyLoggedInAccount"
|
||||||
const val NOSTR_PUBKEY = "nostr_pubkey"
|
|
||||||
const val FOLLOWING_CHANNELS = "following_channels"
|
// val NOSTR_PRIVKEY = "nostr_privkey"
|
||||||
const val HIDDEN_USERS = "hidden_users"
|
// val NOSTR_PUBKEY = "nostr_pubkey"
|
||||||
const val RELAYS = "relays"
|
// val FOLLOWING_CHANNELS = "following_channels"
|
||||||
const val DONT_TRANSLATE_FROM = "dontTranslateFrom"
|
// val HIDDEN_USERS = "hidden_users"
|
||||||
const val LANGUAGE_PREFS = "languagePreferences"
|
// val RELAYS = "relays"
|
||||||
const val TRANSLATE_TO = "translateTo"
|
// val DONT_TRANSLATE_FROM = "dontTranslateFrom"
|
||||||
const val ZAP_AMOUNTS = "zapAmounts"
|
// val LANGUAGE_PREFS = "languagePreferences"
|
||||||
const val LATEST_CONTACT_LIST = "latestContactList"
|
// val TRANSLATE_TO = "translateTo"
|
||||||
const val HIDE_DELETE_REQUEST_INFO = "hideDeleteRequestInfo"
|
// 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" }
|
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,47 +57,88 @@ class LocalPreferences(context: Context) {
|
|||||||
|
|
||||||
fun clearEncryptedStorage() {
|
fun clearEncryptedStorage() {
|
||||||
encryptedPreferences.edit().apply {
|
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()
|
}.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) {
|
fun saveToEncryptedStorage(account: Account) {
|
||||||
|
val npub = account.loggedIn.pubKey.toNpub()
|
||||||
|
val myPrefs = prefKeysForAccount(npub)
|
||||||
|
|
||||||
encryptedPreferences.edit().apply {
|
encryptedPreferences.edit().apply {
|
||||||
account.loggedIn.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHex()) }
|
putString(PrefKeys.CURRENT_ACCOUNT, npub)
|
||||||
account.loggedIn.pubKey.let { putString(PrefKeys.NOSTR_PUBKEY, it.toHex()) }
|
account.loggedIn.privKey?.let { putString(myPrefs.NOSTR_PRIVKEY, it.toHex()) }
|
||||||
account.followingChannels.let { putStringSet(PrefKeys.FOLLOWING_CHANNELS, it) }
|
account.loggedIn.pubKey.let { putString(myPrefs.NOSTR_PUBKEY, it.toHex()) }
|
||||||
account.hiddenUsers.let { putStringSet(PrefKeys.HIDDEN_USERS, it) }
|
putStringSet(myPrefs.FOLLOWING_CHANNELS, account.followingChannels)
|
||||||
account.localRelays.let { putString(PrefKeys.RELAYS, gson.toJson(it)) }
|
putStringSet(myPrefs.HIDDEN_USERS, account.hiddenUsers)
|
||||||
account.dontTranslateFrom.let { putStringSet(PrefKeys.DONT_TRANSLATE_FROM, it) }
|
putString(myPrefs.RELAYS, gson.toJson(account.localRelays))
|
||||||
account.languagePreferences.let { putString(PrefKeys.LANGUAGE_PREFS, gson.toJson(it)) }
|
putStringSet(myPrefs.DONT_TRANSLATE_FROM, account.dontTranslateFrom)
|
||||||
account.translateTo.let { putString(PrefKeys.TRANSLATE_TO, it) }
|
putString(myPrefs.LANGUAGE_PREFS, gson.toJson(account.languagePreferences))
|
||||||
account.zapAmountChoices.let { putString(PrefKeys.ZAP_AMOUNTS, gson.toJson(it)) }
|
putString(myPrefs.TRANSLATE_TO, account.translateTo)
|
||||||
account.backupContactList.let { putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(it)) }
|
putString(myPrefs.ZAP_AMOUNTS, gson.toJson(account.zapAmountChoices))
|
||||||
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, account.hideDeleteRequestInfo)
|
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()
|
}.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadFromEncryptedStorage(): Account? {
|
fun loadFromEncryptedStorage(): Account? {
|
||||||
encryptedPreferences.apply {
|
encryptedPreferences.apply {
|
||||||
val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
|
val npub = getString(PrefKeys.CURRENT_ACCOUNT, null) ?: return null
|
||||||
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null)
|
val myPrefs = prefKeysForAccount(npub)
|
||||||
val followingChannels = getStringSet(PrefKeys.FOLLOWING_CHANNELS, null) ?: setOf()
|
|
||||||
val hiddenUsers = getStringSet(PrefKeys.HIDDEN_USERS, emptySet()) ?: setOf()
|
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(
|
val localRelays = gson.fromJson(
|
||||||
getString(PrefKeys.RELAYS, "[]"),
|
getString(myPrefs.RELAYS, "[]"),
|
||||||
object : TypeToken<Set<RelaySetupInfo>>() {}.type
|
object : TypeToken<Set<RelaySetupInfo>>() {}.type
|
||||||
) ?: setOf<RelaySetupInfo>()
|
) ?: setOf<RelaySetupInfo>()
|
||||||
|
|
||||||
val dontTranslateFrom = getStringSet(PrefKeys.DONT_TRANSLATE_FROM, null) ?: setOf()
|
val dontTranslateFrom = getStringSet(myPrefs.DONT_TRANSLATE_FROM, null) ?: setOf()
|
||||||
val translateTo = getString(PrefKeys.TRANSLATE_TO, null) ?: Locale.getDefault().language
|
val translateTo = getString(myPrefs.TRANSLATE_TO, null) ?: Locale.getDefault().language
|
||||||
|
|
||||||
val zapAmountChoices = gson.fromJson(
|
val zapAmountChoices = gson.fromJson(
|
||||||
getString(PrefKeys.ZAP_AMOUNTS, "[]"),
|
getString(myPrefs.ZAP_AMOUNTS, "[]"),
|
||||||
object : TypeToken<List<Long>>() {}.type
|
object : TypeToken<List<Long>>() {}.type
|
||||||
) ?: listOf(500L, 1000L, 5000L)
|
) ?: listOf(500L, 1000L, 5000L)
|
||||||
|
|
||||||
val latestContactList = try {
|
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
|
Event.gson.fromJson(it, Event::class.java).getRefinedEvent(true) as ContactListEvent
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
@@ -83,32 +147,28 @@ class LocalPreferences(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val languagePreferences = try {
|
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>
|
gson.fromJson(it, object : TypeToken<Map<String, String>>() {}.type) as Map<String, String>
|
||||||
} ?: mapOf<String, String>()
|
} ?: mapOf()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
e.printStackTrace()
|
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(
|
||||||
return Account(
|
Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()),
|
||||||
Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()),
|
followingChannels,
|
||||||
followingChannels,
|
hiddenUsers,
|
||||||
hiddenUsers,
|
localRelays,
|
||||||
localRelays,
|
dontTranslateFrom,
|
||||||
dontTranslateFrom,
|
languagePreferences,
|
||||||
languagePreferences,
|
translateTo,
|
||||||
translateTo,
|
zapAmountChoices,
|
||||||
zapAmountChoices,
|
hideDeleteRequestInfo,
|
||||||
hideDeleteRequestInfo,
|
latestContactList
|
||||||
latestContactList
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.ModalBottomSheetState
|
||||||
import androidx.compose.material.ScaffoldState
|
import androidx.compose.material.ScaffoldState
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
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 com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DrawerContent(
|
fun DrawerContent(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
scaffoldState: ScaffoldState,
|
scaffoldState: ScaffoldState,
|
||||||
|
sheetState: ModalBottomSheetState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
accountStateViewModel: AccountStateViewModel
|
accountStateViewModel: AccountStateViewModel
|
||||||
) {
|
) {
|
||||||
@@ -88,6 +92,7 @@ fun DrawerContent(
|
|||||||
account.userProfile(),
|
account.userProfile(),
|
||||||
navController,
|
navController,
|
||||||
scaffoldState,
|
scaffoldState,
|
||||||
|
sheetState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1F),
|
.weight(1F),
|
||||||
@@ -214,15 +219,18 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ListContent(
|
fun ListContent(
|
||||||
accountUser: User?,
|
accountUser: User?,
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
scaffoldState: ScaffoldState,
|
scaffoldState: ScaffoldState,
|
||||||
|
sheetState: ModalBottomSheetState,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
accountViewModel: AccountStateViewModel,
|
accountViewModel: AccountStateViewModel,
|
||||||
account: Account
|
account: Account
|
||||||
) {
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var backupDialogOpen by remember { mutableStateOf(false) }
|
var backupDialogOpen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column(modifier = modifier.fillMaxHeight()) {
|
Column(modifier = modifier.fillMaxHeight()) {
|
||||||
@@ -260,11 +268,18 @@ fun ListContent(
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
IconRow(
|
IconRow(
|
||||||
stringResource(R.string.log_out),
|
title = "Accounts",
|
||||||
R.drawable.ic_logout,
|
icon = R.drawable.manage_accounts,
|
||||||
MaterialTheme.colors.onBackground,
|
tint = MaterialTheme.colors.onBackground,
|
||||||
onClick = { accountViewModel.logOff() }
|
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) {
|
if (backupDialogOpen) {
|
||||||
|
|||||||
@@ -36,62 +36,95 @@ sealed class Route(
|
|||||||
val buildScreen: (AccountViewModel, AccountStateViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit
|
val buildScreen: (AccountViewModel, AccountStateViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit
|
||||||
) {
|
) {
|
||||||
object Home : Route(
|
object Home : Route(
|
||||||
"Home",
|
route = "Home",
|
||||||
R.drawable.ic_home,
|
icon = R.drawable.ic_home,
|
||||||
hasNewItems = { acc, cache, ctx -> homeHasNewItems(acc, cache, ctx) },
|
hasNewItems = { accountViewModel, cache, context ->
|
||||||
buildScreen = { acc, accSt, nav -> { _ -> HomeScreen(acc, nav) } }
|
homeHasNewItems(accountViewModel, cache, context)
|
||||||
|
},
|
||||||
|
buildScreen = { accountViewModel, _, navController ->
|
||||||
|
{ HomeScreen(accountViewModel, navController) }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Search : Route(
|
object Search : Route(
|
||||||
"Search",
|
route = "Search",
|
||||||
R.drawable.ic_globe,
|
icon = R.drawable.ic_globe,
|
||||||
buildScreen = { acc, accSt, nav -> { _ -> SearchScreen(acc, nav) } }
|
buildScreen = { accountViewModel, _, navController ->
|
||||||
|
{ SearchScreen(accountViewModel, navController) }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Notification : Route(
|
object Notification : Route(
|
||||||
"Notification",
|
route = "Notification",
|
||||||
R.drawable.ic_notifications,
|
icon = R.drawable.ic_notifications,
|
||||||
hasNewItems = { acc, cache, ctx -> notificationHasNewItems(acc, cache, ctx) },
|
hasNewItems = { accountViewModel, cache, context ->
|
||||||
buildScreen = { acc, accSt, nav -> { _ -> NotificationScreen(acc, nav) } }
|
notificationHasNewItems(accountViewModel, cache, context)
|
||||||
|
},
|
||||||
|
buildScreen = { accountViewModel, _, navController ->
|
||||||
|
{ NotificationScreen(accountViewModel, navController) }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Message : Route(
|
object Message : Route(
|
||||||
"Message",
|
route = "Message",
|
||||||
R.drawable.ic_dm,
|
icon = R.drawable.ic_dm,
|
||||||
hasNewItems = { acc, cache, ctx -> messagesHasNewItems(acc, cache, ctx) },
|
hasNewItems = { accountViewModel, cache, context ->
|
||||||
buildScreen = { acc, accSt, nav -> { _ -> ChatroomListScreen(acc, nav) } }
|
messagesHasNewItems(accountViewModel, cache, context)
|
||||||
|
},
|
||||||
|
buildScreen = { accountViewModel, _, navController ->
|
||||||
|
{ ChatroomListScreen(accountViewModel, navController) }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Filters : Route(
|
object Filters : Route(
|
||||||
"Filters",
|
route = "Filters",
|
||||||
R.drawable.ic_security,
|
icon = R.drawable.ic_security,
|
||||||
buildScreen = { acc, accSt, nav -> { _ -> FiltersScreen(acc, nav) } }
|
buildScreen = { accountViewModel, _, navController ->
|
||||||
|
{ FiltersScreen(accountViewModel, navController) }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Profile : Route(
|
object Profile : Route(
|
||||||
"User/{id}",
|
route = "User/{id}",
|
||||||
R.drawable.ic_profile,
|
icon = R.drawable.ic_profile,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
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(
|
object Note : Route(
|
||||||
"Note/{id}",
|
route = "Note/{id}",
|
||||||
R.drawable.ic_moments,
|
icon = R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
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(
|
object Room : Route(
|
||||||
"Room/{id}",
|
route = "Room/{id}",
|
||||||
R.drawable.ic_moments,
|
icon = R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
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(
|
object Channel : Route(
|
||||||
"Channel/{id}",
|
route = "Channel/{id}",
|
||||||
R.drawable.ic_moments,
|
icon = R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
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
|
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)
|
val lastTime = cache.load("Notification", context)
|
||||||
|
|
||||||
NotificationFeedFilter.account = account
|
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
|
ChatroomListKnownFeedFilter.account = account
|
||||||
|
|
||||||
val note = ChatroomListKnownFeedFilter.feed().firstOrNull {
|
val note = ChatroomListKnownFeedFilter.feed().firstOrNull {
|
||||||
|
|||||||
@@ -78,7 +78,12 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomFeedView
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
|
||||||
|
|
||||||
@Composable
|
@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 accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account
|
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.padding
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
import androidx.compose.material.DrawerValue
|
import androidx.compose.material.DrawerValue
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.ModalBottomSheetLayout
|
||||||
|
import androidx.compose.material.ModalBottomSheetValue
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.rememberDrawerState
|
import androidx.compose.material.rememberDrawerState
|
||||||
|
import androidx.compose.material.rememberModalBottomSheetState
|
||||||
import androidx.compose.material.rememberScaffoldState
|
import androidx.compose.material.rememberScaffoldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -19,6 +23,7 @@ import androidx.navigation.NavHostController
|
|||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.vitorpamplona.amethyst.buttons.NewChannelButton
|
import com.vitorpamplona.amethyst.buttons.NewChannelButton
|
||||||
import com.vitorpamplona.amethyst.buttons.NewNoteButton
|
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.AppBottomBar
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
|
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.AppTopBar
|
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.AccountState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, startingPage: String? = null) {
|
fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, startingPage: String? = null) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
|
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
|
||||||
|
val sheetState = rememberModalBottomSheetState(
|
||||||
|
initialValue = ModalBottomSheetValue.Hidden,
|
||||||
|
confirmValueChange = { it != ModalBottomSheetValue.HalfExpanded },
|
||||||
|
skipHalfExpanded = true
|
||||||
|
)
|
||||||
|
|
||||||
Scaffold(
|
ModalBottomSheetLayout(
|
||||||
modifier = Modifier
|
sheetState = sheetState,
|
||||||
.background(MaterialTheme.colors.primaryVariant)
|
sheetContent = {
|
||||||
.statusBarsPadding(),
|
AccountSwitchBottomSheet(accountViewModel = accountViewModel, sheetState = sheetState)
|
||||||
bottomBar = {
|
}
|
||||||
AppBottomBar(navController, accountViewModel)
|
|
||||||
},
|
|
||||||
topBar = {
|
|
||||||
AppTopBar(navController, scaffoldState, accountViewModel)
|
|
||||||
},
|
|
||||||
drawerContent = {
|
|
||||||
DrawerContent(navController, scaffoldState, accountViewModel, accountStateViewModel)
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
FloatingButton(navController, accountStateViewModel)
|
|
||||||
},
|
|
||||||
scaffoldState = scaffoldState
|
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(bottom = it.calculateBottomPadding())) {
|
Scaffold(
|
||||||
AppNavigation(navController, accountViewModel, accountStateViewModel, startingPage)
|
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