- adds an account switcher to the notification click
- removes the use of nostr: for notifications - enables a new screen to be routed when new logins happen
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.findParameterValue
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
class URIParserTest {
|
||||||
|
@Test
|
||||||
|
fun testNEventWithAccount() {
|
||||||
|
val test = "nevent1qqsp9wg5r3vkuv5al0459h3h44yxh6r0y7chgvu3pkdxhcj99t3msrgpzdmhxue69uhhwmm59e6hg7r09ehkuef0qgsdhcrqt2w8x9et446j8ge8kgmd2h4ykc6wsrnc4yqnmdu3lr74ktqrqsqqqqqp578kku?account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"
|
||||||
|
val testUri = URI(test)
|
||||||
|
|
||||||
|
// assertEquals("nostr", testUri.scheme)
|
||||||
|
assertEquals(null, testUri.authority)
|
||||||
|
assertEquals(null, testUri.host)
|
||||||
|
assertEquals(-1, testUri.port)
|
||||||
|
assertEquals(null, testUri.userInfo)
|
||||||
|
assertEquals("nevent1qqsp9wg5r3vkuv5al0459h3h44yxh6r0y7chgvu3pkdxhcj99t3msrgpzdmhxue69uhhwmm59e6hg7r09ehkuef0qgsdhcrqt2w8x9et446j8ge8kgmd2h4ykc6wsrnc4yqnmdu3lr74ktqrqsqqqqqp578kku", testUri.path)
|
||||||
|
assertEquals(null, testUri.fragment)
|
||||||
|
assertEquals("account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.rawQuery)
|
||||||
|
|
||||||
|
assertEquals("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.findParameterValue("account"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNotificationsWithAccount() {
|
||||||
|
val test = "notifications?account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"
|
||||||
|
val testUri = URI(test)
|
||||||
|
|
||||||
|
// assertEquals("nostr", testUri.scheme)
|
||||||
|
assertEquals(null, testUri.authority)
|
||||||
|
assertEquals(null, testUri.host)
|
||||||
|
assertEquals(-1, testUri.port)
|
||||||
|
assertEquals(null, testUri.userInfo)
|
||||||
|
assertEquals("notifications", testUri.path)
|
||||||
|
assertEquals(null, testUri.fragment)
|
||||||
|
assertEquals("account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.rawQuery)
|
||||||
|
|
||||||
|
assertEquals("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.findParameterValue("account"))
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-5
@@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendDM
|
|||||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification
|
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification
|
||||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.quartz.encoders.toNpub
|
||||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||||
import com.vitorpamplona.quartz.events.DraftEvent
|
import com.vitorpamplona.quartz.events.DraftEvent
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
@@ -176,7 +177,7 @@ class EventNotificationConsumer(
|
|||||||
val content = chatNote.event?.content() ?: ""
|
val content = chatNote.event?.content() ?: ""
|
||||||
val user = chatNote.author?.toBestDisplayName() ?: ""
|
val user = chatNote.author?.toBestDisplayName() ?: ""
|
||||||
val userPicture = chatNote.author?.profilePicture()
|
val userPicture = chatNote.author?.profilePicture()
|
||||||
val noteUri = chatNote.toNEvent()
|
val noteUri = chatNote.toNEvent() + "?account=" + acc.keyPair.pubKey.toNpub()
|
||||||
notificationManager()
|
notificationManager()
|
||||||
.sendDMNotification(
|
.sendDMNotification(
|
||||||
event.id,
|
event.id,
|
||||||
@@ -216,7 +217,7 @@ class EventNotificationConsumer(
|
|||||||
decryptContent(note, signer) { content ->
|
decryptContent(note, signer) { content ->
|
||||||
val user = note.author?.toBestDisplayName() ?: ""
|
val user = note.author?.toBestDisplayName() ?: ""
|
||||||
val userPicture = note.author?.profilePicture()
|
val userPicture = note.author?.profilePicture()
|
||||||
val noteUri = note.toNEvent()
|
val noteUri = note.toNEvent() + "?account=" + acc.keyPair.pubKey.toNpub()
|
||||||
notificationManager()
|
notificationManager()
|
||||||
.sendDMNotification(event.id, content, user, event.createdAt, userPicture, noteUri, applicationContext)
|
.sendDMNotification(event.id, content, user, event.createdAt, userPicture, noteUri, applicationContext)
|
||||||
}
|
}
|
||||||
@@ -322,7 +323,7 @@ class EventNotificationConsumer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val userPicture = senderInfo.first.profilePicture()
|
val userPicture = senderInfo.first.profilePicture()
|
||||||
val noteUri = "nostr:Notifications"
|
val noteUri = "notifications?account=" + acc.keyPair.pubKey.toNpub()
|
||||||
|
|
||||||
Log.d(TAG, "Notify ${event.id} $content $title $noteUri")
|
Log.d(TAG, "Notify ${event.id} $content $title $noteUri")
|
||||||
|
|
||||||
@@ -353,9 +354,9 @@ class EventNotificationConsumer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val userPicture = senderInfo.first.profilePicture()
|
val userPicture = senderInfo.first.profilePicture()
|
||||||
val noteUri = "nostr:Notifications"
|
val noteUri = "notifications?account=" + acc.keyPair.pubKey.toNpub()
|
||||||
|
|
||||||
Log.d(TAG, "Notify ${event.id} $content $title $noteUri")
|
Log.d(TAG, "Notify ${event.id} $title $noteUri")
|
||||||
|
|
||||||
notificationManager()
|
notificationManager()
|
||||||
.sendZapNotification(
|
.sendZapNotification(
|
||||||
|
|||||||
-6
@@ -221,9 +221,6 @@ object NotificationUtils {
|
|||||||
.setContentTitle(messageTitle)
|
.setContentTitle(messageTitle)
|
||||||
.setContentText(stringRes(applicationContext, R.string.app_notification_private_message))
|
.setContentText(stringRes(applicationContext, R.string.app_notification_private_message))
|
||||||
.setLargeIcon(picture?.bitmap)
|
.setLargeIcon(picture?.bitmap)
|
||||||
// .setGroup(messageTitle)
|
|
||||||
// .setGroup(notificationGroupKey) //-> Might need a Group summary as well before we
|
|
||||||
// activate this
|
|
||||||
.setContentIntent(contentPendingIntent)
|
.setContentIntent(contentPendingIntent)
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
@@ -239,9 +236,6 @@ object NotificationUtils {
|
|||||||
.setContentTitle(messageTitle)
|
.setContentTitle(messageTitle)
|
||||||
.setContentText(messageBody)
|
.setContentText(messageBody)
|
||||||
.setLargeIcon(picture?.bitmap)
|
.setLargeIcon(picture?.bitmap)
|
||||||
// .setGroup(messageTitle)
|
|
||||||
// .setGroup(notificationGroupKey) //-> Might need a Group summary as well before we
|
|
||||||
// activate this
|
|
||||||
.setContentIntent(contentPendingIntent)
|
.setContentIntent(contentPendingIntent)
|
||||||
.setPublicVersion(builderPublic.build())
|
.setPublicVersion(builderPublic.build())
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
|||||||
@@ -256,11 +256,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun uriToRoute(uri: String?): String? =
|
fun uriToRoute(uri: String?): String? =
|
||||||
if (uri.equals("nostr:Notifications", true)) {
|
if (uri?.startsWith("notifications", true) == true || uri?.startsWith("nostr:notifications", true) == true) {
|
||||||
Route.Notification.route.replace("{scrollToTop}", "true")
|
Route.Notification.route.replace("{scrollToTop}", "true")
|
||||||
} else {
|
} else {
|
||||||
if (uri?.startsWith("nostr:Hashtag?id=") == true) {
|
if (uri?.startsWith("hashtag?id=") == true || uri?.startsWith("nostr:hashtag?id=") == true) {
|
||||||
Route.Hashtag.route.replace("{id}", uri.removePrefix("nostr:Hashtag?id="))
|
Route.Hashtag.route.replace("{id}", uri.removePrefix("nostr:").removePrefix("hashtag?id="))
|
||||||
} else {
|
} else {
|
||||||
val nip19 = Nip19Bech32.uriToRoute(uri)?.entity
|
val nip19 = Nip19Bech32.uriToRoute(uri)?.entity
|
||||||
when (nip19) {
|
when (nip19) {
|
||||||
@@ -280,6 +280,7 @@ fun uriToRoute(uri: String?): String? =
|
|||||||
"Event/${nip19.hex}"
|
"Event/${nip19.hex}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Nip19Bech32.NAddress -> {
|
is Nip19Bech32.NAddress -> {
|
||||||
if (nip19.kind == CommunityDefinitionEvent.KIND) {
|
if (nip19.kind == CommunityDefinitionEvent.KIND) {
|
||||||
"Community/${nip19.atag}"
|
"Community/${nip19.atag}"
|
||||||
@@ -289,12 +290,14 @@ fun uriToRoute(uri: String?): String? =
|
|||||||
"Event/${nip19.atag}"
|
"Event/${nip19.atag}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Nip19Bech32.NEmbed -> {
|
is Nip19Bech32.NEmbed -> {
|
||||||
if (LocalCache.getNoteIfExists(nip19.event.id) == null) {
|
if (LocalCache.getNoteIfExists(nip19.event.id) == null) {
|
||||||
LocalCache.verifyAndConsume(nip19.event, null)
|
LocalCache.verifyAndConsume(nip19.event, null)
|
||||||
}
|
}
|
||||||
"Event/${nip19.event.id}"
|
"Event/${nip19.event.id}"
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -183,7 +183,7 @@ class MarkdownMediaRenderer(
|
|||||||
richTextStringBuilder: RichTextString.Builder,
|
richTextStringBuilder: RichTextString.Builder,
|
||||||
) {
|
) {
|
||||||
val tagWithoutHash = tag.removePrefix("#")
|
val tagWithoutHash = tag.removePrefix("#")
|
||||||
renderAsCompleteLink(tag, "nostr:Hashtag?id=$tagWithoutHash", richTextStringBuilder)
|
renderAsCompleteLink(tag, "nostr:nashtag?id=$tagWithoutHash", richTextStringBuilder)
|
||||||
|
|
||||||
val hashtagIcon: HashtagIcon? = checkForHashtagWithIcon(tagWithoutHash)
|
val hashtagIcon: HashtagIcon? = checkForHashtagWithIcon(tagWithoutHash)
|
||||||
if (hashtagIcon != null) {
|
if (hashtagIcon != null) {
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ import com.vitorpamplona.amethyst.ui.uriToRoute
|
|||||||
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.net.URI
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
|
||||||
fun NavBackStackEntry.id(): String? = arguments?.getString("id")
|
fun NavBackStackEntry.id(): String? = arguments?.getString("id")
|
||||||
@@ -349,6 +350,14 @@ private fun NavigateIfIntentRequested(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
accountStateViewModel: AccountStateViewModel,
|
accountStateViewModel: AccountStateViewModel,
|
||||||
) {
|
) {
|
||||||
|
accountViewModel.firstRoute?.let {
|
||||||
|
accountViewModel.firstRoute = null
|
||||||
|
val currentRoute = getRouteWithArguments(nav.controller)
|
||||||
|
if (!isSameRoute(currentRoute, it)) {
|
||||||
|
nav.newStack(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val activity = LocalContext.current.getActivity()
|
val activity = LocalContext.current.getActivity()
|
||||||
|
|
||||||
if (activity.intent.action == Intent.ACTION_SEND) {
|
if (activity.intent.action == Intent.ACTION_SEND) {
|
||||||
@@ -394,12 +403,17 @@ private fun NavigateIfIntentRequested(
|
|||||||
|
|
||||||
LaunchedEffect(intentNextPage) {
|
LaunchedEffect(intentNextPage) {
|
||||||
if (actionableNextPage != null) {
|
if (actionableNextPage != null) {
|
||||||
actionableNextPage?.let {
|
actionableNextPage?.let { nextRoute ->
|
||||||
val currentRoute = getRouteWithArguments(nav.controller)
|
val npub = runCatching { URI(intentNextPage.removePrefix("nostr:")).findParameterValue("account") }.getOrNull()
|
||||||
if (!isSameRoute(currentRoute, it)) {
|
if (npub != null && accountStateViewModel.currentAccount() != npub) {
|
||||||
nav.newStack(it)
|
accountStateViewModel.switchUserSync(npub, nextRoute)
|
||||||
|
} else {
|
||||||
|
val currentRoute = getRouteWithArguments(nav.controller)
|
||||||
|
if (!isSameRoute(currentRoute, nextRoute)) {
|
||||||
|
nav.newStack(nextRoute)
|
||||||
|
}
|
||||||
|
actionableNextPage = null
|
||||||
}
|
}
|
||||||
actionableNextPage = null
|
|
||||||
}
|
}
|
||||||
} else if (intentNextPage.contains("ncryptsec1")) {
|
} else if (intentNextPage.contains("ncryptsec1")) {
|
||||||
// login functions
|
// login functions
|
||||||
@@ -439,14 +453,22 @@ private fun NavigateIfIntentRequested(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val uri = intent.data?.toString()
|
val uri = intent.data?.toString()
|
||||||
|
|
||||||
if (!uri.isNullOrBlank()) {
|
if (!uri.isNullOrBlank()) {
|
||||||
// navigation functions
|
// navigation functions
|
||||||
val newPage = uriToRoute(uri)
|
val newPage = uriToRoute(uri)
|
||||||
|
|
||||||
if (newPage != null) {
|
if (newPage != null) {
|
||||||
val currentRoute = getRouteWithArguments(nav.controller)
|
scope.launch {
|
||||||
if (!isSameRoute(currentRoute, newPage)) {
|
val npub = runCatching { URI(uri.removePrefix("nostr:")).findParameterValue("account") }.getOrNull()
|
||||||
nav.newStack(newPage)
|
if (npub != null && accountStateViewModel.currentAccount() != npub) {
|
||||||
|
accountStateViewModel.switchUserSync(npub, newPage)
|
||||||
|
} else {
|
||||||
|
val currentRoute = getRouteWithArguments(nav.controller)
|
||||||
|
if (!isSameRoute(currentRoute, newPage)) {
|
||||||
|
nav.newStack(newPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (uri.contains("ncryptsec")) {
|
} else if (uri.contains("ncryptsec")) {
|
||||||
// login functions
|
// login functions
|
||||||
@@ -508,3 +530,14 @@ val slideOutHorizontallyToEnd = slideOutHorizontally(animationSpec = tween(), ta
|
|||||||
|
|
||||||
val scaleIn = scaleIn(animationSpec = tween(), initialScale = 0.9f)
|
val scaleIn = scaleIn(animationSpec = tween(), initialScale = 0.9f)
|
||||||
val scaleOut = scaleOut(animationSpec = tween(), targetScale = 0.9f)
|
val scaleOut = scaleOut(animationSpec = tween(), targetScale = 0.9f)
|
||||||
|
|
||||||
|
fun URI.findParameterValue(parameterName: String): String? =
|
||||||
|
rawQuery
|
||||||
|
?.split('&')
|
||||||
|
?.map {
|
||||||
|
val parts = it.split('=')
|
||||||
|
val name = parts.firstOrNull() ?: ""
|
||||||
|
val value = parts.drop(1).firstOrNull() ?: ""
|
||||||
|
Pair(name, value)
|
||||||
|
}?.firstOrNull { it.first == parameterName }
|
||||||
|
?.second
|
||||||
|
|||||||
@@ -97,23 +97,7 @@ fun AccountScreen(
|
|||||||
) {
|
) {
|
||||||
LoggedInPage(
|
LoggedInPage(
|
||||||
state.accountSettings,
|
state.accountSettings,
|
||||||
accountStateViewModel,
|
state.route,
|
||||||
sharedPreferencesViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposableEffect(key1 = accountState) {
|
|
||||||
onDispose {
|
|
||||||
state.currentViewModelStore.viewModelStore.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is AccountState.LoggedInViewOnly -> {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalViewModelStoreOwner provides state.currentViewModelStore,
|
|
||||||
) {
|
|
||||||
LoggedInPage(
|
|
||||||
state.accountSettings,
|
|
||||||
accountStateViewModel,
|
accountStateViewModel,
|
||||||
sharedPreferencesViewModel,
|
sharedPreferencesViewModel,
|
||||||
)
|
)
|
||||||
@@ -132,6 +116,7 @@ fun AccountScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
fun LoggedInPage(
|
fun LoggedInPage(
|
||||||
accountSettings: AccountSettings,
|
accountSettings: AccountSettings,
|
||||||
|
route: String?,
|
||||||
accountStateViewModel: AccountStateViewModel,
|
accountStateViewModel: AccountStateViewModel,
|
||||||
sharedPreferencesViewModel: SharedPreferencesViewModel,
|
sharedPreferencesViewModel: SharedPreferencesViewModel,
|
||||||
) {
|
) {
|
||||||
@@ -145,6 +130,8 @@ fun LoggedInPage(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
accountViewModel.firstRoute = route
|
||||||
|
|
||||||
LaunchedEffect(key1 = accountViewModel) {
|
LaunchedEffect(key1 = accountViewModel) {
|
||||||
accountViewModel.restartServices()
|
accountViewModel.restartServices()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||||
|
|
||||||
sealed class AccountState {
|
sealed class AccountState {
|
||||||
@@ -27,14 +28,10 @@ sealed class AccountState {
|
|||||||
|
|
||||||
object LoggedOff : AccountState()
|
object LoggedOff : AccountState()
|
||||||
|
|
||||||
class LoggedInViewOnly(
|
@Stable
|
||||||
val accountSettings: AccountSettings,
|
|
||||||
) : AccountState() {
|
|
||||||
val currentViewModelStore = AccountCentricViewModelStore(accountSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoggedIn(
|
class LoggedIn(
|
||||||
val accountSettings: AccountSettings,
|
val accountSettings: AccountSettings,
|
||||||
|
var route: String? = null,
|
||||||
) : AccountState() {
|
) : AccountState() {
|
||||||
val currentViewModelStore = AccountCentricViewModelStore(accountSettings)
|
val currentViewModelStore = AccountCentricViewModelStore(accountSettings)
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-29
@@ -27,7 +27,6 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.vitorpamplona.amethyst.AccountInfo
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences.currentAccount
|
|
||||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||||
import com.vitorpamplona.amethyst.model.DefaultChannels
|
import com.vitorpamplona.amethyst.model.DefaultChannels
|
||||||
import com.vitorpamplona.amethyst.model.DefaultDMRelayList
|
import com.vitorpamplona.amethyst.model.DefaultDMRelayList
|
||||||
@@ -81,10 +80,10 @@ class AccountStateViewModel : ViewModel() {
|
|||||||
viewModelScope.launch { tryLoginExistingAccount() }
|
viewModelScope.launch { tryLoginExistingAccount() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun tryLoginExistingAccount() =
|
private suspend fun tryLoginExistingAccount(route: String? = null) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
LocalPreferences.loadCurrentAccountFromEncryptedStorage()
|
LocalPreferences.loadCurrentAccountFromEncryptedStorage()
|
||||||
}?.let { startUI(it) } ?: run { requestLoginUI() }
|
}?.let { startUI(it, route) } ?: run { requestLoginUI() }
|
||||||
|
|
||||||
private suspend fun requestLoginUI() {
|
private suspend fun requestLoginUI() {
|
||||||
_accountContent.update { AccountState.LoggedOff }
|
_accountContent.update { AccountState.LoggedOff }
|
||||||
@@ -162,22 +161,20 @@ class AccountStateViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
suspend fun startUI(accountSettings: AccountSettings) =
|
suspend fun startUI(
|
||||||
withContext(Dispatchers.Main) {
|
accountSettings: AccountSettings,
|
||||||
if (accountSettings.isWriteable()) {
|
route: String? = null,
|
||||||
_accountContent.update { AccountState.LoggedIn(accountSettings) }
|
) = withContext(Dispatchers.Main) {
|
||||||
} else {
|
_accountContent.update { AccountState.LoggedIn(accountSettings, route) }
|
||||||
_accountContent.update { AccountState.LoggedInViewOnly(accountSettings) }
|
|
||||||
}
|
|
||||||
|
|
||||||
collectorJob?.cancel()
|
collectorJob?.cancel()
|
||||||
collectorJob =
|
collectorJob =
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
accountSettings.saveable.debounce(1000).collect {
|
accountSettings.saveable.debounce(1000).collect {
|
||||||
LocalPreferences.saveToEncryptedStorage(it.accountSettings)
|
LocalPreferences.saveToEncryptedStorage(it.accountSettings)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun prepareLogoutOrSwitch() =
|
private fun prepareLogoutOrSwitch() =
|
||||||
when (val state = _accountContent.value) {
|
when (val state = _accountContent.value) {
|
||||||
@@ -186,11 +183,6 @@ class AccountStateViewModel : ViewModel() {
|
|||||||
state.currentViewModelStore.viewModelStore.clear()
|
state.currentViewModelStore.viewModelStore.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
is AccountState.LoggedInViewOnly -> {
|
|
||||||
collectorJob?.cancel()
|
|
||||||
state.currentViewModelStore.viewModelStore.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,22 +308,39 @@ class AccountStateViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun switchUser(accountInfo: AccountInfo) {
|
fun switchUser(accountInfo: AccountInfo) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
prepareLogoutOrSwitch()
|
switchUserSync(accountInfo)
|
||||||
LocalPreferences.switchToAccount(accountInfo)
|
|
||||||
tryLoginExistingAccount()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun switchUserSync(
|
||||||
|
npub: String,
|
||||||
|
route: String,
|
||||||
|
): Boolean {
|
||||||
|
if (npub != LocalPreferences.currentAccount()) {
|
||||||
|
val account = LocalPreferences.allSavedAccounts().firstOrNull { it.npub == npub }
|
||||||
|
if (account != null) {
|
||||||
|
switchUserSync(account, route)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun switchUserSync(
|
||||||
|
accountInfo: AccountInfo,
|
||||||
|
route: String? = null,
|
||||||
|
) {
|
||||||
|
prepareLogoutOrSwitch()
|
||||||
|
LocalPreferences.switchToAccount(accountInfo)
|
||||||
|
tryLoginExistingAccount(route)
|
||||||
|
}
|
||||||
|
|
||||||
fun currentAccount() =
|
fun currentAccount() =
|
||||||
when (val state = _accountContent.value) {
|
when (val state = _accountContent.value) {
|
||||||
is AccountState.LoggedIn ->
|
is AccountState.LoggedIn ->
|
||||||
state.accountSettings.keyPair.pubKey
|
state.accountSettings.keyPair.pubKey
|
||||||
.toNpub()
|
.toNpub()
|
||||||
|
|
||||||
is AccountState.LoggedInViewOnly ->
|
|
||||||
state.accountSettings.keyPair.pubKey
|
|
||||||
.toNpub()
|
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -153,6 +153,8 @@ class AccountViewModel(
|
|||||||
Dao {
|
Dao {
|
||||||
val account = Account(accountSettings, accountSettings.createSigner(), viewModelScope)
|
val account = Account(accountSettings, accountSettings.createSigner(), viewModelScope)
|
||||||
|
|
||||||
|
var firstRoute: String? = null
|
||||||
|
|
||||||
// TODO: contact lists are not notes yet
|
// TODO: contact lists are not notes yet
|
||||||
// val kind3Relays: StateFlow<ContactListEvent?> = observeByAuthor(ContactListEvent.KIND, account.signer.pubKey)
|
// val kind3Relays: StateFlow<ContactListEvent?> = observeByAuthor(ContactListEvent.KIND, account.signer.pubKey)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.notifications.PushNotificationReceiverService"
|
android:name=".service.notifications.PushNotificationReceiverService"
|
||||||
android:exported="false">
|
android:exported="true"
|
||||||
|
>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
Reference in New Issue
Block a user