mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-02 17:08:04 +02:00
Merge remote-tracking branch 'origin/camera' into camera
This commit is contained in:
commit
a63d50c24f
.idea/inspectionProfiles
amethyst
build.gradle
src
fdroid/java/com/vitorpamplona/amethyst
service/notifications
ui/components
main/java/com/vitorpamplona/amethyst
Amethyst.ktDebugUtils.kt
model
service
ui
AppScreen.ktMainActivity.kt
actions
EditPostView.ktEditPostViewModel.ktInformationDialog.ktJoinUserOrChannelView.ktNewMediaModel.ktNewMediaView.ktNewPostView.ktNewPostViewModel.ktNewUserMetadataViewModel.ktNotifyRequestDialog.ktRelaySelectionDialog.kt
mediaServers
relays
AddDMRelayListDialog.ktAddSearchRelayListDialog.ktAllRelayListView.ktBasicRelaySetupInfoClickableRow.ktBasicRelaySetupInfoDialog.ktDMRelayListView.ktKind3RelayListView.ktKind3RelaySetupInfoProposalDialog.ktKind3RelaySetupInfoProposalRow.ktLocalRelayListView.ktNip65RelayListView.ktPrivateOutboxRelayListView.ktRelayInformationDialog.ktRelayNameAndRemoveButton.ktSearchRelayListView.kt
buttons
components
ClickableBox.ktClickableNoteTag.ktClickableRoute.ktClickableUserTag.ktDisplayErrorMessages.ktDisplayNotifyMessages.ktExpandableRichTextViewer.ktMediaCompressor.ktRichTextViewer.ktVideoView.ktZoomableContentDialog.kt
markdown
dal
feeds
navigation
AmethystClickableIcon.ktAppBottomBar.ktAppNavigation.ktAppTopBar.ktDrawerContent.ktFeedFilterSpinner.ktHeightDecreaser.ktINav.ktRoutes.ktTopBarExtensibleWithBackButton.ktTopBarWithBackButton.kt
note
BadgeCompose.ktBlankNote.ktBlockReportChecker.ktChannelCardCompose.ktMessageSetCompose.ktMultiSetCompose.ktNIP05VerificationDisplay.ktNoteCompose.ktNoteQuickActionMenu.ktPollNote.ktReactionsRow.ktRelayListBox.ktRelayListRow.ktReplyInformation.ktUpdateReactionTypeDialog.ktUpdateZapAmountDialog.ktUserCompose.ktUserProfilePicture.ktZapNoteCompose.ktZapUserSetCompose.kt
elements
4
.idea/inspectionProfiles/Project_Default.xml
generated
4
.idea/inspectionProfiles/Project_Default.xml
generated
@ -3,15 +3,19 @@
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
|
@ -16,9 +16,9 @@ android {
|
||||
applicationId "com.vitorpamplona.amethyst"
|
||||
minSdk libs.versions.android.minSdk.get().toInteger()
|
||||
targetSdk libs.versions.android.targetSdk.get().toInteger()
|
||||
versionCode 402
|
||||
versionName "0.90.5"
|
||||
buildConfigField "String", "RELEASE_NOTES_ID", "\"5d9a4fd0aba7ffefbf989152479237bd182578e58e59eb2fbfe94f6de8011803\""
|
||||
versionCode 404
|
||||
versionName "0.91.0"
|
||||
buildConfigField "String", "RELEASE_NOTES_ID", "\"fba090fe820d4ca42c2d808400268be68389337e4cac552e5aaa876e929a0eaf\""
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
@ -84,7 +84,7 @@ class PushMessageReceiver : MessagingReceiver() {
|
||||
endpoint: String,
|
||||
instance: String,
|
||||
) {
|
||||
val sanitizedEndpoint = endpoint.dropLast(5)
|
||||
val sanitizedEndpoint = if (endpoint.endsWith("?up=1")) endpoint.dropLast(5) else endpoint
|
||||
if (sanitizedEndpoint != pushHandler.getSavedEndpoint()) {
|
||||
Log.d(TAG, "New endpoint provided:- $endpoint for Instance: $instance ${pushHandler.getSavedEndpoint()} $sanitizedEndpoint")
|
||||
pushHandler.setEndpoint(sanitizedEndpoint)
|
||||
|
@ -55,10 +55,10 @@ import com.halilibo.richtext.ui.resolveDefaults
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler
|
||||
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingsRow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.checkifItNeedsToRequestNotificationPermission
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
@ -24,6 +24,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
|
||||
@ -38,7 +39,7 @@ fun TranslatableRichTextViewer(
|
||||
id: String,
|
||||
callbackUri: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) = ExpandableRichTextViewer(
|
||||
content,
|
||||
canPreview,
|
||||
|
@ -59,10 +59,15 @@ class Amethyst : Application() {
|
||||
applicationIOScope.cancel()
|
||||
}
|
||||
|
||||
fun nip95cache() = safeCacheDir.resolve("NIP95")
|
||||
|
||||
val videoCache: VideoCache by lazy {
|
||||
val newCache = VideoCache()
|
||||
runBlocking {
|
||||
newCache.initFileCache(this@Amethyst)
|
||||
newCache.initFileCache(
|
||||
this@Amethyst,
|
||||
safeCacheDir.resolve("exoplayer"),
|
||||
)
|
||||
}
|
||||
newCache
|
||||
}
|
||||
@ -70,7 +75,7 @@ class Amethyst : Application() {
|
||||
val coilCache: DiskCache by lazy {
|
||||
DiskCache
|
||||
.Builder()
|
||||
.directory(applicationContext.safeCacheDir.resolve("image_cache"))
|
||||
.directory(safeCacheDir.resolve("image_cache"))
|
||||
.maxSizePercent(0.2)
|
||||
.maximumMaxSizeBytes(1024 * 1024 * 1024) // 1GB
|
||||
.build()
|
||||
|
180
amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt
Normal file
180
amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt
Normal file
@ -0,0 +1,180 @@
|
||||
/**
|
||||
* 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 android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Debug
|
||||
import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import coil.Coil
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrCommunityDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrGeohashDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
|
||||
import com.vitorpamplona.ammolite.relays.Client
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
|
||||
fun debugState(context: Context) {
|
||||
Client
|
||||
.allSubscriptions()
|
||||
.forEach { Log.d("STATE DUMP", "${it.key} ${it.value.joinToString { it.filter.toDebugJson() }}") }
|
||||
|
||||
NostrAccountDataSource.printCounter()
|
||||
NostrChannelDataSource.printCounter()
|
||||
NostrChatroomDataSource.printCounter()
|
||||
NostrChatroomListDataSource.printCounter()
|
||||
NostrCommunityDataSource.printCounter()
|
||||
NostrDiscoveryDataSource.printCounter()
|
||||
NostrHashtagDataSource.printCounter()
|
||||
NostrGeohashDataSource.printCounter()
|
||||
NostrHomeDataSource.printCounter()
|
||||
NostrSearchEventOrUserDataSource.printCounter()
|
||||
NostrSingleChannelDataSource.printCounter()
|
||||
NostrSingleEventDataSource.printCounter()
|
||||
NostrSingleUserDataSource.printCounter()
|
||||
NostrThreadDataSource.printCounter()
|
||||
NostrUserProfileDataSource.printCounter()
|
||||
NostrVideoDataSource.printCounter()
|
||||
|
||||
val totalMemoryMb = Runtime.getRuntime().totalMemory() / (1024 * 1024)
|
||||
val freeMemoryMb = Runtime.getRuntime().freeMemory() / (1024 * 1024)
|
||||
val maxMemoryMb = Runtime.getRuntime().maxMemory() / (1024 * 1024)
|
||||
|
||||
val jvmHeapAllocatedMb = totalMemoryMb - freeMemoryMb
|
||||
|
||||
Log.d("STATE DUMP", "Total Heap Allocated: " + jvmHeapAllocatedMb + "/" + maxMemoryMb + " MB")
|
||||
|
||||
val nativeHeap = Debug.getNativeHeapAllocatedSize() / (1024 * 1024)
|
||||
val maxNative = Debug.getNativeHeapSize() / (1024 * 1024)
|
||||
|
||||
Log.d("STATE DUMP", "Total Native Heap Allocated: " + nativeHeap + "/" + maxNative + " MB")
|
||||
|
||||
val activityManager: ActivityManager? = context.getSystemService()
|
||||
if (activityManager != null) {
|
||||
val isLargeHeap = (context.applicationInfo.flags and ApplicationInfo.FLAG_LARGE_HEAP) != 0
|
||||
val memClass = if (isLargeHeap) activityManager.largeMemoryClass else activityManager.memoryClass
|
||||
|
||||
Log.d("STATE DUMP", "Memory Class " + memClass + " MB (largeHeap $isLargeHeap)")
|
||||
}
|
||||
|
||||
Log.d("STATE DUMP", "Connected Relays: " + RelayPool.connectedRelays())
|
||||
|
||||
val imageLoader = Coil.imageLoader(context)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Image Disk Cache ${(imageLoader.diskCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.diskCache?.maxSize ?: 0) / (1024 * 1024)} MB",
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Image Memory Cache ${(imageLoader.memoryCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.memoryCache?.maxSize ?: 0) / (1024 * 1024)} MB",
|
||||
)
|
||||
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Notes: " +
|
||||
LocalCache.notes.filter { _, it -> it.liveSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.notes.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.notes.filter { _, it -> it.event != null }.size +
|
||||
" / " +
|
||||
LocalCache.notes.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Addressables: " +
|
||||
LocalCache.addressables.filter { _, it -> it.liveSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.addressables.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.addressables.filter { _, it -> it.event != null }.size +
|
||||
" / " +
|
||||
LocalCache.addressables.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Users: " +
|
||||
LocalCache.users.filter { _, it -> it.liveSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.users.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.users.filter { _, it -> it.latestMetadata != null }.size +
|
||||
" / " +
|
||||
LocalCache.users.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Deletion Events: " +
|
||||
LocalCache.deletionIndex.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Observable Events: " +
|
||||
LocalCache.observablesByKindAndETag.size +
|
||||
" / " +
|
||||
LocalCache.observablesByKindAndAuthor.size,
|
||||
)
|
||||
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Spam: " +
|
||||
LocalCache.antiSpam.spamMessages.size() + " / " + LocalCache.antiSpam.recentMessages.size(),
|
||||
)
|
||||
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Memory used by Events: " +
|
||||
LocalCache.notes.sumOfLong { _, note -> note.event?.countMemory() ?: 0L } / (1024 * 1024) +
|
||||
" MB",
|
||||
)
|
||||
|
||||
val qttNotes = LocalCache.notes.countByGroup { _, it -> it.event?.kind() }
|
||||
val qttAddressables = LocalCache.addressables.countByGroup { _, it -> it.event?.kind() }
|
||||
|
||||
val bytesNotes =
|
||||
LocalCache.notes
|
||||
.sumByGroup(groupMap = { _, it -> it.event?.kind() }, sumOf = { _, it -> it.event?.countMemory() ?: 0L })
|
||||
val bytesAddressables =
|
||||
LocalCache.addressables
|
||||
.sumByGroup(groupMap = { _, it -> it.event?.kind() }, sumOf = { _, it -> it.event?.countMemory() ?: 0L })
|
||||
|
||||
qttNotes.toList().sortedByDescending { bytesNotes.get(it.first) }.forEach { (kind, qtt) ->
|
||||
Log.d("STATE DUMP", "Kind ${kind.toString().padStart(5,' ')}:\t${qtt.toString().padStart(6,' ')} elements\t${bytesNotes.get(kind)?.div((1024 * 1024))}MB ")
|
||||
}
|
||||
qttAddressables.toList().sortedByDescending { bytesNotes.get(it.first) }.forEach { (kind, qtt) ->
|
||||
Log.d("STATE DUMP", "Kind ${kind.toString().padStart(5,' ')}:\t${qtt.toString().padStart(6,' ')} elements\t${bytesAddressables.get(kind)?.div((1024 * 1024))}MB ")
|
||||
}
|
||||
}
|
@ -632,7 +632,7 @@ class Account(
|
||||
authorsPerRelay(
|
||||
liveHomeFollowLists.value?.usersPlusMe?.map { getNIP65RelayListNote(it) } ?: emptyList(),
|
||||
connectToRelays.value.filter { it.feedTypes.contains(FeedType.FOLLOWS) && it.read }.map { it.url },
|
||||
),
|
||||
).ifEmpty { null },
|
||||
)
|
||||
}
|
||||
|
||||
@ -708,7 +708,7 @@ class Account(
|
||||
authorsPerRelay(
|
||||
liveStoriesFollowLists.value?.usersPlusMe?.map { getNIP65RelayListNote(it) } ?: emptyList(),
|
||||
connectToRelays.value.filter { it.feedTypes.contains(FeedType.FOLLOWS) && it.read }.map { it.url },
|
||||
),
|
||||
).ifEmpty { null },
|
||||
)
|
||||
}
|
||||
|
||||
@ -761,8 +761,8 @@ class Account(
|
||||
SharingStarted.Eagerly,
|
||||
authorsPerRelay(
|
||||
liveDiscoveryFollowLists.value?.usersPlusMe?.map { getNIP65RelayListNote(it) } ?: emptyList(),
|
||||
connectToRelays.value.filter { it.feedTypes.contains(FeedType.FOLLOWS) && it.read }.map { it.url },
|
||||
),
|
||||
connectToRelays.value.filter { it.read }.map { it.url },
|
||||
).ifEmpty { null },
|
||||
)
|
||||
}
|
||||
|
||||
@ -1192,7 +1192,12 @@ class Account(
|
||||
|
||||
LocalCache.consume(event, zappedNote) { it.response(signer) { onResponse(it) } }
|
||||
|
||||
Client.send(event, nip47.relayUri, wcListener.feedTypes) { wcListener.destroy() }
|
||||
Client.send(
|
||||
signedEvent = event,
|
||||
relay = nip47.relayUri,
|
||||
feedTypes = wcListener.feedTypes,
|
||||
onDone = { wcListener.destroy() },
|
||||
)
|
||||
|
||||
onSent()
|
||||
}
|
||||
@ -3291,9 +3296,13 @@ class Account(
|
||||
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
|
||||
}
|
||||
|
||||
settings.backupPrivateHomeRelayList?.let {
|
||||
Log.d("AccountRegisterObservers", "Loading saved search relay list ${it.toJson()}")
|
||||
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
|
||||
settings.backupPrivateHomeRelayList?.let { event ->
|
||||
Log.d("AccountRegisterObservers", "Loading saved search relay list ${event.toJson()}")
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
event.privateTags(signer) {
|
||||
LocalCache.verifyAndConsume(event, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settings.backupMuteList?.let {
|
||||
|
@ -32,7 +32,6 @@ import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.Nip47WalletConnect
|
||||
import com.vitorpamplona.quartz.encoders.RelayUrlFormatter
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||
@ -162,7 +161,7 @@ class AccountSettings(
|
||||
} else {
|
||||
when (val packageName = externalSignerPackageName) {
|
||||
null -> NostrSignerInternal(keyPair)
|
||||
else -> NostrSignerExternal(keyPair.pubKey.toHexKey(), ExternalSignerLauncher(keyPair.pubKey.toNpub(), packageName))
|
||||
else -> NostrSignerExternal(keyPair.pubKey.toHexKey(), ExternalSignerLauncher(keyPair.pubKey.toHexKey(), packageName))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,21 +47,20 @@ import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
|
||||
import com.vitorpamplona.amethyst.ui.components.HashTag
|
||||
import com.vitorpamplona.amethyst.ui.components.RenderRegular
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.quartz.events.EmptyTagList
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderHashTagIcons() {
|
||||
val nav: (String) -> Unit = {}
|
||||
|
||||
fun RenderHashTagIconsPreview() {
|
||||
ThemeComparisonColumn {
|
||||
RenderRegular(
|
||||
"Testing rendering of hashtags: #Bitcoin, #nostr, #lightning, #zap, #amethyst, #cashu, #plebs, #coffee, #skullofsatoshi, #grownostr, #footstr, #tunestr, #weed, #mate",
|
||||
EmptyTagList,
|
||||
) { word, state ->
|
||||
when (word) {
|
||||
is HashTagSegment -> HashTag(word, nav)
|
||||
is HashTagSegment -> HashTag(word, EmptyNav)
|
||||
is RegularTextSegment -> Text(word.segmentText)
|
||||
}
|
||||
}
|
||||
|
@ -1876,7 +1876,7 @@ object LocalCache {
|
||||
}
|
||||
|
||||
try {
|
||||
val cachePath = File(Amethyst.instance.applicationContext.cacheDir, "NIP95")
|
||||
val cachePath = Amethyst.instance.nip95cache()
|
||||
cachePath.mkdirs()
|
||||
val file = File(cachePath, event.id)
|
||||
if (!file.exists()) {
|
||||
|
@ -67,7 +67,9 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.containsAny
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.math.BigDecimal
|
||||
@ -764,8 +766,32 @@ open class Note(
|
||||
}
|
||||
}
|
||||
|
||||
if (thisEvent is BaseTextNoteEvent) {
|
||||
if (accountChoices.hiddenWordsCase.isNotEmpty() && thisEvent.content.containsAny(accountChoices.hiddenWordsCase)) {
|
||||
if (accountChoices.hiddenWordsCase.isNotEmpty()) {
|
||||
if (thisEvent is BaseTextNoteEvent && thisEvent.content.containsAny(accountChoices.hiddenWordsCase)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (author?.toBestDisplayName()?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (author?.profilePicture()?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (author?.info?.banner?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (author?.info?.about?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (author?.info?.lud06?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (author?.info?.lud16?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -848,6 +874,15 @@ class NoteFlowSet(
|
||||
val reports = NoteBundledRefresherFlow(u)
|
||||
val relays = NoteBundledRefresherFlow(u)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun author() =
|
||||
metadata.stateFlow.flatMapLatest {
|
||||
it.note.author
|
||||
?.flow()
|
||||
?.metadata
|
||||
?.stateFlow ?: MutableStateFlow(null)
|
||||
}
|
||||
|
||||
fun isInUse(): Boolean =
|
||||
metadata.stateFlow.subscriptionCount.value > 0 ||
|
||||
reports.stateFlow.subscriptionCount.value > 0 ||
|
||||
|
@ -149,7 +149,7 @@ class Nip96Uploader(
|
||||
.addFormDataPart("expiration", "")
|
||||
.addFormDataPart("size", length.toString())
|
||||
.also { body ->
|
||||
alt?.let { body.addFormDataPart("alt", it) }
|
||||
alt?.ifBlank { null }?.let { body.addFormDataPart("alt", it) }
|
||||
sensitiveContent?.let { body.addFormDataPart("content-warning", it) }
|
||||
contentType?.let { body.addFormDataPart("content_type", it) }
|
||||
}.addFormDataPart(
|
||||
|
@ -170,7 +170,7 @@ object NostrDiscoveryDataSource : AmethystNostrDataSource("DiscoveryFeed") {
|
||||
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
types = if (follows == null) setOf(FeedType.GLOBAL) else setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = followsRelays,
|
||||
@ -185,7 +185,7 @@ object NostrDiscoveryDataSource : AmethystNostrDataSource("DiscoveryFeed") {
|
||||
),
|
||||
follows?.let {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
tags = mapOf("p" to it),
|
||||
@ -246,7 +246,7 @@ object NostrDiscoveryDataSource : AmethystNostrDataSource("DiscoveryFeed") {
|
||||
val follows = account.liveDiscoveryListAuthorsPerRelay.value
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
types = if (follows == null) setOf(FeedType.GLOBAL) else setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
|
@ -43,13 +43,16 @@ class VideoCache {
|
||||
|
||||
lateinit var cacheDataSourceFactory: CacheDataSource.Factory
|
||||
|
||||
suspend fun initFileCache(context: Context) {
|
||||
suspend fun initFileCache(
|
||||
context: Context,
|
||||
cachePath: File,
|
||||
) {
|
||||
exoDatabaseProvider = StandaloneDatabaseProvider(context)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
simpleCache =
|
||||
SimpleCache(
|
||||
File(context.cacheDir, "exoplayer"),
|
||||
cachePath,
|
||||
leastRecentlyUsedCacheEvictor,
|
||||
exoDatabaseProvider,
|
||||
)
|
||||
|
@ -26,10 +26,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.google.accompanist.adaptive.calculateDisplayFeatures
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
||||
@Composable
|
||||
@ -53,16 +50,3 @@ fun prepareSharedViewModel(act: MainActivity): SharedPreferencesViewModel {
|
||||
|
||||
return sharedPreferencesViewModel
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppScreen(sharedPreferencesViewModel: SharedPreferencesViewModel) {
|
||||
AmethystTheme(sharedPreferencesViewModel) {
|
||||
val accountStateViewModel: AccountStateViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
accountStateViewModel.tryLoginExistingAccountAsync()
|
||||
}
|
||||
|
||||
AccountScreen(accountStateViewModel, sharedPreferencesViewModel)
|
||||
}
|
||||
}
|
||||
|
@ -34,16 +34,21 @@ import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.debugState
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
|
||||
import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils
|
||||
import com.vitorpamplona.amethyst.ui.components.DEFAULT_MUTED_SETTING
|
||||
import com.vitorpamplona.amethyst.ui.components.keepPlayingMutex
|
||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
import com.vitorpamplona.amethyst.ui.navigation.debugState
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
||||
import com.vitorpamplona.quartz.encoders.Nip47WalletConnect
|
||||
@ -78,7 +83,16 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
setContent {
|
||||
val sharedPreferencesViewModel = prepareSharedViewModel(act = this)
|
||||
AppScreen(sharedPreferencesViewModel = sharedPreferencesViewModel)
|
||||
|
||||
AmethystTheme(sharedPreferencesViewModel) {
|
||||
val accountStateViewModel: AccountStateViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
accountStateViewModel.tryLoginExistingAccountAsync()
|
||||
}
|
||||
|
||||
AccountScreen(accountStateViewModel, sharedPreferencesViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,9 +94,10 @@ import com.vitorpamplona.amethyst.ui.components.BechLink
|
||||
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.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.UserLine
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
@ -119,7 +120,7 @@ fun EditPostView(
|
||||
edit: Note,
|
||||
versionLookingAt: Note?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val postViewModel: EditPostViewModel = viewModel()
|
||||
postViewModel.prepare(edit, versionLookingAt, accountViewModel)
|
||||
@ -330,8 +331,8 @@ fun EditPostView(
|
||||
ImageVideoDescription(
|
||||
url,
|
||||
accountViewModel.account.settings.defaultFileServer,
|
||||
onAdd = { alt, server, sensitiveContent ->
|
||||
postViewModel.upload(url, alt, sensitiveContent, false, server, accountViewModel::toast, context)
|
||||
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)
|
||||
}
|
||||
|
@ -148,6 +148,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
galleryUri: Uri,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
mediaQuality: Int,
|
||||
isPrivate: Boolean = false,
|
||||
server: ServerOption,
|
||||
onError: (String, String) -> Unit,
|
||||
@ -223,6 +224,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
isUploadingImage = false
|
||||
onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it))
|
||||
},
|
||||
mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,12 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Done
|
||||
@ -32,9 +35,14 @@ import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
||||
@ -50,15 +58,11 @@ fun InformationDialog(
|
||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val stack = stringRes(id = R.string.stack)
|
||||
val str =
|
||||
val str = textContent ?: throwable.localizedMessage ?: throwable.message ?: throwable.javaClass.simpleName
|
||||
|
||||
val stack =
|
||||
remember(throwable) {
|
||||
val writer = StringWriter()
|
||||
textContent?.let {
|
||||
writer.append(it)
|
||||
writer.append("\n\n")
|
||||
}
|
||||
writer.append(stack)
|
||||
writer.append("\n")
|
||||
|
||||
throwable.printStackTrace(PrintWriter(writer))
|
||||
@ -66,13 +70,14 @@ fun InformationDialog(
|
||||
writer.toString()
|
||||
}
|
||||
|
||||
InformationDialog(title = title, textContent = str, buttonColors, onDismiss)
|
||||
InformationDialog(title = title, textContent = str, moreInfo = stack, buttonColors, onDismiss)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InformationDialog(
|
||||
title: String,
|
||||
textContent: String,
|
||||
moreInfo: String? = null,
|
||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
@ -85,20 +90,37 @@ fun InformationDialog(
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = onDismiss,
|
||||
colors = buttonColors,
|
||||
contentPadding = PaddingValues(horizontal = Size16dp),
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(all = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
moreInfo?.let {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
TextButton(onClick = {
|
||||
clipboardManager.setText(AnnotatedString(it))
|
||||
}) {
|
||||
Text(stringRes(R.string.copy_stack_to_clipboard))
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = onDismiss,
|
||||
colors = buttonColors,
|
||||
contentPadding = PaddingValues(horizontal = Size16dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Done,
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
Text(stringRes(R.string.error_dialog_button_ok))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Done,
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
Text(stringRes(R.string.error_dialog_button_ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -75,12 +75,13 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
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.SearchBarViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchBarViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
@ -102,7 +103,7 @@ import kotlinx.coroutines.withContext
|
||||
fun JoinUserOrChannelView(
|
||||
onClose: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val searchBarViewModel: SearchBarViewModel =
|
||||
viewModel(
|
||||
@ -126,7 +127,7 @@ fun JoinUserOrChannelView(
|
||||
searchBarViewModel: SearchBarViewModel,
|
||||
onClose: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
@ -183,7 +184,7 @@ fun JoinUserOrChannelView(
|
||||
private fun RenderSearch(
|
||||
searchBarViewModel: SearchBarViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
@ -324,7 +325,7 @@ private fun RenderSearchResults(
|
||||
searchBarViewModel: SearchBarViewModel,
|
||||
listState: LazyListState,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
if (searchBarViewModel.isSearching) {
|
||||
val users by searchBarViewModel.searchResultsUsers.collectAsStateWithLifecycle()
|
||||
@ -347,7 +348,7 @@ private fun RenderSearchResults(
|
||||
key = { _, item -> "u" + item.pubkeyHex },
|
||||
) { _, item ->
|
||||
UserComposeForChat(item, accountViewModel) {
|
||||
accountViewModel.createChatRoomFor(item) { nav("Room/$it") }
|
||||
accountViewModel.createChatRoomFor(item) { nav.nav("Room/$it") }
|
||||
|
||||
searchBarViewModel.clear()
|
||||
}
|
||||
@ -366,7 +367,7 @@ private fun RenderSearchResults(
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||
) {
|
||||
nav("Channel/${item.idHex}")
|
||||
nav.nav("Channel/${item.idHex}")
|
||||
searchBarViewModel.clear()
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,7 @@ open class NewMediaModel : ViewModel() {
|
||||
fun upload(
|
||||
context: Context,
|
||||
relayList: List<RelaySetupInfo>? = null,
|
||||
mediaQuality: Int,
|
||||
onError: (String) -> Unit = {},
|
||||
) {
|
||||
isUploadingImage = true
|
||||
@ -166,6 +167,7 @@ open class NewMediaModel : ViewModel() {
|
||||
uploadingDescription.value = null
|
||||
onError(stringRes(context, R.string.error_when_compressing_media, it))
|
||||
},
|
||||
mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -44,22 +44,26 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
@ -67,10 +71,12 @@ import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.Nip96MediaServers
|
||||
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.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.placeholderText
|
||||
import com.vitorpamplona.quartz.events.FileServersEvent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@ -84,7 +90,7 @@ fun NewMediaView(
|
||||
onClose: () -> Unit,
|
||||
postViewModel: NewMediaModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val account = accountViewModel.account
|
||||
val resolver = LocalContext.current.contentResolver
|
||||
@ -99,6 +105,7 @@ fun NewMediaView(
|
||||
|
||||
var showRelaysDialog by remember { mutableStateOf(false) }
|
||||
var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() }
|
||||
var mediaQualitySlider by remember { mutableIntStateOf(1) } // 0 = Low, 1 = Medium, 2 = High
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = { onClose() },
|
||||
@ -159,7 +166,7 @@ fun NewMediaView(
|
||||
PostButton(
|
||||
onPost = {
|
||||
onClose()
|
||||
postViewModel.upload(context, relayList) {
|
||||
postViewModel.upload(context, relayList, mediaQualitySlider) {
|
||||
accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it)
|
||||
}
|
||||
postViewModel.selectedServer?.let {
|
||||
@ -179,6 +186,59 @@ fun NewMediaView(
|
||||
modifier = Modifier.fillMaxWidth().verticalScroll(scrollState),
|
||||
) {
|
||||
ImageVideoPost(postViewModel, accountViewModel)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
|
||||
.padding(vertical = 8.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1.0f),
|
||||
verticalArrangement = Arrangement.spacedBy(Size5dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(context, R.string.media_compression_quality_label),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(context, R.string.media_compression_quality_explainer),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text =
|
||||
when (mediaQualitySlider) {
|
||||
0 -> stringRes(R.string.media_compression_quality_low)
|
||||
1 -> stringRes(R.string.media_compression_quality_medium)
|
||||
2 -> stringRes(R.string.media_compression_quality_high)
|
||||
else -> stringRes(R.string.media_compression_quality_medium)
|
||||
},
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
|
||||
Slider(
|
||||
value = mediaQualitySlider.toFloat(),
|
||||
onValueChange = { mediaQualitySlider = it.toInt() },
|
||||
valueRange = 0f..2f,
|
||||
steps = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@ -141,6 +142,7 @@ 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.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
||||
@ -203,7 +205,7 @@ fun NewPostView(
|
||||
draft: Note? = null,
|
||||
enableMessageInterface: Boolean = false,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val postViewModel: NewPostViewModel = viewModel()
|
||||
@ -506,8 +508,8 @@ fun NewPostView(
|
||||
ImageVideoDescription(
|
||||
url,
|
||||
accountViewModel.account.settings.defaultFileServer,
|
||||
onAdd = { alt, server, sensitiveContent ->
|
||||
postViewModel.upload(url, alt, sensitiveContent, false, server, accountViewModel::toast, context)
|
||||
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)
|
||||
}
|
||||
@ -1686,7 +1688,7 @@ fun CreateButton(
|
||||
fun ImageVideoDescription(
|
||||
uri: Uri,
|
||||
defaultServer: Nip96MediaServers.ServerName,
|
||||
onAdd: (String, ServerOption, Boolean) -> Unit,
|
||||
onAdd: (String, ServerOption, Boolean, Int) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
onError: (Int) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
@ -1744,6 +1746,7 @@ fun ImageVideoDescription(
|
||||
}
|
||||
var message by remember { mutableStateOf("") }
|
||||
var sensitiveContent by remember { mutableStateOf(false) }
|
||||
var mediaQualitySlider by remember { mutableIntStateOf(1) } // 0 = Low, 1 = Medium, 2 = High
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
@ -1922,12 +1925,66 @@ fun ImageVideoDescription(
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
|
||||
.padding(vertical = 8.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1.0f),
|
||||
verticalArrangement = Arrangement.spacedBy(Size5dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.media_compression_quality_label),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(R.string.media_compression_quality_explainer),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text =
|
||||
when (mediaQualitySlider) {
|
||||
0 -> stringRes(R.string.media_compression_quality_low)
|
||||
1 -> stringRes(R.string.media_compression_quality_medium)
|
||||
2 -> stringRes(R.string.media_compression_quality_high)
|
||||
else -> stringRes(R.string.media_compression_quality_medium)
|
||||
},
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
|
||||
Slider(
|
||||
value = mediaQualitySlider.toFloat(),
|
||||
onValueChange = { mediaQualitySlider = it.toInt() },
|
||||
valueRange = 0f..2f,
|
||||
steps = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 10.dp),
|
||||
onClick = { onAdd(message, selectedServer, sensitiveContent) },
|
||||
onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider) },
|
||||
shape = QuoteBorder,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
|
@ -834,6 +834,7 @@ open class NewPostViewModel : ViewModel() {
|
||||
galleryUri: Uri,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
mediaQuality: Int,
|
||||
isPrivate: Boolean = false,
|
||||
server: ServerOption,
|
||||
onError: (title: String, message: String) -> Unit,
|
||||
@ -909,6 +910,7 @@ open class NewPostViewModel : ViewModel() {
|
||||
isUploadingImage = false
|
||||
onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it))
|
||||
},
|
||||
mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.Nip96Uploader
|
||||
import com.vitorpamplona.amethyst.ui.components.CompressorQuality
|
||||
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.events.GitHubIdentity
|
||||
@ -211,6 +212,8 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
|
||||
onError(stringRes(context, R.string.error_when_compressing_media), stringRes(context, it))
|
||||
},
|
||||
// Use MEDIUM quality as default
|
||||
mediaQuality = CompressorQuality.MEDIUM,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
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.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
||||
@ -52,7 +54,7 @@ fun NotifyRequestDialog(
|
||||
textContent: String,
|
||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
@ -72,7 +74,7 @@ fun NotifyRequestDialog(
|
||||
id = textContent,
|
||||
callbackUri = null,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
nav = rememberExtendedNav(nav, onDismiss),
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
|
@ -48,6 +48,7 @@ import androidx.compose.ui.window.DialogProperties
|
||||
import com.vitorpamplona.amethyst.R
|
||||
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.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
@ -74,7 +75,7 @@ fun RelaySelectionDialog(
|
||||
onClose: () -> Unit,
|
||||
onPost: (list: ImmutableList<RelaySetupInfo>) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
|
@ -58,6 +58,7 @@ 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.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertPadding
|
||||
@ -70,7 +71,7 @@ import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||
fun MediaServersListView(
|
||||
onClose: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val mediaServersViewModel: MediaServersViewModel = viewModel()
|
||||
val mediaServersState by mediaServersViewModel.fileServers.collectAsStateWithLifecycle()
|
||||
|
@ -44,6 +44,7 @@ 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.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
@ -55,7 +56,7 @@ import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
fun AddDMRelayListDialog(
|
||||
onClose: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val postViewModel: DMRelayListViewModel = viewModel()
|
||||
|
||||
|
@ -44,6 +44,7 @@ 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.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
@ -55,7 +56,7 @@ import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
fun AddSearchRelayListDialog(
|
||||
onClose: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val postViewModel: SearchRelayListViewModel = viewModel()
|
||||
|
||||
|
@ -51,6 +51,8 @@ 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.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
@ -61,13 +63,23 @@ import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||
import com.vitorpamplona.ammolite.relays.Constants
|
||||
import com.vitorpamplona.ammolite.relays.RelayStat
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AllRelayListView(
|
||||
onClose: () -> Unit,
|
||||
relayToAdd: String = "",
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
MappedAllRelayListView(onClose, relayToAdd, accountViewModel, rememberExtendedNav(nav, onClose))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MappedAllRelayListView(
|
||||
onClose: () -> Unit,
|
||||
relayToAdd: String = "",
|
||||
accountViewModel: AccountViewModel,
|
||||
newNav: INav,
|
||||
) {
|
||||
val kind3ViewModel: Kind3RelayListViewModel = viewModel()
|
||||
val kind3FeedState by kind3ViewModel.relays.collectAsStateWithLifecycle()
|
||||
@ -179,7 +191,7 @@ fun AllRelayListView(
|
||||
Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
}
|
||||
renderNip65HomeItems(homeFeedState, nip65ViewModel, accountViewModel, onClose, nav)
|
||||
renderNip65HomeItems(homeFeedState, nip65ViewModel, accountViewModel, newNav)
|
||||
|
||||
item {
|
||||
SettingsCategory(
|
||||
@ -187,7 +199,7 @@ fun AllRelayListView(
|
||||
stringRes(R.string.public_notif_section_explainer),
|
||||
)
|
||||
}
|
||||
renderNip65NotifItems(notifFeedState, nip65ViewModel, accountViewModel, onClose, nav)
|
||||
renderNip65NotifItems(notifFeedState, nip65ViewModel, accountViewModel, newNav)
|
||||
|
||||
item {
|
||||
SettingsCategory(
|
||||
@ -195,7 +207,7 @@ fun AllRelayListView(
|
||||
stringRes(R.string.private_inbox_section_explainer),
|
||||
)
|
||||
}
|
||||
renderDMItems(dmFeedState, dmViewModel, accountViewModel, onClose, nav)
|
||||
renderDMItems(dmFeedState, dmViewModel, accountViewModel, newNav)
|
||||
|
||||
item {
|
||||
SettingsCategory(
|
||||
@ -203,7 +215,7 @@ fun AllRelayListView(
|
||||
stringRes(R.string.private_outbox_section_explainer),
|
||||
)
|
||||
}
|
||||
renderPrivateOutboxItems(privateOutboxFeedState, privateOutboxViewModel, accountViewModel, onClose, nav)
|
||||
renderPrivateOutboxItems(privateOutboxFeedState, privateOutboxViewModel, accountViewModel, newNav)
|
||||
|
||||
item {
|
||||
SettingsCategoryWithButton(
|
||||
@ -214,7 +226,7 @@ fun AllRelayListView(
|
||||
},
|
||||
)
|
||||
}
|
||||
renderSearchItems(searchFeedState, searchViewModel, accountViewModel, onClose, nav)
|
||||
renderSearchItems(searchFeedState, searchViewModel, accountViewModel, newNav)
|
||||
|
||||
item {
|
||||
SettingsCategory(
|
||||
@ -222,7 +234,7 @@ fun AllRelayListView(
|
||||
stringRes(R.string.local_section_explainer),
|
||||
)
|
||||
}
|
||||
renderLocalItems(localFeedState, localViewModel, accountViewModel, onClose, nav)
|
||||
renderLocalItems(localFeedState, localViewModel, accountViewModel, newNav)
|
||||
|
||||
item {
|
||||
SettingsCategoryWithButton(
|
||||
@ -233,7 +245,7 @@ fun AllRelayListView(
|
||||
},
|
||||
)
|
||||
}
|
||||
renderKind3Items(kind3FeedState, kind3ViewModel, accountViewModel, onClose, nav, relayToAdd)
|
||||
renderKind3Items(kind3FeedState, kind3ViewModel, accountViewModel, newNav, relayToAdd)
|
||||
|
||||
if (kind3Proposals.isNotEmpty()) {
|
||||
item {
|
||||
@ -242,7 +254,7 @@ fun AllRelayListView(
|
||||
stringRes(R.string.kind_3_recommended_section_description),
|
||||
)
|
||||
}
|
||||
renderKind3ProposalItems(kind3Proposals, kind3ViewModel, accountViewModel, onClose, nav)
|
||||
renderKind3ProposalItems(kind3Proposals, kind3ViewModel, accountViewModel, newNav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.actions.relays
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@ -31,6 +32,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -41,6 +44,7 @@ import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChatMaxWidth
|
||||
import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun BasicRelaySetupInfoClickableRow(
|
||||
item: BasicRelaySetupInfo,
|
||||
@ -50,7 +54,17 @@ fun BasicRelaySetupInfoClickableRow(
|
||||
onClick: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth().clickable(onClick = onClick)) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = {
|
||||
clipboardManager.setText(AnnotatedString(item.briefInfo.url))
|
||||
},
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = HalfVertPadding,
|
||||
|
@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||
import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
@ -39,7 +40,7 @@ fun BasicRelaySetupInfoDialog(
|
||||
item: BasicRelaySetupInfo,
|
||||
onDelete: (BasicRelaySetupInfo) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
||||
val context = LocalContext.current
|
||||
|
@ -29,6 +29,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
@ -38,15 +40,16 @@ fun DMRelayList(
|
||||
postViewModel: DMRelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val newNav = rememberExtendedNav(nav, onClose)
|
||||
val feedState by postViewModel.relays.collectAsStateWithLifecycle()
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
LazyColumn(
|
||||
contentPadding = FeedPadding,
|
||||
) {
|
||||
renderDMItems(feedState, postViewModel, accountViewModel, onClose, nav)
|
||||
renderDMItems(feedState, postViewModel, accountViewModel, newNav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,18 +58,15 @@ fun LazyListScope.renderDMItems(
|
||||
feedState: List<BasicRelaySetupInfo>,
|
||||
postViewModel: DMRelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> "DM" + item.url }) { index, item ->
|
||||
BasicRelaySetupInfoDialog(
|
||||
item,
|
||||
onDelete = { postViewModel.deleteRelay(item) },
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
onClose()
|
||||
nav(it)
|
||||
}
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
|
@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.actions.relays
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -59,8 +58,10 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -70,6 +71,8 @@ import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||
import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@ -101,12 +104,14 @@ fun Kind3RelayListView(
|
||||
postViewModel: Kind3RelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
relayToAdd: String,
|
||||
) {
|
||||
val newNav = rememberExtendedNav(nav, onClose)
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
LazyColumn(contentPadding = FeedPadding) {
|
||||
renderKind3Items(feedState, postViewModel, accountViewModel, onClose, nav, relayToAdd)
|
||||
renderKind3Items(feedState, postViewModel, accountViewModel, newNav, relayToAdd)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,8 +120,7 @@ fun LazyListScope.renderKind3Items(
|
||||
feedState: List<Kind3BasicRelaySetupInfo>,
|
||||
postViewModel: Kind3RelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
relayToAdd: String,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> "kind3" + item.url }) { index, item ->
|
||||
@ -131,10 +135,8 @@ fun LazyListScope.renderKind3Items(
|
||||
onToggleSearch = { postViewModel.toggleSearch(it) },
|
||||
onDelete = { postViewModel.deleteRelay(it) },
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
onClose()
|
||||
nav(it)
|
||||
}
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
@ -147,8 +149,7 @@ fun LazyListScope.renderKind3ProposalItems(
|
||||
feedState: List<Kind3RelayProposalSetupInfo>,
|
||||
postViewModel: Kind3RelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> "kind3proposal" + item.url }) { index, item ->
|
||||
Kind3RelaySetupInfoProposalDialog(
|
||||
@ -157,10 +158,7 @@ fun LazyListScope.renderKind3ProposalItems(
|
||||
postViewModel.addRelay(item)
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
nav = {
|
||||
onClose()
|
||||
nav(it)
|
||||
},
|
||||
nav = nav,
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
@ -213,7 +211,7 @@ fun LoadRelayInfo(
|
||||
onToggleSearch: (Kind3BasicRelaySetupInfo) -> Unit,
|
||||
onDelete: (Kind3BasicRelaySetupInfo) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
||||
val context = LocalContext.current
|
||||
@ -292,6 +290,7 @@ fun LoadRelayInfo(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ClickableRelayItem(
|
||||
item: Kind3BasicRelaySetupInfo,
|
||||
@ -307,7 +306,17 @@ fun ClickableRelayItem(
|
||||
onDelete: (Kind3BasicRelaySetupInfo) -> Unit,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth().clickable(onClick = onClick)) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = {
|
||||
clipboardManager.setText(AnnotatedString(item.briefInfo.url))
|
||||
},
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 5.dp),
|
||||
@ -677,6 +686,7 @@ private fun ActiveToggles(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun FirstLine(
|
||||
item: Kind3BasicRelaySetupInfo,
|
||||
@ -684,11 +694,18 @@ private fun FirstLine(
|
||||
onDelete: (Kind3BasicRelaySetupInfo) -> Unit,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
|
||||
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = item.briefInfo.displayUrl,
|
||||
modifier = Modifier.clickable(onClick = onClick),
|
||||
modifier =
|
||||
Modifier.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = {
|
||||
clipboardManager.setText(AnnotatedString(item.briefInfo.url))
|
||||
},
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||
import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
@ -39,7 +40,7 @@ fun Kind3RelaySetupInfoProposalDialog(
|
||||
item: Kind3RelayProposalSetupInfo,
|
||||
onAdd: (Kind3RelayProposalSetupInfo) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
||||
val context = LocalContext.current
|
||||
|
@ -47,6 +47,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.AddRelayButton
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
@ -72,7 +74,7 @@ fun Kind3RelaySetupInfoProposalRow(
|
||||
onAdd: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
@ -138,7 +140,6 @@ fun Kind3RelaySetupInfoProposalRow(
|
||||
@Preview
|
||||
@Composable
|
||||
fun UsedByPreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
ThemeComparisonColumn {
|
||||
UsedBy(
|
||||
item =
|
||||
@ -151,9 +152,9 @@ fun UsedByPreview() {
|
||||
paidRelay = false,
|
||||
users = listOf("User1", "User2", "User3", "User4"),
|
||||
),
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
}
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = EmptyNav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +163,7 @@ fun UsedByPreview() {
|
||||
fun UsedBy(
|
||||
item: Kind3RelayProposalSetupInfo,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
FlowRow(verticalArrangement = Arrangement.Center) {
|
||||
item.users.getOrNull(0)?.let {
|
||||
|
@ -29,6 +29,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
@ -38,15 +40,16 @@ fun LocalRelayList(
|
||||
postViewModel: LocalRelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val newNav = rememberExtendedNav(nav, onClose)
|
||||
val feedState by postViewModel.relays.collectAsStateWithLifecycle()
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
LazyColumn(
|
||||
contentPadding = FeedPadding,
|
||||
) {
|
||||
renderLocalItems(feedState, postViewModel, accountViewModel, onClose, nav)
|
||||
renderLocalItems(feedState, postViewModel, accountViewModel, newNav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,18 +58,15 @@ fun LazyListScope.renderLocalItems(
|
||||
feedState: List<BasicRelaySetupInfo>,
|
||||
postViewModel: LocalRelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> "Local" + item.url }) { index, item ->
|
||||
BasicRelaySetupInfoDialog(
|
||||
item,
|
||||
onDelete = { postViewModel.deleteRelay(item) },
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
onClose()
|
||||
nav(it)
|
||||
}
|
||||
nav,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
|
@ -29,6 +29,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
@ -38,8 +40,10 @@ fun Nip65RelayList(
|
||||
postViewModel: Nip65RelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val newNav = rememberExtendedNav(nav, onClose)
|
||||
|
||||
val homeFeedState by postViewModel.homeRelays.collectAsStateWithLifecycle()
|
||||
val notifFeedState by postViewModel.notificationRelays.collectAsStateWithLifecycle()
|
||||
|
||||
@ -47,8 +51,8 @@ fun Nip65RelayList(
|
||||
LazyColumn(
|
||||
contentPadding = FeedPadding,
|
||||
) {
|
||||
renderNip65HomeItems(homeFeedState, postViewModel, accountViewModel, onClose, nav)
|
||||
renderNip65NotifItems(notifFeedState, postViewModel, accountViewModel, onClose, nav)
|
||||
renderNip65HomeItems(homeFeedState, postViewModel, accountViewModel, newNav)
|
||||
renderNip65NotifItems(notifFeedState, postViewModel, accountViewModel, newNav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,18 +61,15 @@ fun LazyListScope.renderNip65HomeItems(
|
||||
feedState: List<BasicRelaySetupInfo>,
|
||||
postViewModel: Nip65RelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> "Nip65Home" + item.url }) { index, item ->
|
||||
BasicRelaySetupInfoDialog(
|
||||
item,
|
||||
onDelete = { postViewModel.deleteHomeRelay(item) },
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
onClose()
|
||||
nav(it)
|
||||
}
|
||||
nav,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
@ -81,18 +82,15 @@ fun LazyListScope.renderNip65NotifItems(
|
||||
feedState: List<BasicRelaySetupInfo>,
|
||||
postViewModel: Nip65RelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> "Nip65Notif" + item.url }) { index, item ->
|
||||
BasicRelaySetupInfoDialog(
|
||||
item,
|
||||
onDelete = { postViewModel.deleteNotifRelay(item) },
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
onClose()
|
||||
nav(it)
|
||||
}
|
||||
nav,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
|
16
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt
16
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt
@ -29,6 +29,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
@ -38,15 +40,16 @@ fun PrivateOutboxRelayList(
|
||||
postViewModel: PrivateOutboxRelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val newNav = rememberExtendedNav(nav, onClose)
|
||||
val feedState by postViewModel.relays.collectAsStateWithLifecycle()
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
LazyColumn(
|
||||
contentPadding = FeedPadding,
|
||||
) {
|
||||
renderPrivateOutboxItems(feedState, postViewModel, accountViewModel, onClose, nav)
|
||||
renderPrivateOutboxItems(feedState, postViewModel, accountViewModel, newNav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,18 +58,15 @@ fun LazyListScope.renderPrivateOutboxItems(
|
||||
feedState: List<BasicRelaySetupInfo>,
|
||||
postViewModel: PrivateOutboxRelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> "Outbox" + item.url }) { index, item ->
|
||||
BasicRelaySetupInfoDialog(
|
||||
item,
|
||||
onDelete = { postViewModel.deleteRelay(item) },
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
onClose()
|
||||
nav(it)
|
||||
}
|
||||
nav,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
|
@ -55,6 +55,8 @@ import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||
@ -79,8 +81,10 @@ fun RelayInformationDialog(
|
||||
relayBriefInfo: RelayBriefInfoCache.RelayBriefInfo,
|
||||
relayInfo: Nip11RelayInformation,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val newNav = rememberExtendedNav(nav, onClose)
|
||||
|
||||
val messages =
|
||||
remember(relayBriefInfo) {
|
||||
RelayStats
|
||||
@ -152,10 +156,7 @@ fun RelayInformationDialog(
|
||||
Section(stringRes(R.string.owner))
|
||||
|
||||
relayInfo.pubkey?.let {
|
||||
DisplayOwnerInformation(it, accountViewModel) {
|
||||
onClose()
|
||||
nav(it)
|
||||
}
|
||||
DisplayOwnerInformation(it, accountViewModel, newNav)
|
||||
}
|
||||
}
|
||||
item {
|
||||
@ -300,7 +301,7 @@ fun RelayInformationDialog(
|
||||
backgroundColor = color,
|
||||
id = msg.hashCode().toString(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
nav = newNav,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -353,7 +354,7 @@ private fun DisplaySoftwareInformation(relayInfo: Nip11RelayInformation) {
|
||||
private fun DisplayOwnerInformation(
|
||||
userHex: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadUser(baseUserHex = userHex, accountViewModel) {
|
||||
CrossfadeIfEnabled(it, accountViewModel = accountViewModel) {
|
||||
|
@ -20,7 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.actions.relays
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@ -34,6 +35,8 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@ -41,6 +44,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.WarningColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RelayNameAndRemoveButton(
|
||||
item: BasicRelaySetupInfo,
|
||||
@ -48,11 +52,18 @@ fun RelayNameAndRemoveButton(
|
||||
onDelete: (BasicRelaySetupInfo) -> Unit,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
|
||||
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = item.briefInfo.displayUrl,
|
||||
modifier = Modifier.clickable(onClick = onClick),
|
||||
modifier =
|
||||
Modifier.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = {
|
||||
clipboardManager.setText(AnnotatedString(item.briefInfo.url))
|
||||
},
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
@ -29,6 +29,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
@ -38,15 +40,17 @@ fun SearchRelayList(
|
||||
postViewModel: SearchRelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val feedState by postViewModel.relays.collectAsStateWithLifecycle()
|
||||
|
||||
val newNav = rememberExtendedNav(nav, onClose)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
LazyColumn(
|
||||
contentPadding = FeedPadding,
|
||||
) {
|
||||
renderSearchItems(feedState, postViewModel, accountViewModel, onClose, nav)
|
||||
renderSearchItems(feedState, postViewModel, accountViewModel, newNav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,18 +59,15 @@ fun LazyListScope.renderSearchItems(
|
||||
feedState: List<BasicRelaySetupInfo>,
|
||||
postViewModel: SearchRelayListViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> "Search" + item.url }) { index, item ->
|
||||
BasicRelaySetupInfoDialog(
|
||||
item,
|
||||
onDelete = { postViewModel.deleteRelay(item) },
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
onClose()
|
||||
nav(it)
|
||||
}
|
||||
nav,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
|
@ -1,69 +0,0 @@
|
||||
/**
|
||||
* 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.buttons
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
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.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
||||
|
||||
@Composable
|
||||
fun NewChannelButton(accountViewModel: AccountViewModel) {
|
||||
var wantsToPost by remember { mutableStateOf(false) }
|
||||
|
||||
if (wantsToPost) {
|
||||
NewChannelView({ wantsToPost = false }, accountViewModel = accountViewModel)
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
onClick = { wantsToPost = true },
|
||||
modifier = Size55Modifier,
|
||||
shape = CircleShape,
|
||||
colors =
|
||||
ButtonDefaults.outlinedButtonColors(containerColor = MaterialTheme.colorScheme.primary),
|
||||
contentPadding = ZeroPadding,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Add,
|
||||
contentDescription = stringRes(R.string.new_channel),
|
||||
modifier = Modifier.size(26.dp),
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
@ -25,13 +25,12 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
|
||||
|
||||
@Composable
|
||||
fun ClickableBox(
|
||||
@ -43,7 +42,7 @@ fun ClickableBox(
|
||||
modifier.clickable(
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false, radius = Size24dp),
|
||||
indication = ripple24dp,
|
||||
onClick = onClick,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
@ -64,7 +63,7 @@ fun ClickableBox(
|
||||
modifier.combinedClickable(
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false, radius = Size24dp),
|
||||
indication = ripple24dp,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
),
|
||||
|
@ -26,6 +26,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -34,13 +35,13 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
fun ClickableNoteTag(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val route = routeFor(baseNote, accountViewModel.userProfile())
|
||||
|
||||
ClickableText(
|
||||
text = AnnotatedString("@${baseNote.idNote().toShortenHex()}"),
|
||||
onClick = { nav("Note/${baseNote.idHex}") },
|
||||
onClick = { nav.nav("Note/${baseNote.idHex}") },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary),
|
||||
)
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ import androidx.compose.ui.unit.sp
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
||||
import com.vitorpamplona.amethyst.ui.note.njumpLink
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -78,7 +79,7 @@ fun ClickableRoute(
|
||||
word: String,
|
||||
nip19: Nip19Bech32.ParseReturn,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
when (val entity = nip19.entity) {
|
||||
is Nip19Bech32.NPub -> DisplayUser(entity.hex, nip19.nip19raw, nip19.additionalChars, accountViewModel, nav)
|
||||
@ -122,7 +123,7 @@ private fun LoadAndDisplayEvent(
|
||||
event: Event,
|
||||
additionalChars: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadOrCreateNote(event, accountViewModel) {
|
||||
if (it != null) {
|
||||
@ -150,7 +151,7 @@ fun DisplayEvent(
|
||||
nip19: String,
|
||||
additionalChars: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadNote(hex, accountViewModel) {
|
||||
if (it != null) {
|
||||
@ -178,7 +179,7 @@ private fun DisplayNoteLink(
|
||||
kind: Int?,
|
||||
addedCharts: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteState by it.live().metadata.observeAsState()
|
||||
|
||||
@ -233,7 +234,7 @@ private fun DisplayAddress(
|
||||
originalNip19: String,
|
||||
additionalChars: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var noteBase by remember(nip19) { mutableStateOf(accountViewModel.getNoteIfExists(nip19.atag)) }
|
||||
|
||||
@ -277,7 +278,7 @@ public fun DisplayUser(
|
||||
originalNip19: String,
|
||||
additionalChars: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var userBase by
|
||||
remember(userHex) {
|
||||
@ -312,7 +313,7 @@ public fun DisplayUser(
|
||||
public fun RenderUserAsClickableText(
|
||||
baseUser: User,
|
||||
additionalChars: String?,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val userState by baseUser.live().userMetadataInfo.observeAsState()
|
||||
|
||||
@ -335,7 +336,7 @@ fun CreateClickableText(
|
||||
fontWeight: FontWeight? = null,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
route: String,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
CreateClickableText(
|
||||
clickablePart,
|
||||
@ -344,7 +345,7 @@ fun CreateClickableText(
|
||||
overrideColor,
|
||||
fontWeight,
|
||||
fontSize,
|
||||
) { nav(route) }
|
||||
) { nav.nav(route) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -627,7 +628,7 @@ fun CreateClickableTextWithEmoji(
|
||||
fontWeight: FontWeight = FontWeight.Normal,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
route: String,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
tags: ImmutableListOfLists<String>?,
|
||||
) {
|
||||
CustomEmojiChecker(
|
||||
@ -658,7 +659,7 @@ fun CreateClickableTextWithEmoji(
|
||||
suffix,
|
||||
nonClickablePartStyle,
|
||||
) {
|
||||
nav(route)
|
||||
nav.nav(route)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -29,11 +29,12 @@ import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
|
||||
@Composable
|
||||
fun ClickableUserTag(
|
||||
user: User,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val route = remember { "User/${user.pubkeyHex}" }
|
||||
|
||||
@ -44,7 +45,7 @@ fun ClickableUserTag(
|
||||
|
||||
ClickableText(
|
||||
text = userName,
|
||||
onClick = { nav(route) },
|
||||
onClick = { nav.nav(route) },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary),
|
||||
)
|
||||
}
|
||||
|
73
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/DisplayErrorMessages.kt
Normal file
73
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/DisplayErrorMessages.kt
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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 androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ResourceToastMsg
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThrowableToastMsg
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@Composable
|
||||
fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
|
||||
val openDialogMsg = accountViewModel.toasts.collectAsStateWithLifecycle(null)
|
||||
|
||||
openDialogMsg.value?.let { obj ->
|
||||
when (obj) {
|
||||
is ResourceToastMsg ->
|
||||
if (obj.params != null) {
|
||||
InformationDialog(
|
||||
stringRes(obj.titleResId),
|
||||
stringRes(obj.resourceId, *obj.params),
|
||||
) {
|
||||
accountViewModel.clearToasts()
|
||||
}
|
||||
} else {
|
||||
InformationDialog(
|
||||
stringRes(obj.titleResId),
|
||||
stringRes(obj.resourceId),
|
||||
) {
|
||||
accountViewModel.clearToasts()
|
||||
}
|
||||
}
|
||||
|
||||
is StringToastMsg ->
|
||||
InformationDialog(
|
||||
obj.title,
|
||||
obj.msg,
|
||||
) {
|
||||
accountViewModel.clearToasts()
|
||||
}
|
||||
|
||||
is ThrowableToastMsg ->
|
||||
InformationDialog(
|
||||
stringRes(obj.titleResId),
|
||||
obj.msg,
|
||||
obj.throwable,
|
||||
) {
|
||||
accountViewModel.clearToasts()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/DisplayNotifyMessages.kt
Normal file
54
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/DisplayNotifyMessages.kt
Normal file
@ -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 androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NotifyRequestDialog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.encoders.RelayUrlFormatter
|
||||
|
||||
@Composable
|
||||
fun DisplayNotifyMessages(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val openDialogMsg =
|
||||
accountViewModel.account.transientPaymentRequests.collectAsStateWithLifecycle(null)
|
||||
|
||||
openDialogMsg.value?.firstOrNull()?.let { request ->
|
||||
NotifyRequestDialog(
|
||||
title =
|
||||
stringRes(
|
||||
id = R.string.payment_required_title,
|
||||
RelayUrlFormatter.displayUrl(request.relayUrl),
|
||||
),
|
||||
textContent = request.description,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
) {
|
||||
accountViewModel.dismissPaymentRequest(request)
|
||||
}
|
||||
}
|
||||
}
|
@ -42,6 +42,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.richtext.ExpandableTextCutOffCalculator
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.getGradient
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@ -66,7 +67,7 @@ fun ExpandableRichTextViewer(
|
||||
id: String,
|
||||
callbackUri: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var showFullText by
|
||||
remember {
|
||||
|
@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.components
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.core.net.toUri
|
||||
import com.abedelazizshe.lightcompressorlibrary.CompressionListener
|
||||
@ -46,10 +47,20 @@ class MediaCompressor {
|
||||
applicationContext: Context,
|
||||
onReady: (Uri, String?, Long?) -> Unit,
|
||||
onError: (Int) -> Unit,
|
||||
mediaQuality: CompressorQuality,
|
||||
) {
|
||||
checkNotInMainThread()
|
||||
|
||||
if (contentType?.startsWith("video", true) == true) {
|
||||
val videoQuality =
|
||||
when (mediaQuality) {
|
||||
CompressorQuality.VERY_LOW -> VideoQuality.VERY_LOW
|
||||
CompressorQuality.LOW -> VideoQuality.LOW
|
||||
CompressorQuality.MEDIUM -> VideoQuality.MEDIUM
|
||||
CompressorQuality.HIGH -> VideoQuality.HIGH
|
||||
CompressorQuality.VERY_HIGH -> VideoQuality.VERY_HIGH
|
||||
}
|
||||
Log.d("MediaCompressor", "Using video compression $mediaQuality")
|
||||
VideoCompressor.start(
|
||||
// => This is required
|
||||
context = applicationContext,
|
||||
@ -65,7 +76,7 @@ class MediaCompressor {
|
||||
appSpecificStorageConfiguration = AppSpecificStorageConfiguration(),
|
||||
configureWith =
|
||||
Configuration(
|
||||
quality = VideoQuality.MEDIUM,
|
||||
quality = videoQuality,
|
||||
// => required name
|
||||
videoNames = listOf(UUID.randomUUID().toString()),
|
||||
),
|
||||
@ -110,10 +121,19 @@ class MediaCompressor {
|
||||
!contentType.contains("gif") &&
|
||||
!contentType.contains("svg")
|
||||
) {
|
||||
val imageQuality =
|
||||
when (mediaQuality) {
|
||||
CompressorQuality.VERY_LOW -> 40
|
||||
CompressorQuality.LOW -> 50
|
||||
CompressorQuality.MEDIUM -> 60
|
||||
CompressorQuality.HIGH -> 80
|
||||
CompressorQuality.VERY_HIGH -> 90
|
||||
}
|
||||
try {
|
||||
Log.d("MediaCompressor", "Using image compression $mediaQuality")
|
||||
val compressedImageFile =
|
||||
Compressor.compress(applicationContext, from(uri, contentType, applicationContext)) {
|
||||
default(width = 640, format = Bitmap.CompressFormat.JPEG)
|
||||
default(width = 640, format = Bitmap.CompressFormat.JPEG, quality = imageQuality)
|
||||
}
|
||||
onReady(compressedImageFile.toUri(), contentType, compressedImageFile.length())
|
||||
} catch (e: Exception) {
|
||||
@ -162,4 +182,28 @@ class MediaCompressor {
|
||||
}
|
||||
return arrayOf(name, extension)
|
||||
}
|
||||
|
||||
fun intToCompressorQuality(mediaQualityFloat: Int): CompressorQuality =
|
||||
when (mediaQualityFloat) {
|
||||
0 -> CompressorQuality.LOW
|
||||
1 -> CompressorQuality.MEDIUM
|
||||
2 -> CompressorQuality.HIGH
|
||||
else -> CompressorQuality.MEDIUM
|
||||
}
|
||||
|
||||
fun compressorQualityToInt(compressorQuality: CompressorQuality): Int =
|
||||
when (compressorQuality) {
|
||||
CompressorQuality.LOW -> 0
|
||||
CompressorQuality.MEDIUM -> 1
|
||||
CompressorQuality.HIGH -> 2
|
||||
else -> 1
|
||||
}
|
||||
}
|
||||
|
||||
enum class CompressorQuality {
|
||||
VERY_LOW,
|
||||
LOW,
|
||||
MEDIUM,
|
||||
HIGH,
|
||||
VERY_HIGH,
|
||||
}
|
||||
|
@ -85,6 +85,8 @@ import com.vitorpamplona.amethyst.model.checkForHashtagWithIcon
|
||||
import com.vitorpamplona.amethyst.service.CachedRichTextParser
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.markdown.RenderContentAsMarkdown
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -115,7 +117,7 @@ fun RichTextViewer(
|
||||
backgroundColor: MutableState<Color>,
|
||||
callbackUri: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (remember(content) { isMarkdown(content) }) {
|
||||
@ -129,7 +131,7 @@ fun RichTextViewer(
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderStrangeNamePreview() {
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
Column(modifier = Modifier.padding(10.dp)) {
|
||||
RenderRegular(
|
||||
@ -152,7 +154,7 @@ fun RenderStrangeNamePreview() {
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderRegularPreview() {
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
Column(modifier = Modifier.padding(10.dp)) {
|
||||
RenderRegular(
|
||||
@ -192,7 +194,7 @@ fun RenderRegularPreview() {
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderRegularPreview2() {
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
RenderRegular(
|
||||
"#Amethyst v0.84.1: ncryptsec support (NIP-49)",
|
||||
EmptyTagList,
|
||||
@ -229,7 +231,7 @@ fun RenderRegularPreview3() {
|
||||
arrayOf("proxy", "https://misskey.io/notes/9q0x6gtdysir03qh", "activitypub"),
|
||||
),
|
||||
)
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
|
||||
RenderRegular(
|
||||
@ -266,7 +268,7 @@ private fun RenderRegular(
|
||||
backgroundColor: MutableState<Color>,
|
||||
callbackUri: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
RenderRegular(content, tags, callbackUri) { word, state ->
|
||||
if (canPreview) {
|
||||
@ -359,7 +361,7 @@ private fun RenderWordWithoutPreview(
|
||||
state: RichTextViewerState,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
when (word) {
|
||||
// Don't preview Images
|
||||
@ -390,7 +392,7 @@ private fun RenderWordWithPreview(
|
||||
quotesLeft: Int,
|
||||
callbackUri: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
when (word) {
|
||||
is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||
@ -448,7 +450,7 @@ fun BechLink(
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val loadedLink by produceCachedState(cache = accountViewModel.bechLinkCache, key = word)
|
||||
|
||||
@ -488,7 +490,7 @@ fun DisplayFullNote(
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
NoteCompose(
|
||||
baseNote = note,
|
||||
@ -510,7 +512,7 @@ fun DisplayFullNote(
|
||||
@Composable
|
||||
fun HashTag(
|
||||
segment: HashTagSegment,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val background = MaterialTheme.colorScheme.onBackground
|
||||
@ -542,12 +544,12 @@ fun HashTag(
|
||||
modifier =
|
||||
remember {
|
||||
Modifier.clickable {
|
||||
nav("Hashtag/${segment.hashtag}")
|
||||
nav.nav("Hashtag/${segment.hashtag}")
|
||||
}
|
||||
},
|
||||
inlineContent =
|
||||
if (hashtagIcon != null) {
|
||||
mapOf("inlineContent" to InlineIcon(hashtagIcon))
|
||||
mapOf("inlineContent" to inlineIcon(hashtagIcon))
|
||||
} else {
|
||||
emptyMap()
|
||||
},
|
||||
@ -555,7 +557,7 @@ fun HashTag(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InlineIcon(hashtagIcon: HashtagIcon) =
|
||||
private fun inlineIcon(hashtagIcon: HashtagIcon) =
|
||||
InlineTextContent(inlinePlaceholder) {
|
||||
Icon(
|
||||
imageVector = hashtagIcon.icon,
|
||||
@ -569,7 +571,7 @@ private fun InlineIcon(hashtagIcon: HashtagIcon) =
|
||||
fun TagLink(
|
||||
word: HashIndexUserSegment,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadUser(baseUserHex = word.hex, accountViewModel) {
|
||||
if (it == null) {
|
||||
@ -610,7 +612,7 @@ fun TagLink(
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadNote(baseNoteHex = word.hex, accountViewModel) {
|
||||
if (it == null) {
|
||||
@ -639,7 +641,7 @@ private fun DisplayNoteFromTag(
|
||||
quotesLeft: Int,
|
||||
accountViewModel: AccountViewModel,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
if (canPreview && quotesLeft > 0) {
|
||||
NoteCompose(
|
||||
@ -662,7 +664,7 @@ private fun DisplayNoteFromTag(
|
||||
private fun DisplayUserFromTag(
|
||||
baseUser: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val meta by baseUser.live().userMetadataInfo.observeAsState(baseUser.info)
|
||||
|
||||
|
@ -826,12 +826,18 @@ private fun RenderVideoPlayer(
|
||||
keepPlaying.value = newKeepPlaying
|
||||
}
|
||||
|
||||
AnimatedSaveButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size110dp)) { context ->
|
||||
saveImage(videoUri, mimeType, context, accountViewModel)
|
||||
}
|
||||
if (!videoUri.endsWith(".m3u8")) {
|
||||
AnimatedSaveButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size110dp)) { context ->
|
||||
saveImage(videoUri, mimeType, context, accountViewModel)
|
||||
}
|
||||
|
||||
AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle ->
|
||||
ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, null, null, null, mimeType, toggle)
|
||||
AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle ->
|
||||
ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, null, null, null, mimeType, toggle)
|
||||
}
|
||||
} else {
|
||||
AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size110dp)) { popupExpanded, toggle ->
|
||||
ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, null, null, null, mimeType, toggle)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
controller.volume = 0f
|
||||
|
@ -24,7 +24,6 @@ import android.Manifest
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
@ -54,7 +53,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.SideEffect
|
||||
@ -144,22 +142,6 @@ fun ZoomableImageDialog(
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(key1 = Unit) {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
view.windowInsetsController?.hide(
|
||||
WindowInsets.Type.systemBars(),
|
||||
)
|
||||
}
|
||||
|
||||
onDispose {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
view.windowInsetsController?.show(
|
||||
WindowInsets.Type.systemBars(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) {
|
||||
DialogContent(allImages, imageUrl, onDismiss, accountViewModel)
|
||||
@ -273,34 +255,36 @@ private fun DialogContent(
|
||||
ShareImageAction(accountViewModel = accountViewModel, popupExpanded = popupExpanded, myContent, onDismiss = { popupExpanded.value = false })
|
||||
}
|
||||
|
||||
val localContext = LocalContext.current
|
||||
if (myContent !is MediaUrlContent || !myContent.url.endsWith(".m3u8")) {
|
||||
val localContext = LocalContext.current
|
||||
|
||||
val writeStoragePermissionState =
|
||||
rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) { isGranted ->
|
||||
if (isGranted) {
|
||||
saveImage(myContent, localContext, accountViewModel)
|
||||
val writeStoragePermissionState =
|
||||
rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) { isGranted ->
|
||||
if (isGranted) {
|
||||
saveImage(myContent, localContext, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ||
|
||||
writeStoragePermissionState.status.isGranted
|
||||
) {
|
||||
saveImage(myContent, localContext, accountViewModel)
|
||||
} else {
|
||||
writeStoragePermissionState.launchPermissionRequest()
|
||||
}
|
||||
},
|
||||
contentPadding = PaddingValues(horizontal = Size5dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors().copy(containerColor = MaterialTheme.colorScheme.background),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Download,
|
||||
modifier = Size20Modifier,
|
||||
contentDescription = stringRes(R.string.save_to_gallery),
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ||
|
||||
writeStoragePermissionState.status.isGranted
|
||||
) {
|
||||
saveImage(myContent, localContext, accountViewModel)
|
||||
} else {
|
||||
writeStoragePermissionState.launchPermissionRequest()
|
||||
}
|
||||
},
|
||||
contentPadding = PaddingValues(horizontal = Size5dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors().copy(containerColor = MaterialTheme.colorScheme.background),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Download,
|
||||
modifier = Size20Modifier,
|
||||
contentDescription = stringRes(R.string.save_to_gallery),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import com.vitorpamplona.amethyst.ui.components.DisplayFullNote
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayUser
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadedBechLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font17SP
|
||||
@ -59,7 +60,7 @@ class MarkdownMediaRenderer(
|
||||
val backgroundColor: MutableState<Color>,
|
||||
val callbackUri: String? = null,
|
||||
val accountViewModel: AccountViewModel,
|
||||
val nav: (String) -> Unit,
|
||||
val nav: INav,
|
||||
) : MediaRenderer {
|
||||
val parser = RichTextParser()
|
||||
|
||||
|
20
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt
20
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt
@ -41,6 +41,8 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.MarkdownTextStyle
|
||||
@ -65,7 +67,7 @@ fun RenderContentAsMarkdown(
|
||||
backgroundColor: MutableState<Color>,
|
||||
callbackUri: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
val onClick =
|
||||
@ -73,7 +75,7 @@ fun RenderContentAsMarkdown(
|
||||
{ link: String ->
|
||||
val route = uriToRoute(link)
|
||||
if (route != null) {
|
||||
nav(route)
|
||||
nav.nav(route)
|
||||
} else {
|
||||
runCatching { uri.openUri(link) }
|
||||
}
|
||||
@ -116,7 +118,7 @@ fun RenderContentAsMarkdown(
|
||||
fun RenderContentAsMarkdownPreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
ThemeComparisonRow {
|
||||
val background = MaterialTheme.colorScheme.background
|
||||
@ -168,7 +170,7 @@ fun RenderContentAsMarkdownPreview() {
|
||||
fun RenderContentAsMarkdownListsPreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
ThemeComparisonRow {
|
||||
val background = MaterialTheme.colorScheme.background
|
||||
@ -218,7 +220,7 @@ fun RenderContentAsMarkdownListsPreview() {
|
||||
fun RenderContentAsMarkdownCodePreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
ThemeComparisonRow {
|
||||
val background = MaterialTheme.colorScheme.background
|
||||
@ -272,7 +274,7 @@ fun RenderContentAsMarkdownCodePreview() {
|
||||
fun RenderContentAsMarkdownTablesPreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
ThemeComparisonRow {
|
||||
val background = MaterialTheme.colorScheme.background
|
||||
@ -313,7 +315,7 @@ fun RenderContentAsMarkdownTablesPreview() {
|
||||
fun RenderContentAsMarkdownFootNotesPreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
ThemeComparisonRow {
|
||||
val background = MaterialTheme.colorScheme.background
|
||||
@ -350,7 +352,7 @@ fun RenderContentAsMarkdownFootNotesPreview() {
|
||||
fun RenderContentAsMarkdownUserPreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
@ -412,7 +414,7 @@ fun RenderContentAsMarkdownUserPreview() {
|
||||
fun RenderContentAsMarkdownNotePreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
|
@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.screen.ZapReqResponse
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ZapReqResponse
|
||||
import com.vitorpamplona.quartz.events.LnZapEventInterface
|
||||
|
||||
class UserProfileZapsFeedFilter(
|
||||
|
@ -21,7 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.ui.feeds
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
@ -54,6 +58,8 @@ class FeedContentState(
|
||||
|
||||
private var lastFeedKey: String? = null
|
||||
|
||||
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
fun sendToTop() {
|
||||
if (scrolltoTopPending) return
|
||||
|
||||
@ -72,16 +78,21 @@ class FeedContentState(
|
||||
fun refreshSuspended() {
|
||||
checkNotInMainThread()
|
||||
|
||||
lastFeedKey = localFilter.feedKey()
|
||||
val notes = localFilter.loadTop().distinctBy { it.idHex }.toImmutableList()
|
||||
isRefreshing.value = true
|
||||
try {
|
||||
lastFeedKey = localFilter.feedKey()
|
||||
val notes = localFilter.loadTop().distinctBy { it.idHex }.toImmutableList()
|
||||
|
||||
val oldNotesState = _feedContent.value
|
||||
if (oldNotesState is FeedState.Loaded) {
|
||||
if (!equalImmutableLists(notes, oldNotesState.feed.value.list)) {
|
||||
val oldNotesState = _feedContent.value
|
||||
if (oldNotesState is FeedState.Loaded) {
|
||||
if (!equalImmutableLists(notes, oldNotesState.feed.value.list)) {
|
||||
updateFeed(notes)
|
||||
}
|
||||
} else {
|
||||
updateFeed(notes)
|
||||
}
|
||||
} else {
|
||||
updateFeed(notes)
|
||||
} finally {
|
||||
isRefreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
@ -38,7 +39,7 @@ fun RefresheableFeedContentStateView(
|
||||
enablePullRefresh: Boolean = true,
|
||||
scrollStateKey: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
RefresheableBox(feedContentState, enablePullRefresh) {
|
||||
SaveableFeedContentState(feedContentState, scrollStateKey) { listState ->
|
||||
@ -88,7 +89,7 @@ fun RenderFeedContentState(
|
||||
feedContentState: FeedContentState,
|
||||
accountViewModel: AccountViewModel,
|
||||
listState: LazyListState,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
routeForLastRead: String?,
|
||||
onLoaded: @Composable (FeedState.Loaded) -> Unit = { FeedLoaded(it, listState, routeForLastRead, accountViewModel, nav) },
|
||||
onEmpty: @Composable () -> Unit = { FeedEmpty(feedContentState::invalidateData) },
|
||||
|
@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.theme.DividerThickness
|
||||
@ -43,7 +44,7 @@ fun FeedLoaded(
|
||||
listState: LazyListState,
|
||||
routeForLastRead: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val items by loaded.feed.collectAsStateWithLifecycle()
|
||||
|
||||
|
@ -20,6 +20,10 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.feeds
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
||||
interface InvalidatableContent {
|
||||
fun invalidateData(ignoreIfDoing: Boolean = false)
|
||||
|
||||
val isRefreshing: MutableState<Boolean>
|
||||
}
|
||||
|
39
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AmethystClickableIcon.kt
Normal file
39
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AmethystClickableIcon.kt
Normal file
@ -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.ui.navigation
|
||||
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.vitorpamplona.amethyst.debugState
|
||||
import com.vitorpamplona.amethyst.ui.note.AmethystIcon
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size40dp
|
||||
|
||||
@Composable
|
||||
fun AmethystClickableIcon() {
|
||||
val context = LocalContext.current
|
||||
|
||||
IconButton(
|
||||
onClick = { debugState(context) },
|
||||
) {
|
||||
AmethystIcon(Size40dp)
|
||||
}
|
||||
}
|
@ -50,7 +50,6 @@ import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
@ -118,17 +117,17 @@ fun IfKeyboardClosed(inner: @Composable () -> Unit) {
|
||||
|
||||
@Composable
|
||||
fun AppBottomBar(
|
||||
selectedRoute: Route?,
|
||||
accountViewModel: AccountViewModel,
|
||||
navEntryState: State<NavBackStackEntry?>,
|
||||
nav: (Route, Boolean) -> Unit,
|
||||
) {
|
||||
IfKeyboardClosed { RenderBottomMenu(accountViewModel, navEntryState, nav) }
|
||||
IfKeyboardClosed { RenderBottomMenu(selectedRoute, accountViewModel, nav) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderBottomMenu(
|
||||
selectedRoute: Route?,
|
||||
accountViewModel: AccountViewModel,
|
||||
navEntryState: State<NavBackStackEntry?>,
|
||||
nav: (Route, Boolean) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
@ -142,30 +141,17 @@ private fun RenderBottomMenu(
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
NavigationBar(tonalElevation = Size0dp) {
|
||||
NavigationBar(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
tonalElevation = Size0dp,
|
||||
) {
|
||||
bottomNavigationItems.forEach { item ->
|
||||
ObserveSelection(item, navEntryState) { selected ->
|
||||
HasNewItemsIcon(selected, item, accountViewModel, nav)
|
||||
}
|
||||
HasNewItemsIcon(item == selectedRoute, item, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ObserveSelection(
|
||||
route: Route,
|
||||
navEntryState: State<NavBackStackEntry?>,
|
||||
content: @Composable (Boolean) -> Unit,
|
||||
) {
|
||||
content(
|
||||
navEntryState.value
|
||||
?.destination
|
||||
?.route
|
||||
?.startsWith(route.base) ?: false,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.HasNewItemsIcon(
|
||||
selected: Boolean,
|
||||
@ -211,7 +197,7 @@ fun AddNotifIconIfNeeded(
|
||||
accountViewModel: AccountViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val flow = accountViewModel.notificationDots.hasNewItems[route] ?: return
|
||||
val flow = accountViewModel.hasNewItems[route] ?: return
|
||||
val hasNewItems by flow.collectAsStateWithLifecycle()
|
||||
if (hasNewItems) {
|
||||
NotificationDotIcon(modifier)
|
||||
|
@ -26,6 +26,10 @@ import android.content.Intent
|
||||
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.slideOutHorizontally
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@ -36,327 +40,261 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayErrorMessages
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayNotifyMessages
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
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.BookmarkListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DraftListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.GeoHashScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HiddenUsersScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRedirectScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ProfileScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThreadScreen
|
||||
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
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChatroomScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChatroomScreenByAuthor
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.CommunityScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.CommunityScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.DiscoverScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.NIP90ContentDiscoveryScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.drafts.DraftListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.DvmContentDiscoveryScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.HashtagScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.HomeScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.ThreadScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.VideoScreen
|
||||
import com.vitorpamplona.amethyst.ui.uriToRoute
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URLDecoder
|
||||
|
||||
fun NavBackStackEntry.id(): String? = arguments?.getString("id")
|
||||
|
||||
fun NavBackStackEntry.message(): String? =
|
||||
arguments?.getString("message")?.let {
|
||||
URLDecoder.decode(it, "utf-8")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppNavigation(
|
||||
navController: NavHostController,
|
||||
accountViewModel: AccountViewModel,
|
||||
accountStateViewModel: AccountStateViewModel,
|
||||
sharedPreferencesViewModel: SharedPreferencesViewModel,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val nav =
|
||||
remember(navController) {
|
||||
{ route: String ->
|
||||
scope.launch {
|
||||
if (getRouteWithArguments(navController) != route) {
|
||||
navController.navigate(route)
|
||||
}
|
||||
}
|
||||
Unit
|
||||
}
|
||||
}
|
||||
val nav = rememberNav()
|
||||
|
||||
NavHost(
|
||||
navController,
|
||||
startDestination = Route.Home.route,
|
||||
enterTransition = { fadeIn(animationSpec = tween(200)) },
|
||||
exitTransition = { fadeOut(animationSpec = tween(200)) },
|
||||
) {
|
||||
Route.Home.let { route ->
|
||||
AccountSwitcherAndLeftDrawerLayout(accountViewModel, accountStateViewModel, nav) {
|
||||
NavHost(
|
||||
navController = nav.controller,
|
||||
startDestination = Route.Home.route,
|
||||
enterTransition = { fadeIn(animationSpec = tween(200)) },
|
||||
exitTransition = { fadeOut(animationSpec = tween(200)) },
|
||||
) {
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = { it ->
|
||||
val nip47 = it.arguments?.getString("nip47")
|
||||
Route.Home.route,
|
||||
Route.Home.arguments,
|
||||
) {
|
||||
val nip47 = it.arguments?.getString("nip47")
|
||||
|
||||
HomeScreen(
|
||||
newThreadsFeedState = accountViewModel.feedStates.homeNewThreads,
|
||||
repliesFeedState = accountViewModel.feedStates.homeReplies,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
nip47 = nip47,
|
||||
)
|
||||
HomeScreen(accountViewModel, nav, nip47)
|
||||
|
||||
if (nip47 != null) {
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
launch {
|
||||
delay(1000)
|
||||
it.arguments?.remove("nip47")
|
||||
}
|
||||
if (nip47 != null) {
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
launch {
|
||||
delay(1000)
|
||||
it.arguments?.remove("nip47")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Message.route,
|
||||
content = {
|
||||
ChatroomListScreen(
|
||||
accountViewModel.feedStates.dmKnown,
|
||||
accountViewModel.feedStates.dmNew,
|
||||
composable(Route.Message.route) { ChatroomListScreen(accountViewModel, nav) }
|
||||
composable(Route.Video.route) { VideoScreen(accountViewModel, nav) }
|
||||
composable(Route.Discover.route) { DiscoverScreen(accountViewModel, nav) }
|
||||
composable(Route.Notification.route) { NotificationScreen(sharedPreferencesViewModel, accountViewModel, nav) }
|
||||
|
||||
composable(Route.Search.route) { SearchScreen(accountViewModel, nav) }
|
||||
|
||||
composable(
|
||||
Route.BlockedUsers.route,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) { SecurityFiltersScreen(accountViewModel, nav) }
|
||||
|
||||
composable(
|
||||
Route.Bookmarks.route,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) { BookmarkListScreen(accountViewModel, nav) }
|
||||
|
||||
composable(
|
||||
Route.Drafts.route,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) { DraftListScreen(accountViewModel, nav) }
|
||||
|
||||
composable(
|
||||
Route.ContentDiscovery.route,
|
||||
Route.ContentDiscovery.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
DvmContentDiscoveryScreen(it.id(), accountViewModel, nav)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Profile.route,
|
||||
Route.Profile.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
ProfileScreen(it.id(), accountViewModel, nav)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Note.route,
|
||||
Route.Note.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
ThreadScreen(it.id(), accountViewModel, nav)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Hashtag.route,
|
||||
Route.Hashtag.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
HashtagScreen(it.id(), accountViewModel, nav)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Geohash.route,
|
||||
Route.Geohash.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
GeoHashScreen(it.id(), accountViewModel, nav)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Community.route,
|
||||
Route.Community.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
CommunityScreen(it.id(), accountViewModel, nav)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Room.route,
|
||||
Route.Room.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
ChatroomScreen(
|
||||
roomId = it.id(),
|
||||
draftMessage = it.message(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.RoomByAuthor.route,
|
||||
Route.RoomByAuthor.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
ChatroomScreenByAuthor(it.id(), null, accountViewModel, nav)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Channel.route,
|
||||
Route.Channel.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
ChannelScreen(
|
||||
channelId = it.id(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Event.route,
|
||||
Route.Event.arguments,
|
||||
) {
|
||||
LoadRedirectScreen(
|
||||
eventId = it.id(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
composable(
|
||||
Route.Settings.route,
|
||||
Route.Settings.arguments,
|
||||
enterTransition = { slideInHorizontallyFromEnd },
|
||||
exitTransition = { scaleOut },
|
||||
popEnterTransition = { scaleIn },
|
||||
popExitTransition = { slideOutHorizontallyToEnd },
|
||||
) {
|
||||
SettingsScreen(
|
||||
sharedPreferencesViewModel,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Route.Video.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
VideoScreen(
|
||||
videoFeedContentState = accountViewModel.feedStates.videoFeed,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Discover.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
DiscoverScreen(
|
||||
discoveryContentNIP89FeedContentState = accountViewModel.feedStates.discoverDVMs,
|
||||
discoveryMarketplaceFeedContentState = accountViewModel.feedStates.discoverMarketplace,
|
||||
discoveryLiveFeedContentState = accountViewModel.feedStates.discoverLive,
|
||||
discoveryCommunityFeedContentState = accountViewModel.feedStates.discoverCommunities,
|
||||
discoveryChatFeedContentState = accountViewModel.feedStates.discoverPublicChats,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Search.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
SearchScreen(
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Notification.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
NotificationScreen(
|
||||
notifFeedContentState = accountViewModel.feedStates.notifications,
|
||||
notifSummaryState = accountViewModel.feedStates.notificationSummary,
|
||||
sharedPreferencesViewModel = sharedPreferencesViewModel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, nav) })
|
||||
composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, nav) })
|
||||
composable(Route.Drafts.route, content = { DraftListScreen(accountViewModel, nav) })
|
||||
|
||||
Route.ContentDiscovery.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
it.arguments?.getString("id")?.let { id ->
|
||||
NIP90ContentDiscoveryScreen(
|
||||
appDefinitionEventId = id,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Profile.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
ProfileScreen(
|
||||
userId = it.arguments?.getString("id"),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Note.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
ThreadScreen(
|
||||
noteId = it.arguments?.getString("id"),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Hashtag.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
HashtagScreen(
|
||||
tag = it.arguments?.getString("id"),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Geohash.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
GeoHashScreen(
|
||||
tag = it.arguments?.getString("id"),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Community.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
CommunityScreen(
|
||||
aTagHex = it.arguments?.getString("id"),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Room.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
val decodedMessage =
|
||||
it.arguments?.getString("message")?.let {
|
||||
URLDecoder.decode(it, "utf-8")
|
||||
}
|
||||
ChatroomScreen(
|
||||
roomId = it.arguments?.getString("id"),
|
||||
draftMessage = decodedMessage,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.RoomByAuthor.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
ChatroomScreenByAuthor(
|
||||
authorPubKeyHex = it.arguments?.getString("id"),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Channel.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
ChannelScreen(
|
||||
channelId = it.arguments?.getString("id"),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Event.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
LoadRedirectScreen(
|
||||
eventId = it.arguments?.getString("id"),
|
||||
accountViewModel = accountViewModel,
|
||||
navController = navController,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Settings.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
SettingsScreen(
|
||||
sharedPreferencesViewModel,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigateIfIntentRequested(nav.controller, accountViewModel)
|
||||
|
||||
DisplayErrorMessages(accountViewModel)
|
||||
DisplayNotifyMessages(accountViewModel, nav)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NavigateIfIntentRequested(
|
||||
navController: NavHostController,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val activity = LocalContext.current.getActivity()
|
||||
|
||||
var currentIntentNextPage by remember {
|
||||
@ -397,6 +335,8 @@ fun AppNavigation(
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
DisposableEffect(activity) {
|
||||
val consumer =
|
||||
Consumer<Intent> { intent ->
|
||||
@ -452,3 +392,9 @@ private fun isSameRoute(
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
val slideInHorizontallyFromEnd = slideInHorizontally(animationSpec = tween(), initialOffsetX = { it })
|
||||
val slideOutHorizontallyToEnd = slideOutHorizontally(animationSpec = tween(), targetOffsetX = { it })
|
||||
|
||||
val scaleIn = scaleIn(animationSpec = tween(), initialScale = 0.9f)
|
||||
val scaleOut = scaleOut(animationSpec = tween(), targetScale = 0.9f)
|
||||
|
@ -20,554 +20,67 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.navigation
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Debug
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
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.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.map
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import coil.Coil
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrCommunityDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrGeohashDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.note.AmethystIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadCityName
|
||||
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.SearchIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.types.LongCommunityHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.types.ShortCommunityHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.CodeName
|
||||
import com.vitorpamplona.amethyst.ui.screen.CodeNameType
|
||||
import com.vitorpamplona.amethyst.ui.screen.CommunityName
|
||||
import com.vitorpamplona.amethyst.ui.screen.FollowListState
|
||||
import com.vitorpamplona.amethyst.ui.screen.GeoHashName
|
||||
import com.vitorpamplona.amethyst.ui.screen.HashtagName
|
||||
import com.vitorpamplona.amethyst.ui.screen.Name
|
||||
import com.vitorpamplona.amethyst.ui.screen.PeopleListName
|
||||
import com.vitorpamplona.amethyst.ui.screen.ResourceName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DislayGeoTagHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.GeoHashActionOptions
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagActionOptions
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadRoom
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadRoomByAuthor
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LongChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LongRoomHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.RoomNameOnlyDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ShortChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.observeAppDefinition
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size22Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size40dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.ammolite.relays.Client
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun AppTopBar(
|
||||
navEntryState: State<NavBackStackEntry?>,
|
||||
openDrawer: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
val currentRoute by
|
||||
remember(navEntryState.value) {
|
||||
derivedStateOf {
|
||||
navEntryState.value
|
||||
?.destination
|
||||
?.route
|
||||
?.substringBefore("?")
|
||||
}
|
||||
}
|
||||
|
||||
val id by
|
||||
remember(navEntryState.value) {
|
||||
derivedStateOf { navEntryState.value?.arguments?.getString("id") }
|
||||
}
|
||||
|
||||
RenderTopRouteBar(currentRoute, id, accountViewModel.feedStates.feedListOptions, openDrawer, accountViewModel, nav, navPopBack)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderTopRouteBar(
|
||||
currentRoute: String?,
|
||||
id: String?,
|
||||
followLists: FollowListState,
|
||||
openDrawer: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
when (currentRoute) {
|
||||
Route.Home.base -> HomeTopBar(followLists, openDrawer, accountViewModel, nav)
|
||||
Route.Video.base -> StoriesTopBar(followLists, openDrawer, accountViewModel, nav)
|
||||
Route.Discover.base -> DiscoveryTopBar(followLists, openDrawer, accountViewModel, nav)
|
||||
Route.Notification.base -> NotificationTopBar(followLists, openDrawer, accountViewModel, nav)
|
||||
Route.Settings.base -> TopBarWithBackButton(stringRes(id = R.string.application_preferences), navPopBack)
|
||||
Route.Bookmarks.base -> TopBarWithBackButton(stringRes(id = R.string.bookmarks), navPopBack)
|
||||
Route.Drafts.base -> TopBarWithBackButton(stringRes(id = R.string.drafts), navPopBack)
|
||||
|
||||
else -> {
|
||||
if (id != null) {
|
||||
when (currentRoute) {
|
||||
Route.Channel.base -> ChannelTopBar(id, accountViewModel, nav, navPopBack)
|
||||
Route.RoomByAuthor.base -> RoomByAuthorTopBar(id, accountViewModel, nav, navPopBack)
|
||||
Route.Room.base -> RoomTopBar(id, accountViewModel, nav, navPopBack)
|
||||
Route.Community.base -> CommunityTopBar(id, accountViewModel, nav, navPopBack)
|
||||
Route.Hashtag.base -> HashTagTopBar(id, accountViewModel, navPopBack)
|
||||
Route.Geohash.base -> GeoHashTopBar(id, accountViewModel, navPopBack)
|
||||
Route.Note.base -> ThreadTopBar(id, accountViewModel, navPopBack)
|
||||
Route.ContentDiscovery.base -> DvmTopBar(id, accountViewModel, nav, navPopBack)
|
||||
else -> MainTopBar(openDrawer, accountViewModel, nav)
|
||||
}
|
||||
} else {
|
||||
MainTopBar(openDrawer, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThreadTopBar(
|
||||
id: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
FlexibleTopBarWithBackButton(
|
||||
title = { Text(stringRes(id = R.string.thread_title)) },
|
||||
popBack = navPopBack,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GeoHashTopBar(
|
||||
tag: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
FlexibleTopBarWithBackButton(
|
||||
title = {
|
||||
DislayGeoTagHeader(tag, remember { Modifier.weight(1f) })
|
||||
GeoHashActionOptions(tag, accountViewModel)
|
||||
},
|
||||
popBack = navPopBack,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HashTagTopBar(
|
||||
tag: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
FlexibleTopBarWithBackButton(
|
||||
title = {
|
||||
Text(
|
||||
remember(tag) { "#$tag" },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
HashtagActionOptions(tag, accountViewModel)
|
||||
},
|
||||
popBack = navPopBack,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CommunityTopBar(
|
||||
id: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
LoadAddressableNote(aTagHex = id, accountViewModel) { baseNote ->
|
||||
if (baseNote != null) {
|
||||
FlexibleTopBarWithBackButton(
|
||||
title = { ShortCommunityHeader(baseNote, accountViewModel, nav) },
|
||||
extendableRow = {
|
||||
Column(Modifier.verticalScroll(rememberScrollState())) {
|
||||
LongCommunityHeader(baseNote = baseNote, accountViewModel = accountViewModel, nav = nav)
|
||||
}
|
||||
},
|
||||
popBack = navPopBack,
|
||||
)
|
||||
} else {
|
||||
Spacer(BottomTopHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomTopBar(
|
||||
id: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
LoadRoom(roomId = id, accountViewModel) { room ->
|
||||
if (room != null) {
|
||||
RenderRoomTopBar(room, accountViewModel, nav, navPopBack)
|
||||
} else {
|
||||
Spacer(BottomTopHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DvmTopBar(
|
||||
appDefinitionId: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
FlexibleTopBarWithBackButton(
|
||||
title = {
|
||||
LoadNote(baseNoteHex = appDefinitionId, accountViewModel = accountViewModel) { appDefinitionNote ->
|
||||
if (appDefinitionNote != null) {
|
||||
val card = observeAppDefinition(appDefinitionNote)
|
||||
|
||||
card.cover?.let {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(Size34dp)
|
||||
.clip(shape = CircleShape),
|
||||
)
|
||||
} ?: run { NoteAuthorPicture(baseNote = appDefinitionNote, size = Size34dp, accountViewModel = accountViewModel) }
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
Text(
|
||||
text = card.name,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
popBack = navPopBack,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomByAuthorTopBar(
|
||||
id: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
LoadRoomByAuthor(authorPubKeyHex = id, accountViewModel) { room ->
|
||||
if (room != null) {
|
||||
RenderRoomTopBar(room, accountViewModel, nav, navPopBack)
|
||||
} else {
|
||||
Spacer(BottomTopHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderRoomTopBar(
|
||||
room: ChatroomKey,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
if (room.users.size == 1) {
|
||||
FlexibleTopBarWithBackButton(
|
||||
title = {
|
||||
LoadUser(baseUserHex = room.users.first(), accountViewModel) { baseUser ->
|
||||
if (baseUser != null) {
|
||||
ClickableUserPicture(
|
||||
baseUser = baseUser,
|
||||
accountViewModel = accountViewModel,
|
||||
size = Size34dp,
|
||||
)
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
UsernameDisplay(baseUser, Modifier.weight(1f), fontWeight = FontWeight.Normal, accountViewModel = accountViewModel)
|
||||
}
|
||||
}
|
||||
},
|
||||
extendableRow = {
|
||||
LoadUser(baseUserHex = room.users.first(), accountViewModel) {
|
||||
if (it != null) {
|
||||
UserCompose(
|
||||
baseUser = it,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
popBack = navPopBack,
|
||||
)
|
||||
} else {
|
||||
FlexibleTopBarWithBackButton(
|
||||
title = {
|
||||
NonClickableUserPictures(
|
||||
room = room,
|
||||
accountViewModel = accountViewModel,
|
||||
size = Size34dp,
|
||||
)
|
||||
|
||||
RoomNameOnlyDisplay(
|
||||
room,
|
||||
Modifier
|
||||
.padding(start = 10.dp)
|
||||
.weight(1f),
|
||||
fontWeight = FontWeight.Normal,
|
||||
accountViewModel,
|
||||
)
|
||||
},
|
||||
extendableRow = {
|
||||
LongRoomHeader(room = room, accountViewModel = accountViewModel, nav = nav)
|
||||
},
|
||||
popBack = navPopBack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChannelTopBar(
|
||||
id: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
navPopBack: () -> Unit,
|
||||
) {
|
||||
LoadChannel(baseChannelHex = id, accountViewModel) { baseChannel ->
|
||||
FlexibleTopBarWithBackButton(
|
||||
title = {
|
||||
ShortChannelHeader(
|
||||
baseChannel = baseChannel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
showFlag = true,
|
||||
)
|
||||
},
|
||||
extendableRow = {
|
||||
LongChannelHeader(baseChannel = baseChannel, accountViewModel = accountViewModel, nav = nav)
|
||||
},
|
||||
popBack = navPopBack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoriesTopBar(
|
||||
followLists: FollowListState,
|
||||
openDrawer: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
GenericMainTopBar(openDrawer, accountViewModel, nav) {
|
||||
val list by accountViewModel.account.settings.defaultStoriesFollowList
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
FollowListWithRoutes(
|
||||
followListsModel = followLists,
|
||||
listName = list,
|
||||
) { listName ->
|
||||
accountViewModel.account.settings.changeDefaultStoriesFollowList(listName.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeTopBar(
|
||||
followLists: FollowListState,
|
||||
openDrawer: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
GenericMainTopBar(openDrawer, accountViewModel, nav) {
|
||||
val list by accountViewModel.account.settings.defaultHomeFollowList
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
FollowListWithRoutes(
|
||||
followListsModel = followLists,
|
||||
listName = list,
|
||||
) { listName ->
|
||||
if (listName.type == CodeNameType.ROUTE) {
|
||||
nav(listName.code)
|
||||
} else {
|
||||
accountViewModel.account.settings.changeDefaultHomeFollowList(listName.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NotificationTopBar(
|
||||
followLists: FollowListState,
|
||||
openDrawer: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
GenericMainTopBar(openDrawer, accountViewModel, nav) {
|
||||
val list by accountViewModel.account.settings.defaultNotificationFollowList
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
FollowListWithoutRoutes(
|
||||
followListsModel = followLists,
|
||||
listName = list,
|
||||
) { listName ->
|
||||
accountViewModel.account.settings.changeDefaultNotificationFollowList(listName.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DiscoveryTopBar(
|
||||
followLists: FollowListState,
|
||||
openDrawer: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
GenericMainTopBar(openDrawer, accountViewModel, nav) {
|
||||
val list by accountViewModel.account.settings.defaultDiscoveryFollowList
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
FollowListWithoutRoutes(
|
||||
followListsModel = followLists,
|
||||
listName = list,
|
||||
) { listName ->
|
||||
accountViewModel.account.settings.changeDefaultDiscoveryFollowList(listName.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainTopBar(
|
||||
openDrawer: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
GenericMainTopBar(openDrawer, accountViewModel, nav) { AmethystClickableIcon() }
|
||||
GenericMainTopBar(accountViewModel, nav) { AmethystClickableIcon() }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GenericMainTopBar(
|
||||
openDrawer: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Column(modifier = BottomTopHeight) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
LoggedInUserPictureDrawer(accountViewModel, openDrawer)
|
||||
},
|
||||
actions = { SearchButton { nav(Route.Search.route) } },
|
||||
)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchButton(onClick: () -> Unit) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
) {
|
||||
SearchIcon(modifier = Size22Modifier, MaterialTheme.colorScheme.placeholderText)
|
||||
}
|
||||
TopAppBar(
|
||||
scrollBehavior = rememberHeightDecreaser(),
|
||||
title = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
LoggedInUserPictureDrawer(accountViewModel, nav::openDrawer)
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { nav.nav(Route.Search.route) }) {
|
||||
SearchIcon(modifier = Size22Modifier, MaterialTheme.colorScheme.placeholderText)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -575,14 +88,14 @@ private fun LoggedInUserPictureDrawer(
|
||||
accountViewModel: AccountViewModel,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val profilePicture by
|
||||
accountViewModel.account
|
||||
.userProfile()
|
||||
.live()
|
||||
.profilePictureChanges
|
||||
.observeAsState()
|
||||
|
||||
IconButton(onClick = onClick) {
|
||||
val profilePicture by
|
||||
accountViewModel.account
|
||||
.userProfile()
|
||||
.live()
|
||||
.profilePictureChanges
|
||||
.observeAsState()
|
||||
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = accountViewModel.userProfile().pubkeyHex,
|
||||
model = profilePicture,
|
||||
@ -603,8 +116,9 @@ fun FollowListWithRoutes(
|
||||
) {
|
||||
val allLists by followListsModel.kind3GlobalPeopleRoutes.collectAsStateWithLifecycle()
|
||||
|
||||
SimpleTextSpinner(
|
||||
FeedFilterSpinner(
|
||||
placeholderCode = listName,
|
||||
explainer = stringRes(R.string.select_list_to_filter),
|
||||
options = allLists,
|
||||
onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.kind3Follow) },
|
||||
)
|
||||
@ -618,383 +132,10 @@ fun FollowListWithoutRoutes(
|
||||
) {
|
||||
val allLists by followListsModel.kind3GlobalPeople.collectAsStateWithLifecycle()
|
||||
|
||||
SimpleTextSpinner(
|
||||
FeedFilterSpinner(
|
||||
placeholderCode = listName,
|
||||
explainer = stringRes(R.string.select_list_to_filter),
|
||||
options = allLists,
|
||||
onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.kind3Follow) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleTextSpinner(
|
||||
placeholderCode: String,
|
||||
options: ImmutableList<CodeName>,
|
||||
onSelect: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
var optionsShowing by remember { mutableStateOf(false) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val selectAnOption =
|
||||
stringRes(
|
||||
id = R.string.select_an_option,
|
||||
)
|
||||
|
||||
var currentText by
|
||||
remember(placeholderCode, options) {
|
||||
mutableStateOf(
|
||||
options.firstOrNull { it.code == placeholderCode }?.name?.name(context) ?: selectAnOption,
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Spacer(modifier = Size20Modifier)
|
||||
Text(currentText)
|
||||
Icon(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
null,
|
||||
modifier = Size20Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.matchParentSize()
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
) {
|
||||
optionsShowing = true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (optionsShowing) {
|
||||
options.isNotEmpty().also {
|
||||
SpinnerSelectionDialog(
|
||||
options = options,
|
||||
onDismiss = { optionsShowing = false },
|
||||
onSelect = {
|
||||
currentText = options[it].name.name(context)
|
||||
optionsShowing = false
|
||||
onSelect(it)
|
||||
},
|
||||
) {
|
||||
RenderOption(it.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderOption(option: Name) {
|
||||
when (option) {
|
||||
is GeoHashName -> {
|
||||
LoadCityName(option.geoHashTag) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(text = "/g/$it", color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
is HashtagName -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(text = option.name(), color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
is ResourceName -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(id = option.resourceId),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
is PeopleListName -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
val noteState by
|
||||
option.note
|
||||
.live()
|
||||
.metadata
|
||||
.observeAsState()
|
||||
|
||||
val name = (noteState?.note?.event as? PeopleListEvent)?.nameOrTitle() ?: option.note.dTag() ?: ""
|
||||
|
||||
Text(text = name, color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
is CommunityName -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
val name by
|
||||
option.note
|
||||
.live()
|
||||
.metadata
|
||||
.map { "/n/" + ((it.note as? AddressableNote)?.dTag() ?: "") }
|
||||
.observeAsState()
|
||||
|
||||
Text(text = name ?: "", color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TopBarWithBackButton(
|
||||
caption: String,
|
||||
popBack: () -> Unit,
|
||||
) {
|
||||
Column(modifier = BottomTopHeight) {
|
||||
TopAppBar(
|
||||
title = { Text(caption) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = popBack,
|
||||
modifier = Modifier,
|
||||
) {
|
||||
ArrowBackIcon()
|
||||
}
|
||||
},
|
||||
actions = {},
|
||||
)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FlexibleTopBarWithBackButton(
|
||||
title: @Composable RowScope.() -> Unit,
|
||||
extendableRow: (@Composable () -> Unit)? = null,
|
||||
popBack: () -> Unit,
|
||||
) {
|
||||
Column {
|
||||
MyExtensibleTopAppBar(
|
||||
title = title,
|
||||
extendableRow = extendableRow,
|
||||
navigationIcon = { IconButton(onClick = popBack) { ArrowBackIcon() } },
|
||||
actions = {},
|
||||
)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AmethystClickableIcon() {
|
||||
val context = LocalContext.current
|
||||
|
||||
IconButton(
|
||||
onClick = { debugState(context) },
|
||||
) {
|
||||
AmethystIcon(Size40dp)
|
||||
}
|
||||
}
|
||||
|
||||
fun debugState(context: Context) {
|
||||
Client
|
||||
.allSubscriptions()
|
||||
.forEach { Log.d("STATE DUMP", "${it.key} ${it.value.joinToString { it.filter.toDebugJson() }}") }
|
||||
|
||||
NostrAccountDataSource.printCounter()
|
||||
NostrChannelDataSource.printCounter()
|
||||
NostrChatroomDataSource.printCounter()
|
||||
NostrChatroomListDataSource.printCounter()
|
||||
NostrCommunityDataSource.printCounter()
|
||||
NostrDiscoveryDataSource.printCounter()
|
||||
NostrHashtagDataSource.printCounter()
|
||||
NostrGeohashDataSource.printCounter()
|
||||
NostrHomeDataSource.printCounter()
|
||||
NostrSearchEventOrUserDataSource.printCounter()
|
||||
NostrSingleChannelDataSource.printCounter()
|
||||
NostrSingleEventDataSource.printCounter()
|
||||
NostrSingleUserDataSource.printCounter()
|
||||
NostrThreadDataSource.printCounter()
|
||||
NostrUserProfileDataSource.printCounter()
|
||||
NostrVideoDataSource.printCounter()
|
||||
|
||||
val totalMemoryMb = Runtime.getRuntime().totalMemory() / (1024 * 1024)
|
||||
val freeMemoryMb = Runtime.getRuntime().freeMemory() / (1024 * 1024)
|
||||
val maxMemoryMb = Runtime.getRuntime().maxMemory() / (1024 * 1024)
|
||||
|
||||
val jvmHeapAllocatedMb = totalMemoryMb - freeMemoryMb
|
||||
|
||||
Log.d("STATE DUMP", "Total Heap Allocated: " + jvmHeapAllocatedMb + "/" + maxMemoryMb + " MB")
|
||||
|
||||
val nativeHeap = Debug.getNativeHeapAllocatedSize() / (1024 * 1024)
|
||||
val maxNative = Debug.getNativeHeapSize() / (1024 * 1024)
|
||||
|
||||
Log.d("STATE DUMP", "Total Native Heap Allocated: " + nativeHeap + "/" + maxNative + " MB")
|
||||
|
||||
val activityManager: ActivityManager? = context.getSystemService()
|
||||
if (activityManager != null) {
|
||||
val isLargeHeap = (context.applicationInfo.flags and ApplicationInfo.FLAG_LARGE_HEAP) != 0
|
||||
val memClass = if (isLargeHeap) activityManager.largeMemoryClass else activityManager.memoryClass
|
||||
|
||||
Log.d("STATE DUMP", "Memory Class " + memClass + " MB (largeHeap $isLargeHeap)")
|
||||
}
|
||||
|
||||
Log.d("STATE DUMP", "Connected Relays: " + RelayPool.connectedRelays())
|
||||
|
||||
val imageLoader = Coil.imageLoader(context)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Image Disk Cache ${(imageLoader.diskCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.diskCache?.maxSize ?: 0) / (1024 * 1024)} MB",
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Image Memory Cache ${(imageLoader.memoryCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.memoryCache?.maxSize ?: 0) / (1024 * 1024)} MB",
|
||||
)
|
||||
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Notes: " +
|
||||
LocalCache.notes.filter { _, it -> it.liveSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.notes.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.notes.filter { _, it -> it.event != null }.size +
|
||||
" / " +
|
||||
LocalCache.notes.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Addressables: " +
|
||||
LocalCache.addressables.filter { _, it -> it.liveSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.addressables.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.addressables.filter { _, it -> it.event != null }.size +
|
||||
" / " +
|
||||
LocalCache.addressables.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Users: " +
|
||||
LocalCache.users.filter { _, it -> it.liveSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.users.filter { _, it -> it.flowSet != null }.size +
|
||||
" / " +
|
||||
LocalCache.users.filter { _, it -> it.latestMetadata != null }.size +
|
||||
" / " +
|
||||
LocalCache.users.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Deletion Events: " +
|
||||
LocalCache.deletionIndex.size(),
|
||||
)
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Observable Events: " +
|
||||
LocalCache.observablesByKindAndETag.size +
|
||||
" / " +
|
||||
LocalCache.observablesByKindAndAuthor.size,
|
||||
)
|
||||
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Spam: " +
|
||||
LocalCache.antiSpam.spamMessages.size() + " / " + LocalCache.antiSpam.recentMessages.size(),
|
||||
)
|
||||
|
||||
Log.d(
|
||||
"STATE DUMP",
|
||||
"Memory used by Events: " +
|
||||
LocalCache.notes.sumOfLong { _, note -> note.event?.countMemory() ?: 0L } / (1024 * 1024) +
|
||||
" MB",
|
||||
)
|
||||
|
||||
val qttNotes = LocalCache.notes.countByGroup { _, it -> it.event?.kind() }
|
||||
val qttAddressables = LocalCache.addressables.countByGroup { _, it -> it.event?.kind() }
|
||||
|
||||
val bytesNotes =
|
||||
LocalCache.notes
|
||||
.sumByGroup(groupMap = { _, it -> it.event?.kind() }, sumOf = { _, it -> it.event?.countMemory() ?: 0L })
|
||||
val bytesAddressables =
|
||||
LocalCache.addressables
|
||||
.sumByGroup(groupMap = { _, it -> it.event?.kind() }, sumOf = { _, it -> it.event?.countMemory() ?: 0L })
|
||||
|
||||
qttNotes.toList().sortedByDescending { bytesNotes.get(it.first) }.forEach { (kind, qtt) ->
|
||||
Log.d("STATE DUMP", "Kind ${kind.toString().padStart(5,' ')}:\t${qtt.toString().padStart(6,' ')} elements\t${bytesNotes.get(kind)?.div((1024 * 1024))}MB ")
|
||||
}
|
||||
qttAddressables.toList().sortedByDescending { bytesNotes.get(it.first) }.forEach { (kind, qtt) ->
|
||||
Log.d("STATE DUMP", "Kind ${kind.toString().padStart(5,' ')}:\t${qtt.toString().padStart(6,' ')} elements\t${bytesAddressables.get(kind)?.div((1024 * 1024))}MB ")
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MyExtensibleTopAppBar(
|
||||
title: @Composable RowScope.() -> Unit,
|
||||
extendableRow: (@Composable () -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
navigationIcon: @Composable (() -> Unit)? = null,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
Modifier.clickable { expanded.value = !expanded.value },
|
||||
) {
|
||||
Row(modifier = BottomTopHeight) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
title()
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
navigationIcon = {
|
||||
if (navigationIcon == null) {
|
||||
Spacer(TitleInsetWithoutIcon)
|
||||
} else {
|
||||
Row(TitleIconModifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
navigationIcon()
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = actions,
|
||||
)
|
||||
}
|
||||
|
||||
if (expanded.value && extendableRow != null) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column { extendableRow() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this should probably be part of the touch target of the start and end icons, clarify this
|
||||
private val AppBarHorizontalPadding = 4.dp
|
||||
|
||||
// Start inset for the title when there is no navigation icon provided
|
||||
private val TitleInsetWithoutIcon = Modifier.width(16.dp - AppBarHorizontalPadding)
|
||||
|
||||
// Start inset for the title when there is a navigation icon provided
|
||||
private val TitleIconModifier = Modifier.width(48.dp - AppBarHorizontalPadding)
|
||||
|
@ -29,11 +29,15 @@ 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.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
@ -44,7 +48,6 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@ -92,10 +95,10 @@ import com.vitorpamplona.amethyst.ui.components.ClickableText
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadStatuses
|
||||
import com.vitorpamplona.amethyst.ui.qrcode.ShowQRDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ShowQRDialog
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
@ -115,23 +118,20 @@ import com.vitorpamplona.ammolite.relays.RelayPoolStatus
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun DrawerContent(
|
||||
nav: (String) -> Unit,
|
||||
drawerState: DrawerState,
|
||||
nav: INav,
|
||||
openSheet: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val onClickUser = {
|
||||
nav("User/${accountViewModel.userProfile().pubkeyHex}")
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
Unit
|
||||
nav.nav(routeFor(accountViewModel.userProfile()))
|
||||
nav.closeDrawer()
|
||||
}
|
||||
|
||||
ModalDrawerSheet(
|
||||
windowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Bottom + WindowInsetsSides.Start),
|
||||
drawerContainerColor = MaterialTheme.colorScheme.background,
|
||||
drawerTonalElevation = 0.dp,
|
||||
) {
|
||||
@ -148,7 +148,7 @@ fun DrawerContent(
|
||||
)
|
||||
|
||||
Column(drawerSpacing) {
|
||||
EditStatusBoxes(accountViewModel.account.userProfile(), accountViewModel, drawerState)
|
||||
EditStatusBoxes(accountViewModel.account.userProfile(), accountViewModel, nav)
|
||||
}
|
||||
|
||||
FollowingAndFollowerCounts(accountViewModel.account, onClickUser)
|
||||
@ -160,7 +160,6 @@ fun DrawerContent(
|
||||
|
||||
ListContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
drawerState,
|
||||
openSheet,
|
||||
accountViewModel,
|
||||
nav,
|
||||
@ -170,7 +169,6 @@ fun DrawerContent(
|
||||
|
||||
BottomContent(
|
||||
accountViewModel.account.userProfile(),
|
||||
drawerState,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
@ -265,16 +263,16 @@ fun ProfileContentTemplate(
|
||||
private fun EditStatusBoxes(
|
||||
baseAccountUser: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
drawerState: DrawerState,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadStatuses(user = baseAccountUser, accountViewModel) { statuses ->
|
||||
if (statuses.isEmpty()) {
|
||||
StatusEditBar(accountViewModel = accountViewModel, drawerState = drawerState)
|
||||
StatusEditBar(accountViewModel = accountViewModel, nav = nav)
|
||||
} else {
|
||||
statuses.forEach {
|
||||
val originalStatus by it.live().content.observeAsState()
|
||||
|
||||
StatusEditBar(originalStatus, it.address, accountViewModel, drawerState = drawerState)
|
||||
StatusEditBar(originalStatus, it.address, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -285,14 +283,14 @@ fun StatusEditBar(
|
||||
savedStatus: String? = null,
|
||||
tag: ATag? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
drawerState: DrawerState,
|
||||
nav: INav,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val currentStatus = remember { mutableStateOf(savedStatus ?: "") }
|
||||
val hasChanged = remember { derivedStateOf { currentStatus.value != (savedStatus ?: "") } }
|
||||
LaunchedEffect(drawerState.isClosed) {
|
||||
if (drawerState.isClosed) {
|
||||
LaunchedEffect(nav.drawerState.isClosed) {
|
||||
if (nav.drawerState.isClosed) {
|
||||
focusManager.clearFocus(true)
|
||||
}
|
||||
}
|
||||
@ -434,14 +432,12 @@ fun WatchFollower(
|
||||
@Composable
|
||||
fun ListContent(
|
||||
modifier: Modifier,
|
||||
drawerState: DrawerState,
|
||||
openSheet: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val route = remember(accountViewModel) { "User/${accountViewModel.userProfile().pubkeyHex}" }
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var wantsToEditRelays by remember { mutableStateOf(false) }
|
||||
var editMediaServers by remember { mutableStateOf(false) }
|
||||
|
||||
@ -465,7 +461,6 @@ fun ListContent(
|
||||
icon = Route.Profile.icon,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
nav = nav,
|
||||
drawerState = drawerState,
|
||||
route = route,
|
||||
)
|
||||
|
||||
@ -474,7 +469,6 @@ fun ListContent(
|
||||
icon = Route.Bookmarks.icon,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
nav = nav,
|
||||
drawerState = drawerState,
|
||||
route = Route.Bookmarks.route,
|
||||
)
|
||||
|
||||
@ -483,24 +477,23 @@ fun ListContent(
|
||||
icon = Route.Drafts.icon,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
nav = nav,
|
||||
drawerState = drawerState,
|
||||
route = Route.Drafts.route,
|
||||
)
|
||||
|
||||
IconRowRelays(
|
||||
accountViewModel = accountViewModel,
|
||||
onClick = {
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav.closeDrawer()
|
||||
wantsToEditRelays = true
|
||||
},
|
||||
)
|
||||
|
||||
IconRow(
|
||||
title = "Media Servers",
|
||||
title = stringRes(R.string.media_servers),
|
||||
icon = androidx.media3.ui.R.drawable.exo_icon_repeat_all,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav.closeDrawer()
|
||||
editMediaServers = true
|
||||
},
|
||||
)
|
||||
@ -510,7 +503,6 @@ fun ListContent(
|
||||
icon = Route.BlockedUsers.icon,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
nav = nav,
|
||||
drawerState = drawerState,
|
||||
route = Route.BlockedUsers.route,
|
||||
)
|
||||
|
||||
@ -520,7 +512,7 @@ fun ListContent(
|
||||
icon = R.drawable.ic_key,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav.closeDrawer()
|
||||
backupDialogOpen = true
|
||||
},
|
||||
)
|
||||
@ -536,14 +528,14 @@ fun ListContent(
|
||||
icon = R.drawable.ic_tor,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onLongClick = {
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav.closeDrawer()
|
||||
conectOrbotDialogOpen = true
|
||||
},
|
||||
onClick = {
|
||||
if (checked) {
|
||||
disconnectTorDialog = true
|
||||
} else {
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav.closeDrawer()
|
||||
conectOrbotDialogOpen = true
|
||||
}
|
||||
},
|
||||
@ -554,7 +546,6 @@ fun ListContent(
|
||||
icon = Route.Settings.icon,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
nav = nav,
|
||||
drawerState = drawerState,
|
||||
route = Route.Settings.route,
|
||||
)
|
||||
|
||||
@ -660,18 +651,16 @@ fun NavigationRow(
|
||||
title: String,
|
||||
icon: Int,
|
||||
tint: Color,
|
||||
nav: (String) -> Unit,
|
||||
drawerState: DrawerState,
|
||||
nav: INav,
|
||||
route: String,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
IconRow(
|
||||
title,
|
||||
icon,
|
||||
tint,
|
||||
onClick = {
|
||||
nav(route)
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav.closeDrawer()
|
||||
nav.nav(route)
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -754,9 +743,8 @@ fun IconRowRelays(
|
||||
@Composable
|
||||
fun BottomContent(
|
||||
user: User,
|
||||
drawerState: DrawerState,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
@ -788,8 +776,8 @@ fun BottomContent(
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
nav("Note/${BuildConfig.RELEASE_NOTES_ID}")
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav.nav("Note/${BuildConfig.RELEASE_NOTES_ID}")
|
||||
nav.closeDrawer()
|
||||
},
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
@ -797,7 +785,7 @@ fun BottomContent(
|
||||
IconButton(
|
||||
onClick = {
|
||||
dialogOpen = true
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav.closeDrawer()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
@ -816,8 +804,8 @@ fun BottomContent(
|
||||
accountViewModel,
|
||||
onScan = {
|
||||
dialogOpen = false
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
nav(it)
|
||||
nav.closeDrawer()
|
||||
nav.nav(it)
|
||||
},
|
||||
onClose = { dialogOpen = false },
|
||||
)
|
||||
|
193
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/FeedFilterSpinner.kt
Normal file
193
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/FeedFilterSpinner.kt
Normal file
@ -0,0 +1,193 @@
|
||||
/**
|
||||
* 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.navigation
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.map
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadCityName
|
||||
import com.vitorpamplona.amethyst.ui.screen.CodeName
|
||||
import com.vitorpamplona.amethyst.ui.screen.CommunityName
|
||||
import com.vitorpamplona.amethyst.ui.screen.GeoHashName
|
||||
import com.vitorpamplona.amethyst.ui.screen.HashtagName
|
||||
import com.vitorpamplona.amethyst.ui.screen.Name
|
||||
import com.vitorpamplona.amethyst.ui.screen.PeopleListName
|
||||
import com.vitorpamplona.amethyst.ui.screen.ResourceName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun FeedFilterSpinner(
|
||||
placeholderCode: String,
|
||||
explainer: String,
|
||||
options: ImmutableList<CodeName>,
|
||||
onSelect: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var optionsShowing by remember { mutableStateOf(false) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val selectAnOption =
|
||||
stringRes(
|
||||
id = R.string.select_an_option,
|
||||
)
|
||||
|
||||
var currentText by
|
||||
remember(placeholderCode, options) {
|
||||
mutableStateOf(
|
||||
options.firstOrNull { it.code == placeholderCode }?.name?.name(context) ?: selectAnOption,
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Spacer(modifier = Size20Modifier)
|
||||
Text(currentText)
|
||||
Icon(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
contentDescription = explainer,
|
||||
modifier = Size20Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.matchParentSize()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
) {
|
||||
optionsShowing = true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (optionsShowing) {
|
||||
options.isNotEmpty().also {
|
||||
SpinnerSelectionDialog(
|
||||
options = options,
|
||||
onDismiss = { optionsShowing = false },
|
||||
onSelect = {
|
||||
currentText = options[it].name.name(context)
|
||||
optionsShowing = false
|
||||
onSelect(it)
|
||||
},
|
||||
) {
|
||||
RenderOption(it.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderOption(option: Name) {
|
||||
when (option) {
|
||||
is GeoHashName -> {
|
||||
LoadCityName(option.geoHashTag) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(text = "/g/$it", color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
is HashtagName -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(text = option.name(), color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
is ResourceName -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(id = option.resourceId),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
is PeopleListName -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
val noteState by
|
||||
option.note
|
||||
.live()
|
||||
.metadata
|
||||
.observeAsState()
|
||||
|
||||
val name = (noteState?.note?.event as? PeopleListEvent)?.nameOrTitle() ?: option.note.dTag() ?: ""
|
||||
|
||||
Text(text = name, color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
is CommunityName -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
val name by
|
||||
option.note
|
||||
.live()
|
||||
.metadata
|
||||
.map { "/n/" + ((it.note as? AddressableNote)?.dTag() ?: "") }
|
||||
.observeAsState()
|
||||
|
||||
Text(text = name ?: "", color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.navigation
|
||||
|
||||
import androidx.compose.animation.core.AnimationSpec
|
||||
import androidx.compose.animation.core.DecayAnimationSpec
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.TopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import com.vitorpamplona.amethyst.ui.theme.TopBarSize
|
||||
|
||||
// This is a hack to decrease the height for the
|
||||
// default TopBar without having to reimplement it.
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun rememberHeightDecreaser(): TopAppBarScrollBehavior {
|
||||
val height =
|
||||
LocalDensity.current.run {
|
||||
TopBarSize.toPx()
|
||||
}
|
||||
|
||||
return remember(height) {
|
||||
HeightDecreaser(height)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
class HeightDecreaser(
|
||||
height: Float,
|
||||
) : TopAppBarScrollBehavior {
|
||||
override val state: TopAppBarState =
|
||||
TopAppBarState(
|
||||
initialHeightOffsetLimit = -Float.MAX_VALUE,
|
||||
initialHeightOffset = 0f,
|
||||
initialContentOffset = 0f,
|
||||
).also {
|
||||
it.heightOffset = height
|
||||
}
|
||||
|
||||
override val isPinned: Boolean = true
|
||||
override val snapAnimationSpec: AnimationSpec<Float>? = null
|
||||
override val flingAnimationSpec: DecayAnimationSpec<Float>? = null
|
||||
override var nestedScrollConnection =
|
||||
object : NestedScrollConnection {
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset = available
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 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.navigation
|
||||
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Composable
|
||||
fun rememberNav(): Nav {
|
||||
val navController = rememberNavController()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
return remember(navController, scope) {
|
||||
Nav(navController, scope)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberExtendedNav(
|
||||
nav: INav,
|
||||
onClose: () -> Unit,
|
||||
): INav = nav.onNavigate(onClose)
|
||||
|
||||
@Stable
|
||||
interface INav {
|
||||
val drawerState: DrawerState
|
||||
|
||||
fun nav(route: String)
|
||||
|
||||
fun newStack(route: String)
|
||||
|
||||
fun popBack()
|
||||
|
||||
fun popUpTo(
|
||||
route: String,
|
||||
upTo: String,
|
||||
)
|
||||
|
||||
fun closeDrawer()
|
||||
|
||||
fun openDrawer()
|
||||
}
|
||||
|
||||
@Stable
|
||||
class Nav(
|
||||
val controller: NavHostController,
|
||||
val scope: CoroutineScope,
|
||||
) : INav {
|
||||
override val drawerState = DrawerState(DrawerValue.Closed)
|
||||
|
||||
override fun closeDrawer() {
|
||||
scope.launch { drawerState.close() }
|
||||
}
|
||||
|
||||
override fun openDrawer() {
|
||||
scope.launch { drawerState.open() }
|
||||
}
|
||||
|
||||
override fun nav(route: String) {
|
||||
scope.launch {
|
||||
if (getRouteWithArguments(controller) != route) {
|
||||
controller.navigate(route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun newStack(route: String) {
|
||||
scope.launch {
|
||||
controller.navigate(route) {
|
||||
popUpTo(Route.Home.route)
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun popBack() {
|
||||
scope.launch {
|
||||
controller.navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
override fun popUpTo(
|
||||
route: String,
|
||||
upTo: String,
|
||||
) {
|
||||
scope.launch {
|
||||
controller.navigate(route) { popUpTo(route) { inclusive = true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
object EmptyNav : INav {
|
||||
override val drawerState = DrawerState(DrawerValue.Closed)
|
||||
|
||||
override fun closeDrawer() {
|
||||
runBlocking {
|
||||
drawerState.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun openDrawer() {
|
||||
runBlocking {
|
||||
drawerState.open()
|
||||
}
|
||||
}
|
||||
|
||||
override fun nav(route: String) {
|
||||
}
|
||||
|
||||
override fun newStack(route: String) {
|
||||
}
|
||||
|
||||
override fun popBack() {
|
||||
}
|
||||
|
||||
override fun popUpTo(
|
||||
route: String,
|
||||
upTo: String,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
fun INav.onNavigate(runOnNavigate: () -> Unit): INav = ObservableNavigate(this, runOnNavigate)
|
||||
|
||||
class ObservableNavigate(
|
||||
val nav: INav,
|
||||
val onNavigate: () -> Unit,
|
||||
) : INav {
|
||||
override val drawerState: DrawerState = nav.drawerState
|
||||
|
||||
override fun nav(route: String) {
|
||||
onNavigate()
|
||||
nav.nav(route)
|
||||
}
|
||||
|
||||
override fun newStack(route: String) {
|
||||
onNavigate()
|
||||
nav.newStack(route)
|
||||
}
|
||||
|
||||
override fun popBack() {
|
||||
onNavigate()
|
||||
nav.popBack()
|
||||
}
|
||||
|
||||
override fun popUpTo(
|
||||
route: String,
|
||||
upTo: String,
|
||||
) {
|
||||
onNavigate()
|
||||
nav.popUpTo(route, upTo)
|
||||
}
|
||||
|
||||
override fun closeDrawer() {
|
||||
nav.closeDrawer()
|
||||
}
|
||||
|
||||
override fun openDrawer() {
|
||||
nav.openDrawer()
|
||||
}
|
||||
}
|
@ -32,20 +32,10 @@ import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size23dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||
import com.vitorpamplona.quartz.events.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.events.RepostEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@ -58,9 +48,6 @@ sealed class Route(
|
||||
val notifSize: Modifier = Modifier.size(Size23dp),
|
||||
val iconSize: Modifier = Modifier.size(Size20dp),
|
||||
val contentDescriptor: Int = R.string.route,
|
||||
val hasNewItems: (Account, Set<com.vitorpamplona.amethyst.model.Note>) -> Boolean = { _, _ ->
|
||||
false
|
||||
},
|
||||
val arguments: ImmutableList<NamedNavArgument> = persistentListOf(),
|
||||
) {
|
||||
object Home :
|
||||
@ -78,9 +65,6 @@ sealed class Route(
|
||||
},
|
||||
).toImmutableList(),
|
||||
contentDescriptor = R.string.route_home,
|
||||
hasNewItems = { accountViewModel, newNotes ->
|
||||
HomeLatestItem.hasNewItems(accountViewModel, newNotes)
|
||||
},
|
||||
)
|
||||
|
||||
object Global :
|
||||
@ -118,9 +102,6 @@ sealed class Route(
|
||||
Route(
|
||||
route = "Notification",
|
||||
icon = R.drawable.ic_notifications,
|
||||
hasNewItems = { accountViewModel, newNotes ->
|
||||
NotificationLatestItem.hasNewItems(accountViewModel, newNotes)
|
||||
},
|
||||
contentDescriptor = R.string.route_notifications,
|
||||
)
|
||||
|
||||
@ -128,9 +109,6 @@ sealed class Route(
|
||||
Route(
|
||||
route = "Message",
|
||||
icon = R.drawable.ic_dm,
|
||||
hasNewItems = { accountViewModel, newNotes ->
|
||||
MessagesLatestItem.hasNewItems(accountViewModel, newNotes)
|
||||
},
|
||||
contentDescriptor = R.string.route_messages,
|
||||
)
|
||||
|
||||
@ -239,147 +217,6 @@ sealed class Route(
|
||||
route = "Settings",
|
||||
icon = R.drawable.ic_settings,
|
||||
)
|
||||
|
||||
companion object {
|
||||
val InvertedLayouts =
|
||||
setOf(
|
||||
Channel.route,
|
||||
Room.route,
|
||||
RoomByAuthor.route,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open class LatestItem {
|
||||
var newestItemPerAccount: Map<String, Note?> = mapOf()
|
||||
|
||||
fun getNewestItem(account: Account): Note? = newestItemPerAccount[account.userProfile().pubkeyHex]
|
||||
|
||||
fun clearNewestItem(account: Account) {
|
||||
val userHex = account.userProfile().pubkeyHex
|
||||
if (newestItemPerAccount.contains(userHex)) {
|
||||
newestItemPerAccount = newestItemPerAccount - userHex
|
||||
}
|
||||
}
|
||||
|
||||
fun updateNewestItem(
|
||||
newNotes: Set<Note>,
|
||||
account: Account,
|
||||
filter: AdditiveFeedFilter<Note>,
|
||||
): Note? {
|
||||
val newestItem = newestItemPerAccount[account.userProfile().pubkeyHex]
|
||||
|
||||
// Block list got updated
|
||||
val newNewest =
|
||||
if (newestItem == null || !account.isAcceptable(newestItem)) {
|
||||
filterMore(filter.feed(), account).firstOrNull { it.createdAt() != null && account.isAcceptable(it) }
|
||||
} else {
|
||||
filter
|
||||
.sort(
|
||||
filterMore(filter.applyFilter(newNotes), account) + newestItem,
|
||||
).firstOrNull { it.createdAt() != null && account.isAcceptable(it) }
|
||||
}
|
||||
|
||||
newestItemPerAccount = newestItemPerAccount + Pair(account.userProfile().pubkeyHex, newNewest)
|
||||
|
||||
return newestItemPerAccount[account.userProfile().pubkeyHex]
|
||||
}
|
||||
|
||||
open fun filterMore(
|
||||
newItems: Set<Note>,
|
||||
account: Account,
|
||||
): Set<Note> = newItems
|
||||
|
||||
open fun filterMore(
|
||||
newItems: List<Note>,
|
||||
account: Account,
|
||||
): List<Note> = newItems
|
||||
}
|
||||
|
||||
object HomeLatestItem : LatestItem() {
|
||||
fun hasNewItems(
|
||||
account: Account,
|
||||
newNotes: Set<Note>,
|
||||
): Boolean {
|
||||
checkNotInMainThread()
|
||||
|
||||
val lastTime = account.loadLastRead("HomeFollows")
|
||||
|
||||
val newestItem = updateNewestItem(newNotes, account, HomeNewThreadFeedFilter(account))
|
||||
|
||||
return (newestItem?.createdAt() ?: 0) > lastTime
|
||||
}
|
||||
|
||||
override fun filterMore(
|
||||
newItems: Set<Note>,
|
||||
account: Account,
|
||||
): Set<Note> {
|
||||
// removes reposts from the dot notifications.
|
||||
return newItems.filter { it.event !is GenericRepostEvent && it.event !is RepostEvent }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
object NotificationLatestItem : LatestItem() {
|
||||
fun hasNewItems(
|
||||
account: Account,
|
||||
newNotes: Set<Note>,
|
||||
): Boolean {
|
||||
checkNotInMainThread()
|
||||
|
||||
val lastTime = account.loadLastRead("Notification")
|
||||
|
||||
val newestItem = updateNewestItem(newNotes, account, NotificationFeedFilter(account))
|
||||
|
||||
return (newestItem?.createdAt() ?: 0) > lastTime
|
||||
}
|
||||
}
|
||||
|
||||
object MessagesLatestItem : LatestItem() {
|
||||
fun hasNewItems(
|
||||
account: Account,
|
||||
newNotes: Set<Note>,
|
||||
): Boolean {
|
||||
checkNotInMainThread()
|
||||
|
||||
// Checks if the current newest item is still unread.
|
||||
// If so, there is no need to check anything else
|
||||
if (isNew(getNewestItem(account), account)) {
|
||||
return true
|
||||
}
|
||||
|
||||
clearNewestItem(account)
|
||||
|
||||
// gets the newest of the unread items
|
||||
val newestItem = updateNewestItem(newNotes, account, ChatroomListKnownFeedFilter(account))
|
||||
|
||||
return isNew(newestItem, account)
|
||||
}
|
||||
|
||||
fun isNew(
|
||||
it: Note?,
|
||||
account: Account,
|
||||
): Boolean {
|
||||
if (it == null) return false
|
||||
|
||||
val currentUser = account.userProfile().pubkeyHex
|
||||
val room = (it.event as? ChatroomKeyable)?.chatroomKey(currentUser)
|
||||
return if (room != null) {
|
||||
val lastRead = account.loadLastRead("Room/${room.hashCode()}")
|
||||
(it.createdAt() ?: 0) > lastRead
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun filterMore(
|
||||
newItems: Set<Note>,
|
||||
account: Account,
|
||||
): Set<Note> = newItems.filter { isNew(it, account) }.toSet()
|
||||
|
||||
override fun filterMore(
|
||||
newItems: List<Note>,
|
||||
account: Account,
|
||||
): List<Note> = newItems.filter { isNew(it, account) }
|
||||
}
|
||||
|
||||
fun getRouteWithArguments(navController: NavHostController): String? {
|
||||
|
111
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarExtensibleWithBackButton.kt
Normal file
111
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarExtensibleWithBackButton.kt
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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.navigation
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||
|
||||
@Composable
|
||||
fun TopBarExtensibleWithBackButton(
|
||||
title: @Composable RowScope.() -> Unit,
|
||||
extendableRow: (@Composable () -> Unit)? = null,
|
||||
popBack: () -> Unit,
|
||||
) {
|
||||
MyExtensibleTopAppBar(
|
||||
title = title,
|
||||
extendableRow = extendableRow,
|
||||
navigationIcon = { IconButton(onClick = popBack) { ArrowBackIcon() } },
|
||||
actions = {},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MyExtensibleTopAppBar(
|
||||
title: @Composable RowScope.() -> Unit,
|
||||
extendableRow: (@Composable () -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
navigationIcon: @Composable (() -> Unit)? = null,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
Modifier.clickable { expanded.value = !expanded.value },
|
||||
) {
|
||||
TopAppBar(
|
||||
scrollBehavior = rememberHeightDecreaser(),
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
title()
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
navigationIcon = {
|
||||
if (navigationIcon == null) {
|
||||
Spacer(TitleInsetWithoutIcon)
|
||||
} else {
|
||||
Row(TitleIconModifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
navigationIcon()
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = actions,
|
||||
)
|
||||
|
||||
if (expanded.value && extendableRow != null) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column { extendableRow() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this should probably be part of the touch target of the start and end icons, clarify this
|
||||
private val AppBarHorizontalPadding = 4.dp
|
||||
|
||||
// Start inset for the title when there is no navigation icon provided
|
||||
private val TitleInsetWithoutIcon = Modifier.width(16.dp - AppBarHorizontalPadding)
|
||||
|
||||
// Start inset for the title when there is a navigation icon provided
|
||||
private val TitleIconModifier = Modifier.width(48.dp - AppBarHorizontalPadding)
|
50
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarWithBackButton.kt
Normal file
50
amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarWithBackButton.kt
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.navigation
|
||||
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TopBarWithBackButton(
|
||||
caption: String,
|
||||
popBack: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
scrollBehavior = rememberHeightDecreaser(),
|
||||
title = { Text(caption) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = popBack,
|
||||
modifier = Modifier,
|
||||
) {
|
||||
ArrowBackIcon()
|
||||
}
|
||||
},
|
||||
actions = {},
|
||||
)
|
||||
}
|
@ -43,6 +43,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton
|
||||
import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay
|
||||
@ -58,7 +59,7 @@ fun BadgeCompose(
|
||||
isInnerNote: Boolean = false,
|
||||
routeForLastRead: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteState by likeSetCard.note
|
||||
.live()
|
||||
@ -87,7 +88,7 @@ fun BadgeCompose(
|
||||
routeFor(
|
||||
note,
|
||||
accountViewModel.userProfile(),
|
||||
)?.let { nav(it) }
|
||||
)?.let { nav.nav(it) }
|
||||
},
|
||||
),
|
||||
) {
|
||||
|
@ -39,6 +39,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@ -96,7 +98,7 @@ fun BlankNote(
|
||||
@Preview
|
||||
fun HiddenNotePreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
val nav: (String) -> Unit = {}
|
||||
val nav = EmptyNav
|
||||
|
||||
ThemeComparisonColumn(
|
||||
toPreview = {
|
||||
@ -117,7 +119,7 @@ fun HiddenNote(
|
||||
isHiddenAuthor: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
|
@ -27,6 +27,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
@ -36,7 +37,7 @@ fun CheckHiddenFeedWatchBlockAndReport(
|
||||
showHiddenWarning: Boolean,
|
||||
ignoreAllBlocksAndReports: Boolean = false,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
normalNote: @Composable (canPreview: Boolean) -> Unit,
|
||||
) {
|
||||
if (ignoreAllBlocksAndReports) {
|
||||
@ -55,7 +56,7 @@ fun WatchBlockAndReport(
|
||||
showHiddenWarning: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
normalNote: @Composable (canPreview: Boolean) -> Unit,
|
||||
) {
|
||||
val isHidden by accountViewModel.createIsHiddenFlow(note).collectAsStateWithLifecycle()
|
||||
|
@ -70,6 +70,7 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelHeader
|
||||
@ -77,7 +78,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.EndedFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LiveFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.OfflineFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ScheduledFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.observeAppDefinition
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.observeAppDefinition
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfUrlIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.showAmountAxis
|
||||
@ -116,7 +117,7 @@ fun ChannelCardCompose(
|
||||
forceEventKind: Int?,
|
||||
isHiddenFeed: Boolean = false,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel) {
|
||||
if (forceEventKind == null || baseNote.event?.kind() == forceEventKind) {
|
||||
@ -148,7 +149,7 @@ fun NormalChannelCard(
|
||||
modifier: Modifier = Modifier,
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
|
||||
CheckNewAndRenderChannelCard(
|
||||
@ -171,7 +172,7 @@ private fun CheckNewAndRenderChannelCard(
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
showPopup: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val backgroundColor =
|
||||
calculateBackgroundColor(
|
||||
@ -201,7 +202,7 @@ private fun CheckNewAndRenderChannelCard(
|
||||
fun InnerChannelCardWithReactions(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
when (baseNote.event) {
|
||||
is LiveActivitiesEvent -> {
|
||||
@ -226,7 +227,7 @@ fun InnerChannelCardWithReactions(
|
||||
fun InnerCardRow(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(StdPadding) {
|
||||
SensitivityWarning(
|
||||
@ -246,7 +247,7 @@ fun InnerCardRow(
|
||||
fun InnerCardBox(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(HalfPadding) {
|
||||
SensitivityWarning(
|
||||
@ -262,7 +263,7 @@ fun InnerCardBox(
|
||||
private fun RenderNoteRow(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
when (baseNote.event) {
|
||||
is LiveActivitiesEvent -> {
|
||||
@ -291,7 +292,7 @@ data class ClassifiedsThumb(
|
||||
fun RenderClassifiedsThumb(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = baseNote.event as? ClassifiedsEvent ?: return
|
||||
|
||||
@ -414,7 +415,7 @@ data class LiveActivityCard(
|
||||
fun RenderLiveActivityThumb(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = baseNote.event as? LiveActivitiesEvent ?: return
|
||||
|
||||
@ -547,7 +548,7 @@ data class DVMCard(
|
||||
fun RenderCommunitiesThumb(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = baseNote.event as? CommunityDefinitionEvent ?: return
|
||||
|
||||
@ -757,7 +758,7 @@ private fun LoadParticipants(
|
||||
fun RenderContentDVMThumb(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
// downloads user metadata to pre-load the NIP-65 relays.
|
||||
val user =
|
||||
@ -845,7 +846,7 @@ fun RenderContentDVMThumb(
|
||||
fun RenderChannelThumb(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = baseNote.event as? ChannelCreateEvent ?: return
|
||||
|
||||
@ -859,7 +860,7 @@ fun RenderChannelThumb(
|
||||
baseNote: Note,
|
||||
channel: Channel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val channelUpdates by channel.live.observeAsState()
|
||||
|
||||
|
@ -37,6 +37,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -50,7 +51,7 @@ fun MessageSetCompose(
|
||||
routeForLastRead: String,
|
||||
showHidden: Boolean = false,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val baseNote = remember { messageSetCard.note }
|
||||
|
||||
@ -80,7 +81,7 @@ fun MessageSetCompose(
|
||||
routeFor(
|
||||
baseNote,
|
||||
accountViewModel.userProfile(),
|
||||
)?.let { nav(it) }
|
||||
)?.let { nav.nav(it) }
|
||||
}
|
||||
},
|
||||
onLongClick = enablePopup,
|
||||
|
@ -65,6 +65,7 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.authorRouteFor
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu
|
||||
@ -101,7 +102,7 @@ fun MultiSetCompose(
|
||||
routeForLastRead: String,
|
||||
showHidden: Boolean = false,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val baseNote = remember { multiSetCard.note }
|
||||
|
||||
@ -123,7 +124,7 @@ fun MultiSetCompose(
|
||||
.background(backgroundColor.value)
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
scope.launch { routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) } }
|
||||
scope.launch { routeFor(baseNote, accountViewModel.userProfile())?.let { nav.nav(it) } }
|
||||
},
|
||||
onLongClick = { popupExpanded.value = true },
|
||||
).padding(
|
||||
@ -163,7 +164,7 @@ private fun Galeries(
|
||||
multiSetCard: MultiSetCard,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
if (multiSetCard.zapEvents.isNotEmpty()) {
|
||||
DecryptAndRenderZapGallery(multiSetCard, backgroundColor, accountViewModel, nav)
|
||||
@ -184,7 +185,7 @@ private fun Galeries(
|
||||
fun RenderLikeGallery(
|
||||
reactionType: String,
|
||||
likeEvents: ImmutableList<Note>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
if (likeEvents.isNotEmpty()) {
|
||||
@ -226,7 +227,7 @@ fun DecryptAndRenderZapGallery(
|
||||
multiSetCard: MultiSetCard,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val zapEvents by
|
||||
produceState(initialValue = accountViewModel.cachedDecryptAmountMessageInGroup(multiSetCard.zapEvents)) {
|
||||
@ -241,7 +242,7 @@ fun RenderZapGallery(
|
||||
zapEvents: ImmutableList<ZapAmountCommentNotification>,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Box(
|
||||
@ -259,7 +260,7 @@ fun RenderZapGallery(
|
||||
@Composable
|
||||
fun RenderBoostGallery(
|
||||
boostEvents: ImmutableList<Note>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Row(
|
||||
@ -280,7 +281,7 @@ fun RenderBoostGallery(
|
||||
@Composable
|
||||
fun RenderBoostGallery(
|
||||
noteToGetBoostEvents: NoteState,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Row(
|
||||
@ -321,7 +322,7 @@ fun MapZaps(
|
||||
fun AuthorGalleryZaps(
|
||||
authorNotes: ImmutableList<ZapAmountCommentNotification>,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Column(modifier = StdStartPadding) {
|
||||
@ -367,9 +368,9 @@ private fun ParseAuthorCommentAndAmount(
|
||||
|
||||
fun click(
|
||||
content: ZapAmountCommentNotification,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
content.user?.let { nav(routeFor(it)) }
|
||||
content.user?.let { nav.nav(routeFor(it)) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -377,7 +378,7 @@ private fun RenderState(
|
||||
content: ZapAmountCommentNotification,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.clickable { click(content, nav) },
|
||||
@ -404,7 +405,7 @@ val commentTextSize = 12.sp
|
||||
private fun DisplayAuthorCommentAndAmount(
|
||||
authorComment: ZapAmountCommentNotification,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Box(modifier = Size35Modifier, contentAlignment = Alignment.BottomCenter) {
|
||||
@ -446,7 +447,7 @@ fun CrossfadeToDisplayAmount(amount: String) {
|
||||
fun CrossfadeToDisplayComment(
|
||||
comment: String,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
TranslatableRichTextViewer(
|
||||
@ -466,7 +467,7 @@ fun CrossfadeToDisplayComment(
|
||||
@Composable
|
||||
fun AuthorGallery(
|
||||
authorNotes: ImmutableList<Note>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Column(modifier = StdStartPadding) {
|
||||
@ -478,7 +479,7 @@ fun AuthorGallery(
|
||||
@Composable
|
||||
fun AuthorGallery(
|
||||
noteToGetBoostEvents: NoteState,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Column(modifier = StdStartPadding) {
|
||||
@ -491,10 +492,10 @@ fun AuthorGallery(
|
||||
@Composable
|
||||
private fun BoxedAuthor(
|
||||
note: Note,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Box(modifier = Size35Modifier.clickable(onClick = { nav(authorRouteFor(note)) })) {
|
||||
Box(modifier = Size35Modifier.clickable(onClick = { nav.nav(authorRouteFor(note)) })) {
|
||||
WatchAuthorWithBlank(note, Size35Modifier, accountViewModel) { author ->
|
||||
WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor(
|
||||
author,
|
||||
|
@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadStatuses
|
||||
@ -116,7 +117,7 @@ fun ObserveDisplayNip05Status(
|
||||
baseNote: Note,
|
||||
columnModifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
WatchAuthor(baseNote = baseNote) {
|
||||
ObserveDisplayNip05Status(it, columnModifier, accountViewModel, nav)
|
||||
@ -128,7 +129,7 @@ fun ObserveDisplayNip05Status(
|
||||
baseUser: User,
|
||||
columnModifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val nip05 by baseUser.live().nip05Changes.observeAsState(baseUser.nip05())
|
||||
|
||||
@ -158,7 +159,7 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
|
||||
baseUser: User,
|
||||
columnModifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(modifier = columnModifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
@ -188,7 +189,7 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
|
||||
fun ObserveRotateStatuses(
|
||||
statuses: ImmutableList<AddressableNote>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
ObserveAllStatusesToAvoidSwitchigAllTheTime(statuses)
|
||||
|
||||
@ -211,7 +212,7 @@ fun ObserveAllStatusesToAvoidSwitchigAllTheTime(statuses: ImmutableList<Addressa
|
||||
fun RotateStatuses(
|
||||
statuses: ImmutableList<AddressableNote>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var indexToDisplay by remember(statuses) { mutableIntStateOf(0) }
|
||||
|
||||
@ -242,7 +243,7 @@ fun DisplayUsersNpub(npub: String) {
|
||||
fun DisplayStatus(
|
||||
addressableNote: AddressableNote,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteState by addressableNote.live().metadata.observeAsState()
|
||||
val noteEvent = noteState?.note?.event ?: return
|
||||
@ -266,7 +267,7 @@ fun DisplayStatusInner(
|
||||
nostrATag: ATag?,
|
||||
nostrHexID: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
when (type) {
|
||||
"music" ->
|
||||
@ -311,7 +312,7 @@ fun DisplayStatusInner(
|
||||
routeFor(
|
||||
note,
|
||||
accountViewModel.userProfile(),
|
||||
)?.let { nav(it) }
|
||||
)?.let { nav.nav(it) }
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
@ -333,7 +334,7 @@ fun DisplayStatusInner(
|
||||
routeFor(
|
||||
it,
|
||||
accountViewModel.userProfile(),
|
||||
)?.let { nav(it) }
|
||||
)?.let { nav.nav(it) }
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
|
@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.layouts.GenericRepostLayout
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.BoostedMark
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayEditStatus
|
||||
@ -204,7 +205,7 @@ fun NoteCompose(
|
||||
quotesLeft: Int,
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
WatchNoteEvent(
|
||||
baseNote = baseNote,
|
||||
@ -250,7 +251,7 @@ fun AcceptableNote(
|
||||
quotesLeft: Int,
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
if (isQuotedNote || isBoostedNote) {
|
||||
when (baseNote.event) {
|
||||
@ -382,7 +383,7 @@ private fun CheckNewAndRenderNote(
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
showPopup: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val backgroundColor =
|
||||
calculateBackgroundColor(
|
||||
@ -423,7 +424,7 @@ fun ClickableNote(
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
showPopup: () -> Unit,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val updatedModifier =
|
||||
@ -437,7 +438,7 @@ fun ClickableNote(
|
||||
} else {
|
||||
baseNote
|
||||
}
|
||||
routeFor(redirectToNote, accountViewModel.userProfile())?.let { nav(it) }
|
||||
routeFor(redirectToNote, accountViewModel.userProfile())?.let { nav.nav(it) }
|
||||
},
|
||||
onLongClick = showPopup,
|
||||
).background(backgroundColor.value)
|
||||
@ -457,7 +458,7 @@ fun InnerNoteWithReactions(
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val notBoostedNorQuote = !isBoostedNote && !isQuotedNote
|
||||
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
|
||||
@ -539,7 +540,7 @@ fun NoteBody(
|
||||
backgroundColor: MutableState<Color>,
|
||||
editState: State<GenericLoadable<EditState>>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
FirstUserInfoRow(
|
||||
baseNote = baseNote,
|
||||
@ -594,7 +595,7 @@ private fun RenderNoteRow(
|
||||
unPackReply: Boolean,
|
||||
editState: State<GenericLoadable<EditState>>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
when (val noteEvent = baseNote.event) {
|
||||
is AppDefinitionEvent -> RenderAppDefinition(baseNote, accountViewModel, nav)
|
||||
@ -819,7 +820,7 @@ fun RenderDraft(
|
||||
unPackReply: Boolean,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
ObserveDraftEvent(note, accountViewModel) {
|
||||
val edits = remember { mutableStateOf(GenericLoadable.Empty<EditState>()) }
|
||||
@ -850,7 +851,7 @@ fun RenderRepost(
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
note.replyTo?.lastOrNull { it.event !is CommunityDefinitionEvent }?.let {
|
||||
NoteCompose(
|
||||
@ -880,7 +881,7 @@ fun ReplyNoteComposition(
|
||||
replyingDirectlyTo: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
NoteCompose(
|
||||
baseNote = replyingDirectlyTo,
|
||||
@ -900,7 +901,7 @@ fun SecondUserInfoRow(
|
||||
note: Note,
|
||||
editState: State<GenericLoadable<EditState>>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event ?: return
|
||||
val noteAuthor = note.author ?: return
|
||||
@ -981,7 +982,7 @@ fun FirstUserInfoRow(
|
||||
showAuthorPicture: Boolean,
|
||||
editState: State<GenericLoadable<EditState>>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Row(verticalAlignment = CenterVertically, modifier = UserNameRowHeight) {
|
||||
val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
|
||||
@ -1090,7 +1091,7 @@ fun observeEdits(
|
||||
private fun BadgeBox(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
if (accountViewModel.settings.featureSet == FeatureSetType.COMPLETE) {
|
||||
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
|
||||
@ -1104,7 +1105,7 @@ private fun BadgeBox(
|
||||
@Composable
|
||||
private fun RenderAuthorImages(
|
||||
baseNote: Note,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
|
||||
@ -1161,7 +1162,7 @@ private fun RepostNoteAuthorPicture(
|
||||
baseNote: Note,
|
||||
baseRepost: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
GenericRepostLayout(
|
||||
baseAuthorPicture = {
|
||||
|
@ -87,6 +87,8 @@ 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.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@ -153,7 +155,7 @@ fun LongPressToQuickAction(
|
||||
note = baseNote,
|
||||
onDismiss = { popupExpanded.value = false },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = {},
|
||||
nav = EmptyNav,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -174,7 +176,7 @@ fun LongPressToQuickActionGallery(
|
||||
note = baseNote,
|
||||
onDismiss = { popupExpanded.value = false },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = {},
|
||||
nav = EmptyNav,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -185,7 +187,7 @@ fun NoteQuickActionMenuGallery(
|
||||
note: Note,
|
||||
onDismiss: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
DeleteFromGalleryDialog(note, accountViewModel) {
|
||||
onDismiss()
|
||||
@ -197,7 +199,7 @@ fun NoteQuickActionMenu(
|
||||
note: Note,
|
||||
onDismiss: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val editDraftDialog = remember { mutableStateOf(false) }
|
||||
|
||||
@ -208,7 +210,7 @@ fun NoteQuickActionMenu(
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
draft = note,
|
||||
nav = { },
|
||||
nav = EmptyNav,
|
||||
)
|
||||
}
|
||||
|
||||
@ -227,7 +229,7 @@ fun NoteQuickActionMenu(
|
||||
onDismiss: () -> Unit,
|
||||
onWantsToEditDraft: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val showSelectTextDialog = remember { mutableStateOf(false) }
|
||||
val showDeleteAlertDialog = remember { mutableStateOf(false) }
|
||||
|
@ -38,7 +38,6 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bolt
|
||||
import androidx.compose.material.icons.outlined.Bolt
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@ -73,6 +72,7 @@ import com.vitorpamplona.amethyst.R
|
||||
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.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg
|
||||
@ -84,6 +84,7 @@ import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
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.toImmutableListOfLists
|
||||
@ -99,7 +100,7 @@ fun PollNote(
|
||||
canPreview: Boolean,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val pollViewModel: PollNoteViewModel = viewModel(key = "PollNoteViewModel${baseNote.idHex}")
|
||||
|
||||
@ -122,7 +123,7 @@ fun PollNote(
|
||||
canPreview: Boolean,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
WatchZapsAndUpdateTallies(baseNote, pollViewModel)
|
||||
|
||||
@ -157,7 +158,7 @@ private fun OptionNote(
|
||||
accountViewModel: AccountViewModel,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val tags = remember(baseNote) { baseNote.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList }
|
||||
|
||||
@ -225,7 +226,7 @@ private fun RenderOptionAfterVote(
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
@ -295,7 +296,7 @@ private fun RenderOptionBeforeVote(
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
@ -331,7 +332,7 @@ fun ZapVote(
|
||||
nonClickablePrepend: @Composable () -> Unit,
|
||||
clickablePrepend: @Composable () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val isLoggedUser by remember { derivedStateOf { accountViewModel.isLoggedUser(baseNote.author) } }
|
||||
|
||||
@ -356,7 +357,7 @@ fun ZapVote(
|
||||
Modifier.combinedClickable(
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false, radius = 24.dp),
|
||||
indication = ripple24dp,
|
||||
onClick = {
|
||||
if (!accountViewModel.isWriteable()) {
|
||||
accountViewModel.toast(
|
||||
@ -460,7 +461,7 @@ fun ZapVote(
|
||||
title = toast.title,
|
||||
textContent = toast.msg,
|
||||
onClickStartMessage = {
|
||||
baseNote.author?.let { nav(routeToMessage(it, toast.msg, accountViewModel)) }
|
||||
baseNote.author?.let { nav.nav(routeToMessage(it, toast.msg, accountViewModel)) }
|
||||
},
|
||||
onDismiss = { showErrorMessageDialog = null },
|
||||
)
|
||||
|
@ -48,7 +48,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CardDefaults
|
||||
@ -105,6 +104,7 @@ 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.routeToMessage
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -128,7 +128,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size19Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size22Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size28Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
@ -136,6 +135,7 @@ import com.vitorpamplona.amethyst.ui.theme.TinyBorders
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.reactionBox
|
||||
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.selectedReactionBoxModifier
|
||||
import com.vitorpamplona.quartz.encoders.Nip30CustomEmoji
|
||||
import com.vitorpamplona.quartz.events.BaseTextNoteEvent
|
||||
@ -156,7 +156,7 @@ fun ReactionsRow(
|
||||
addPadding: Boolean,
|
||||
editState: State<GenericLoadable<EditState>>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val wantsToSeeReactions = remember(baseNote) { mutableStateOf(false) }
|
||||
|
||||
@ -178,7 +178,7 @@ private fun InnerReactionRow(
|
||||
wantsToSeeReactions: MutableState<Boolean>,
|
||||
editState: State<GenericLoadable<EditState>>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
GenericInnerReactionRow(
|
||||
showReactionDetail = showReactionDetail,
|
||||
@ -446,7 +446,7 @@ private fun RenderShowIndividualReactionsButton(
|
||||
@Composable
|
||||
private fun ReactionDetailGallery(
|
||||
baseNote: Note,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val defaultBackgroundColor = MaterialTheme.colorScheme.background
|
||||
@ -479,7 +479,7 @@ private fun ReactionDetailGallery(
|
||||
@Composable
|
||||
private fun WatchBoostsAndRenderGallery(
|
||||
baseNote: Note,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val boostsEvents by baseNote.live().boosts.observeAsState()
|
||||
@ -498,7 +498,7 @@ private fun WatchBoostsAndRenderGallery(
|
||||
@Composable
|
||||
private fun WatchReactionsAndRenderGallery(
|
||||
baseNote: Note,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||
@ -521,7 +521,7 @@ private fun WatchReactionsAndRenderGallery(
|
||||
private fun WatchZapAndRenderGallery(
|
||||
baseNote: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val zapsState by baseNote.live().zaps.observeAsState()
|
||||
@ -553,7 +553,7 @@ private fun BoostWithDialog(
|
||||
editState: State<GenericLoadable<EditState>>,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var wantsToQuote by remember { mutableStateOf<Note?>(null) }
|
||||
var wantsToFork by remember { mutableStateOf<Note?>(null) }
|
||||
@ -605,7 +605,7 @@ private fun ReplyReactionWithDialog(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var wantsToReplyTo by remember { mutableStateOf<Note?>(null) }
|
||||
|
||||
@ -830,7 +830,7 @@ fun LikeReaction(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
iconSize: Dp = Size18dp,
|
||||
heartSizeModifier: Modifier = Size18Modifier,
|
||||
iconFontSize: TextUnit = Font14SP,
|
||||
@ -983,7 +983,7 @@ fun ZapReaction(
|
||||
iconSize: Dp = Size20dp,
|
||||
iconSizeModifier: Modifier = Size20Modifier,
|
||||
animationSize: Dp = 14.dp,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var wantsToZap by remember { mutableStateOf(false) }
|
||||
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
||||
@ -1007,7 +1007,7 @@ fun ZapReaction(
|
||||
iconSizeModifier.combinedClickable(
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false, radius = Size24dp),
|
||||
indication = ripple24dp,
|
||||
onClick = {
|
||||
zapClick(
|
||||
baseNote,
|
||||
@ -1061,7 +1061,7 @@ fun ZapReaction(
|
||||
baseNote.author?.let {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val route = routeToMessage(it, msg, accountViewModel)
|
||||
nav(route)
|
||||
nav.nav(route)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -52,6 +52,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
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.ShowMoreRelaysButtonBoxModifer
|
||||
@ -64,7 +65,7 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
fun RelayBadges(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
|
||||
@ -87,7 +88,7 @@ fun RenderAllRelayList(
|
||||
modifier: Modifier = Modifier,
|
||||
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteRelays by baseNote
|
||||
.flow()
|
||||
@ -105,7 +106,7 @@ fun RenderClosedRelayList(
|
||||
modifier: Modifier = Modifier,
|
||||
verticalAlignment: Alignment.Vertical = Alignment.Top,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Row(modifier, verticalAlignment = verticalAlignment) {
|
||||
WatchAndRenderRelay(baseNote, 0, accountViewModel, nav)
|
||||
@ -119,7 +120,7 @@ fun WatchAndRenderRelay(
|
||||
baseNote: Note,
|
||||
relayIndex: Int,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteRelays by baseNote
|
||||
.flow()
|
||||
|
@ -20,8 +20,10 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@ -42,6 +44,8 @@ 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.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@ -53,6 +57,7 @@ import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||
import com.vitorpamplona.amethyst.ui.actions.relays.RelayInformationDialog
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
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.RelayIconFilter
|
||||
@ -61,13 +66,14 @@ import com.vitorpamplona.amethyst.ui.theme.Size17dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.relayIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
|
||||
@Composable
|
||||
public fun RelayBadgesHorizontal(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
|
||||
@ -109,11 +115,12 @@ fun ChatRelayExpandButton(onClick: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RenderRelay(
|
||||
relay: RelayBriefInfoCache.RelayBriefInfo,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val relayInfo by
|
||||
produceState(
|
||||
@ -143,11 +150,17 @@ fun RenderRelay(
|
||||
)
|
||||
}
|
||||
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val clickableModifier =
|
||||
remember(relay) {
|
||||
Modifier
|
||||
.size(Size17dp)
|
||||
.clickable(
|
||||
.combinedClickable(
|
||||
indication = ripple24dp,
|
||||
interactionSource = MutableInteractionSource(),
|
||||
onLongClick = {
|
||||
clipboardManager.setText(AnnotatedString(relay.url))
|
||||
},
|
||||
onClick = {
|
||||
accountViewModel.retrieveRelayDocument(
|
||||
relay.url,
|
||||
|
@ -38,6 +38,7 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||
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.StdVertSpacer
|
||||
@ -51,7 +52,7 @@ fun ReplyInformationChannel(
|
||||
replyTo: ImmutableList<Note>?,
|
||||
mentions: ImmutableList<String>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var sortedMentions by remember { mutableStateOf<ImmutableList<User>>(persistentListOf()) }
|
||||
|
||||
@ -67,7 +68,7 @@ fun ReplyInformationChannel(
|
||||
ReplyInformationChannel(
|
||||
replyTo,
|
||||
sortedMentions,
|
||||
onUserTagClick = { nav("User/${it.pubkeyHex}") },
|
||||
onUserTagClick = { nav.nav("User/${it.pubkeyHex}") },
|
||||
)
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ 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
|
||||
@ -145,7 +146,7 @@ class UpdateReactionTypeViewModel(
|
||||
fun UpdateReactionTypeDialog(
|
||||
onClose: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val postViewModel: UpdateReactionTypeViewModel =
|
||||
viewModel(
|
||||
@ -326,7 +327,7 @@ private fun RenderReactionOption(
|
||||
@Composable
|
||||
private fun EmojiSelector(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
onClick: ((EmojiUrl) -> Unit)? = null,
|
||||
) {
|
||||
LoadAddressableNote(
|
||||
@ -361,7 +362,7 @@ private fun EmojiSelector(
|
||||
fun EmojiCollectionGallery(
|
||||
emojiCollections: ImmutableList<ATag>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
onClick: ((EmojiUrl) -> Unit)? = null,
|
||||
) {
|
||||
val color = MaterialTheme.colorScheme.background
|
||||
@ -385,14 +386,14 @@ private fun WatchAndRenderNote(
|
||||
emojiPack: AddressableNote,
|
||||
bgColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
onClick: ((EmojiUrl) -> Unit)?,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
Modifier.fillMaxWidth().clickable {
|
||||
scope.launch { routeFor(emojiPack, accountViewModel.userProfile())?.let { nav(it) } }
|
||||
scope.launch { routeFor(emojiPack, accountViewModel.userProfile())?.let { nav.nav(it) } }
|
||||
},
|
||||
) {
|
||||
RenderEmojiPack(
|
||||
|
@ -89,11 +89,11 @@ 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.qrcode.SimpleQrCodeScanner
|
||||
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.getFragmentActivity
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
|
@ -30,6 +30,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
@ -39,12 +40,12 @@ fun UserCompose(
|
||||
baseUser: User,
|
||||
overallModifier: Modifier = StdPadding,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
overallModifier.clickable(
|
||||
onClick = { nav("User/${baseUser.pubkeyHex}") },
|
||||
onClick = { nav.nav("User/${baseUser.pubkeyHex}") },
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
@ -42,6 +42,7 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@ -50,13 +51,13 @@ import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
@Composable
|
||||
fun NoteAuthorPicture(
|
||||
baseNote: Note,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
size: Dp,
|
||||
pictureModifier: Modifier = Modifier,
|
||||
) {
|
||||
NoteAuthorPicture(baseNote, size, accountViewModel, pictureModifier) {
|
||||
nav("User/${it.pubkeyHex}")
|
||||
nav.nav("User/${it.pubkeyHex}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +103,7 @@ fun UserPicture(
|
||||
size: Dp,
|
||||
pictureModifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadUser(baseUserHex = userHex, accountViewModel) {
|
||||
if (it != null) {
|
||||
@ -129,14 +130,14 @@ fun UserPicture(
|
||||
size: Dp,
|
||||
pictureModifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
ClickableUserPicture(
|
||||
baseUser = user,
|
||||
size = size,
|
||||
accountViewModel = accountViewModel,
|
||||
modifier = pictureModifier,
|
||||
onClick = { nav("User/${user.pubkeyHex}") },
|
||||
onClick = { nav.nav("User/${user.pubkeyHex}") },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -45,13 +45,14 @@ import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.screen.ZapReqResponse
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.WatchIsHiddenUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.showAmountAxis
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.FollowButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ShowUserButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.UnfollowButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.WatchIsHiddenUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ZapReqResponse
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
@ -63,7 +64,7 @@ import kotlinx.coroutines.launch
|
||||
fun ZapNoteCompose(
|
||||
baseReqResponse: ZapReqResponse,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val baseNoteRequest by baseReqResponse.zapRequest
|
||||
.live()
|
||||
@ -86,7 +87,7 @@ fun ZapNoteCompose(
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.clickable(
|
||||
onClick = { nav(route) },
|
||||
onClick = { nav.nav(route) },
|
||||
),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
@ -99,7 +100,7 @@ fun ZapNoteCompose(
|
||||
private fun RenderZapNote(
|
||||
baseAuthor: User,
|
||||
zapNote: Note,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Row(
|
||||
|
@ -34,6 +34,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.ZapUserSetCard
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
@ -47,7 +48,7 @@ fun ZapUserSetCompose(
|
||||
isInnerNote: Boolean = false,
|
||||
routeForLastRead: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val backgroundColor =
|
||||
calculateBackgroundColor(
|
||||
@ -59,7 +60,7 @@ fun ZapUserSetCompose(
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.background(backgroundColor.value).clickable {
|
||||
nav("User/${zapSetCard.user.pubkeyHex}")
|
||||
nav.nav("User/${zapSetCard.user.pubkeyHex}")
|
||||
},
|
||||
) {
|
||||
Row(
|
||||
|
@ -40,6 +40,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.relays.AddDMRelayListDialog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
@ -58,7 +60,7 @@ fun AddInboxRelayForDMCardPreview() {
|
||||
ThemeComparisonColumn {
|
||||
AddInboxRelayForDMCard(
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = {},
|
||||
nav = EmptyNav,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -66,7 +68,7 @@ fun AddInboxRelayForDMCardPreview() {
|
||||
@Composable
|
||||
fun ObserveRelayListForDMsAndDisplayIfNotFound(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
ObserveRelayListForDMs(
|
||||
accountViewModel = accountViewModel,
|
||||
@ -115,7 +117,7 @@ fun ObserveRelayListForDMs(
|
||||
@Composable
|
||||
fun AddInboxRelayForDMCard(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(modifier = StdPadding) {
|
||||
Card(
|
||||
|
@ -40,6 +40,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.relays.AddSearchRelayListDialog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
@ -58,7 +60,7 @@ fun AddInboxRelayForSearchCardPreview() {
|
||||
ThemeComparisonColumn {
|
||||
AddInboxRelayForSearchCard(
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = {},
|
||||
nav = EmptyNav,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -66,7 +68,7 @@ fun AddInboxRelayForSearchCardPreview() {
|
||||
@Composable
|
||||
fun ObserveRelayListForSearchAndDisplayIfNotFound(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
ObserveRelayListForSearch(
|
||||
accountViewModel = accountViewModel,
|
||||
@ -115,7 +117,7 @@ fun ObserveRelayListForSearch(
|
||||
@Composable
|
||||
fun AddInboxRelayForSearchCard(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(modifier = StdPadding) {
|
||||
Card(
|
||||
|
@ -30,6 +30,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
@ -39,7 +40,7 @@ import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||
fun DisplayFollowingCommunityInPost(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(HalfStartPadding) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) { DisplayCommunity(baseNote, nav) }
|
||||
@ -49,7 +50,7 @@ fun DisplayFollowingCommunityInPost(
|
||||
@Composable
|
||||
private fun DisplayCommunity(
|
||||
note: Note,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val communityTag =
|
||||
remember(note) { note.event?.getTagOfAddressableKind(CommunityDefinitionEvent.KIND) } ?: return
|
||||
@ -59,7 +60,7 @@ private fun DisplayCommunity(
|
||||
|
||||
ClickableText(
|
||||
text = displayTag,
|
||||
onClick = { nav(route) },
|
||||
onClick = { nav.nav(route) },
|
||||
style =
|
||||
LocalTextStyle.current.copy(
|
||||
color =
|
||||
|
@ -36,13 +36,14 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
fun DisplayFollowingHashtagsInPost(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val userFollowState by accountViewModel.account.liveKind3Follows.collectAsStateWithLifecycle()
|
||||
var firstTag by remember(baseNote) { mutableStateOf<String?>(null) }
|
||||
@ -65,14 +66,14 @@ fun DisplayFollowingHashtagsInPost(
|
||||
@Composable
|
||||
private fun DisplayTagList(
|
||||
firstTag: String,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
val displayTag = remember(firstTag) { AnnotatedString(" #$firstTag") }
|
||||
val route = remember(firstTag) { "Hashtag/$firstTag" }
|
||||
|
||||
ClickableText(
|
||||
text = displayTag,
|
||||
onClick = { nav(route) },
|
||||
onClick = { nav.nav(route) },
|
||||
style =
|
||||
LocalTextStyle.current.copy(
|
||||
color =
|
||||
|
@ -26,18 +26,19 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadCityName
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
|
||||
@Composable
|
||||
fun DisplayLocation(
|
||||
geohashStr: String,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadCityName(geohashStr) { cityName ->
|
||||
ClickableText(
|
||||
text = AnnotatedString(cityName),
|
||||
onClick = { nav("Geohash/$geohashStr") },
|
||||
onClick = { nav.nav("Geohash/$geohashStr") },
|
||||
style =
|
||||
LocalTextStyle.current.copy(
|
||||
color =
|
||||
|
@ -63,6 +63,7 @@ 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
|
||||
@ -83,7 +84,7 @@ fun DisplayReward(
|
||||
baseReward: Reward,
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
@ -94,7 +95,7 @@ fun DisplayReward(
|
||||
) {
|
||||
ClickableText(
|
||||
text = AnnotatedString("#bounty"),
|
||||
onClick = { nav("Hashtag/bounty") },
|
||||
onClick = { nav.nav("Hashtag/bounty") },
|
||||
style =
|
||||
LocalTextStyle.current.copy(
|
||||
color =
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user