mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-17 13:21:50 +01:00
Improves multiple error dialogs appearing when zapping from the reactions bar in the feed.
This commit is contained in:
parent
d76de0123a
commit
fd538ebde1
4
.idea/inspectionProfiles/Project_Default.xml
generated
4
.idea/inspectionProfiles/Project_Default.xml
generated
@ -65,6 +65,10 @@
|
|||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
|
@ -331,7 +331,7 @@ fun EditPostView(
|
|||||||
it,
|
it,
|
||||||
accountViewModel.account.settings.defaultFileServer,
|
accountViewModel.account.settings.defaultFileServer,
|
||||||
onAdd = { alt, server, sensitiveContent, mediaQuality ->
|
onAdd = { alt, server, sensitiveContent, mediaQuality ->
|
||||||
postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel::toast, context)
|
postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, context)
|
||||||
if (server.type != ServerType.NIP95) {
|
if (server.type != ServerType.NIP95) {
|
||||||
accountViewModel.account.settings.changeDefaultFileServer(server)
|
accountViewModel.account.settings.changeDefaultFileServer(server)
|
||||||
}
|
}
|
||||||
@ -365,7 +365,7 @@ fun EditPostView(
|
|||||||
postViewModel.wantsInvoice = false
|
postViewModel.wantsInvoice = false
|
||||||
},
|
},
|
||||||
onClose = { postViewModel.wantsInvoice = false },
|
onClose = { postViewModel.wantsInvoice = false },
|
||||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ fun NewMediaView(
|
|||||||
|
|
||||||
PostButton(
|
PostButton(
|
||||||
onPost = {
|
onPost = {
|
||||||
postViewModel.upload(context, relayList, onClose, accountViewModel::toast)
|
postViewModel.upload(context, relayList, onClose, accountViewModel.toastManager::toast)
|
||||||
postViewModel.selectedServer?.let {
|
postViewModel.selectedServer?.let {
|
||||||
if (it.type != ServerType.NIP95) {
|
if (it.type != ServerType.NIP95) {
|
||||||
account.settings.changeDefaultFileServer(it)
|
account.settings.changeDefaultFileServer(it)
|
||||||
|
@ -198,7 +198,7 @@ fun NewUserMetadataScreen(
|
|||||||
tint = MaterialTheme.colorScheme.placeholderText,
|
tint = MaterialTheme.colorScheme.placeholderText,
|
||||||
modifier = Modifier.padding(start = 5.dp),
|
modifier = Modifier.padding(start = 5.dp),
|
||||||
) {
|
) {
|
||||||
postViewModel.uploadForPicture(it, context, onError = accountViewModel::toast)
|
postViewModel.uploadForPicture(it, context, onError = accountViewModel.toastManager::toast)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
@ -223,7 +223,7 @@ fun NewUserMetadataScreen(
|
|||||||
tint = MaterialTheme.colorScheme.placeholderText,
|
tint = MaterialTheme.colorScheme.placeholderText,
|
||||||
modifier = Modifier.padding(start = 5.dp),
|
modifier = Modifier.padding(start = 5.dp),
|
||||||
) {
|
) {
|
||||||
postViewModel.uploadForBanner(it, context, onError = accountViewModel::toast)
|
postViewModel.uploadForBanner(it, context, onError = accountViewModel.toastManager::toast)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
@ -218,7 +218,7 @@ fun RelaySelectionDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.unable_to_download_relay_document),
|
stringRes(context, R.string.unable_to_download_relay_document),
|
||||||
msg,
|
msg,
|
||||||
)
|
)
|
||||||
|
@ -103,7 +103,7 @@ fun BasicRelaySetupInfoDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.unable_to_download_relay_document),
|
stringRes(context, R.string.unable_to_download_relay_document),
|
||||||
msg,
|
msg,
|
||||||
)
|
)
|
||||||
|
@ -280,7 +280,7 @@ fun LoadRelayInfo(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.unable_to_download_relay_document),
|
stringRes(context, R.string.unable_to_download_relay_document),
|
||||||
msg,
|
msg,
|
||||||
)
|
)
|
||||||
|
@ -105,7 +105,7 @@ fun Kind3RelaySetupInfoProposalDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.unable_to_download_relay_document),
|
stringRes(context, R.string.unable_to_download_relay_document),
|
||||||
msg,
|
msg,
|
||||||
)
|
)
|
||||||
|
@ -64,7 +64,7 @@ fun RelayStatusRow(
|
|||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onLongPress = {
|
onLongPress = {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_from_relay,
|
R.string.read_from_relay,
|
||||||
R.string.read_from_relay_description,
|
R.string.read_from_relay_description,
|
||||||
)
|
)
|
||||||
@ -91,7 +91,7 @@ fun RelayStatusRow(
|
|||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onLongPress = {
|
onLongPress = {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.write_to_relay,
|
R.string.write_to_relay,
|
||||||
R.string.write_to_relay_description,
|
R.string.write_to_relay_description,
|
||||||
)
|
)
|
||||||
@ -118,7 +118,7 @@ fun RelayStatusRow(
|
|||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onLongPress = {
|
onLongPress = {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.errors,
|
R.string.errors,
|
||||||
R.string.errors_description,
|
R.string.errors_description,
|
||||||
)
|
)
|
||||||
@ -150,7 +150,7 @@ fun RelayStatusRow(
|
|||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onLongPress = {
|
onLongPress = {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.spam,
|
R.string.spam,
|
||||||
R.string.spam_description,
|
R.string.spam_description,
|
||||||
)
|
)
|
||||||
|
@ -116,7 +116,7 @@ fun CashuPreview(
|
|||||||
CashuPreviewNew(
|
CashuPreviewNew(
|
||||||
it,
|
it,
|
||||||
accountViewModel::meltCashu,
|
accountViewModel::meltCashu,
|
||||||
accountViewModel::toast,
|
accountViewModel.toastManager::toast,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1235,10 +1235,10 @@ private fun saveMediaToGallery(
|
|||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
localContext = localContext,
|
localContext = localContext,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
accountViewModel.toast(R.string.video_saved_to_the_gallery, R.string.video_saved_to_the_gallery)
|
accountViewModel.toastManager.toast(R.string.video_saved_to_the_gallery, R.string.video_saved_to_the_gallery)
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
accountViewModel.toast(R.string.failed_to_save_the_video, null, it)
|
accountViewModel.toastManager.toast(R.string.failed_to_save_the_video, null, it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -312,10 +312,10 @@ private fun saveMediaToGallery(
|
|||||||
forceProxy = useTor,
|
forceProxy = useTor,
|
||||||
localContext,
|
localContext,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
accountViewModel.toast(success, success)
|
accountViewModel.toastManager.toast(success, success)
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
accountViewModel.toast(failure, null, it)
|
accountViewModel.toastManager.toast(failure, null, it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else if (content is MediaPreloadedContent) {
|
} else if (content is MediaPreloadedContent) {
|
||||||
@ -325,10 +325,10 @@ private fun saveMediaToGallery(
|
|||||||
content.mimeType,
|
content.mimeType,
|
||||||
localContext,
|
localContext,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
accountViewModel.toast(success, success)
|
accountViewModel.toastManager.toast(success, success)
|
||||||
},
|
},
|
||||||
onError = { innerIt ->
|
onError = { innerIt ->
|
||||||
accountViewModel.toast(failure, null, innerIt)
|
accountViewModel.toastManager.toast(failure, null, innerIt)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -726,7 +726,7 @@ fun ShareImageAction(
|
|||||||
val n19 = Nip19Parser.uriToRoute(postNostrUri)?.entity as? NEvent
|
val n19 = Nip19Parser.uriToRoute(postNostrUri)?.entity as? NEvent
|
||||||
if (n19 != null) {
|
if (n19 != null) {
|
||||||
accountViewModel.addMediaToGallery(n19.hex, videoUri, n19.relay.getOrNull(0), blurhash, dim, hash, mimeType) // TODO Whole list or first?
|
accountViewModel.addMediaToGallery(n19.hex, videoUri, n19.relay.getOrNull(0), blurhash, dim, hash, mimeType) // TODO Whole list or first?
|
||||||
accountViewModel.toast(R.string.media_added, R.string.media_added_to_profile_gallery)
|
accountViewModel.toastManager.toast(R.string.media_added, R.string.media_added_to_profile_gallery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,20 +18,24 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* 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.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components.toasts
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.MultiErrorToastMsg
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.MultiUserErrorMessageDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ResourceToastMsg
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThrowableToastMsg
|
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
|
fun DisplayErrorMessages(
|
||||||
val openDialogMsg = accountViewModel.toasts.collectAsStateWithLifecycle(null)
|
toastManager: ToastManager,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val openDialogMsg = toastManager.toasts.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
openDialogMsg.value?.let { obj ->
|
openDialogMsg.value?.let { obj ->
|
||||||
when (obj) {
|
when (obj) {
|
||||||
@ -41,14 +45,14 @@ fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
|
|||||||
stringRes(obj.titleResId),
|
stringRes(obj.titleResId),
|
||||||
stringRes(obj.resourceId, *obj.params),
|
stringRes(obj.resourceId, *obj.params),
|
||||||
) {
|
) {
|
||||||
accountViewModel.clearToasts()
|
toastManager.clearToasts()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
InformationDialog(
|
InformationDialog(
|
||||||
stringRes(obj.titleResId),
|
stringRes(obj.titleResId),
|
||||||
stringRes(obj.resourceId),
|
stringRes(obj.resourceId),
|
||||||
) {
|
) {
|
||||||
accountViewModel.clearToasts()
|
toastManager.clearToasts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +61,7 @@ fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
|
|||||||
obj.title,
|
obj.title,
|
||||||
obj.msg,
|
obj.msg,
|
||||||
) {
|
) {
|
||||||
accountViewModel.clearToasts()
|
toastManager.clearToasts()
|
||||||
}
|
}
|
||||||
|
|
||||||
is ThrowableToastMsg ->
|
is ThrowableToastMsg ->
|
||||||
@ -66,8 +70,10 @@ fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
|
|||||||
obj.msg,
|
obj.msg,
|
||||||
obj.throwable,
|
obj.throwable,
|
||||||
) {
|
) {
|
||||||
accountViewModel.clearToasts()
|
toastManager.clearToasts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is MultiErrorToastMsg -> MultiUserErrorMessageDialog(obj, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.components.toasts
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class ResourceToastMsg(
|
||||||
|
val titleResId: Int,
|
||||||
|
val resourceId: Int,
|
||||||
|
val params: Array<out String>? = null,
|
||||||
|
) : ToastMsg()
|
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.components.toasts
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class StringToastMsg(
|
||||||
|
val title: String,
|
||||||
|
val msg: String,
|
||||||
|
) : ToastMsg()
|
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.components.toasts
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class ThrowableToastMsg(
|
||||||
|
val titleResId: Int,
|
||||||
|
val msg: String? = null,
|
||||||
|
val throwable: Throwable,
|
||||||
|
) : ToastMsg()
|
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.components.toasts
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.MultiErrorToastMsg
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
class ToastManager {
|
||||||
|
val toasts = MutableStateFlow<ToastMsg?>(null)
|
||||||
|
|
||||||
|
fun clearToasts() {
|
||||||
|
toasts.tryEmit(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toast(
|
||||||
|
title: String,
|
||||||
|
message: String,
|
||||||
|
) {
|
||||||
|
toasts.tryEmit(StringToastMsg(title, message))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toast(
|
||||||
|
titleResId: Int,
|
||||||
|
resourceId: Int,
|
||||||
|
) {
|
||||||
|
toasts.tryEmit(ResourceToastMsg(titleResId, resourceId))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toast(
|
||||||
|
titleResId: Int,
|
||||||
|
message: String?,
|
||||||
|
throwable: Throwable,
|
||||||
|
) {
|
||||||
|
toasts.tryEmit(ThrowableToastMsg(titleResId, message, throwable))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toast(
|
||||||
|
titleResId: Int,
|
||||||
|
resourceId: Int,
|
||||||
|
vararg params: String,
|
||||||
|
) {
|
||||||
|
toasts.tryEmit(ResourceToastMsg(titleResId, resourceId, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toast(
|
||||||
|
titleResId: Int,
|
||||||
|
message: String,
|
||||||
|
user: User?,
|
||||||
|
) {
|
||||||
|
val current = toasts.value
|
||||||
|
if (current is MultiErrorToastMsg && current.titleResId == titleResId) {
|
||||||
|
current.add(message, user)
|
||||||
|
} else {
|
||||||
|
toasts.tryEmit(MultiErrorToastMsg(titleResId).also { it.add(message, user) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toast(
|
||||||
|
titleResId: Int,
|
||||||
|
data: UserBasedErrorMessage,
|
||||||
|
) {
|
||||||
|
val current = toasts.value
|
||||||
|
if (current is MultiErrorToastMsg && current.titleResId == titleResId) {
|
||||||
|
current.add(data)
|
||||||
|
} else {
|
||||||
|
toasts.tryEmit(MultiErrorToastMsg(titleResId).also { it.add(data) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.components.toasts
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
open class ToastMsg
|
@ -18,11 +18,10 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* 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.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.components.toasts.multiline
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -31,56 +30,44 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.Done
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size30dp
|
import com.vitorpamplona.amethyst.ui.theme.Size30dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size40dp
|
import com.vitorpamplona.amethyst.ui.theme.Size40dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun MultiUserErrorMessageContentPreview() {
|
fun ErrorListPreview() {
|
||||||
val accountViewModel = mockAccountViewModel()
|
val accountViewModel = mockAccountViewModel()
|
||||||
val nav = EmptyNav
|
|
||||||
|
|
||||||
var user1: User? = null
|
var user1: User? = null
|
||||||
var user2: User? = null
|
var user2: User? = null
|
||||||
@ -88,107 +75,43 @@ fun MultiUserErrorMessageContentPreview() {
|
|||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
user1 = LocalCache.getOrCreateUser("aaabccaabbccaabbcc")
|
user1 = LocalCache.getOrCreateUser("aaabccaabbccaabbccabdd")
|
||||||
user2 = LocalCache.getOrCreateUser("bbbccabbbccabbbcca")
|
user2 = LocalCache.getOrCreateUser("bbbccabbbccabbbccaabdd")
|
||||||
user3 = LocalCache.getOrCreateUser("ccaadaccaadaccaada")
|
user3 = LocalCache.getOrCreateUser("ccaadaccaadaccaadaabdd")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val model: UserBasedErrorMessageViewModel = viewModel()
|
val model = MultiErrorToastMsg(R.string.error_dialog_zap_error)
|
||||||
model.add("Could not fetch invoice from https://minibits.cash/.well-known/lnurlp/victorieeman: There are too many unpaid invoices for this name.", user1)
|
model.add("Could not fetch invoice from https://minibits.cash/.well-known/lnurlp/victorieeman: There are too many unpaid invoices for this name.", user1)
|
||||||
model.add("No Wallets found to pay a lightning invoice. Please install a Lightning wallet to use zaps.", user2)
|
model.add("No Wallets found to pay a lightning invoice. Please install a Lightning wallet to use zaps.", user2)
|
||||||
model.add("Could not fetch invoice", user3)
|
model.add("Could not fetch invoice", user3)
|
||||||
|
|
||||||
ThemeComparisonColumn {
|
ThemeComparisonColumn {
|
||||||
MultiUserErrorMessageDialogInner(
|
ErrorList(
|
||||||
title = "Couldn't not zap",
|
|
||||||
model = model,
|
model = model,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = EmptyNav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
|
||||||
class UserBasedErrorMessageViewModel : ViewModel() {
|
|
||||||
val errors = MutableStateFlow<List<UserBasedErrorMessage>>(emptyList())
|
|
||||||
val hasErrors = errors.map { it.isNotEmpty() }
|
|
||||||
|
|
||||||
fun add(
|
|
||||||
message: String,
|
|
||||||
user: User?,
|
|
||||||
) {
|
|
||||||
add(UserBasedErrorMessage(message, user))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(newError: UserBasedErrorMessage) {
|
|
||||||
errors.update {
|
|
||||||
it + newError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearErrors() {
|
|
||||||
errors.update {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserBasedErrorMessage(
|
|
||||||
val error: String,
|
|
||||||
val user: User?,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MultiUserErrorMessageDialog(
|
fun ErrorList(
|
||||||
title: String,
|
model: MultiErrorToastMsg,
|
||||||
model: UserBasedErrorMessageViewModel,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val hasErrors by model.hasErrors.collectAsStateWithLifecycle(false)
|
val errorState by model.errors.collectAsStateWithLifecycle()
|
||||||
if (hasErrors) {
|
LazyColumn {
|
||||||
MultiUserErrorMessageDialogInner(title, model, accountViewModel, nav)
|
itemsIndexed(errorState) { index, it ->
|
||||||
|
ErrorRow(it, accountViewModel, nav)
|
||||||
|
if (index < errorState.size - 1) {
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MultiUserErrorMessageDialogInner(
|
|
||||||
title: String,
|
|
||||||
model: UserBasedErrorMessageViewModel,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
nav: INav,
|
|
||||||
) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = model::clearErrors,
|
|
||||||
title = { Text(title) },
|
|
||||||
text = {
|
|
||||||
val errorState by model.errors.collectAsStateWithLifecycle(emptyList())
|
|
||||||
LazyColumn {
|
|
||||||
itemsIndexed(errorState) { index, it ->
|
|
||||||
ErrorRow(it, accountViewModel, nav)
|
|
||||||
if (index < errorState.size - 1) {
|
|
||||||
HorizontalDivider(thickness = DividerThickness)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
Button(
|
|
||||||
onClick = model::clearErrors,
|
|
||||||
contentPadding = PaddingValues(horizontal = Size16dp),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Done,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Spacer(StdHorzSpacer)
|
|
||||||
Text(stringRes(R.string.error_dialog_button_ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ErrorRow(
|
fun ErrorRow(
|
||||||
errorState: UserBasedErrorMessage,
|
errorState: UserBasedErrorMessage,
|
||||||
@ -201,7 +124,7 @@ fun ErrorRow(
|
|||||||
) {
|
) {
|
||||||
errorState.user?.let {
|
errorState.user?.let {
|
||||||
Column(Modifier.width(Size40dp), horizontalAlignment = Alignment.Start) {
|
Column(Modifier.width(Size40dp), horizontalAlignment = Alignment.Start) {
|
||||||
UserPicture(errorState.user, Size30dp, Modifier, accountViewModel, nav)
|
UserPicture(it, Size30dp, Modifier, accountViewModel, nav)
|
||||||
Spacer(StdVertSpacer)
|
Spacer(StdVertSpacer)
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Size30Modifier,
|
modifier = Size30Modifier,
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.components.toasts.multiline
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.ToastMsg
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class MultiErrorToastMsg(
|
||||||
|
val titleResId: Int,
|
||||||
|
) : ToastMsg() {
|
||||||
|
val errors = MutableStateFlow<List<UserBasedErrorMessage>>(emptyList())
|
||||||
|
|
||||||
|
fun add(
|
||||||
|
message: String,
|
||||||
|
user: User?,
|
||||||
|
) {
|
||||||
|
add(UserBasedErrorMessage(message, user))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(newError: UserBasedErrorMessage) {
|
||||||
|
errors.update {
|
||||||
|
it + newError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserBasedErrorMessage(
|
||||||
|
val error: String,
|
||||||
|
val user: User?,
|
||||||
|
)
|
@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.components.toasts.multiline
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Done
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun MultiUserErrorMessageContentPreview() {
|
||||||
|
val accountViewModel = mockAccountViewModel()
|
||||||
|
val nav = EmptyNav
|
||||||
|
|
||||||
|
var user1: User? = null
|
||||||
|
var user2: User? = null
|
||||||
|
var user3: User? = null
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
user1 = LocalCache.getOrCreateUser("aaabccaabbccaabbccabdd")
|
||||||
|
user2 = LocalCache.getOrCreateUser("bbbccabbbccabbbccaabdd")
|
||||||
|
user3 = LocalCache.getOrCreateUser("ccaadaccaadaccaadaabdd")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val model = MultiErrorToastMsg(R.string.error_dialog_zap_error)
|
||||||
|
model.add("Could not fetch invoice from https://minibits.cash/.well-known/lnurlp/victorieeman: There are too many unpaid invoices for this name.", user1)
|
||||||
|
model.add("No Wallets found to pay a lightning invoice. Please install a Lightning wallet to use zaps.", user2)
|
||||||
|
model.add("Could not fetch invoice", user3)
|
||||||
|
|
||||||
|
ThemeComparisonColumn {
|
||||||
|
MultiUserErrorMessageDialog(
|
||||||
|
model = model,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiUserErrorMessageDialog(
|
||||||
|
model: MultiErrorToastMsg,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = accountViewModel.toastManager::clearToasts,
|
||||||
|
title = { Text(stringRes(model.titleResId)) },
|
||||||
|
text = {
|
||||||
|
ErrorList(model, accountViewModel, nav)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = accountViewModel.toastManager::clearToasts,
|
||||||
|
contentPadding = PaddingValues(horizontal = Size16dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Done,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Spacer(StdHorzSpacer)
|
||||||
|
Text(stringRes(R.string.error_dialog_button_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -51,8 +51,8 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataScreen
|
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataScreen
|
||||||
import com.vitorpamplona.amethyst.ui.actions.relays.AllRelayListView
|
import com.vitorpamplona.amethyst.ui.actions.relays.AllRelayListView
|
||||||
import com.vitorpamplona.amethyst.ui.components.DisplayErrorMessages
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.DisplayNotifyMessages
|
import com.vitorpamplona.amethyst.ui.components.DisplayNotifyMessages
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.DisplayErrorMessages
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
|
||||||
@ -354,7 +354,7 @@ fun AppNavigation(
|
|||||||
|
|
||||||
NavigateIfIntentRequested(nav, accountViewModel, accountStateViewModel)
|
NavigateIfIntentRequested(nav, accountViewModel, accountStateViewModel)
|
||||||
|
|
||||||
DisplayErrorMessages(accountViewModel)
|
DisplayErrorMessages(accountViewModel.toastManager, accountViewModel, nav)
|
||||||
DisplayNotifyMessages(accountViewModel, nav)
|
DisplayNotifyMessages(accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,7 +437,7 @@ private fun NavigateIfIntentRequested(
|
|||||||
|
|
||||||
actionableNextPage = null
|
actionableNextPage = null
|
||||||
} else {
|
} else {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.invalid_nip19_uri,
|
R.string.invalid_nip19_uri,
|
||||||
R.string.invalid_nip19_uri_description,
|
R.string.invalid_nip19_uri_description,
|
||||||
intentNextPage,
|
intentNextPage,
|
||||||
@ -492,7 +492,7 @@ private fun NavigateIfIntentRequested(
|
|||||||
} else {
|
} else {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.invalid_nip19_uri,
|
R.string.invalid_nip19_uri,
|
||||||
R.string.invalid_nip19_uri_description,
|
R.string.invalid_nip19_uri_description,
|
||||||
uri,
|
uri,
|
||||||
|
@ -551,7 +551,7 @@ fun ListContent(
|
|||||||
accountViewModel.setTorSettings(torSettings)
|
accountViewModel.setTorSettings(torSettings)
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.could_not_connect_to_tor),
|
stringRes(context, R.string.could_not_connect_to_tor),
|
||||||
it,
|
it,
|
||||||
)
|
)
|
||||||
|
@ -76,11 +76,11 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.StringToastMsg
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockVitorAccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockVitorAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
@ -530,23 +530,23 @@ fun ZapVote(
|
|||||||
indication = ripple24dp,
|
indication = ripple24dp,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_send_zaps,
|
R.string.login_with_a_private_key_to_be_able_to_send_zaps,
|
||||||
)
|
)
|
||||||
} else if (pollViewModel.isPollClosed()) {
|
} else if (pollViewModel.isPollClosed()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.poll_unable_to_vote,
|
R.string.poll_unable_to_vote,
|
||||||
R.string.poll_is_closed_explainer,
|
R.string.poll_is_closed_explainer,
|
||||||
)
|
)
|
||||||
} else if (isLoggedUser) {
|
} else if (isLoggedUser) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.poll_unable_to_vote,
|
R.string.poll_unable_to_vote,
|
||||||
R.string.poll_author_no_vote,
|
R.string.poll_author_no_vote,
|
||||||
)
|
)
|
||||||
} else if (pollViewModel.isVoteAmountAtomic() && poolOption.zappedByLoggedIn.value) {
|
} else if (pollViewModel.isVoteAmountAtomic() && poolOption.zappedByLoggedIn.value) {
|
||||||
// only allow one vote per option when min==max, i.e. atomic vote amount specified
|
// only allow one vote per option when min==max, i.e. atomic vote amount specified
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.poll_unable_to_vote,
|
R.string.poll_unable_to_vote,
|
||||||
R.string.one_vote_per_user_on_atomic_votes,
|
R.string.one_vote_per_user_on_atomic_votes,
|
||||||
)
|
)
|
||||||
|
@ -99,12 +99,12 @@ import androidx.lifecycle.MediatorLiveData
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource.user
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius
|
import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius
|
||||||
@ -662,7 +662,7 @@ fun ReplyReaction(
|
|||||||
modifier = iconSizeModifier,
|
modifier = iconSizeModifier,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (baseNote.isDraft()) {
|
if (baseNote.isDraft()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.draft_note,
|
R.string.draft_note,
|
||||||
R.string.it_s_not_possible_to_reply_to_a_draft_note,
|
R.string.it_s_not_possible_to_reply_to_a_draft_note,
|
||||||
)
|
)
|
||||||
@ -670,7 +670,7 @@ fun ReplyReaction(
|
|||||||
if (accountViewModel.isWriteable()) {
|
if (accountViewModel.isWriteable()) {
|
||||||
onPress()
|
onPress()
|
||||||
} else {
|
} else {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_reply,
|
R.string.login_with_a_private_key_to_be_able_to_reply,
|
||||||
)
|
)
|
||||||
@ -986,7 +986,7 @@ private fun likeClick(
|
|||||||
onWantsToSignReaction: () -> Unit,
|
onWantsToSignReaction: () -> Unit,
|
||||||
) {
|
) {
|
||||||
if (baseNote.isDraft()) {
|
if (baseNote.isDraft()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.draft_note,
|
R.string.draft_note,
|
||||||
R.string.it_s_not_possible_to_react_to_a_draft_note,
|
R.string.it_s_not_possible_to_react_to_a_draft_note,
|
||||||
)
|
)
|
||||||
@ -995,12 +995,12 @@ private fun likeClick(
|
|||||||
|
|
||||||
val choices = accountViewModel.reactionChoices()
|
val choices = accountViewModel.reactionChoices()
|
||||||
if (choices.isEmpty()) {
|
if (choices.isEmpty()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.no_reactions_setup,
|
R.string.no_reactions_setup,
|
||||||
R.string.no_reaction_type_setup_long_press_to_change,
|
R.string.no_reaction_type_setup_long_press_to_change,
|
||||||
)
|
)
|
||||||
} else if (!accountViewModel.isWriteable()) {
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_like_posts,
|
R.string.login_with_a_private_key_to_like_posts,
|
||||||
)
|
)
|
||||||
@ -1025,7 +1025,6 @@ fun ZapReaction(
|
|||||||
var wantsToZap by remember { mutableStateOf(false) }
|
var wantsToZap by remember { mutableStateOf(false) }
|
||||||
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
||||||
var wantsToSetCustomZap by remember { mutableStateOf(false) }
|
var wantsToSetCustomZap by remember { mutableStateOf(false) }
|
||||||
val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
|
|
||||||
var wantsToPay by
|
var wantsToPay by
|
||||||
remember(baseNote) {
|
remember(baseNote) {
|
||||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||||
@ -1060,7 +1059,7 @@ fun ZapReaction(
|
|||||||
onError = { _, message, user ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(message, user)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPayViaIntent = { wantsToPay = it },
|
onPayViaIntent = { wantsToPay = it },
|
||||||
@ -1089,7 +1088,7 @@ fun ZapReaction(
|
|||||||
onError = { _, message, user ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(message, user)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
||||||
@ -1097,13 +1096,6 @@ fun ZapReaction(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiUserErrorMessageDialog(
|
|
||||||
title = stringRes(id = R.string.error_dialog_zap_error),
|
|
||||||
model = errorViewModel,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (wantsToChangeZapAmount) {
|
if (wantsToChangeZapAmount) {
|
||||||
UpdateZapAmountDialog(
|
UpdateZapAmountDialog(
|
||||||
onClose = { wantsToChangeZapAmount = false },
|
onClose = { wantsToChangeZapAmount = false },
|
||||||
@ -1120,12 +1112,12 @@ fun ZapReaction(
|
|||||||
wantsToPay = persistentListOf()
|
wantsToPay = persistentListOf()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(it)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
justShowError = {
|
justShowError = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
errorViewModel.add(it)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1137,7 +1129,7 @@ fun ZapReaction(
|
|||||||
onError = { _, message, user ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(message, user)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
||||||
@ -1191,7 +1183,7 @@ fun zapClick(
|
|||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (baseNote.isDraft()) {
|
if (baseNote.isDraft()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.draft_note,
|
R.string.draft_note,
|
||||||
R.string.it_s_not_possible_to_zap_to_a_draft_note,
|
R.string.it_s_not_possible_to_zap_to_a_draft_note,
|
||||||
)
|
)
|
||||||
@ -1201,12 +1193,12 @@ fun zapClick(
|
|||||||
val choices = accountViewModel.zapAmountChoices()
|
val choices = accountViewModel.zapAmountChoices()
|
||||||
|
|
||||||
if (choices.isEmpty()) {
|
if (choices.isEmpty()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.error_dialog_zap_error,
|
R.string.error_dialog_zap_error,
|
||||||
R.string.no_zap_amount_setup_long_press_to_change,
|
R.string.no_zap_amount_setup_long_press_to_change,
|
||||||
)
|
)
|
||||||
} else if (!accountViewModel.isWriteable()) {
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.error_dialog_zap_error,
|
R.string.error_dialog_zap_error,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_send_zaps,
|
R.string.login_with_a_private_key_to_be_able_to_send_zaps,
|
||||||
)
|
)
|
||||||
|
@ -169,7 +169,7 @@ fun RenderRelay(
|
|||||||
openRelayDialog = true
|
openRelayDialog = true
|
||||||
},
|
},
|
||||||
onError = { url, errorCode, exceptionMessage ->
|
onError = { url, errorCode, exceptionMessage ->
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.unable_to_download_relay_document,
|
R.string.unable_to_download_relay_document,
|
||||||
when (errorCode) {
|
when (errorCode) {
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
|
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
|
||||||
|
@ -337,12 +337,12 @@ fun UpdateZapAmountContent(
|
|||||||
postViewModel.updateNIP47(nip47uri)
|
postViewModel.updateNIP47(nip47uri)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
if (e.message != null) {
|
if (e.message != null) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.error_parsing_nip47_title),
|
stringRes(context, R.string.error_parsing_nip47_title),
|
||||||
stringRes(context, R.string.error_parsing_nip47, nip47uri, e.message!!),
|
stringRes(context, R.string.error_parsing_nip47, nip47uri, e.message!!),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.error_parsing_nip47_title),
|
stringRes(context, R.string.error_parsing_nip47_title),
|
||||||
stringRes(context, R.string.error_parsing_nip47_no_error, nip47uri),
|
stringRes(context, R.string.error_parsing_nip47_no_error, nip47uri),
|
||||||
)
|
)
|
||||||
@ -510,12 +510,12 @@ fun UpdateZapAmountContent(
|
|||||||
postViewModel.updateNIP47(it)
|
postViewModel.updateNIP47(it)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
if (e.message != null) {
|
if (e.message != null) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.error_parsing_nip47_title),
|
stringRes(context, R.string.error_parsing_nip47_title),
|
||||||
stringRes(context, R.string.error_parsing_nip47, it, e.message!!),
|
stringRes(context, R.string.error_parsing_nip47, it, e.message!!),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.error_parsing_nip47_title),
|
stringRes(context, R.string.error_parsing_nip47_title),
|
||||||
stringRes(context, R.string.error_parsing_nip47_no_error, it),
|
stringRes(context, R.string.error_parsing_nip47_no_error, it),
|
||||||
)
|
)
|
||||||
@ -611,7 +611,7 @@ fun UpdateZapAmountContent(
|
|||||||
context = context,
|
context = context,
|
||||||
keyguardLauncher = keyguardLauncher,
|
keyguardLauncher = keyguardLauncher,
|
||||||
onApproved = { showPassword = true },
|
onApproved = { showPassword = true },
|
||||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
showPassword = false
|
showPassword = false
|
||||||
|
@ -66,6 +66,7 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||||
|
@ -201,7 +201,7 @@ fun ShowFollowingOrUnfollowingButton(
|
|||||||
if (isFollowing) {
|
if (isFollowing) {
|
||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_unfollow,
|
R.string.login_with_a_private_key_to_be_able_to_unfollow,
|
||||||
)
|
)
|
||||||
@ -212,7 +212,7 @@ fun ShowFollowingOrUnfollowingButton(
|
|||||||
} else {
|
} else {
|
||||||
FollowButton {
|
FollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_follow,
|
R.string.login_with_a_private_key_to_be_able_to_follow,
|
||||||
)
|
)
|
||||||
|
@ -75,7 +75,7 @@ fun DisplayOts(
|
|||||||
val fullDateTime =
|
val fullDateTime =
|
||||||
SimpleDateFormat.getDateTimeInstance().format(Date(unixtimestamp * 1000))
|
SimpleDateFormat.getDateTimeInstance().format(Date(unixtimestamp * 1000))
|
||||||
|
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.ots_info_title,
|
R.string.ots_info_title,
|
||||||
R.string.ots_info_description,
|
R.string.ots_info_description,
|
||||||
fullDateTime,
|
fullDateTime,
|
||||||
|
@ -58,7 +58,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
@ -72,10 +71,8 @@ import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
|||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.MultiUserErrorMessageDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserBasedErrorMessageViewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
|
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
|
||||||
@ -293,7 +290,6 @@ fun ZapDonationButton(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
var wantsToZap by remember { mutableStateOf<ImmutableList<Long>?>(null) }
|
var wantsToZap by remember { mutableStateOf<ImmutableList<Long>?>(null) }
|
||||||
val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
|
|
||||||
var wantsToPay by
|
var wantsToPay by
|
||||||
remember(baseNote) {
|
remember(baseNote) {
|
||||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||||
@ -320,7 +316,7 @@ fun ZapDonationButton(
|
|||||||
onError = { _, message, toUser ->
|
onError = { _, message, toUser ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(message, toUser)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, toUser)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPayViaIntent = { wantsToPay = it },
|
onPayViaIntent = { wantsToPay = it },
|
||||||
@ -344,7 +340,7 @@ fun ZapDonationButton(
|
|||||||
onError = { _, message, user ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(message, user)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
@ -354,13 +350,6 @@ fun ZapDonationButton(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiUserErrorMessageDialog(
|
|
||||||
title = stringRes(id = R.string.error_dialog_zap_error),
|
|
||||||
model = errorViewModel,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (wantsToPay.isNotEmpty()) {
|
if (wantsToPay.isNotEmpty()) {
|
||||||
PayViaIntentDialog(
|
PayViaIntentDialog(
|
||||||
payingInvoices = wantsToPay,
|
payingInvoices = wantsToPay,
|
||||||
@ -370,12 +359,12 @@ fun ZapDonationButton(
|
|||||||
wantsToPay = persistentListOf()
|
wantsToPay = persistentListOf()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(it)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
justShowError = {
|
justShowError = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
errorViewModel.add(it)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -441,7 +430,7 @@ fun customZapClick(
|
|||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (baseNote.isDraft()) {
|
if (baseNote.isDraft()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.draft_note,
|
R.string.draft_note,
|
||||||
R.string.it_s_not_possible_to_zap_to_a_draft_note,
|
R.string.it_s_not_possible_to_zap_to_a_draft_note,
|
||||||
)
|
)
|
||||||
@ -451,12 +440,12 @@ fun customZapClick(
|
|||||||
val choices = accountViewModel.zapAmountChoices()
|
val choices = accountViewModel.zapAmountChoices()
|
||||||
|
|
||||||
if (choices.isEmpty()) {
|
if (choices.isEmpty()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.error_dialog_zap_error),
|
stringRes(context, R.string.error_dialog_zap_error),
|
||||||
stringRes(context, R.string.no_zap_amount_setup_long_press_to_change),
|
stringRes(context, R.string.no_zap_amount_setup_long_press_to_change),
|
||||||
)
|
)
|
||||||
} else if (!accountViewModel.isWriteable()) {
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
stringRes(context, R.string.error_dialog_zap_error),
|
stringRes(context, R.string.error_dialog_zap_error),
|
||||||
stringRes(context, R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
stringRes(context, R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
||||||
)
|
)
|
||||||
|
@ -221,7 +221,7 @@ fun DisplayFileList(
|
|||||||
ContextCompat.startActivity(context, intent, null)
|
ContextCompat.startActivity(context, intent, null)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
accountViewModel.toast(R.string.torrent_failure, R.string.torrent_no_apps)
|
accountViewModel.toastManager.toast(R.string.torrent_failure, R.string.torrent_no_apps)
|
||||||
}
|
}
|
||||||
}, Modifier.size(Size30dp)) {
|
}, Modifier.size(Size30dp)) {
|
||||||
DownloadForOfflineIcon(Size20dp, MaterialTheme.colorScheme.onBackground)
|
DownloadForOfflineIcon(Size20dp, MaterialTheme.colorScheme.onBackground)
|
||||||
|
@ -156,7 +156,7 @@ private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) {
|
|||||||
contract = ActivityResultContracts.StartActivityForResult(),
|
contract = ActivityResultContracts.StartActivityForResult(),
|
||||||
onResult = { result ->
|
onResult = { result ->
|
||||||
if (result.resultCode != Activity.RESULT_OK) {
|
if (result.resultCode != Activity.RESULT_OK) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.sign_request_rejected,
|
R.string.sign_request_rejected,
|
||||||
R.string.sign_request_rejected_description,
|
R.string.sign_request_rejected_description,
|
||||||
)
|
)
|
||||||
@ -183,7 +183,7 @@ private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
Log.e("Signer", "Error opening Signer app", e)
|
Log.e("Signer", "Error opening Signer app", e)
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.error_opening_external_signer,
|
R.string.error_opening_external_signer,
|
||||||
R.string.error_opening_external_signer_description,
|
R.string.error_opening_external_signer_description,
|
||||||
)
|
)
|
||||||
@ -203,7 +203,7 @@ private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
Log.e("Signer", "Error opening Signer app", e)
|
Log.e("Signer", "Error opening Signer app", e)
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.error_opening_external_signer,
|
R.string.error_opening_external_signer,
|
||||||
R.string.error_opening_external_signer_description,
|
R.string.error_opening_external_signer_description,
|
||||||
)
|
)
|
||||||
|
@ -321,7 +321,7 @@ private fun NSecCopyButton(accountViewModel: AccountViewModel) {
|
|||||||
context = context,
|
context = context,
|
||||||
keyguardLauncher = keyguardLauncher,
|
keyguardLauncher = keyguardLauncher,
|
||||||
onApproved = { copyNSec(context, scope, accountViewModel.account, clipboardManager) },
|
onApproved = { copyNSec(context, scope, accountViewModel.account, clipboardManager) },
|
||||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
shape = ButtonBorder,
|
shape = ButtonBorder,
|
||||||
@ -371,7 +371,7 @@ private fun EncryptNSecCopyButton(
|
|||||||
context = context,
|
context = context,
|
||||||
keyguardLauncher = keyguardLauncher,
|
keyguardLauncher = keyguardLauncher,
|
||||||
onApproved = { encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager) },
|
onApproved = { encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager) },
|
||||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
shape = ButtonBorder,
|
shape = ButtonBorder,
|
||||||
@ -496,7 +496,7 @@ private fun QrCodeButtonBase(
|
|||||||
context = context,
|
context = context,
|
||||||
keyguardLauncher = keyguardLauncher,
|
keyguardLauncher = keyguardLauncher,
|
||||||
onApproved = { dialogOpen = true },
|
onApproved = { dialogOpen = true },
|
||||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
|||||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||||
import com.vitorpamplona.amethyst.ui.actions.Dao
|
import com.vitorpamplona.amethyst.ui.actions.Dao
|
||||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.toasts.ToastManager
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
||||||
@ -123,8 +124,6 @@ import kotlinx.coroutines.CancellationException
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@ -137,25 +136,6 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Immutable open class ToastMsg
|
|
||||||
|
|
||||||
@Immutable class StringToastMsg(
|
|
||||||
val title: String,
|
|
||||||
val msg: String,
|
|
||||||
) : ToastMsg()
|
|
||||||
|
|
||||||
@Immutable class ResourceToastMsg(
|
|
||||||
val titleResId: Int,
|
|
||||||
val resourceId: Int,
|
|
||||||
val params: Array<out String>? = null,
|
|
||||||
) : ToastMsg()
|
|
||||||
|
|
||||||
@Immutable class ThrowableToastMsg(
|
|
||||||
val titleResId: Int,
|
|
||||||
val msg: String? = null,
|
|
||||||
val throwable: Throwable,
|
|
||||||
) : ToastMsg()
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class AccountViewModel(
|
class AccountViewModel(
|
||||||
accountSettings: AccountSettings,
|
accountSettings: AccountSettings,
|
||||||
@ -189,7 +169,7 @@ class AccountViewModel(
|
|||||||
val dmRelays: StateFlow<ChatMessageRelayListEvent?> = observeByAuthor(ChatMessageRelayListEvent.KIND, account.signer.pubKey)
|
val dmRelays: StateFlow<ChatMessageRelayListEvent?> = observeByAuthor(ChatMessageRelayListEvent.KIND, account.signer.pubKey)
|
||||||
val searchRelays: StateFlow<SearchRelayListEvent?> = observeByAuthor(SearchRelayListEvent.KIND, account.signer.pubKey)
|
val searchRelays: StateFlow<SearchRelayListEvent?> = observeByAuthor(SearchRelayListEvent.KIND, account.signer.pubKey)
|
||||||
|
|
||||||
val toasts = MutableSharedFlow<ToastMsg?>(0, 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
val toastManager = ToastManager()
|
||||||
|
|
||||||
val feedStates = AccountFeedContentStates(this)
|
val feedStates = AccountFeedContentStates(this)
|
||||||
|
|
||||||
@ -269,40 +249,6 @@ class AccountViewModel(
|
|||||||
Route.Notification to notificationHasNewItemsFlow,
|
Route.Notification to notificationHasNewItemsFlow,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun clearToasts() {
|
|
||||||
viewModelScope.launch { toasts.emit(null) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toast(
|
|
||||||
title: String,
|
|
||||||
message: String,
|
|
||||||
) {
|
|
||||||
viewModelScope.launch { toasts.emit(StringToastMsg(title, message)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toast(
|
|
||||||
titleResId: Int,
|
|
||||||
resourceId: Int,
|
|
||||||
) {
|
|
||||||
viewModelScope.launch { toasts.emit(ResourceToastMsg(titleResId, resourceId)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toast(
|
|
||||||
titleResId: Int,
|
|
||||||
message: String?,
|
|
||||||
throwable: Throwable,
|
|
||||||
) {
|
|
||||||
viewModelScope.launch { toasts.emit(ThrowableToastMsg(titleResId, message, throwable)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toast(
|
|
||||||
titleResId: Int,
|
|
||||||
resourceId: Int,
|
|
||||||
vararg params: String,
|
|
||||||
) {
|
|
||||||
viewModelScope.launch { toasts.emit(ResourceToastMsg(titleResId, resourceId, params)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isWriteable(): Boolean = account.isWriteable()
|
fun isWriteable(): Boolean = account.isWriteable()
|
||||||
|
|
||||||
fun userProfile(): User = account.userProfile()
|
fun userProfile(): User = account.userProfile()
|
||||||
@ -1406,7 +1352,7 @@ class AccountViewModel(
|
|||||||
onMore: () -> Unit,
|
onMore: () -> Unit,
|
||||||
) {
|
) {
|
||||||
if (baseNote.isDraft()) {
|
if (baseNote.isDraft()) {
|
||||||
toast(
|
toastManager.toast(
|
||||||
R.string.draft_note,
|
R.string.draft_note,
|
||||||
R.string.it_s_not_possible_to_quote_to_a_draft_note,
|
R.string.it_s_not_possible_to_quote_to_a_draft_note,
|
||||||
)
|
)
|
||||||
@ -1420,7 +1366,7 @@ class AccountViewModel(
|
|||||||
onMore()
|
onMore()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast(
|
toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_boost_posts,
|
R.string.login_with_a_private_key_to_be_able_to_boost_posts,
|
||||||
)
|
)
|
||||||
@ -1766,6 +1712,7 @@ fun mockAccountViewModel(): AccountViewModel {
|
|||||||
KeyPair(
|
KeyPair(
|
||||||
privKey = Hex.decode("0f761f8a5a481e26f06605a1d9b3e9eba7a107d351f43c43a57469b788274499"),
|
privKey = Hex.decode("0f761f8a5a481e26f06605a1d9b3e9eba7a107d351f43c43a57469b788274499"),
|
||||||
pubKey = Hex.decode("989c3734c46abac7ce3ce229971581a5a6ee39cdd6aa7261a55823fa7f8c4799"),
|
pubKey = Hex.decode("989c3734c46abac7ce3ce229971581a5a6ee39cdd6aa7261a55823fa7f8c4799"),
|
||||||
|
forceReplacePubkey = false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
sharedPreferencesViewModel.sharedPrefs,
|
sharedPreferencesViewModel.sharedPrefs,
|
||||||
|
@ -527,7 +527,7 @@ fun NewPostScreen(
|
|||||||
it,
|
it,
|
||||||
accountViewModel.account.settings.defaultFileServer,
|
accountViewModel.account.settings.defaultFileServer,
|
||||||
onAdd = { alt, server, sensitiveContent, mediaQuality ->
|
onAdd = { alt, server, sensitiveContent, mediaQuality ->
|
||||||
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, false, server, accountViewModel::toast, context)
|
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, false, server, accountViewModel.toastManager::toast, context)
|
||||||
if (server.type != ServerType.NIP95) {
|
if (server.type != ServerType.NIP95) {
|
||||||
accountViewModel.account.settings.changeDefaultFileServer(server)
|
accountViewModel.account.settings.changeDefaultFileServer(server)
|
||||||
}
|
}
|
||||||
@ -558,7 +558,7 @@ fun NewPostScreen(
|
|||||||
postViewModel.wantsInvoice = false
|
postViewModel.wantsInvoice = false
|
||||||
},
|
},
|
||||||
onClose = { postViewModel.wantsInvoice = false },
|
onClose = { postViewModel.wantsInvoice = false },
|
||||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ fun RoomChatFileUploadDialog(
|
|||||||
},
|
},
|
||||||
upload = {
|
upload = {
|
||||||
channelScreenModel.upload(
|
channelScreenModel.upload(
|
||||||
onError = accountViewModel::toast,
|
onError = accountViewModel.toastManager::toast,
|
||||||
context = context,
|
context = context,
|
||||||
onceUploaded = onUpload,
|
onceUploaded = onUpload,
|
||||||
)
|
)
|
||||||
|
@ -126,7 +126,7 @@ fun ChannelFileUploadDialog(
|
|||||||
},
|
},
|
||||||
upload = {
|
upload = {
|
||||||
channelScreenModel.upload(
|
channelScreenModel.upload(
|
||||||
onError = accountViewModel::toast,
|
onError = accountViewModel.toastManager::toast,
|
||||||
context = context,
|
context = context,
|
||||||
onceUploaded = onUpload,
|
onceUploaded = onUpload,
|
||||||
)
|
)
|
||||||
|
@ -70,11 +70,9 @@ import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
|||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.DVMCard
|
import com.vitorpamplona.amethyst.ui.note.DVMCard
|
||||||
import com.vitorpamplona.amethyst.ui.note.MultiUserErrorMessageDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserBasedErrorMessageViewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
|
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
||||||
@ -442,7 +440,6 @@ fun ZapDVMButton(
|
|||||||
val noteAuthor = baseNote.author ?: return
|
val noteAuthor = baseNote.author ?: return
|
||||||
|
|
||||||
var wantsToZap by remember { mutableStateOf<List<Long>?>(null) }
|
var wantsToZap by remember { mutableStateOf<List<Long>?>(null) }
|
||||||
val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
|
|
||||||
var wantsToPay by
|
var wantsToPay by
|
||||||
remember(baseNote) {
|
remember(baseNote) {
|
||||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||||
@ -472,7 +469,7 @@ fun ZapDVMButton(
|
|||||||
onError = { _, message, toUser ->
|
onError = { _, message, toUser ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(message, toUser)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, toUser)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPayViaIntent = { wantsToPay = it },
|
onPayViaIntent = { wantsToPay = it },
|
||||||
@ -496,7 +493,7 @@ fun ZapDVMButton(
|
|||||||
onError = { _, message, user ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(message, user)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
@ -506,13 +503,6 @@ fun ZapDVMButton(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiUserErrorMessageDialog(
|
|
||||||
title = stringRes(id = R.string.error_dialog_zap_error),
|
|
||||||
model = errorViewModel,
|
|
||||||
accountViewModel,
|
|
||||||
nav,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (wantsToPay.isNotEmpty()) {
|
if (wantsToPay.isNotEmpty()) {
|
||||||
PayViaIntentDialog(
|
PayViaIntentDialog(
|
||||||
payingInvoices = wantsToPay,
|
payingInvoices = wantsToPay,
|
||||||
@ -522,12 +512,12 @@ fun ZapDVMButton(
|
|||||||
wantsToPay = persistentListOf()
|
wantsToPay = persistentListOf()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
errorViewModel.add(it)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
justShowError = {
|
justShowError = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
errorViewModel.add(it)
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -208,7 +208,7 @@ fun GeoHashActionOptions(
|
|||||||
if (isFollowingTag) {
|
if (isFollowingTag) {
|
||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_unfollow,
|
R.string.login_with_a_private_key_to_be_able_to_unfollow,
|
||||||
)
|
)
|
||||||
@ -219,7 +219,7 @@ fun GeoHashActionOptions(
|
|||||||
} else {
|
} else {
|
||||||
FollowButton {
|
FollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_follow,
|
R.string.login_with_a_private_key_to_be_able_to_follow,
|
||||||
)
|
)
|
||||||
|
@ -187,7 +187,7 @@ fun HashtagActionOptions(
|
|||||||
if (isFollowingTag) {
|
if (isFollowingTag) {
|
||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_unfollow,
|
R.string.login_with_a_private_key_to_be_able_to_unfollow,
|
||||||
)
|
)
|
||||||
@ -198,7 +198,7 @@ fun HashtagActionOptions(
|
|||||||
} else {
|
} else {
|
||||||
FollowButton {
|
FollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_follow,
|
R.string.login_with_a_private_key_to_be_able_to_follow,
|
||||||
)
|
)
|
||||||
|
@ -56,7 +56,7 @@ fun DisplayFollowUnfollowButton(
|
|||||||
if (isLoggedInFollowingUser) {
|
if (isLoggedInFollowingUser) {
|
||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_unfollow,
|
R.string.login_with_a_private_key_to_be_able_to_unfollow,
|
||||||
)
|
)
|
||||||
@ -68,7 +68,7 @@ fun DisplayFollowUnfollowButton(
|
|||||||
if (isUserFollowingLoggedIn) {
|
if (isUserFollowingLoggedIn) {
|
||||||
FollowButton(R.string.follow_back) {
|
FollowButton(R.string.follow_back) {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_follow,
|
R.string.login_with_a_private_key_to_be_able_to_follow,
|
||||||
)
|
)
|
||||||
@ -79,7 +79,7 @@ fun DisplayFollowUnfollowButton(
|
|||||||
} else {
|
} else {
|
||||||
FollowButton(R.string.follow) {
|
FollowButton(R.string.follow) {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_follow,
|
R.string.login_with_a_private_key_to_be_able_to_follow,
|
||||||
)
|
)
|
||||||
|
@ -130,7 +130,7 @@ fun DisplayLNAddress(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClose = { zapExpanded = false },
|
onClose = { zapExpanded = false },
|
||||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,7 +331,7 @@ fun MutedWordActionOptions(
|
|||||||
if (isMutedWord == true) {
|
if (isMutedWord == true) {
|
||||||
ShowWordButton {
|
ShowWordButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_show_word,
|
R.string.login_with_a_private_key_to_be_able_to_show_word,
|
||||||
)
|
)
|
||||||
@ -342,7 +342,7 @@ fun MutedWordActionOptions(
|
|||||||
} else {
|
} else {
|
||||||
HideWordButton {
|
HideWordButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_hide_word,
|
R.string.login_with_a_private_key_to_be_able_to_hide_word,
|
||||||
)
|
)
|
||||||
@ -393,7 +393,7 @@ private fun hideIfWritable(
|
|||||||
currentWordToAdd: MutableState<String>,
|
currentWordToAdd: MutableState<String>,
|
||||||
) {
|
) {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.read_only_user,
|
R.string.read_only_user,
|
||||||
R.string.login_with_a_private_key_to_be_able_to_hide_word,
|
R.string.login_with_a_private_key_to_be_able_to_hide_word,
|
||||||
)
|
)
|
||||||
|
@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
|||||||
class KeyPair(
|
class KeyPair(
|
||||||
privKey: ByteArray? = null,
|
privKey: ByteArray? = null,
|
||||||
pubKey: ByteArray? = null,
|
pubKey: ByteArray? = null,
|
||||||
|
forceReplacePubkey: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val privKey: ByteArray?
|
val privKey: ByteArray?
|
||||||
val pubKey: ByteArray
|
val pubKey: ByteArray
|
||||||
@ -44,7 +45,11 @@ class KeyPair(
|
|||||||
} else {
|
} else {
|
||||||
// as private key is provided, ignore the public key and set keys according to private key
|
// as private key is provided, ignore the public key and set keys according to private key
|
||||||
this.privKey = privKey
|
this.privKey = privKey
|
||||||
this.pubKey = Nip01.pubKeyCreate(privKey)
|
if (pubKey == null || forceReplacePubkey) {
|
||||||
|
this.pubKey = Nip01.pubKeyCreate(privKey)
|
||||||
|
} else {
|
||||||
|
this.pubKey = pubKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user