mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-05 14:24:38 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0b14edef6 | ||
|
|
aae2ffebd7 | ||
|
|
7b11d0ce81 | ||
|
|
26ba75c7c9 | ||
|
|
67fb3bf488 | ||
|
|
24b29396cb | ||
|
|
25c3532637 | ||
|
|
cff6ee457d | ||
|
|
c0ba6f5b00 | ||
|
|
44d8b8ff33 | ||
|
|
f495acb570 | ||
|
|
ecfc8bcd87 | ||
|
|
724bea6bbb | ||
|
|
6826db13a4 | ||
|
|
c5dd2c5651 | ||
|
|
236ff2d680 | ||
|
|
13e2546d36 | ||
|
|
549b9f53e5 | ||
|
|
974c022aed | ||
|
|
a57566dd84 | ||
|
|
72e86cf391 | ||
|
|
3b23cded56 | ||
|
|
121337eb83 | ||
|
|
fe87d642c9 | ||
|
|
b8112ff1aa | ||
|
|
1eee0bd1ae | ||
|
|
f48335e3be | ||
|
|
fcc4befb22 | ||
|
|
e28a1896ec | ||
|
|
f10e66e6a5 | ||
|
|
825a227e2d | ||
|
|
3446fdaf7f | ||
|
|
89de272ea1 |
@@ -16,8 +16,8 @@ android {
|
||||
applicationId "com.vitorpamplona.amethyst"
|
||||
minSdk libs.versions.android.minSdk.get().toInteger()
|
||||
targetSdk libs.versions.android.targetSdk.get().toInteger()
|
||||
versionCode 408
|
||||
versionName "0.92.3"
|
||||
versionCode 412
|
||||
versionName "0.92.7"
|
||||
buildConfigField "String", "RELEASE_NOTES_ID", "\"98f2acaf9b3eb70d87607942e576f9a086ac28adf37869da9a29f4815cecac86\""
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -103,6 +103,24 @@
|
||||
<data android:scheme="nostr+walletconnect" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="New Post">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="New Post">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="New Post">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
|
||||
@@ -140,7 +140,10 @@ class Amethyst : Application() {
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
println("Trim Memory $level")
|
||||
GlobalScope.launch(Dispatchers.Default) { serviceManager.trimMemory() }
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
println("Trim Memory Inside $level")
|
||||
serviceManager.trimMemory()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -357,7 +357,7 @@ class Account(
|
||||
mappedRelaySet.map { relay ->
|
||||
val nip65setup = nip65RelaySet?.firstOrNull { relay.url == it.relayUrl }
|
||||
if (nip65setup != null) {
|
||||
val write = nip65setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.BOTH || nip65setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.READ
|
||||
val write = nip65setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.BOTH || nip65setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.WRITE
|
||||
|
||||
RelaySetupInfo(
|
||||
relay.url,
|
||||
@@ -377,7 +377,7 @@ class Account(
|
||||
|
||||
nip65RelaySet?.forEach { newNip65Setup ->
|
||||
if (mappedRelaySet.none { it.url == newNip65Setup.relayUrl }) {
|
||||
val write = newNip65Setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.BOTH || newNip65Setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.READ
|
||||
val write = newNip65Setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.BOTH || newNip65Setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.WRITE
|
||||
|
||||
mappedRelaySet = mappedRelaySet +
|
||||
RelaySetupInfo(
|
||||
|
||||
+2
-1
@@ -154,8 +154,9 @@ class EventNotificationConsumer(
|
||||
acc: AccountSettings,
|
||||
) {
|
||||
if (
|
||||
event.createdAt > TimeUtils.fifteenMinutesAgo() &&
|
||||
// old event being re-broadcasted
|
||||
event.createdAt > TimeUtils.fifteenMinutesAgo() &&
|
||||
// don't display if it comes from me.
|
||||
event.pubKey != signer.pubKey
|
||||
) { // from the user
|
||||
Log.d(TAG, "Notifying")
|
||||
|
||||
@@ -97,6 +97,9 @@ import com.vitorpamplona.amethyst.ui.components.VideoView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ImageVideoDescription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.UserLine
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
|
||||
@@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.SearchIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchBarViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@@ -47,6 +47,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
|
||||
@@ -30,8 +30,10 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
@@ -40,13 +42,17 @@ import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -70,13 +76,18 @@ import androidx.compose.ui.window.DialogProperties
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.Nip96MediaServers
|
||||
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
||||
import com.vitorpamplona.amethyst.ui.components.VideoView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingSwitchItem
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.FileServersEvent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -84,6 +95,7 @@ import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NewMediaView(
|
||||
uri: Uri,
|
||||
@@ -118,9 +130,66 @@ fun NewMediaView(
|
||||
decorFitsSystemWindows = false,
|
||||
),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
SetDialogToEdgeToEdge()
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
|
||||
Box {
|
||||
IconButton(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
onClick = { showRelaysDialog = true },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.relays),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.height(25.dp),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PostButton(
|
||||
onPost = {
|
||||
onClose()
|
||||
postViewModel.upload(context, relayList, mediaQualitySlider) {
|
||||
accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it)
|
||||
}
|
||||
postViewModel.selectedServer?.let {
|
||||
if (!it.isNip95) {
|
||||
account.settings.changeDefaultFileServer(it.server)
|
||||
}
|
||||
}
|
||||
},
|
||||
isActive = postViewModel.canPost(),
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
Row {
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
CloseButton(
|
||||
onPress = {
|
||||
postViewModel.cancel()
|
||||
onClose()
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
colors =
|
||||
TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
),
|
||||
)
|
||||
},
|
||||
) { pad ->
|
||||
if (showRelaysDialog) {
|
||||
RelaySelectionDialog(
|
||||
preSelectedList = relayList,
|
||||
@@ -131,61 +200,21 @@ fun NewMediaView(
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
Surface(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 10.dp, end = 10.dp, top = 10.dp)
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.padding(pad)
|
||||
.consumeWindowInsets(pad)
|
||||
.imePadding(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CloseButton(
|
||||
onPress = {
|
||||
postViewModel.cancel()
|
||||
onClose()
|
||||
},
|
||||
)
|
||||
|
||||
Box {
|
||||
IconButton(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
onClick = { showRelaysDialog = true },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.relays),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.height(25.dp),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PostButton(
|
||||
onPost = {
|
||||
onClose()
|
||||
postViewModel.upload(context, relayList, mediaQualitySlider) {
|
||||
accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it)
|
||||
}
|
||||
postViewModel.selectedServer?.let {
|
||||
if (!it.isNip95) {
|
||||
account.settings.changeDefaultFileServer(it.server)
|
||||
}
|
||||
}
|
||||
},
|
||||
isActive = postViewModel.canPost(),
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().verticalScroll(scrollState),
|
||||
modifier = Modifier.fillMaxWidth().weight(1f).verticalScroll(scrollState),
|
||||
) {
|
||||
ImageVideoPost(postViewModel, accountViewModel)
|
||||
|
||||
@@ -194,7 +223,6 @@ fun NewMediaView(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
|
||||
.padding(vertical = 8.dp),
|
||||
) {
|
||||
Column(
|
||||
|
||||
@@ -981,6 +981,10 @@ open class NewPostViewModel : ViewModel() {
|
||||
draftTextChanges.trySend("")
|
||||
}
|
||||
|
||||
open fun addToMessage(it: String) {
|
||||
updateMessage(TextFieldValue(message.text + " " + it))
|
||||
}
|
||||
|
||||
open fun updateMessage(it: TextFieldValue) {
|
||||
message = it
|
||||
urlPreview = findUrlInMessage()
|
||||
|
||||
@@ -46,6 +46,8 @@ import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||
import com.vitorpamplona.amethyst.ui.actions.relays.RelayInformationDialog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
|
||||
+2
-2
@@ -55,11 +55,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.Nip96MediaServers
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.relays.SettingsCategoryWithButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
|
||||
+2
-2
@@ -42,10 +42,10 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
|
||||
+2
-2
@@ -42,10 +42,10 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
|
||||
+2
-2
@@ -49,11 +49,11 @@ import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.MinHorzSpacer
|
||||
|
||||
+1
-1
@@ -50,7 +50,6 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
@@ -61,6 +60,7 @@ import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
|
||||
@Composable
|
||||
fun SetDialogToEdgeToEdge() {
|
||||
val activityWindow = getActivityWindow()
|
||||
val dialogWindow = (LocalView.current.parent as? DialogWindowProvider)?.window
|
||||
val parentView = LocalView.current.parent as View
|
||||
SideEffect {
|
||||
if (activityWindow != null && dialogWindow != null) {
|
||||
val attributes = WindowManager.LayoutParams()
|
||||
attributes.copyFrom(activityWindow.attributes)
|
||||
attributes.type = dialogWindow.attributes.type
|
||||
dialogWindow.attributes = attributes
|
||||
parentView.layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
activityWindow.decorView.width,
|
||||
activityWindow.decorView.height,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.view.Window
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import com.vitorpamplona.amethyst.ui.navigation.getActivity
|
||||
|
||||
// Window utils
|
||||
@Composable
|
||||
fun getDialogWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window
|
||||
|
||||
@Composable
|
||||
fun getActivityWindow(): Window? = LocalView.current.context.getActivityWindow()
|
||||
|
||||
private tailrec fun Context.getActivityWindow(): Window? =
|
||||
when (this) {
|
||||
is Activity -> window
|
||||
is ContextWrapper -> baseContext.getActivityWindow()
|
||||
else -> null
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getActivity(): Activity? = LocalView.current.context.getActivity()
|
||||
|
||||
private tailrec fun Context.getActivity(): Activity? =
|
||||
when (this) {
|
||||
is Activity -> this
|
||||
is ContextWrapper -> baseContext.getActivity()
|
||||
else -> null
|
||||
}
|
||||
-27
@@ -20,11 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.util.Log
|
||||
import android.view.Window
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
@@ -62,7 +58,6 @@ import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import androidx.core.net.toUri
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import coil.compose.AsyncImage
|
||||
@@ -820,25 +815,3 @@ private fun HashVerificationSymbol(verifiedHash: Boolean) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Window utils
|
||||
@Composable
|
||||
fun getDialogWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window
|
||||
|
||||
@Composable fun getActivityWindow(): Window? = LocalView.current.context.getActivityWindow()
|
||||
|
||||
private tailrec fun Context.getActivityWindow(): Window? =
|
||||
when (this) {
|
||||
is Activity -> window
|
||||
is ContextWrapper -> baseContext.getActivityWindow()
|
||||
else -> null
|
||||
}
|
||||
|
||||
@Composable fun getActivity(): Activity? = LocalView.current.context.getActivity()
|
||||
|
||||
private tailrec fun Context.getActivity(): Activity? =
|
||||
when (this) {
|
||||
is Activity -> this
|
||||
is ContextWrapper -> baseContext.getActivity()
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -23,13 +23,17 @@ package com.vitorpamplona.amethyst.ui.navigation
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -52,6 +56,7 @@ import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRedirectScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NewPostScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarks.BookmarkListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChatroomListScreen
|
||||
@@ -270,15 +275,49 @@ fun AppNavigation(
|
||||
composable(
|
||||
Route.NIP47Setup.route,
|
||||
Route.NIP47Setup.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
enterTransition = { slideInVerticallyFromBottom },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
popExitTransition = { slideOutVerticallyToBottom },
|
||||
) {
|
||||
val nip47 = it.arguments?.getString("nip47")
|
||||
|
||||
NIP47SetupScreen(accountViewModel, nav, nip47)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.NewPost.route,
|
||||
Route.NewPost.arguments,
|
||||
enterTransition = { slideInVerticallyFromBottom },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutVerticallyToBottom },
|
||||
) {
|
||||
val draftMessage = it.arguments?.getString("message")?.ifBlank { null }
|
||||
val attachment =
|
||||
it.arguments?.getString("attachment")?.ifBlank { null }?.let {
|
||||
Uri.parse(it)
|
||||
}
|
||||
val baseReplyTo = it.arguments?.getString("baseReplyTo")
|
||||
val quote = it.arguments?.getString("quote")
|
||||
val fork = it.arguments?.getString("fork")
|
||||
val version = it.arguments?.getString("version")
|
||||
val draft = it.arguments?.getString("draft")
|
||||
val enableMessageInterface = it.arguments?.getBoolean("enableMessageInterface") ?: false
|
||||
|
||||
NewPostScreen(
|
||||
message = draftMessage,
|
||||
attachment = attachment,
|
||||
baseReplyTo = baseReplyTo?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
||||
quote = quote?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
||||
fork = fork?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
||||
version = version?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
||||
draft = draft?.let { hex -> accountViewModel.getNoteIfExists(hex) },
|
||||
enableMessageInterface = enableMessageInterface,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,88 +334,129 @@ private fun NavigateIfIntentRequested(
|
||||
accountStateViewModel: AccountStateViewModel,
|
||||
) {
|
||||
val activity = LocalContext.current.getActivity()
|
||||
var newAccount by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
var currentIntentNextPage by remember {
|
||||
mutableStateOf(
|
||||
activity.intent
|
||||
?.data
|
||||
?.toString()
|
||||
?.ifBlank { null },
|
||||
)
|
||||
}
|
||||
if (activity.intent.action == Intent.ACTION_SEND) {
|
||||
// avoids restarting the new Post screen when the intent is for the screen.
|
||||
// Microsoft's swift key sends Gifs as new actions
|
||||
if (isBaseRoute(nav.controller, Route.NewPost.base)) return
|
||||
|
||||
currentIntentNextPage?.let { intentNextPage ->
|
||||
var actionableNextPage by remember {
|
||||
mutableStateOf(uriToRoute(intentNextPage))
|
||||
// saves the intent to avoid processing again
|
||||
var message by remember {
|
||||
mutableStateOf(
|
||||
activity.intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||
it.ifBlank { null }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(intentNextPage) {
|
||||
if (actionableNextPage != null) {
|
||||
actionableNextPage?.let {
|
||||
val currentRoute = getRouteWithArguments(nav.controller)
|
||||
if (!isSameRoute(currentRoute, it)) {
|
||||
nav.newStack(it)
|
||||
}
|
||||
actionableNextPage = null
|
||||
}
|
||||
} else if (intentNextPage.contains("ncryptsec1")) {
|
||||
// login functions
|
||||
Nip19Bech32.tryParseAndClean(intentNextPage)?.let {
|
||||
newAccount = it
|
||||
}
|
||||
var media by remember {
|
||||
mutableStateOf(
|
||||
(activity.intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri),
|
||||
)
|
||||
}
|
||||
|
||||
actionableNextPage = null
|
||||
} else {
|
||||
accountViewModel.toast(
|
||||
R.string.invalid_nip19_uri,
|
||||
R.string.invalid_nip19_uri_description,
|
||||
intentNextPage,
|
||||
)
|
||||
nav.newStack(buildNewPostRoute(draftMessage = message, attachment = media))
|
||||
|
||||
media = null
|
||||
message = null
|
||||
} else {
|
||||
var newAccount by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
var currentIntentNextPage by remember {
|
||||
mutableStateOf(
|
||||
activity.intent
|
||||
?.data
|
||||
?.toString()
|
||||
?.ifBlank { null },
|
||||
)
|
||||
}
|
||||
|
||||
currentIntentNextPage?.let { intentNextPage ->
|
||||
var actionableNextPage by remember {
|
||||
mutableStateOf(uriToRoute(intentNextPage))
|
||||
}
|
||||
|
||||
currentIntentNextPage = null
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
DisposableEffect(nav, activity) {
|
||||
val consumer =
|
||||
Consumer<Intent> { intent ->
|
||||
val uri = intent.data?.toString()
|
||||
if (!uri.isNullOrBlank()) {
|
||||
// navigation functions
|
||||
val newPage = uriToRoute(uri)
|
||||
|
||||
if (newPage != null) {
|
||||
LaunchedEffect(intentNextPage) {
|
||||
if (actionableNextPage != null) {
|
||||
actionableNextPage?.let {
|
||||
val currentRoute = getRouteWithArguments(nav.controller)
|
||||
if (!isSameRoute(currentRoute, newPage)) {
|
||||
nav.newStack(newPage)
|
||||
if (!isSameRoute(currentRoute, it)) {
|
||||
nav.newStack(it)
|
||||
}
|
||||
} else if (uri.contains("ncryptsec")) {
|
||||
// login functions
|
||||
Nip19Bech32.tryParseAndClean(uri)?.let {
|
||||
newAccount = it
|
||||
actionableNextPage = null
|
||||
}
|
||||
} else if (intentNextPage.contains("ncryptsec1")) {
|
||||
// login functions
|
||||
Nip19Bech32.tryParseAndClean(intentNextPage)?.let {
|
||||
newAccount = it
|
||||
}
|
||||
|
||||
actionableNextPage = null
|
||||
} else {
|
||||
accountViewModel.toast(
|
||||
R.string.invalid_nip19_uri,
|
||||
R.string.invalid_nip19_uri_description,
|
||||
intentNextPage,
|
||||
)
|
||||
}
|
||||
|
||||
currentIntentNextPage = null
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
DisposableEffect(nav, activity) {
|
||||
val consumer =
|
||||
Consumer<Intent> { intent ->
|
||||
if (intent.action == Intent.ACTION_SEND) {
|
||||
// avoids restarting the new Post screen when the intent is for the screen.
|
||||
// Microsoft's swift key sends Gifs as new actions
|
||||
if (!isBaseRoute(nav.controller, Route.NewPost.base)) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||
nav.newStack(buildNewPostRoute(draftMessage = it))
|
||||
}
|
||||
|
||||
(intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri)?.let {
|
||||
nav.newStack(buildNewPostRoute(attachment = it))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
delay(1000)
|
||||
accountViewModel.toast(
|
||||
R.string.invalid_nip19_uri,
|
||||
R.string.invalid_nip19_uri_description,
|
||||
uri,
|
||||
)
|
||||
val uri = intent.data?.toString()
|
||||
if (!uri.isNullOrBlank()) {
|
||||
// navigation functions
|
||||
val newPage = uriToRoute(uri)
|
||||
|
||||
if (newPage != null) {
|
||||
val currentRoute = getRouteWithArguments(nav.controller)
|
||||
if (!isSameRoute(currentRoute, newPage)) {
|
||||
nav.newStack(newPage)
|
||||
}
|
||||
} else if (uri.contains("ncryptsec")) {
|
||||
// login functions
|
||||
Nip19Bech32.tryParseAndClean(uri)?.let {
|
||||
newAccount = it
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
delay(1000)
|
||||
accountViewModel.toast(
|
||||
R.string.invalid_nip19_uri,
|
||||
R.string.invalid_nip19_uri_description,
|
||||
uri,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.addOnNewIntentListener(consumer)
|
||||
onDispose { activity.removeOnNewIntentListener(consumer) }
|
||||
}
|
||||
activity.addOnNewIntentListener(consumer)
|
||||
onDispose { activity.removeOnNewIntentListener(consumer) }
|
||||
}
|
||||
|
||||
if (newAccount != null) {
|
||||
AddAccountDialog(newAccount, accountStateViewModel) { newAccount = null }
|
||||
if (newAccount != null) {
|
||||
AddAccountDialog(newAccount, accountStateViewModel) { newAccount = null }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,6 +484,9 @@ private fun isSameRoute(
|
||||
return false
|
||||
}
|
||||
|
||||
val slideInVerticallyFromBottom = slideInVertically(animationSpec = tween(), initialOffsetY = { it })
|
||||
val slideOutVerticallyToBottom = slideOutVertically(animationSpec = tween(), targetOffsetY = { it })
|
||||
|
||||
val slideInHorizontallyFromEnd = slideInHorizontally(animationSpec = tween(), initialOffsetX = { it })
|
||||
val slideOutHorizontallyToEnd = slideOutHorizontally(animationSpec = tween(), targetOffsetX = { it })
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.navigation
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Immutable
|
||||
@@ -39,6 +40,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.net.URLEncoder
|
||||
|
||||
@Immutable
|
||||
sealed class Route(
|
||||
@@ -220,8 +222,34 @@ sealed class Route(
|
||||
},
|
||||
).toImmutableList(),
|
||||
)
|
||||
|
||||
object NewPost :
|
||||
Route(
|
||||
route = "NewPost?message={message}&attachment={attachment}&baseReplyTo={baseReplyTo}"e={quote}&fork={fork}&version={version}&draft={draft}&enableMessageInterface={enableMessageInterface}",
|
||||
icon = R.drawable.ic_moments,
|
||||
arguments =
|
||||
listOf(
|
||||
navArgument("message") { type = NavType.StringType },
|
||||
navArgument("attachment") { type = NavType.StringType },
|
||||
navArgument("baseReplyTo") { type = NavType.StringType },
|
||||
navArgument("quote") { type = NavType.StringType },
|
||||
navArgument("fork") { type = NavType.StringType },
|
||||
navArgument("version") { type = NavType.StringType },
|
||||
navArgument("draft") { type = NavType.StringType },
|
||||
navArgument("enableMessageInterface") { type = NavType.BoolType },
|
||||
).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
fun isBaseRoute(
|
||||
navController: NavHostController,
|
||||
startsWith: String,
|
||||
): Boolean =
|
||||
navController.currentBackStackEntry
|
||||
?.destination
|
||||
?.route
|
||||
?.startsWith(startsWith) ?: false
|
||||
|
||||
fun getRouteWithArguments(navController: NavHostController): String? {
|
||||
val currentEntry = navController.currentBackStackEntry ?: return null
|
||||
return getRouteWithArguments(currentEntry.destination, currentEntry.arguments)
|
||||
@@ -270,3 +298,23 @@ private fun getRouteWithArguments(
|
||||
}
|
||||
return route
|
||||
}
|
||||
|
||||
fun buildNewPostRoute(
|
||||
draftMessage: String? = null,
|
||||
attachment: Uri? = null,
|
||||
baseReplyTo: String? = null,
|
||||
quote: String? = null,
|
||||
fork: String? = null,
|
||||
version: String? = null,
|
||||
draft: String? = null,
|
||||
enableMessageInterface: Boolean = false,
|
||||
): String =
|
||||
"NewPost?" +
|
||||
"message=${draftMessage?.let { URLEncoder.encode(it, "utf-8") } ?: ""}&" +
|
||||
"attachment=${attachment?.let { URLEncoder.encode(it.toString(), "utf-8") } ?: ""}&" +
|
||||
"baseReplyTo=${baseReplyTo ?: ""}&" +
|
||||
"quote=${quote ?: ""}&" +
|
||||
"fork=${fork ?: ""}&" +
|
||||
"version=${version ?: ""}&" +
|
||||
"draft=${draft ?: ""}&" +
|
||||
"enableMessageInterface=$enableMessageInterface"
|
||||
|
||||
@@ -85,10 +85,10 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.components.SelectTextDialog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@@ -201,24 +201,16 @@ fun NoteQuickActionMenu(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val editDraftDialog = remember { mutableStateOf(false) }
|
||||
|
||||
if (editDraftDialog.value) {
|
||||
NewPostView(
|
||||
onClose = {
|
||||
onDismiss()
|
||||
editDraftDialog.value = false
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
draft = note,
|
||||
nav = EmptyNav,
|
||||
)
|
||||
}
|
||||
|
||||
NoteQuickActionMenu(
|
||||
note = note,
|
||||
onDismiss = onDismiss,
|
||||
onWantsToEditDraft = { editDraftDialog.value = true },
|
||||
onWantsToEditDraft = {
|
||||
val route =
|
||||
buildNewPostRoute(
|
||||
draft = note.idHex,
|
||||
)
|
||||
nav.nav(route)
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@@ -59,41 +59,177 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||
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.mockVitorAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.BigPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
|
||||
import com.vitorpamplona.quartz.events.EmptyTagList
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
import com.vitorpamplona.quartz.events.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PollNotePreview() {
|
||||
val event =
|
||||
PollNoteEvent(
|
||||
id = "6ff9bc13d27490f6e3953325260bd996901a143de89886a0608c39e7d0160a72",
|
||||
pubKey = "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
createdAt = 1729186078,
|
||||
content = "Testing polls again",
|
||||
sig = "540101837a8826e2ae28401ee5f4fd8606def8501bec92a74a9e05264bb2c67558b927edf23bb085f5b5f0d91a61c65a25c37a3b92075bf10c9be03dbbe8e94e",
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("poll_option", "0", "OP1"),
|
||||
arrayOf("poll_option", "1", "OP2"),
|
||||
arrayOf("poll_option", "2", "OP3"),
|
||||
arrayOf("value_maximum", "2"),
|
||||
arrayOf("value_minimum", "2"),
|
||||
arrayOf("alt", "Poll event"),
|
||||
),
|
||||
)
|
||||
|
||||
val zapVote =
|
||||
LnZapEvent(
|
||||
id = "2a17fdcd0e387d1623c7313d7aa2848e18dde8a942cfe8a2d6b686ea5f68f01a",
|
||||
pubKey = "79f00d3f5a19ec806189fcab03c1be4ff81d18ee4f653c88fac41fe03570f432",
|
||||
createdAt = 1729186293,
|
||||
content = "Testing polls again",
|
||||
sig = "819cbd8daccd173bc411f71deb4dc0fd7d281797f7f0318d2fbc6c5076e4ebe0aa52c16fd113ca5fcc3ee133cd0c4a11c6d3f77e4fa957d5401da1aff07028d0",
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("p", "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a"),
|
||||
arrayOf("e", "6ff9bc13d27490f6e3953325260bd996901a143de89886a0608c39e7d0160a72"),
|
||||
arrayOf("P", "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"),
|
||||
arrayOf("bolt11", "lnbc20n1pn3zj05dqqnp4qtfc238rdkzsj26waa3l8zgag9damzltzsqcrlscj9gvpc7ch2qs2pp5nz0dn8hg5d8z5z9cgd3x8uhnv68hkgg03v3phqvdm26r0trcm27ssp5qxgfplfnfl9q6lh96stly8t90zgkyy4emlgdhadgp8n60eh8ghzs9qyysgqcqpcxqyz5vqrzjqvdnqyc82a9maxu6c7mee0shqr33u4z9z04wpdwhf96gxzpln8jcrapyqqqqqqp2rcqqqqlgqqqqqzsq2qrzjqw9fu4j39mycmg440ztkraa03u5qhtuc5zfgydsv6ml38qd4azymlapyqqqqqqqp9sqqqqlgqqqq86qqjqrzjq26922n6s5n5undqrf78rjjhgpcczafws45tx8237y7pzx3fg8wwxrgayyqq2mgqqqqqqqqqqqqqqqqq2qzkp9q0nyx5508kumhsa8c5x82c96nuccvszlmann6mzf7qvjagwsmmft3acvvqz5q92uf5et4yr53zcgmvphtg5xephcpe5lqle26kcp530mr3"),
|
||||
arrayOf("preimage", "a2ea9951a3ced7bc9d6099ce28a12f027bb1c2116106a6baf7001122c3e08891"),
|
||||
arrayOf("description", "{\"id\":\"c37b692b2bb23cd543b7643962d336fe3ae96a5330769ea047808b20f80b0c6f\",\"pubkey\":\"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c\",\"created_at\":1729186292,\"kind\":9734,\"tags\":[[\"e\",\"6ff9bc13d27490f6e3953325260bd996901a143de89886a0608c39e7d0160a72\"],[\"p\",\"f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a\"],[\"relays\",\"wss://nos.lol/\",\"wss://vitor.nostr1.com/\"],[\"alt\",\"Zap request\"],[\"poll_option\",\"0\"]],\"content\":\"\",\"sig\":\"83f2243e27d804bad13443937ddf71de7c049e85f71264eaf3a1b751a04844a65ec4bb5916722a1cdf9248f46dc5e03a92be9de543f03d022fb3e7ea08d1cf00\"}"),
|
||||
),
|
||||
)
|
||||
|
||||
val accountViewModel = mockVitorAccountViewModel()
|
||||
val nav = EmptyNav
|
||||
val baseNote: Note?
|
||||
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
LocalCache.justConsume(event, null)
|
||||
LocalCache.consume(zapVote.zapRequest!!)
|
||||
LocalCache.consume(zapVote, null)
|
||||
baseNote = LocalCache.getOrCreateNote("6ff9bc13d27490f6e3953325260bd996901a143de89886a0608c39e7d0160a72")
|
||||
}
|
||||
}
|
||||
|
||||
val color = MaterialTheme.colorScheme.background
|
||||
|
||||
if (baseNote != null) {
|
||||
ThemeComparisonColumn(
|
||||
toPreview = {
|
||||
Column(
|
||||
Modifier.padding(10.dp),
|
||||
) {
|
||||
PollNote(
|
||||
baseNote = baseNote,
|
||||
true,
|
||||
remember { mutableStateOf(color) },
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PollNotePreview2() {
|
||||
val event =
|
||||
PollNoteEvent(
|
||||
id = "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab",
|
||||
pubKey = "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
createdAt = 1729191389,
|
||||
content = "Test",
|
||||
sig = "fc7f83e4dc84082a9a473338d9df2fdb876c59fbc94d49fdbce1e0bfd85cc7d50b14d4f0d08e8a84ef46f99a4e889e261cc898e2caeca4a153d8925254c5a761",
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("poll_option", "0", "Pesquisa em portugues"),
|
||||
arrayOf("poll_option", "1", "Pesquisa em ingles"),
|
||||
arrayOf("value_maximum", "2"),
|
||||
arrayOf("value_minimum", "2"),
|
||||
arrayOf("alt", "Poll event"),
|
||||
),
|
||||
)
|
||||
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
val nav = EmptyNav
|
||||
val baseNote: Note?
|
||||
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
LocalCache.justConsume(event, null)
|
||||
baseNote = LocalCache.getOrCreateNote("3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab")
|
||||
}
|
||||
}
|
||||
|
||||
val color = MaterialTheme.colorScheme.background
|
||||
|
||||
if (baseNote != null) {
|
||||
ThemeComparisonColumn(
|
||||
toPreview = {
|
||||
Column(
|
||||
Modifier.padding(10.dp),
|
||||
) {
|
||||
PollNote(
|
||||
baseNote = baseNote,
|
||||
true,
|
||||
remember { mutableStateOf(color) },
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PollNote(
|
||||
baseNote: Note,
|
||||
@@ -202,6 +338,7 @@ private fun OptionNote(
|
||||
nonClickablePrepend = {},
|
||||
clickablePrepend = {
|
||||
RenderOptionBeforeVote(
|
||||
baseNote,
|
||||
poolOption.descriptor,
|
||||
canPreview,
|
||||
tags,
|
||||
@@ -241,6 +378,9 @@ private fun RenderOptionAfterVote(
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
color = color,
|
||||
gapSize = 0.dp,
|
||||
strokeCap = StrokeCap.Square,
|
||||
drawStopIndicator = {},
|
||||
progress = {
|
||||
poolOption.tally.value.toFloat()
|
||||
},
|
||||
@@ -291,6 +431,7 @@ private fun RenderOptionAfterVote(
|
||||
|
||||
@Composable
|
||||
private fun RenderOptionBeforeVote(
|
||||
baseNote: Note,
|
||||
description: String,
|
||||
canPreview: Boolean,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
@@ -308,17 +449,19 @@ private fun RenderOptionBeforeVote(
|
||||
QuoteBorder,
|
||||
),
|
||||
) {
|
||||
TranslatableRichTextViewer(
|
||||
content = description,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = 1,
|
||||
modifier = BigPadding,
|
||||
tags = tags,
|
||||
backgroundColor = backgroundColor,
|
||||
id = description,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
Column(BigPadding) {
|
||||
TranslatableRichTextViewer(
|
||||
content = description,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = 1,
|
||||
modifier = Modifier,
|
||||
tags = tags,
|
||||
backgroundColor = backgroundColor,
|
||||
id = baseNote.idHex + description,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -81,6 +80,7 @@ import androidx.compose.ui.Alignment.Companion.CenterStart
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.semantics.Role
|
||||
@@ -105,21 +105,22 @@ import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderReaction
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.DarkerGreen
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.Height24dpFilledModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Height4dpFilledModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.NoSoTinyBorders
|
||||
import com.vitorpamplona.amethyst.ui.theme.ReactionRowExpandButton
|
||||
@@ -141,7 +142,7 @@ import com.vitorpamplona.amethyst.ui.theme.TinyBorders
|
||||
import com.vitorpamplona.amethyst.ui.theme.defaultTweenDuration
|
||||
import com.vitorpamplona.amethyst.ui.theme.defaultTweenFloatSpec
|
||||
import com.vitorpamplona.amethyst.ui.theme.defaultTweenIntOffsetSpec
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.fundraiserProgressColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.reactionBox
|
||||
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
|
||||
@@ -348,22 +349,13 @@ fun RenderZapRaiser(
|
||||
}
|
||||
}
|
||||
|
||||
val color =
|
||||
if (zapraiserStatus.progress > 0.99) {
|
||||
DarkerGreen
|
||||
} else {
|
||||
MaterialTheme.colorScheme.mediumImportanceLink
|
||||
}
|
||||
|
||||
LinearProgressIndicator(
|
||||
modifier =
|
||||
remember(details) {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(if (details) 24.dp else 4.dp)
|
||||
},
|
||||
color = color,
|
||||
modifier = if (details) Height24dpFilledModifier else Height4dpFilledModifier,
|
||||
color = MaterialTheme.colorScheme.fundraiserProgressColor,
|
||||
progress = { zapraiserStatus.progress },
|
||||
gapSize = 0.dp,
|
||||
strokeCap = StrokeCap.Square,
|
||||
drawStopIndicator = {},
|
||||
)
|
||||
|
||||
if (details) {
|
||||
@@ -380,7 +372,7 @@ fun RenderZapRaiser(
|
||||
text =
|
||||
stringRes(id = R.string.sats_to_complete, totalPercentage, zapraiserStatus.left),
|
||||
modifier = NoSoTinyBorders,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
// color = MaterialTheme.colorScheme.placeholderText,
|
||||
fontSize = Font14SP,
|
||||
maxLines = 1,
|
||||
)
|
||||
@@ -569,14 +561,17 @@ private fun BoostWithDialog(
|
||||
var wantsToFork by remember { mutableStateOf<Note?>(null) }
|
||||
|
||||
if (wantsToQuote != null) {
|
||||
NewPostView(
|
||||
onClose = { wantsToQuote = null },
|
||||
baseReplyTo = null,
|
||||
quote = wantsToQuote,
|
||||
version = (editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow?.value,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
val route =
|
||||
buildNewPostRoute(
|
||||
quote = wantsToQuote?.idHex,
|
||||
version =
|
||||
(editState.value as? GenericLoadable.Loaded)
|
||||
?.loaded
|
||||
?.modificationToShow
|
||||
?.value
|
||||
?.idHex,
|
||||
)
|
||||
nav.nav(route)
|
||||
}
|
||||
|
||||
if (wantsToFork != null) {
|
||||
@@ -591,14 +586,19 @@ private fun BoostWithDialog(
|
||||
}
|
||||
}
|
||||
|
||||
NewPostView(
|
||||
onClose = { wantsToFork = null },
|
||||
baseReplyTo = replyTo,
|
||||
fork = wantsToFork,
|
||||
version = (editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow?.value,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
val route =
|
||||
buildNewPostRoute(
|
||||
quote = wantsToQuote?.idHex,
|
||||
baseReplyTo = replyTo?.idHex,
|
||||
fork = wantsToFork?.idHex,
|
||||
version =
|
||||
(editState.value as? GenericLoadable.Loaded)
|
||||
?.loaded
|
||||
?.modificationToShow
|
||||
?.value
|
||||
?.idHex,
|
||||
)
|
||||
nav.nav(route)
|
||||
}
|
||||
|
||||
BoostReaction(
|
||||
@@ -620,13 +620,12 @@ private fun ReplyReactionWithDialog(
|
||||
var wantsToReplyTo by remember { mutableStateOf<Note?>(null) }
|
||||
|
||||
if (wantsToReplyTo != null) {
|
||||
NewPostView(
|
||||
onClose = { wantsToReplyTo = null },
|
||||
baseReplyTo = wantsToReplyTo,
|
||||
quote = null,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
val route =
|
||||
buildNewPostRoute(
|
||||
baseReplyTo = wantsToReplyTo?.idHex,
|
||||
quote = null,
|
||||
)
|
||||
nav.nav(route)
|
||||
}
|
||||
|
||||
ReplyReaction(baseNote, grayTint, accountViewModel) { wantsToReplyTo = baseNote }
|
||||
@@ -1302,6 +1301,7 @@ private fun BoostTypeChoicePopup(
|
||||
alignment = Alignment.BottomCenter,
|
||||
offset = IntOffset(0, iconSizePx),
|
||||
onDismissRequest = { visibilityState.targetState = false },
|
||||
properties = PopupProperties(focusable = true),
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visibleState = visibilityState,
|
||||
|
||||
+2
-2
@@ -77,13 +77,13 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.service.firstFullChar
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@@ -86,9 +86,9 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.getFragmentActivity
|
||||
|
||||
@@ -73,8 +73,8 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@@ -61,13 +61,13 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@@ -43,10 +43,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.EditPostView
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
@@ -124,11 +124,6 @@ fun NoteDropDownMenu(
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val wantsToEditDraft =
|
||||
remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
if (wantsToEditPost.value) {
|
||||
// avoids changing while drafting a note and a new event shows up.
|
||||
val versionLookingAt =
|
||||
@@ -148,18 +143,6 @@ fun NoteDropDownMenu(
|
||||
)
|
||||
}
|
||||
|
||||
if (wantsToEditDraft.value) {
|
||||
NewPostView(
|
||||
onClose = {
|
||||
onDismiss()
|
||||
wantsToEditDraft.value = false
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
draft = note,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = true,
|
||||
onDismissRequest = onDismiss,
|
||||
@@ -243,7 +226,11 @@ fun NoteDropDownMenu(
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringRes(R.string.edit_draft)) },
|
||||
onClick = {
|
||||
wantsToEditDraft.value = true
|
||||
val route =
|
||||
buildNewPostRoute(
|
||||
draft = note.idHex,
|
||||
)
|
||||
nav.nav(route)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+18
@@ -1699,3 +1699,21 @@ fun mockAccountViewModel(): AccountViewModel {
|
||||
sharedPreferencesViewModel.sharedPrefs,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun mockVitorAccountViewModel(): AccountViewModel {
|
||||
val sharedPreferencesViewModel: SharedPreferencesViewModel = viewModel()
|
||||
sharedPreferencesViewModel.init()
|
||||
|
||||
return AccountViewModel(
|
||||
AccountSettings(
|
||||
// blank keys
|
||||
keyPair =
|
||||
KeyPair(
|
||||
pubKey = Hex.decode("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"),
|
||||
forcePubKeyCheck = false,
|
||||
),
|
||||
),
|
||||
sharedPreferencesViewModel.sharedPrefs,
|
||||
)
|
||||
}
|
||||
|
||||
+318
-293
@@ -18,12 +18,14 @@
|
||||
* 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.actions
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import android.util.Size
|
||||
import android.widget.Toast
|
||||
@@ -42,7 +44,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -119,9 +121,7 @@ import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
@@ -134,13 +134,23 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.Nip96MediaServers
|
||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPollOption
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPollVoteValueRange
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.RelaySelectionDialog
|
||||
import com.vitorpamplona.amethyst.ui.actions.ServerOption
|
||||
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||
import com.vitorpamplona.amethyst.ui.actions.getPhotoUri
|
||||
import com.vitorpamplona.amethyst.ui.components.BechLink
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.InvoiceRequest
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview
|
||||
import com.vitorpamplona.amethyst.ui.components.VideoView
|
||||
import com.vitorpamplona.amethyst.ui.components.ZapRaiserRequest
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.Nav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
||||
@@ -150,9 +160,6 @@ import com.vitorpamplona.amethyst.ui.note.PollIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.RegularPostIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapSplitIcon
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.MyTextField
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ShowUserSuggestionList
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@@ -190,8 +197,9 @@ import java.lang.Math.round
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun NewPostView(
|
||||
onClose: () -> Unit,
|
||||
fun NewPostScreen(
|
||||
message: String? = null,
|
||||
attachment: Uri? = null,
|
||||
baseReplyTo: Note? = null,
|
||||
quote: Note? = null,
|
||||
fork: Note? = null,
|
||||
@@ -199,18 +207,19 @@ fun NewPostView(
|
||||
draft: Note? = null,
|
||||
enableMessageInterface: Boolean = false,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
nav: Nav,
|
||||
) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val postViewModel: NewPostViewModel = viewModel()
|
||||
postViewModel.wantsDirectMessage = enableMessageInterface
|
||||
|
||||
val context = LocalContext.current
|
||||
val activity = context.getActivity()
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
val scope = rememberCoroutineScope()
|
||||
var showRelaysDialog by remember { mutableStateOf(false) }
|
||||
var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() }
|
||||
|
||||
var showCamera by remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
@@ -229,6 +238,12 @@ fun NewPostView(
|
||||
LaunchedEffect(Unit) {
|
||||
launch(Dispatchers.IO) {
|
||||
postViewModel.load(accountViewModel, baseReplyTo, quote, fork, version, draft)
|
||||
message?.ifBlank { null }?.let {
|
||||
postViewModel.updateMessage(TextFieldValue(it))
|
||||
}
|
||||
attachment?.let {
|
||||
postViewModel.selectImage(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,20 +256,84 @@ fun NewPostView(
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
scope.launch {
|
||||
postViewModel.sendDraftSync(relayList = relayList)
|
||||
onClose()
|
||||
DisposableEffect(nav, activity) {
|
||||
// Microsoft's swift key sends Gifs as new actions
|
||||
|
||||
val consumer =
|
||||
Consumer<Intent> { intent ->
|
||||
if (intent.action == Intent.ACTION_SEND) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.ifBlank { null }?.let {
|
||||
postViewModel.addToMessage(it)
|
||||
}
|
||||
|
||||
(intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri)?.let {
|
||||
postViewModel.selectImage(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity.addOnNewIntentListener(consumer)
|
||||
onDispose { activity.removeOnNewIntentListener(consumer) }
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
|
||||
Box {
|
||||
IconButton(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
onClick = { showRelaysDialog = true },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.relays),
|
||||
contentDescription = stringRes(id = R.string.relay_list_selector),
|
||||
modifier = Modifier.height(25.dp),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
PostButton(
|
||||
onPost = {
|
||||
postViewModel.sendPost(relayList = relayList)
|
||||
scope.launch {
|
||||
delay(100)
|
||||
nav.popBack()
|
||||
}
|
||||
},
|
||||
isActive = postViewModel.canPost(),
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
Row {
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
CloseButton(
|
||||
onPress = {
|
||||
scope.launch {
|
||||
postViewModel.sendDraftSync(relayList = relayList)
|
||||
postViewModel.cancel()
|
||||
delay(100)
|
||||
nav.popBack()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
colors =
|
||||
TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
),
|
||||
)
|
||||
},
|
||||
properties =
|
||||
DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnClickOutside = false,
|
||||
decorFitsSystemWindows = false,
|
||||
),
|
||||
) {
|
||||
) { pad ->
|
||||
if (showRelaysDialog) {
|
||||
RelaySelectionDialog(
|
||||
preSelectedList = relayList,
|
||||
@@ -264,308 +343,254 @@ fun NewPostView(
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
|
||||
Box {
|
||||
IconButton(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
onClick = { showRelaysDialog = true },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.relays),
|
||||
contentDescription = stringRes(id = R.string.relay_list_selector),
|
||||
modifier = Modifier.height(25.dp),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
PostButton(
|
||||
onPost = {
|
||||
postViewModel.sendPost(relayList = relayList)
|
||||
scope.launch {
|
||||
delay(100)
|
||||
onClose()
|
||||
}
|
||||
},
|
||||
isActive = postViewModel.canPost(),
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
Row {
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
CloseButton(
|
||||
onPress = {
|
||||
scope.launch {
|
||||
postViewModel.sendDraftSync(relayList = relayList)
|
||||
postViewModel.cancel()
|
||||
delay(100)
|
||||
onClose()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
colors =
|
||||
TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
),
|
||||
)
|
||||
},
|
||||
) { pad ->
|
||||
Surface(
|
||||
Surface(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(pad)
|
||||
.consumeWindowInsets(pad)
|
||||
.imePadding(),
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(
|
||||
start = Size10dp,
|
||||
top = pad.calculateTopPadding(),
|
||||
end = Size10dp,
|
||||
bottom = pad.calculateBottomPadding(),
|
||||
).fillMaxSize(),
|
||||
Modifier.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(),
|
||||
.padding(
|
||||
start = Size10dp,
|
||||
end = Size10dp,
|
||||
).weight(1f),
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.imePadding()
|
||||
.weight(1f),
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(scrollState),
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(scrollState),
|
||||
postViewModel.originalNote?.let {
|
||||
Row {
|
||||
NoteCompose(
|
||||
baseNote = it,
|
||||
modifier = MaterialTheme.colorScheme.replyModifier,
|
||||
isQuotedNote = true,
|
||||
unPackReply = false,
|
||||
makeItShort = true,
|
||||
quotesLeft = 1,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
Notifying(postViewModel.pTags?.toImmutableList()) {
|
||||
postViewModel.removeFromReplyList(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsDirectMessage) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
postViewModel.originalNote?.let {
|
||||
Row(Modifier.heightIn(max = 200.dp)) {
|
||||
NoteCompose(
|
||||
baseNote = it,
|
||||
modifier = MaterialTheme.colorScheme.replyModifier,
|
||||
isQuotedNote = true,
|
||||
unPackReply = false,
|
||||
makeItShort = true,
|
||||
quotesLeft = 1,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
SendDirectMessageTo(postViewModel = postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsProduct) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
SellProduct(postViewModel = postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = Size10dp),
|
||||
) {
|
||||
BaseUserPicture(
|
||||
accountViewModel.userProfile(),
|
||||
Size35dp,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
MessageField(postViewModel)
|
||||
}
|
||||
|
||||
if (postViewModel.wantsPoll) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
PollField(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
val myUrlPreview = postViewModel.urlPreview
|
||||
if (myUrlPreview != null) {
|
||||
Row(modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||
if (RichTextParser.isValidURL(myUrlPreview)) {
|
||||
if (RichTextParser.isImageUrl(myUrlPreview)) {
|
||||
AsyncImage(
|
||||
model = myUrlPreview,
|
||||
contentDescription = myUrlPreview,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colorScheme.subtleBorder,
|
||||
QuoteBorder,
|
||||
),
|
||||
)
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
} else if (RichTextParser.isVideoUrl(myUrlPreview)) {
|
||||
VideoView(
|
||||
myUrlPreview,
|
||||
mimeType = null,
|
||||
roundedCorner = true,
|
||||
isFiniteHeight = false,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
} else {
|
||||
LoadUrlPreview(myUrlPreview, myUrlPreview, null, accountViewModel)
|
||||
}
|
||||
}
|
||||
} else if (RichTextParser.startsWithNIP19Scheme(myUrlPreview)) {
|
||||
val bgColor = MaterialTheme.colorScheme.background
|
||||
val backgroundColor = remember { mutableStateOf(bgColor) }
|
||||
|
||||
Row {
|
||||
Notifying(postViewModel.pTags?.toImmutableList()) {
|
||||
postViewModel.removeFromReplyList(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsDirectMessage) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
SendDirectMessageTo(postViewModel = postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsProduct) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
SellProduct(postViewModel = postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = Size10dp),
|
||||
) {
|
||||
BaseUserPicture(
|
||||
accountViewModel.userProfile(),
|
||||
Size35dp,
|
||||
BechLink(
|
||||
word = myUrlPreview,
|
||||
canPreview = true,
|
||||
quotesLeft = 1,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
MessageField(postViewModel)
|
||||
} else if (RichTextParser.isUrlWithoutScheme(myUrlPreview)) {
|
||||
LoadUrlPreview("https://$myUrlPreview", myUrlPreview, null, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsPoll) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
PollField(postViewModel)
|
||||
}
|
||||
}
|
||||
if (postViewModel.wantsToMarkAsSensitive) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
ContentSensitivityExplainer(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
val myUrlPreview = postViewModel.urlPreview
|
||||
if (myUrlPreview != null) {
|
||||
Row(modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||
if (RichTextParser.isValidURL(myUrlPreview)) {
|
||||
if (RichTextParser.isImageUrl(myUrlPreview)) {
|
||||
AsyncImage(
|
||||
model = myUrlPreview,
|
||||
contentDescription = myUrlPreview,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colorScheme.subtleBorder,
|
||||
QuoteBorder,
|
||||
),
|
||||
)
|
||||
} else if (RichTextParser.isVideoUrl(myUrlPreview)) {
|
||||
VideoView(
|
||||
myUrlPreview,
|
||||
mimeType = null,
|
||||
roundedCorner = true,
|
||||
isFiniteHeight = false,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
} else {
|
||||
LoadUrlPreview(myUrlPreview, myUrlPreview, null, accountViewModel)
|
||||
}
|
||||
} else if (RichTextParser.startsWithNIP19Scheme(myUrlPreview)) {
|
||||
val bgColor = MaterialTheme.colorScheme.background
|
||||
val backgroundColor = remember { mutableStateOf(bgColor) }
|
||||
if (postViewModel.wantsToAddGeoHash) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
LocationAsHash(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
BechLink(
|
||||
word = myUrlPreview,
|
||||
canPreview = true,
|
||||
quotesLeft = 1,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
} else if (RichTextParser.isUrlWithoutScheme(myUrlPreview)) {
|
||||
LoadUrlPreview("https://$myUrlPreview", myUrlPreview, null, accountViewModel)
|
||||
if (postViewModel.wantsForwardZapTo) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp),
|
||||
) {
|
||||
FowardZapTo(postViewModel, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
val url = postViewModel.contentToAddUrl
|
||||
if (url != null) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
ImageVideoDescription(
|
||||
url,
|
||||
accountViewModel.account.settings.defaultFileServer,
|
||||
onAdd = { alt, server, sensitiveContent, mediaQuality ->
|
||||
postViewModel.upload(url, alt, sensitiveContent, mediaQuality, false, server, accountViewModel::toast, context)
|
||||
if (!server.isNip95) {
|
||||
accountViewModel.account.settings.changeDefaultFileServer(server.server)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onCancel = { postViewModel.contentToAddUrl = null },
|
||||
onError = { scope.launch { Toast.makeText(context, context.resources.getText(it), Toast.LENGTH_SHORT).show() } },
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsToMarkAsSensitive) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
ContentSensitivityExplainer(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsToAddGeoHash) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
LocationAsHash(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsForwardZapTo) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp),
|
||||
) {
|
||||
FowardZapTo(postViewModel, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
val url = postViewModel.contentToAddUrl
|
||||
if (url != null) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
ImageVideoDescription(
|
||||
url,
|
||||
accountViewModel.account.settings.defaultFileServer,
|
||||
onAdd = { alt, server, sensitiveContent, mediaQuality ->
|
||||
postViewModel.upload(url, alt, sensitiveContent, mediaQuality, false, server, accountViewModel::toast, context)
|
||||
if (!server.isNip95) {
|
||||
accountViewModel.account.settings.changeDefaultFileServer(server.server)
|
||||
}
|
||||
if (postViewModel.wantsInvoice) {
|
||||
postViewModel.lnAddress()?.let { lud16 ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
InvoiceRequest(
|
||||
lud16,
|
||||
accountViewModel.account.userProfile().pubkeyHex,
|
||||
accountViewModel,
|
||||
stringRes(id = R.string.lightning_invoice),
|
||||
stringRes(id = R.string.lightning_create_and_add_invoice),
|
||||
onSuccess = {
|
||||
postViewModel.insertAtCursor(it)
|
||||
postViewModel.wantsInvoice = false
|
||||
},
|
||||
onCancel = { postViewModel.contentToAddUrl = null },
|
||||
onError = { scope.launch { Toast.makeText(context, context.resources.getText(it), Toast.LENGTH_SHORT).show() } },
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsInvoice) {
|
||||
postViewModel.lnAddress()?.let { lud16 ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
InvoiceRequest(
|
||||
lud16,
|
||||
accountViewModel.account.userProfile().pubkeyHex,
|
||||
accountViewModel,
|
||||
stringRes(id = R.string.lightning_invoice),
|
||||
stringRes(id = R.string.lightning_create_and_add_invoice),
|
||||
onSuccess = {
|
||||
postViewModel.insertAtCursor(it)
|
||||
postViewModel.wantsInvoice = false
|
||||
},
|
||||
onClose = { postViewModel.wantsInvoice = false },
|
||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsZapraiser && postViewModel.hasLnAddress()) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
ZapRaiserRequest(
|
||||
stringRes(id = R.string.zapraiser),
|
||||
postViewModel,
|
||||
onClose = { postViewModel.wantsInvoice = false },
|
||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShowUserSuggestionList(
|
||||
postViewModel,
|
||||
accountViewModel,
|
||||
modifier = Modifier.heightIn(0.dp, 300.dp),
|
||||
)
|
||||
|
||||
BottomRowActions(postViewModel)
|
||||
if (postViewModel.wantsZapraiser && postViewModel.hasLnAddress()) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
ZapRaiserRequest(
|
||||
stringRes(id = R.string.zapraiser),
|
||||
postViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShowUserSuggestionList(
|
||||
postViewModel,
|
||||
accountViewModel,
|
||||
modifier = Modifier.heightIn(0.dp, 300.dp),
|
||||
)
|
||||
|
||||
BottomRowActions(postViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dialog(
|
||||
// onDismissRequest = {
|
||||
// scope.launch {
|
||||
// postViewModel.sendDraftSync(relayList = relayList)
|
||||
// onClose()
|
||||
// }
|
||||
// },
|
||||
// properties =
|
||||
// DialogProperties(
|
||||
// usePlatformDefaultWidth = false,
|
||||
// dismissOnClickOutside = false,
|
||||
// decorFitsSystemWindows = false,
|
||||
// ),
|
||||
// ) {
|
||||
// }
|
||||
}
|
||||
|
||||
@Composable
|
||||
+6
-15
@@ -49,8 +49,8 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||
@@ -63,25 +63,12 @@ fun ChannelFabColumn(
|
||||
) {
|
||||
var isOpen by remember { mutableStateOf(false) }
|
||||
|
||||
var wantsToSendNewMessage by remember { mutableStateOf(false) }
|
||||
|
||||
var wantsToCreateChannel by remember { mutableStateOf(false) }
|
||||
|
||||
if (wantsToCreateChannel) {
|
||||
NewChannelView({ wantsToCreateChannel = false }, accountViewModel = accountViewModel)
|
||||
}
|
||||
|
||||
if (wantsToSendNewMessage) {
|
||||
NewPostView(
|
||||
onClose = { wantsToSendNewMessage = false },
|
||||
enableMessageInterface = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
// JoinUserOrChannelView({ wantsToJoinChannelOrUser = false }, accountViewModel =
|
||||
// accountViewModel, nav = nav)
|
||||
}
|
||||
|
||||
Column {
|
||||
AnimatedVisibility(
|
||||
visible = isOpen,
|
||||
@@ -91,7 +78,11 @@ fun ChannelFabColumn(
|
||||
Column {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
wantsToSendNewMessage = true
|
||||
val route =
|
||||
buildNewPostRoute(
|
||||
enableMessageInterface = true,
|
||||
)
|
||||
nav.nav(route)
|
||||
isOpen = false
|
||||
},
|
||||
modifier = Size55Modifier,
|
||||
|
||||
+3
-3
@@ -85,10 +85,8 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.ServerOption
|
||||
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||
@@ -107,7 +105,9 @@ import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMs
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMsAndDisplayIfNotFound
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.UserLine
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight
|
||||
@@ -387,7 +387,7 @@ fun PrepareChatroomViewModels(
|
||||
}
|
||||
|
||||
if (draftMessage != null) {
|
||||
LaunchedEffect(key1 = draftMessage) { newPostModel.message = TextFieldValue(draftMessage) }
|
||||
LaunchedEffect(key1 = draftMessage) { newPostModel.updateMessage(TextFieldValue(draftMessage)) }
|
||||
}
|
||||
|
||||
ChatroomScreen(
|
||||
|
||||
+8
-10
@@ -27,8 +27,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -36,9 +34,9 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||
@@ -60,14 +58,14 @@ fun NewCommunityNoteButton(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
var wantsToPost by remember { mutableStateOf(false) }
|
||||
|
||||
if (wantsToPost) {
|
||||
NewPostView({ wantsToPost = false }, note, accountViewModel = accountViewModel, nav = nav)
|
||||
}
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = { wantsToPost = true },
|
||||
onClick = {
|
||||
val route =
|
||||
buildNewPostRoute(
|
||||
baseReplyTo = note.idHex,
|
||||
)
|
||||
nav.nav(route)
|
||||
},
|
||||
modifier = Size55Modifier,
|
||||
shape = CircleShape,
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
|
||||
+5
-10
@@ -27,16 +27,14 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||
@@ -46,14 +44,11 @@ fun NewNoteButton(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
var wantsToPost by remember { mutableStateOf(false) }
|
||||
|
||||
if (wantsToPost) {
|
||||
NewPostView({ wantsToPost = false }, accountViewModel = accountViewModel, nav = nav)
|
||||
}
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = { wantsToPost = true },
|
||||
onClick = {
|
||||
val route = buildNewPostRoute()
|
||||
nav.nav(route)
|
||||
},
|
||||
modifier = Size55Modifier,
|
||||
shape = CircleShape,
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
|
||||
+1
-1
@@ -33,13 +33,13 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.rememberHeightDecreaser
|
||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountContent
|
||||
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
+27
-22
@@ -57,7 +57,6 @@ import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
||||
@@ -72,6 +71,7 @@ import com.vitorpamplona.amethyst.ui.feeds.rememberForeverPagerState
|
||||
import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.BoostReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport
|
||||
@@ -393,29 +393,29 @@ fun ReactionsColumn(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
var wantsToReplyTo by remember { mutableStateOf<Note?>(null) }
|
||||
// var wantsToReplyTo by remember { mutableStateOf<Note?>(null) }
|
||||
|
||||
var wantsToQuote by remember { mutableStateOf<Note?>(null) }
|
||||
// var wantsToQuote by remember { mutableStateOf<Note?>(null) }
|
||||
|
||||
if (wantsToReplyTo != null) {
|
||||
NewPostView(
|
||||
onClose = { wantsToReplyTo = null },
|
||||
baseReplyTo = wantsToReplyTo,
|
||||
quote = null,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
// if (wantsToReplyTo != null) {
|
||||
// NewPostView(
|
||||
// onClose = { wantsToReplyTo = null },
|
||||
// baseReplyTo = wantsToReplyTo,
|
||||
// quote = null,
|
||||
// accountViewModel = accountViewModel,
|
||||
// nav = nav,
|
||||
// )
|
||||
// }
|
||||
|
||||
if (wantsToQuote != null) {
|
||||
NewPostView(
|
||||
onClose = { wantsToQuote = null },
|
||||
baseReplyTo = null,
|
||||
quote = wantsToQuote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
// if (wantsToQuote != null) {
|
||||
// NewPostView(
|
||||
// onClose = { wantsToQuote = null },
|
||||
// baseReplyTo = null,
|
||||
// quote = wantsToQuote,
|
||||
// accountViewModel = accountViewModel,
|
||||
// nav = nav,
|
||||
// )
|
||||
// }
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
@@ -439,7 +439,12 @@ fun ReactionsColumn(
|
||||
iconSizeModifier = Size40Modifier,
|
||||
iconSize = Size40dp,
|
||||
onQuotePress = {
|
||||
wantsToQuote = baseNote
|
||||
// wantsToQuote = baseNote
|
||||
val route =
|
||||
buildNewPostRoute(
|
||||
quote = baseNote.idHex,
|
||||
)
|
||||
nav.nav(route)
|
||||
},
|
||||
onForkPress = {
|
||||
},
|
||||
|
||||
@@ -53,3 +53,6 @@ val DarkWarningColor = Color(0xFFF8DE22)
|
||||
|
||||
val LightAllGoodColor = Color(0xFF339900)
|
||||
val DarkAllGoodColor = Color(0xFF99cc33)
|
||||
|
||||
val LightFundraiserProgressColor = Color(0xFF3DB601)
|
||||
val DarkFundraiserProgressColor = Color(0xFF61A229)
|
||||
|
||||
@@ -178,6 +178,9 @@ val UserNameMaxRowHeight = Modifier.fillMaxWidth()
|
||||
val Height24dpModifier = Modifier.height(24.dp)
|
||||
val Height4dpModifier = Modifier.height(4.dp)
|
||||
|
||||
val Height24dpFilledModifier = Modifier.fillMaxWidth().height(24.dp)
|
||||
val Height4dpFilledModifier = Modifier.fillMaxWidth().height(4.dp)
|
||||
|
||||
val AccountPictureModifier = Modifier.size(55.dp).clip(shape = CircleShape)
|
||||
val HeaderPictureModifier = Modifier.size(34.dp).clip(shape = CircleShape)
|
||||
|
||||
|
||||
@@ -397,6 +397,9 @@ val ColorScheme.warningColor: Color
|
||||
val ColorScheme.allGoodColor: Color
|
||||
get() = if (isLight) LightAllGoodColor else DarkAllGoodColor
|
||||
|
||||
val ColorScheme.fundraiserProgressColor: Color
|
||||
get() = if (isLight) LightFundraiserProgressColor else DarkFundraiserProgressColor
|
||||
|
||||
val ColorScheme.markdownStyle: RichTextStyle
|
||||
get() = if (isLight) MarkDownStyleOnLight else MarkDownStyleOnDark
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@@ -23,4 +23,14 @@ Prijavi se s privatnim ključem za všečkanje sporočila</string>
|
||||
<string name="ui_feature_set_type_complete">Končaj</string>
|
||||
<string name="mute_button">Zvok je prižgan. Klikni da ga utišaš</string>
|
||||
<string name="relay_information_document_error_failed_to_reach_server">Neuspela povezava z %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_failed_to_parse_response">Neuspešna analiza odgovora %1$s: %2$s</string>
|
||||
<string name="cashu_failed_redemption">Cashu-ja ni bilo mogoče unovčiti</string>
|
||||
<string name="cashu_no_wallet_found">Na sistemu ni najdene združljive Cashu denarnice</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">Prejemnikova \"lightning\" storitev na %1$s ni na voljo. Izračunana je bila iz \"lightning\" naslova \'%2$s\'. Napaka: %3$s. Preverite, ali strežnik deluje in ali je \"lightning\" naslov pravilen</string>
|
||||
<string name="error_parsing_json_from_lightning_address_s_invoice_fetch_check_the_user_s_lightning_setup_with_user">Napaka pri razčlenjevanju JSON med pridobivanjem fakture iz %1$s. Preverite uporabnikove nastavitve za \"Lightning\"</string>
|
||||
<string name="select_push_server">Izberi \"UnifiedPush\" aplikacijo</string>
|
||||
<string name="push_server_install_app_description"> Za prejemanje potisnih obvestil namestite aplikacijo, ki podpira [Unified Push](https://unifiedpush.org/), na primer [Ntfy](https://ntfy.sh/).
|
||||
Po namestitvi izberite aplikacijo, ki jo želite uporabljati, v nastavitvah.
|
||||
</string>
|
||||
<string name="http_status_416">Razpon ni zadovoljiv – Strežnik ne more izpolniti vrednosti, navedene v \"request’s Range header\" polju. </string>
|
||||
</resources>
|
||||
|
||||
@@ -520,6 +520,7 @@
|
||||
<string name="light">浅色</string>
|
||||
<string name="dark">深色</string>
|
||||
<string name="application_preferences">应用程序首选项</string>
|
||||
<string name="wallet_connect">钱包绑定</string>
|
||||
<string name="language">语言</string>
|
||||
<string name="theme">主题</string>
|
||||
<string name="automatically_load_images_gifs">图像预览</string>
|
||||
|
||||
+2
@@ -62,6 +62,7 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -212,6 +213,7 @@ private fun TranslationMessage(
|
||||
MaterialTheme.colorScheme.onSurface.copy(
|
||||
alpha = 0.32f,
|
||||
),
|
||||
fontSize = Font14SP,
|
||||
),
|
||||
overflow = TextOverflow.Visible,
|
||||
maxLines = 3,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
|
||||
class UrlDecoderTest {
|
||||
val uri = "content://com.google.android.apps.photos.contentprovider/0/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F1000023553/REQUIRE_ORIGINAL/NONE/image%2Fjpeg/913263593"
|
||||
|
||||
@Test
|
||||
fun testRecursiveDecoding() {
|
||||
val encoded = URLEncoder.encode(uri, "utf-8")
|
||||
assertEquals("content%3A%2F%2Fcom.google.android.apps.photos.contentprovider%2F0%2F1%2Fcontent%253A%252F%252Fmedia%252Fexternal%252Fimages%252Fmedia%252F1000023553%2FREQUIRE_ORIGINAL%2FNONE%2Fimage%252Fjpeg%2F913263593", encoded)
|
||||
|
||||
val decoded = URLDecoder.decode(encoded, "utf-8")
|
||||
assertEquals(uri, decoded)
|
||||
}
|
||||
}
|
||||
@@ -393,17 +393,16 @@ class Relay(
|
||||
}
|
||||
|
||||
if (relayFilters.isNotEmpty()) {
|
||||
val request =
|
||||
writeToSocket(
|
||||
relayFilters.joinToStringLimited(
|
||||
separator = ",",
|
||||
limit = 20,
|
||||
limit = 19,
|
||||
prefix = """["REQ","$requestId",""",
|
||||
postfix = "]",
|
||||
) {
|
||||
it.filter.toJson(url)
|
||||
}
|
||||
|
||||
writeToSocket(request)
|
||||
},
|
||||
)
|
||||
|
||||
afterEOSEPerSubscription[requestId] = false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[versions]
|
||||
accompanistAdaptive = "0.34.0"
|
||||
activityCompose = "1.9.2"
|
||||
activityCompose = "1.9.3"
|
||||
agp = "8.7.1"
|
||||
android-compileSdk = "35"
|
||||
android-minSdk = "26"
|
||||
@@ -9,12 +9,12 @@ androidKotlinGeohash = "1.0"
|
||||
androidxJunit = "1.2.1"
|
||||
appcompat = "1.7.0"
|
||||
audiowaveform = "1.1.1"
|
||||
benchmark = "1.3.2"
|
||||
benchmarkJunit4 = "1.3.2"
|
||||
benchmark = "1.3.3"
|
||||
benchmarkJunit4 = "1.3.3"
|
||||
biometricKtx = "1.2.0-alpha05"
|
||||
blurhash = "1.0.0"
|
||||
coil = "2.7.0"
|
||||
composeBom = "2024.09.03"
|
||||
composeBom = "2024.10.00"
|
||||
coreKtx = "1.13.1"
|
||||
espressoCore = "3.6.1"
|
||||
firebaseBom = "33.4.0"
|
||||
@@ -36,7 +36,7 @@ markdown = "077a2cde64"
|
||||
media3 = "1.4.1"
|
||||
mockk = "1.13.12"
|
||||
kotlinx-coroutines-test = "1.9.0-RC.2"
|
||||
navigationCompose = "2.8.2"
|
||||
navigationCompose = "2.8.3"
|
||||
okhttp = "5.0.0-alpha.14"
|
||||
runner = "1.6.2"
|
||||
rfc3986 = "0.1.0"
|
||||
|
||||
@@ -93,4 +93,21 @@ class Nip04Test {
|
||||
assertTrue(Nip04.isNIP04("zJxfaJ32rN5Dg1ODjOlEew==?iv=EV5bUjcc4OX2Km/zPp4ndQ=="))
|
||||
assertTrue(Nip04.isNIP04("6f8dMstm+udOu7yipSn33orTmwQpWbtfuY95NH+eTU1kArysWJIDkYgI2D25EAGIDJsNd45jOJ2NbVOhFiL3ZP/NWsTwXokk34iyHyA/lkjzugQ1bHXoMD1fP/Ay4hB4al1NHb8HXHKZaxPrErwdRDb8qa/I6dXb/1xxyVvNQBHHvmsM5yIFaPwnCN1DZqXf2KbTA/Ekz7Hy+7R+Sy3TXLQDFpWYqykppkXc7Fs0qSuPRyxz5+anuN0dxZa9GTwTEnBrZPbthKkNRrvZMdTGJ6WumOh9aUq8OJJWy9aOgsXvs7qjN1UqcCqQqYaVnEOhCaqWNDsVtsFrVDj+SaLIBvCiomwF4C4nIgngJ5I69tx0UNI0q+ZnvOGQZ7m1PpW2NYP7Yw43HJNdeUEQAmdCPnh/PJwzLTnIxHmQU7n7SPlMdV0SFa6H8y2HHvex697GAkyE5t8c2uO24OnqIwF1tR3blIqXzTSRl0GA6QvrSj2p4UtnWjvF7xT7RiIEyTtgU/AsihTrXyXzWWZaIBJogpgw6erlZqWjCH7sZy/WoGYEiblobOAqMYxax6vRbeuGtoYksr/myX+x9rfLrYuoDRTw4woXOLmMrrj+Mf0TbAgc3SjdkqdsPU1553rlSqIEZXuFgoWmxvVQDtekgTYyS97G81TDSK9nTJT5ilku8NVq2LgtBXGwsNIw/xekcOUzJke3kpnFPutNaexR1VF3ohIuqRKYRGcd8ADJP2lfwMcaGRiplAmFoaVS1YUhQwYFNq9rMLf7YauRGV4BJg/t9srdGxf5RoKCvRo+XM/nLxxysTR9MVaEP/3lDqjwChMxs+eWfLHE5vRWV8hUEqdrWNZV29gsx5nQpzJ4PARGZVu310pQzc6JAlc2XAhhFk6RamkYJnmCSMnb/RblzIATBi2kNrCVAlaXIon188inB62rEpZGPkRIP7PUfu27S/elLQHBHeGDsxOXsBRo1gl3te+raoBHsxo6zvRnYbwdAQa5taDE63eh+fT6kFI+xYmXNAQkU8Dp0MVhEh4JQI06Ni/AKrvYpC95TXXIphZcF+/Pv/vaGkhG2X9S3uhugwWK?iv=2vWkOQQi0WynNJz/aZ4k2g=="))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isNIP04EncodeWithBug() {
|
||||
assertTrue(
|
||||
Nip04.isNIP04(
|
||||
"QOAYBWa88ConWs2C4kSvNqAcowCtg0ZRtAl7FyLSv9VMaJH4oCiDx0h8VLBnV97HdE4lv" +
|
||||
"TW7AYC1eEw8/t1dbe0qRc3XrOt7MrPAO8yqpy1/3lFB1+10kip0+KdgT8Quvv02wTP8Dqi" +
|
||||
"xpr2fliAIG2ONvDn+O5V0q9aVUN9HitgL/myTyR0T42edmxWeZoMBEOKvJyO80FekSsgVL" +
|
||||
"ASafA/T5z4xs8oG88pSe9wSbSsw0xNjJeh3xLRCLuEuA9KI8hQ1Ys9nEax2UlaB/IL3o77" +
|
||||
"OwBL+rrdUbNHTxYifgygRhg3BaXMsXRFNJbqYeMaRaNbvHkLVAQV2jLY4P/cKHBjEcTC/f" +
|
||||
"lrCc2NCYF34rOQUY5EJVnFzM8qYVw6xNupBHTS7WFx1r60cPjG19P/+yoiTZ6bPdHTU0X2" +
|
||||
"t64ovF2YWUq6/iKAclMaZDhWfrKqf82e62oIff55WQw2bw8A/jtBQVCf66EtEJ2OSFxNaZ" +
|
||||
"rO+A4oLkHDCnAV+6fYzwo89gPOvORcVvSvg55yGiBFUZx9EHS6kdH1SU80/Mbxe2oI=" +
|
||||
"?iv=gxz9pUFJFZHuV+D+hgKEOw==-null",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,14 +153,21 @@ class Nip04(
|
||||
}
|
||||
|
||||
fun isNIP04(encoded: String): Boolean {
|
||||
val l = encoded.length
|
||||
// cleaning up some bug from some client.
|
||||
val cleanedUp = encoded.removeSuffix("-null")
|
||||
|
||||
val l = cleanedUp.length
|
||||
if (l < 28) return false
|
||||
return encoded[l - 28] == '?' && encoded[l - 27] == 'i' && encoded[l - 26] == 'v' && encoded[l - 25] == '='
|
||||
return cleanedUp[l - 28] == '?' &&
|
||||
cleanedUp[l - 27] == 'i' &&
|
||||
cleanedUp[l - 26] == 'v' &&
|
||||
cleanedUp[l - 25] == '='
|
||||
}
|
||||
|
||||
fun decodeFromNIP04(payload: String): EncryptedInfo? =
|
||||
try {
|
||||
val parts = payload.split("?iv=")
|
||||
// cleaning up some bug from some client.
|
||||
val parts = payload.removeSuffix("-null").split("?iv=")
|
||||
EncryptedInfo(
|
||||
ciphertext = Base64.getDecoder().decode(parts[0]),
|
||||
nonce = Base64.getDecoder().decode(parts[1]),
|
||||
|
||||
Reference in New Issue
Block a user