mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
refactor: move the composers, calendars, chess, geocache and voice-reply screens
Clear the next ring of platform blockers and move the screens they freed: - Date pickers (poll, zap-poll, schedule-at) read the local clock through LocalClock and rememberIs24HourClock instead of java.time / DateFormat. - FileSharer.shareTextFile replaces the app's .ics share; PhoneCalendar wraps the system event composer; relativeTimeSpan wraps DateUtils (and NSRelativeDateTimeFormatter on iOS). - rememberWindowViewModel + LocalWindowViewModelStoreOwner keep state that spans destinations (chess lobby and board, the Cordn group draft) without reaching for FragmentActivity. - isHlsMedia moves to commons.video; ChessEventBroadcaster to commonMain; the host gains supportsWritingAssistant. - geo: links and nostr: shares go through LocalUriHandler / TextSharer. 204 of 252 screens are now shared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
This commit is contained in:
+1
-1
@@ -46,6 +46,7 @@ import androidx.media3.common.util.UnstableApi
|
|||||||
import androidx.media3.ui.compose.ContentFrame
|
import androidx.media3.ui.compose.ContentFrame
|
||||||
import androidx.media3.ui.compose.SURFACE_TYPE_TEXTURE_VIEW
|
import androidx.media3.ui.compose.SURFACE_TYPE_TEXTURE_VIEW
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.getDialogWindow
|
import com.vitorpamplona.amethyst.commons.ui.components.getDialogWindow
|
||||||
|
import com.vitorpamplona.amethyst.commons.video.isHlsMedia
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.BottomGradientOverlay
|
import com.vitorpamplona.amethyst.service.playback.composable.controls.BottomGradientOverlay
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.FullscreenSwipeControlsState
|
import com.vitorpamplona.amethyst.service.playback.composable.controls.FullscreenSwipeControlsState
|
||||||
@@ -61,7 +62,6 @@ import com.vitorpamplona.amethyst.service.playback.composable.controls.applyView
|
|||||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.clampViewportShortSide
|
import com.vitorpamplona.amethyst.service.playback.composable.controls.clampViewportShortSide
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.fullscreenSwipeControls
|
import com.vitorpamplona.amethyst.service.playback.composable.controls.fullscreenSwipeControls
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.LoadedMediaItem
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.LoadedMediaItem
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.isHlsMedia
|
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.AudioPlayingAnimation
|
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.AudioPlayingAnimation
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.rememberIsAudioTrack
|
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.rememberIsAudioTrack
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -72,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.PinBottomIconSize
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size50Modifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.Size50Modifier
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||||
|
import com.vitorpamplona.amethyst.commons.video.isHlsMedia
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.service.cast.CastRequest
|
import com.vitorpamplona.amethyst.service.cast.CastRequest
|
||||||
@@ -80,7 +81,6 @@ import com.vitorpamplona.amethyst.service.cast.resolveCastLiveness
|
|||||||
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
|
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.isHlsMedia
|
|
||||||
import com.vitorpamplona.amethyst.service.playback.diskCache.HlsLivenessCache
|
import com.vitorpamplona.amethyst.service.playback.diskCache.HlsLivenessCache
|
||||||
import com.vitorpamplona.amethyst.service.playback.pip.PipVideoActivity
|
import com.vitorpamplona.amethyst.service.playback.pip.PipVideoActivity
|
||||||
import com.vitorpamplona.amethyst.ui.cast.CastDevicePickerDialog
|
import com.vitorpamplona.amethyst.ui.cast.CastDevicePickerDialog
|
||||||
|
|||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 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.service.playback.composable.mediaitem
|
|
||||||
|
|
||||||
import androidx.media3.common.MimeTypes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether a URL plus its imeta mime identifies HLS.
|
|
||||||
*
|
|
||||||
* This is the caller-side form for code that holds a URL and a mime but no `MediaItem` yet — UI that
|
|
||||||
* has a [MediaItemData] or a `MediaUrlContent`. Once a `MediaItem` exists,
|
|
||||||
* `isHlsMediaItem` is the equivalent, and both must answer the same way: the UI decides what to
|
|
||||||
* render, the factory decides how to load it, and a disagreement shows up as a player that streams
|
|
||||||
* something the surrounding chrome says is a still image.
|
|
||||||
*
|
|
||||||
* Defined in terms of [MediaItemCache.toExoPlayerMimeType] rather than re-deriving the rules, so it
|
|
||||||
* cannot drift from the mime that actually reaches ExoPlayer. That normalizer prefers an explicit
|
|
||||||
* mime (mapping the four HLS aliases onto [MimeTypes.APPLICATION_M3U8]) and otherwise falls back to a
|
|
||||||
* **path-anchored** `.m3u8` test.
|
|
||||||
*
|
|
||||||
* Both halves matter. A BUD-10 blossom playlist is `https://host/<sha256>` with no extension at all,
|
|
||||||
* so only the mime identifies it; and anchoring to the path stops `video.mp4?ref=a.m3u8` counting as
|
|
||||||
* HLS on the strength of its query string.
|
|
||||||
*
|
|
||||||
* Note this answers *is it HLS*, which callers use as a proxy for *is it live*. The proxy is
|
|
||||||
* imprecise in the same way for every HLS URL — an on-demand HLS playlist also answers true — and
|
|
||||||
* that imprecision is older than this function. Liveness is only truly knowable from
|
|
||||||
* `#EXT-X-ENDLIST` once the playlist is loaded, which is what `HlsLivenessCache` records.
|
|
||||||
*/
|
|
||||||
fun isHlsMedia(
|
|
||||||
url: String,
|
|
||||||
mimeType: String?,
|
|
||||||
): Boolean = MediaItemCache.toExoPlayerMimeType(mimeType, url) == MimeTypes.APPLICATION_M3U8
|
|
||||||
+2
-26
@@ -25,9 +25,9 @@ import androidx.core.net.toUri
|
|||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.MimeTypes
|
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||||
import com.vitorpamplona.amethyst.commons.ui.state.GenericBaseCache
|
import com.vitorpamplona.amethyst.commons.ui.state.GenericBaseCache
|
||||||
|
import com.vitorpamplona.amethyst.commons.video.normalizeStreamMimeType
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -45,23 +45,7 @@ class MediaItemCache : GenericBaseCache<MediaItemData, LoadedMediaItem>(20) {
|
|||||||
internal fun toExoPlayerMimeType(
|
internal fun toExoPlayerMimeType(
|
||||||
mimeType: String?,
|
mimeType: String?,
|
||||||
videoUri: String? = null,
|
videoUri: String? = null,
|
||||||
): String? {
|
): String? = normalizeStreamMimeType(mimeType, videoUri)
|
||||||
if (!mimeType.isNullOrBlank()) {
|
|
||||||
return when (mimeType.lowercase()) {
|
|
||||||
"application/vnd.apple.mpegurl",
|
|
||||||
"application/x-mpegurl",
|
|
||||||
"audio/x-mpegurl",
|
|
||||||
"audio/mpegurl",
|
|
||||||
-> MimeTypes.APPLICATION_M3U8
|
|
||||||
|
|
||||||
else -> mimeType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (videoUri != null && hasM3u8PathExtension(videoUri)) {
|
|
||||||
return MimeTypes.APPLICATION_M3U8
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// A side-loaded track needs its media type declared: a NIP-71 `text-track` says nothing
|
// A side-loaded track needs its media type declared: a NIP-71 `text-track` says nothing
|
||||||
// about the format, and a blossom-hosted one is `https://host/<sha256>` with no extension
|
// about the format, and a blossom-hosted one is `https://host/<sha256>` with no extension
|
||||||
@@ -81,14 +65,6 @@ class MediaItemCache : GenericBaseCache<MediaItemData, LoadedMediaItem>(20) {
|
|||||||
language != null && type != null -> "$language \u00B7 $type"
|
language != null && type != null -> "$language \u00B7 $type"
|
||||||
else -> language ?: type
|
else -> language ?: type
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restrict `.m3u8` matching to the path component so a query param or
|
|
||||||
// fragment that happens to mention .m3u8 (e.g. `?ref=a.m3u8`) on an
|
|
||||||
// MP4 URI doesn't misroute to HlsMediaSource.
|
|
||||||
private fun hasM3u8PathExtension(uri: String): Boolean {
|
|
||||||
val path = uri.substringBefore('?').substringBefore('#')
|
|
||||||
return path.endsWith(".m3u8", ignoreCase = true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compute(key: MediaItemData): LoadedMediaItem =
|
override suspend fun compute(key: MediaItemData): LoadedMediaItem =
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ import androidx.activity.compose.setContent
|
|||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.LocalWindowViewModelStoreOwner
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.elements.NowProvider
|
import com.vitorpamplona.amethyst.commons.ui.note.elements.NowProvider
|
||||||
import com.vitorpamplona.amethyst.debugState
|
import com.vitorpamplona.amethyst.debugState
|
||||||
import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
|
import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
|
||||||
@@ -61,11 +63,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setContent {
|
setContent {
|
||||||
AmethystTheme {
|
AmethystTheme {
|
||||||
NowProvider {
|
NowProvider {
|
||||||
|
// The Activity outlives every nav destination: screens that share state
|
||||||
|
// across destinations (chess lobby + board, the Cordn group draft) keep it here.
|
||||||
|
CompositionLocalProvider(LocalWindowViewModelStoreOwner provides this) {
|
||||||
AccountScreen(Amethyst.instance.sessionManager)
|
AccountScreen(Amethyst.instance.sessionManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
|||||||
+1
-1
@@ -88,9 +88,9 @@ import com.vitorpamplona.amethyst.commons.ui.components.getDialogWindow
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberViewerControlsVisibility
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberViewerControlsVisibility
|
||||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
||||||
|
import com.vitorpamplona.amethyst.commons.video.isHlsMedia
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.VideoViewInner
|
import com.vitorpamplona.amethyst.service.playback.composable.VideoViewInner
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.isHlsMedia
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
|
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.LocalTabResel
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.TabReselectCoordinator
|
import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.TabReselectCoordinator
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.findQueryParameterValue
|
import com.vitorpamplona.amethyst.commons.ui.navigation.findQueryParameterValue
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.nip22Comments.ReplyCommentPostScreen
|
import com.vitorpamplona.amethyst.commons.ui.note.nip22Comments.ReplyCommentPostScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.share.ShareNoteAsQrScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.apps.recommendations.ProfileAppRecommendationsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.apps.recommendations.ProfileAppRecommendationsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles.ArticlesScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles.ArticlesScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.BackupConflictReviewScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.BackupConflictReviewScreen
|
||||||
@@ -95,7 +96,11 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzInviteScre
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzNewDmScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzNewDmScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.JobBoardScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.JobBoardScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.WorkflowRunBoardScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.WorkflowRunBoardScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.CalendarCollectionsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.create.NewCalendarCollectionScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.create.NewCalendarCollectionScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.detail.CalendarEventDetailScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnCreateGroupScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnCreateMembersScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnGroupInfoScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnGroupInfoScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnGroupListScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnGroupListScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnInvitationsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnInvitationsScreen
|
||||||
@@ -123,6 +128,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannel
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.ephemChat.metadata.NewEphemeralChatScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.ephemChat.metadata.NewEphemeralChatScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.PublicChatChannelScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.PublicChatChannelScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata.ChannelMetadataScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata.ChannelMetadataScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveActivityChannelScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupBrowseScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupBrowseScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupChatScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupChatScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupCreateScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupCreateScreen
|
||||||
@@ -131,6 +137,8 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannel
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupMembersScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupMembersScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupThreadsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupThreadsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.share.ShareToDMScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.share.ShareToDMScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess.ChessGameScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess.ChessLobbyScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.CommunityScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.CommunityScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list.CommunitiesScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list.CommunitiesScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity.EditCommunityScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity.EditCommunityScreen
|
||||||
@@ -150,6 +158,8 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membersh
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membershipManagement.MyEmojiListScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membershipManagement.MyEmojiListScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites.FavoriteAppsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites.FavoriteAppsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list.FollowPacksScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list.FollowPacksScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.create.NewGeocacheScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.detail.GeocacheDetailScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.GeocacheHuntScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.GeocacheHuntScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.NewGeocacheHuntScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.NewGeocacheHuntScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.log.LogGeocacheFindScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.log.LogGeocacheFindScreen
|
||||||
@@ -161,6 +171,8 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.HashtagScre
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.highlights.HighlightsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.highlights.HighlightsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.highlights.NewHighlightScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.highlights.NewHighlightScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.HomeScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.HomeScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.ShortNotePostScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.VoiceReplyScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.nip75Goals.NewGoalScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.nip75Goals.NewGoalScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.interestSets.display.InterestSetScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.interestSets.display.InterestSetScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.interestSets.list.ListOfInterestSetsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.interestSets.list.ListOfInterestSetsScreen
|
||||||
@@ -190,6 +202,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.PodcastsSc
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.EditPodcastShowScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.EditPodcastShowScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.NewPodcastTrailerScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.NewPodcastTrailerScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.PodcastAuthoringScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.PodcastAuthoringScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.PollPostScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.PollsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.PollsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.results.PollResultsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.results.PollResultsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.privacy.PrivacyOptionsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.privacy.PrivacyOptionsScreen
|
||||||
@@ -212,6 +225,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.AudioVisua
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BlockedUsersScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BlockedUsersScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BottomBarSettingsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BottomBarSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.CallSettingsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.CallSettingsScreen
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.ComposeSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.DrawerSettingsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.DrawerSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.HiddenWordsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.HiddenWordsScreen
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.HomeTabsSettingsScreen
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.HomeTabsSettingsScreen
|
||||||
@@ -279,24 +293,16 @@ import com.vitorpamplona.amethyst.ui.note.PayViaIntentScreen
|
|||||||
import com.vitorpamplona.amethyst.ui.note.UpdateReactionTypeScreen
|
import com.vitorpamplona.amethyst.ui.note.UpdateReactionTypeScreen
|
||||||
import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsImageFileScreen
|
import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsImageFileScreen
|
||||||
import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsImageScreen
|
import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsImageScreen
|
||||||
import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsQrScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
|
import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.WebAppScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.WebAppScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarCollectionsScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarReminderSettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarReminderSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.create.NewCalendarEventScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.create.NewCalendarEventScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.detail.CalendarEventDetailScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnCreateGroupScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnCreateMembersScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnGroupChatScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnGroupChatScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveActivityChannelScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupChannelListScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupChannelListScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.MessagesScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.MessagesScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessGameScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessLobbyScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabAccountWatcher
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabAccountWatcher
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabLayer
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabLayer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabPreloader
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabPreloader
|
||||||
@@ -305,15 +311,11 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.FavoriteAppManifestPr
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.NostrAppScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.NostrAppScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.FollowPackFeedScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.FollowPackFeedScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.GeocachesScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.GeocachesScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.create.NewGeocacheScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail.GeocacheDetailScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitNewIssueScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitNewIssueScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryCodeScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryCodeScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryIssuesScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryIssuesScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryPullsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryPullsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.VoiceReplyScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.AccountBackupScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.AccountBackupScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.AddToMusicPlaylistSheet
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.AddToMusicPlaylistSheet
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.NewMusicTrackScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.NewMusicTrackScreen
|
||||||
@@ -322,13 +324,11 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.NappletsScreen
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastEpisodeScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastEpisodeScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.PollPostScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.payment.SendPaymentScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.payment.SendPaymentScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ScanQrImageScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ScanQrImageScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip86.RelayManagementScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip86.RelayManagementScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.scheduledposts.ScheduledPostsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.scheduledposts.ScheduledPostsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ComposeSettingsScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NamecoinSettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NamecoinSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NotificationSettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NotificationSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.OtsSettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.OtsSettingsScreen
|
||||||
|
|||||||
+1
-1
@@ -93,6 +93,7 @@ import com.vitorpamplona.amethyst.commons.resources.share_as_image_watermark
|
|||||||
import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality
|
import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality
|
||||||
import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator
|
import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator
|
||||||
import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState
|
import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.UploadProgressIndicator
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadNote
|
import com.vitorpamplona.amethyst.commons.ui.components.LoadNote
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner
|
import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer
|
import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer
|
||||||
@@ -103,7 +104,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size18Modifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.Size18Modifier
|
||||||
import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource
|
import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|||||||
+1
-1
@@ -48,13 +48,13 @@ import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.note.WatchNoteEvent
|
import com.vitorpamplona.amethyst.commons.ui.note.WatchNoteEvent
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter
|
import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.formatCalendarRange
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.formatCalendarRange
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.rememberRelativeTimeLabel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarAppointmentLines
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarAppointmentLines
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarDateBadge
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarDateBadge
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.detailRouteFor
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.detailRouteFor
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.rememberRelativeTimeLabel
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent
|
import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadUser
|
import com.vitorpamplona.amethyst.commons.ui.note.LoadUser
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay
|
import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices
|
import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess.ChessViewModelFactory
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessViewModelFactory
|
|
||||||
import com.vitorpamplona.quartz.nip64Chess.challenge.offer.LiveChessGameChallengeEvent
|
import com.vitorpamplona.quartz.nip64Chess.challenge.offer.LiveChessGameChallengeEvent
|
||||||
import com.vitorpamplona.quartz.nip64Chess.end.LiveChessGameEndEvent
|
import com.vitorpamplona.quartz.nip64Chess.end.LiveChessGameEndEvent
|
||||||
import com.vitorpamplona.quartz.nip64Chess.game.ChessGameEvent
|
import com.vitorpamplona.quartz.nip64Chess.game.ChessGameEvent
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size50Modifier
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size75Modifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.Size75Modifier
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.VoiceHeightModifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.VoiceHeightModifier
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
||||||
|
import com.vitorpamplona.amethyst.commons.video.isHlsMedia
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
|
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.GetVideoController
|
import com.vitorpamplona.amethyst.service.playback.composable.GetVideoController
|
||||||
@@ -74,7 +75,6 @@ import com.vitorpamplona.amethyst.service.playback.composable.controls.PictureIn
|
|||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.LoadedMediaItem
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.LoadedMediaItem
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.isHlsMedia
|
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.Waveform
|
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.Waveform
|
||||||
import com.vitorpamplona.amethyst.service.playback.pip.PipVideoActivity
|
import com.vitorpamplona.amethyst.service.playback.pip.PipVideoActivity
|
||||||
import com.vitorpamplona.amethyst.ui.components.ShareMediaAction
|
import com.vitorpamplona.amethyst.ui.components.ShareMediaAction
|
||||||
|
|||||||
+2
@@ -99,6 +99,8 @@ class AndroidAccountViewModelHost(
|
|||||||
): Result<AnonymizedResult> = VoiceAnonymizer().anonymize(input.toFile(), VoicePreset.valueOf(presetName))
|
): Result<AnonymizedResult> = VoiceAnonymizer().anonymize(input.toFile(), VoicePreset.valueOf(presetName))
|
||||||
|
|
||||||
// Built on the application context: the assistant outlives any one Activity inside the ViewModel.
|
// Built on the application context: the assistant outlives any one Activity inside the ViewModel.
|
||||||
|
override val supportsWritingAssistant: Boolean get() = WritingAssistantFactory.IS_SUPPORTED
|
||||||
|
|
||||||
override fun createWritingAssistant(): WritingAssistant = WritingAssistantFactory.create(modules.appContext)
|
override fun createWritingAssistant(): WritingAssistant = WritingAssistantFactory.create(modules.appContext)
|
||||||
|
|
||||||
override val savedAccounts: Flow<Set<HexKey>> =
|
override val savedAccounts: Flow<Set<HexKey>> =
|
||||||
|
|||||||
+1
-1
@@ -28,6 +28,7 @@ import com.vitorpamplona.amethyst.commons.model.navigation.BottomBarEntry
|
|||||||
import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem
|
import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.datasource.BadgesFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.datasource.BadgesFilterAssemblerSubscription
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list.datasource.CommunitiesListFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list.datasource.CommunitiesListFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.datasource.DiscoveryFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.datasource.DiscoveryFilterAssemblerSubscription
|
||||||
@@ -51,7 +52,6 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.dataso
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.video.datasource.VideoFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.video.datasource.VideoFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.workouts.datasource.WorkoutsFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.workouts.datasource.WorkoutsFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssemblerSubscription
|
||||||
|
|
||||||
|
|||||||
+1
@@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter
|
import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.formatCalendarRange
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.formatCalendarRange
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.rememberRelativeTimeLabel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.video.UserCardHeader
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.video.UserCardHeader
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||||
|
|||||||
+1
-1
@@ -39,8 +39,8 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded
|
import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar
|
import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
-60
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 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.screen.loggedIn.calendars
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes [content] to a `.ics` file in the app's cache dir and opens the system share sheet so
|
|
||||||
* the user can hand the file to a calendar app, email client, file manager, etc.
|
|
||||||
*
|
|
||||||
* The file lives in `cacheDir/calendar/` — covered by the existing `<cache-path>` entry in
|
|
||||||
* `file_paths.xml`, so [FileProvider] can hand out a `content://` URI without further config.
|
|
||||||
* The receiver gets a read-permission grant via [Intent.FLAG_GRANT_READ_URI_PERMISSION] that
|
|
||||||
* lasts only for the duration of the share.
|
|
||||||
*/
|
|
||||||
fun shareIcs(
|
|
||||||
context: Context,
|
|
||||||
filename: String,
|
|
||||||
content: String,
|
|
||||||
chooserTitle: String,
|
|
||||||
) {
|
|
||||||
val dir = File(context.cacheDir, "calendar").apply { mkdirs() }
|
|
||||||
val file = File(dir, filename)
|
|
||||||
file.writeText(content)
|
|
||||||
|
|
||||||
val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
|
|
||||||
val intent =
|
|
||||||
Intent(Intent.ACTION_SEND).apply {
|
|
||||||
type = "text/calendar"
|
|
||||||
putExtra(Intent.EXTRA_STREAM, uri)
|
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
}
|
|
||||||
val chooser =
|
|
||||||
Intent
|
|
||||||
.createChooser(intent, chooserTitle)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
context.startActivity(chooser)
|
|
||||||
}
|
|
||||||
+1
-1
@@ -38,10 +38,10 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.new_chess_game
|
import com.vitorpamplona.amethyst.commons.resources.new_chess_game
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices
|
import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess.ChessViewModelFactory
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessViewModelFactory
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Floating action button for creating new chess game challenges
|
* Floating action button for creating new chess game challenges
|
||||||
|
|||||||
+1
@@ -37,6 +37,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.CheckHiddenFeedWatchBlockAndReport
|
import com.vitorpamplona.amethyst.commons.ui.note.CheckHiddenFeedWatchBlockAndReport
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.WatchNoteEvent
|
import com.vitorpamplona.amethyst.commons.ui.note.WatchNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery.GalleryThumbnail
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui.note.share
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.commons.model.Note
|
import com.vitorpamplona.amethyst.commons.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.share.QrPayloadMode
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.share.qrPayloadFor
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||||
|
|||||||
@@ -466,7 +466,18 @@ assemblers, `EventSync`, …). Packages are renamed on the way:
|
|||||||
wraps BiometricPrompt with the keyguard fallback. `CalendarTimeFormat` is shared on
|
wraps BiometricPrompt with the keyguard fallback. `CalendarTimeFormat` is shared on
|
||||||
`DateSkeletonFormatter`. Moved: search, the long-form composer, the calendar collection
|
`DateSkeletonFormatter`. Moved: search, the long-form composer, the calendar collection
|
||||||
editor, NWC setup and the zap-amount settings. 185 of 252 screens shared.
|
editor, NWC setup and the zap-amount settings. 185 of 252 screens shared.
|
||||||
- **Next:** 67 screens remain, and what blocks them is the platform itself:
|
- **Wave 10 (2026-09-30):** Home, Discover, the nest lobby and the Marmot group editor
|
||||||
|
first; then the date pickers on `LocalClock` (poll and zap-poll deadlines, schedule-at
|
||||||
|
with its presets), `FileSharer.shareTextFile` (the .ics export), `PhoneCalendar` (the
|
||||||
|
system event composer), `relativeTimeSpan` (`DateUtils` / `NSRelativeDateTimeFormatter`),
|
||||||
|
`rememberWindowViewModel` + `LocalWindowViewModelStoreOwner` (state shared across
|
||||||
|
destinations: the chess lobby and board, the Cordn group draft), `isHlsMedia` in
|
||||||
|
`commons.video`, `ChessEventBroadcaster` in commonMain and `supportsWritingAssistant` on
|
||||||
|
the host. `geo:` links go through `LocalUriHandler`. Moved: the note and poll composers,
|
||||||
|
voice reply, the calendar collections and event detail, the geocache detail and editor,
|
||||||
|
the live-activity channel, chess, Cordn group creation, the QR share and the compose
|
||||||
|
settings. 204 of 252 screens shared.
|
||||||
|
- **Next:** 48 screens remain, and what blocks them is the platform itself:
|
||||||
- `ReactionsRow` (2.7k lines: pay-to-app, voice replies, wallet intents).
|
- `ReactionsRow` (2.7k lines: pay-to-app, voice replies, wallet intents).
|
||||||
- The Nests activity and PiP.
|
- The Nests activity and PiP.
|
||||||
- The call screens.
|
- The call screens.
|
||||||
|
|||||||
+9
@@ -25,6 +25,7 @@ import androidx.datastore.preferences.core.Preferences
|
|||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.emptyPreferences
|
import androidx.datastore.preferences.core.emptyPreferences
|
||||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
@@ -64,3 +65,11 @@ class ChessDismissedGamesStore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where the app keeps the dismissed-chess-games store.
|
||||||
|
*
|
||||||
|
* A new file rather than a migration of `chess_dismissed_games`: anyone who had dismissed a game
|
||||||
|
* sees it once more and dismisses it again.
|
||||||
|
*/
|
||||||
|
fun AppPreferenceStores.chessDismissedGamesStore(): ChessDismissedGamesStore = ChessDismissedGamesStore(getDataStore("chess_dismissed_games_v2"))
|
||||||
|
|||||||
+5
-4
@@ -29,6 +29,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.nip64Chess.jester.JesterProtocol
|
import com.vitorpamplona.quartz.nip64Chess.jester.JesterProtocol
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import com.vitorpamplona.quartz.utils.currentTimeMillis
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,7 +85,7 @@ class ChessEventBroadcaster(
|
|||||||
val dummyFilter =
|
val dummyFilter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds = listOf(JesterProtocol.KIND),
|
kinds = listOf(JesterProtocol.KIND),
|
||||||
since = (System.currentTimeMillis() / 1000) + 3600, // 1 hour in future = no results
|
since = (currentTimeMillis() / 1000) + 3600, // 1 hour in future = no results
|
||||||
limit = 1,
|
limit = 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -136,10 +137,10 @@ class ChessEventBroadcaster(
|
|||||||
targetRelays: Set<NormalizedRelayUrl>,
|
targetRelays: Set<NormalizedRelayUrl>,
|
||||||
timeoutMs: Long,
|
timeoutMs: Long,
|
||||||
): Set<NormalizedRelayUrl> {
|
): Set<NormalizedRelayUrl> {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = currentTimeMillis()
|
||||||
val pollInterval = 200L
|
val pollInterval = 200L
|
||||||
|
|
||||||
while (System.currentTimeMillis() - startTime < timeoutMs) {
|
while (currentTimeMillis() - startTime < timeoutMs) {
|
||||||
val connected = client.connectedRelaysFlow().value
|
val connected = client.connectedRelaysFlow().value
|
||||||
val targetConnected = targetRelays.intersect(connected)
|
val targetConnected = targetRelays.intersect(connected)
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ class ChessEventBroadcaster(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// At least one connected is good enough after half the timeout
|
// At least one connected is good enough after half the timeout
|
||||||
if (targetConnected.isNotEmpty() && System.currentTimeMillis() - startTime > timeoutMs / 2) {
|
if (targetConnected.isNotEmpty() && currentTimeMillis() - startTime > timeoutMs / 2) {
|
||||||
return targetConnected
|
return targetConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.commons.video
|
||||||
|
|
||||||
|
/** The mime HLS playlists are normalized to — the value of media3's `MimeTypes.APPLICATION_M3U8`. */
|
||||||
|
const val HLS_MIME_TYPE = "application/x-mpegURL"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mime a player should be handed for [mimeType] on [url]: an explicit mime wins (the four HLS
|
||||||
|
* aliases map onto [HLS_MIME_TYPE]); without one, a path-anchored `.m3u8` means HLS; otherwise
|
||||||
|
* null, letting the player infer the format.
|
||||||
|
*/
|
||||||
|
fun normalizeStreamMimeType(
|
||||||
|
mimeType: String?,
|
||||||
|
url: String? = null,
|
||||||
|
): String? {
|
||||||
|
if (!mimeType.isNullOrBlank()) {
|
||||||
|
return when (mimeType.lowercase()) {
|
||||||
|
"application/vnd.apple.mpegurl",
|
||||||
|
"application/x-mpegurl",
|
||||||
|
"audio/x-mpegurl",
|
||||||
|
"audio/mpegurl",
|
||||||
|
-> HLS_MIME_TYPE
|
||||||
|
|
||||||
|
else -> mimeType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (url != null && hasM3u8PathExtension(url)) {
|
||||||
|
return HLS_MIME_TYPE
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a URL plus its imeta mime identifies HLS, for code that holds a URL and a mime but no
|
||||||
|
* player item yet. Defined through [normalizeStreamMimeType] so it cannot drift from the mime that
|
||||||
|
* actually reaches the player.
|
||||||
|
*
|
||||||
|
* Both halves matter. A BUD-10 blossom playlist is `https://host/<sha256>` with no extension at all,
|
||||||
|
* so only the mime identifies it; and anchoring to the path stops `video.mp4?ref=a.m3u8` counting as
|
||||||
|
* HLS on the strength of its query string.
|
||||||
|
*
|
||||||
|
* Note this answers *is it HLS*, which callers use as a proxy for *is it live*: an on-demand HLS
|
||||||
|
* playlist also answers true. Liveness is only truly knowable from `#EXT-X-ENDLIST` once the
|
||||||
|
* playlist is loaded.
|
||||||
|
*/
|
||||||
|
fun isHlsMedia(
|
||||||
|
url: String,
|
||||||
|
mimeType: String?,
|
||||||
|
): Boolean = normalizeStreamMimeType(mimeType, url) == HLS_MIME_TYPE
|
||||||
|
|
||||||
|
// Restrict `.m3u8` matching to the path component so a query param or fragment that happens to
|
||||||
|
// mention .m3u8 (e.g. `?ref=a.m3u8`) on an MP4 URI doesn't misroute to the HLS source.
|
||||||
|
private fun hasM3u8PathExtension(uri: String): Boolean {
|
||||||
|
val path = uri.substringBefore('?').substringBefore('#')
|
||||||
|
return path.endsWith(".m3u8", ignoreCase = true)
|
||||||
|
}
|
||||||
+3
@@ -91,6 +91,9 @@ interface AccountViewModelHost {
|
|||||||
presetName: String,
|
presetName: String,
|
||||||
): Result<AnonymizedResult> = Result.failure(UnsupportedOperationException("Voice anonymization is not available on this platform"))
|
): Result<AnonymizedResult> = Result.failure(UnsupportedOperationException("Voice anonymization is not available on this platform"))
|
||||||
|
|
||||||
|
/** Whether this build can offer an on-device writing assistant at all (it shows the setting). */
|
||||||
|
val supportsWritingAssistant: Boolean get() = false
|
||||||
|
|
||||||
/** A new on-device writing assistant, or null where the platform has none. */
|
/** A new on-device writing assistant, or null where the platform has none. */
|
||||||
fun createWritingAssistant(): WritingAssistant? = null
|
fun createWritingAssistant(): WritingAssistant? = null
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -18,11 +18,11 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.playback.composable.mediaitem
|
package com.vitorpamplona.amethyst.commons.video
|
||||||
|
|
||||||
import org.junit.Assert.assertFalse
|
import kotlin.test.Test
|
||||||
import org.junit.Assert.assertTrue
|
import kotlin.test.assertFalse
|
||||||
import org.junit.Test
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The two cases that motivated replacing the old `.m3u8`-substring predicate are
|
* The two cases that motivated replacing the old `.m3u8`-substring predicate are
|
||||||
@@ -39,7 +39,7 @@ class IsHlsMediaTest {
|
|||||||
|
|
||||||
assertTrue(isHlsMedia(blossom, "application/x-mpegurl"))
|
assertTrue(isHlsMedia(blossom, "application/x-mpegurl"))
|
||||||
assertTrue(isHlsMedia(blossom, "application/vnd.apple.mpegurl"))
|
assertTrue(isHlsMedia(blossom, "application/vnd.apple.mpegurl"))
|
||||||
assertFalse("no mime and no extension leaves nothing to go on", isHlsMedia(blossom, null))
|
assertFalse(isHlsMedia(blossom, null), "no mime and no extension leaves nothing to go on")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -54,7 +54,7 @@ class IsHlsMediaTest {
|
|||||||
fun recognisesAPlainM3u8Path() {
|
fun recognisesAPlainM3u8Path() {
|
||||||
assertTrue(isHlsMedia("https://host/live.m3u8", null))
|
assertTrue(isHlsMedia("https://host/live.m3u8", null))
|
||||||
assertTrue(isHlsMedia("https://host/live.M3U8", null))
|
assertTrue(isHlsMedia("https://host/live.M3U8", null))
|
||||||
assertTrue("query strings don't hide the path", isHlsMedia("https://host/live.m3u8?vt=abc", null))
|
assertTrue(isHlsMedia("https://host/live.m3u8?vt=abc", null), "query strings don't hide the path")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
+36
-2
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.commons.ui.components
|
package com.vitorpamplona.amethyst.commons.ui.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -33,8 +34,17 @@ import java.util.zip.ZipOutputStream
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun rememberFileSharer(): FileSharer {
|
actual fun rememberFileSharer(): FileSharer {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
return remember(context) {
|
return remember(context) { AndroidFileSharer(context) }
|
||||||
FileSharer { zipName, files, title ->
|
}
|
||||||
|
|
||||||
|
private class AndroidFileSharer(
|
||||||
|
val context: Context,
|
||||||
|
) : FileSharer {
|
||||||
|
override fun shareTextFilesAsZip(
|
||||||
|
zipName: String,
|
||||||
|
files: List<Pair<String, String>>,
|
||||||
|
title: String,
|
||||||
|
) {
|
||||||
val zipFile = File(context.cacheDir, zipName)
|
val zipFile = File(context.cacheDir, zipName)
|
||||||
ZipOutputStream(FileOutputStream(zipFile)).use { zip ->
|
ZipOutputStream(FileOutputStream(zipFile)).use { zip ->
|
||||||
files.forEach { (name, content) ->
|
files.forEach { (name, content) ->
|
||||||
@@ -57,5 +67,29 @@ actual fun rememberFileSharer(): FileSharer {
|
|||||||
|
|
||||||
context.startActivity(Intent.createChooser(sendIntent, title))
|
context.startActivity(Intent.createChooser(sendIntent, title))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file lives in `cacheDir/shared/`, covered by the `<cache-path>` entry in
|
||||||
|
* `file_paths.xml`; the receiver's read grant lasts only for the share.
|
||||||
|
*/
|
||||||
|
override fun shareTextFile(
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String,
|
||||||
|
content: String,
|
||||||
|
title: String,
|
||||||
|
) {
|
||||||
|
val dir = File(context.cacheDir, "shared").apply { mkdirs() }
|
||||||
|
val file = File(dir, fileName)
|
||||||
|
file.writeText(content)
|
||||||
|
|
||||||
|
val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
|
||||||
|
val intent =
|
||||||
|
Intent(Intent.ACTION_SEND).apply {
|
||||||
|
type = mimeType
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
context.startActivity(Intent.createChooser(intent, title).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -18,17 +18,26 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.provider.CalendarContract
|
import android.provider.CalendarContract
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip52Calendar.parseIsoDateToUnixSeconds
|
import com.vitorpamplona.amethyst.commons.model.nip52Calendar.parseIsoDateToUnixSeconds
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent
|
import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent
|
||||||
import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent
|
import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun rememberPhoneCalendar(): PhoneCalendar {
|
||||||
|
val context = LocalContext.current
|
||||||
|
return remember(context) { PhoneCalendar { addToPhoneCalendar(context, it) } }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the system "New Event" composer (Google Calendar / Samsung / iCloud / etc.) pre-populated
|
* Opens the system "New Event" composer (Google Calendar / Samsung / iCloud / etc.) pre-populated
|
||||||
* with this appointment's title, time, location, and description. The user sees their normal
|
* with this appointment's title, time, location, and description. The user sees their normal
|
||||||
@@ -38,7 +47,7 @@ import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent
|
|||||||
* Returns true if a calendar app handled the intent, false if no handler was found — the caller
|
* Returns true if a calendar app handled the intent, false if no handler was found — the caller
|
||||||
* can decide whether to fall back to the .ics share path.
|
* can decide whether to fall back to the .ics share path.
|
||||||
*/
|
*/
|
||||||
fun addToPhoneCalendar(
|
private fun addToPhoneCalendar(
|
||||||
context: Context,
|
context: Context,
|
||||||
event: Event,
|
event: Event,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
|
||||||
|
actual suspend fun relativeTimeSpan(
|
||||||
|
targetSeconds: Long,
|
||||||
|
nowSeconds: Long,
|
||||||
|
dayResolution: Boolean,
|
||||||
|
): String =
|
||||||
|
DateUtils
|
||||||
|
.getRelativeTimeSpanString(
|
||||||
|
targetSeconds * 1000L,
|
||||||
|
nowSeconds * 1000L,
|
||||||
|
if (dayResolution) DateUtils.DAY_IN_MILLIS else DateUtils.MINUTE_IN_MILLIS,
|
||||||
|
DateUtils.FORMAT_ABBREV_RELATIVE,
|
||||||
|
).toString()
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.actions.mediaServers
|
package com.vitorpamplona.amethyst.commons.ui.actions.mediaServers
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.actions.uploads
|
package com.vitorpamplona.amethyst.commons.ui.actions.uploads
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.actions.uploads
|
package com.vitorpamplona.amethyst.commons.ui.actions.uploads
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
+1
-3
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.actions.uploads
|
package com.vitorpamplona.amethyst.commons.ui.actions.uploads
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -32,7 +32,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.commons.audio.VoicePreset
|
import com.vitorpamplona.amethyst.commons.audio.VoicePreset
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
@@ -44,7 +43,6 @@ fun VoicePresetSelector(
|
|||||||
onPresetSelected: (VoicePreset) -> Unit,
|
onPresetSelected: (VoicePreset) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val isProcessing = processingPreset != null
|
val isProcessing = processingPreset != null
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
+12
-1
@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.commons.ui.components
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
/** Hands generated files to the platform's share sheet. */
|
/** Hands generated files to the platform's share sheet. */
|
||||||
fun interface FileSharer {
|
interface FileSharer {
|
||||||
/**
|
/**
|
||||||
* Packs [files] (name to text content) into a zip named [zipName] and shares it, behind a
|
* Packs [files] (name to text content) into a zip named [zipName] and shares it, behind a
|
||||||
* chooser titled [title] where the platform has one.
|
* chooser titled [title] where the platform has one.
|
||||||
@@ -33,6 +33,17 @@ fun interface FileSharer {
|
|||||||
files: List<Pair<String, String>>,
|
files: List<Pair<String, String>>,
|
||||||
title: String,
|
title: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes [content] to a file named [fileName] and shares it as [mimeType], behind a chooser
|
||||||
|
* titled [title] where the platform has one.
|
||||||
|
*/
|
||||||
|
fun shareTextFile(
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String,
|
||||||
|
content: String,
|
||||||
|
title: String,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
@@ -67,6 +67,7 @@ inline fun <reified VM : ViewModel> rememberViewModel(
|
|||||||
* when the store has no view model yet.
|
* when the store has no view model yet.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
|
@PublishedApi
|
||||||
internal fun <VM : ViewModel> rememberLambdaFactory(factory: () -> VM): ViewModelProvider.Factory {
|
internal fun <VM : ViewModel> rememberLambdaFactory(factory: () -> VM): ViewModelProvider.Factory {
|
||||||
val currentFactory by rememberUpdatedState(factory)
|
val currentFactory by rememberUpdatedState(factory)
|
||||||
return remember {
|
return remember {
|
||||||
|
|||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.commons.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.ViewModelStoreOwner
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The store that outlives every navigation destination in this window: Android's Activity. The
|
||||||
|
* platform entry point provides it above the nav host, whose destinations each get a store of
|
||||||
|
* their own.
|
||||||
|
*/
|
||||||
|
val LocalWindowViewModelStoreOwner: ProvidableCompositionLocal<ViewModelStoreOwner?> = staticCompositionLocalOf { null }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A view model shared by every destination of this window, for state that has to survive
|
||||||
|
* navigating between screens (a draft filled in over two screens, a game the lobby and the board
|
||||||
|
* both watch). Without a window store it falls back to [rememberViewModel], scoped to the caller.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun <VM : ViewModel> rememberWindowViewModel(
|
||||||
|
modelClass: KClass<VM>,
|
||||||
|
key: String,
|
||||||
|
factory: ViewModelProvider.Factory,
|
||||||
|
): VM {
|
||||||
|
val owner = LocalWindowViewModelStoreOwner.current ?: return rememberViewModel(modelClass, key, factory)
|
||||||
|
return remember(owner, key) { ViewModelProvider.create(owner, factory).get(key, modelClass) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun <reified VM : ViewModel> rememberWindowViewModel(
|
||||||
|
key: String,
|
||||||
|
factory: ViewModelProvider.Factory,
|
||||||
|
): VM = rememberWindowViewModel(VM::class, key, factory)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun <reified VM : ViewModel> rememberWindowViewModel(
|
||||||
|
key: String,
|
||||||
|
noinline factory: () -> VM,
|
||||||
|
): VM = rememberWindowViewModel(VM::class, key, rememberLambdaFactory(factory))
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.creators.polls
|
package com.vitorpamplona.amethyst.commons.ui.note.creators.polls
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.creators.polls
|
package com.vitorpamplona.amethyst.commons.ui.note.creators.polls
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
+29
-40
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.creators.scheduling
|
package com.vitorpamplona.amethyst.commons.ui.note.creators.scheduling
|
||||||
|
|
||||||
import android.text.format.DateFormat
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -54,7 +53,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -76,18 +74,15 @@ import com.vitorpamplona.amethyst.commons.resources.schedule_post_time_label
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.schedule_post_warning_multi
|
import com.vitorpamplona.amethyst.commons.resources.schedule_post_warning_multi
|
||||||
import com.vitorpamplona.amethyst.commons.resources.schedule_post_warning_single
|
import com.vitorpamplona.amethyst.commons.resources.schedule_post_warning_single
|
||||||
import com.vitorpamplona.amethyst.commons.resources.schedule_post_warning_title
|
import com.vitorpamplona.amethyst.commons.resources.schedule_post_warning_title
|
||||||
|
import com.vitorpamplona.amethyst.commons.search.calendar.LocalClock
|
||||||
|
import com.vitorpamplona.amethyst.commons.search.calendar.SearchDate
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.rememberIs24HourClock
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.timeAheadNoDot
|
import com.vitorpamplona.amethyst.commons.ui.note.timeAheadNoDot
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
import java.time.DayOfWeek
|
import com.vitorpamplona.quartz.utils.currentTimeMillis
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.temporal.TemporalAdjusters
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Two-stage date + time picker for scheduling a post for future publication.
|
* Two-stage date + time picker for scheduling a post for future publication.
|
||||||
@@ -107,29 +102,26 @@ fun ScheduleAtPicker(
|
|||||||
var showDatePicker by remember { mutableStateOf(false) }
|
var showDatePicker by remember { mutableStateOf(false) }
|
||||||
var showTimePicker by remember { mutableStateOf(false) }
|
var showTimePicker by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val currentTime =
|
// The scheduled time as a local wall-clock time: seconds since the epoch shifted by this zone's offset.
|
||||||
Instant
|
val localSeconds = scheduledForSec + LocalClock.utcOffsetSeconds(scheduledForSec)
|
||||||
.ofEpochMilli(scheduledForSec * 1000)
|
val localSecondOfDay = localSeconds.mod(TimeUtils.ONE_DAY)
|
||||||
.atZone(ZoneId.systemDefault())
|
val currentYear = SearchDate.civilFromDays(localSeconds.floorDiv(TimeUtils.ONE_DAY)).year
|
||||||
.toLocalDateTime()
|
|
||||||
|
|
||||||
val datePickerState =
|
val datePickerState =
|
||||||
rememberDatePickerState(
|
rememberDatePickerState(
|
||||||
initialSelectedDateMillis = scheduledForSec * 1000,
|
initialSelectedDateMillis = scheduledForSec * 1000,
|
||||||
yearRange = currentTime.year..2050,
|
yearRange = currentYear..2050,
|
||||||
selectableDates =
|
selectableDates =
|
||||||
object : SelectableDates {
|
object : SelectableDates {
|
||||||
override fun isSelectableDate(utcTimeMillis: Long): Boolean = utcTimeMillis >= System.currentTimeMillis() - 86_400_000
|
override fun isSelectableDate(utcTimeMillis: Long): Boolean = utcTimeMillis >= currentTimeMillis() - 86_400_000
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val timePickerState =
|
val timePickerState =
|
||||||
rememberTimePickerState(
|
rememberTimePickerState(
|
||||||
initialHour = currentTime.hour,
|
initialHour = localSecondOfDay / TimeUtils.ONE_HOUR,
|
||||||
initialMinute = currentTime.minute,
|
initialMinute = (localSecondOfDay % TimeUtils.ONE_HOUR) / TimeUtils.ONE_MINUTE,
|
||||||
is24Hour = DateFormat.is24HourFormat(context),
|
is24Hour = rememberIs24HourClock(),
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
@@ -220,8 +212,7 @@ fun ScheduleAtPicker(
|
|||||||
(timePickerState.minute * TimeUtils.ONE_MINUTE)
|
(timePickerState.minute * TimeUtils.ONE_MINUTE)
|
||||||
} ?: TimeUtils.oneDayAhead()
|
} ?: TimeUtils.oneDayAhead()
|
||||||
|
|
||||||
val offset: ZoneOffset = ZoneId.systemDefault().rules.getOffset(Instant.now())
|
val rawSec = datetimeLocalTimeZone - LocalClock.utcOffsetSeconds(TimeUtils.now())
|
||||||
val rawSec = datetimeLocalTimeZone - offset.totalSeconds
|
|
||||||
|
|
||||||
onChanged(roundUpToNextQuarterHour(rawSec))
|
onChanged(roundUpToNextQuarterHour(rawSec))
|
||||||
showTimePicker = false
|
showTimePicker = false
|
||||||
@@ -300,24 +291,22 @@ private fun PresetChips(onPick: (Long) -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun presetInOneHour(): Long = (System.currentTimeMillis() / 1000) + 3600
|
private fun presetInOneHour(): Long = (currentTimeMillis() / 1000) + 3600
|
||||||
|
|
||||||
private fun presetTomorrowMorning(): Long {
|
private fun presetTomorrowMorning(): Long = nineAm(LocalClock.today().plusDays(1))
|
||||||
val zone = ZoneId.systemDefault()
|
|
||||||
val tomorrow9am = LocalDate.now(zone).plusDays(1).atTime(LocalTime.of(9, 0))
|
|
||||||
return tomorrow9am.atZone(zone).toEpochSecond()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun presetNextMondayMorning(): Long {
|
private fun presetNextMondayMorning(): Long {
|
||||||
val zone = ZoneId.systemDefault()
|
|
||||||
// Always step at least one day forward — if today is Monday, return next Monday.
|
// Always step at least one day forward — if today is Monday, return next Monday.
|
||||||
val target =
|
val tomorrow = LocalClock.today().plusDays(1)
|
||||||
LocalDate
|
val monday = 1
|
||||||
.now(zone)
|
return nineAm(tomorrow.plusDays((monday - tomorrow.dayOfWeek()).mod(7)))
|
||||||
.plusDays(1)
|
}
|
||||||
.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY))
|
|
||||||
.atTime(LocalTime.of(9, 0))
|
/** 09:00 local time on [date], read with the offset in force at 09:00, not at midnight. */
|
||||||
return target.atZone(zone).toEpochSecond()
|
private fun nineAm(date: SearchDate): Long {
|
||||||
|
val midnight = LocalClock.startOfDay(date)
|
||||||
|
val guess = midnight + 9 * TimeUtils.ONE_HOUR
|
||||||
|
return guess + LocalClock.utcOffsetSeconds(midnight) - LocalClock.utcOffsetSeconds(guess)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -326,9 +315,9 @@ private fun presetNextMondayMorning(): Long {
|
|||||||
* (rare — only if the user picks the exact current quarter-hour), bump forward
|
* (rare — only if the user picks the exact current quarter-hour), bump forward
|
||||||
* one slot.
|
* one slot.
|
||||||
*/
|
*/
|
||||||
internal fun roundUpToNextQuarterHour(epochSec: Long): Long {
|
fun roundUpToNextQuarterHour(epochSec: Long): Long {
|
||||||
val quarter = 15 * 60L
|
val quarter = 15 * 60L
|
||||||
val rounded = ((epochSec + quarter - 1) / quarter) * quarter
|
val rounded = ((epochSec + quarter - 1) / quarter) * quarter
|
||||||
val nowSec = System.currentTimeMillis() / 1000
|
val nowSec = currentTimeMillis() / 1000
|
||||||
return if (rounded <= nowSec) rounded + quarter else rounded
|
return if (rounded <= nowSec) rounded + quarter else rounded
|
||||||
}
|
}
|
||||||
+15
-17
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
package com.vitorpamplona.amethyst.commons.ui.note.creators.zappolls
|
||||||
|
|
||||||
import android.text.format.DateFormat
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -45,7 +44,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
@@ -57,13 +55,14 @@ import com.vitorpamplona.amethyst.commons.resources.confirm
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.next
|
import com.vitorpamplona.amethyst.commons.resources.next
|
||||||
import com.vitorpamplona.amethyst.commons.resources.poll_closing_date_time
|
import com.vitorpamplona.amethyst.commons.resources.poll_closing_date_time
|
||||||
import com.vitorpamplona.amethyst.commons.resources.poll_closing_in
|
import com.vitorpamplona.amethyst.commons.resources.poll_closing_in
|
||||||
|
import com.vitorpamplona.amethyst.commons.search.calendar.LocalClock
|
||||||
|
import com.vitorpamplona.amethyst.commons.search.calendar.SearchDate
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.rememberIs24HourClock
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.timeAheadNoDot
|
import com.vitorpamplona.amethyst.commons.ui.note.timeAheadNoDot
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.ShortNotePostViewModel
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.ShortNotePostViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
import java.time.Instant
|
import com.vitorpamplona.quartz.utils.currentTimeMillis
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -72,26 +71,28 @@ fun ZapPollDeadlinePicker(model: ShortNotePostViewModel) {
|
|||||||
var showTimePicker by remember { mutableStateOf(false) }
|
var showTimePicker by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Get current time details
|
// Get current time details
|
||||||
val currentTime = Instant.ofEpochMilli(model.zapPollClosedAt * 1000).atZone(ZoneId.systemDefault()).toLocalDateTime()
|
// The deadline as a local wall-clock time: seconds since the epoch shifted by this zone's offset.
|
||||||
|
val localSeconds = model.zapPollClosedAt + LocalClock.utcOffsetSeconds(model.zapPollClosedAt)
|
||||||
|
val localSecondOfDay = localSeconds.mod(TimeUtils.ONE_DAY)
|
||||||
|
val currentYear = SearchDate.civilFromDays(localSeconds.floorDiv(TimeUtils.ONE_DAY)).year
|
||||||
|
|
||||||
val datePickerState =
|
val datePickerState =
|
||||||
rememberDatePickerState(
|
rememberDatePickerState(
|
||||||
initialSelectedDateMillis = model.zapPollClosedAt * 1000,
|
initialSelectedDateMillis = model.zapPollClosedAt * 1000,
|
||||||
yearRange = currentTime.year..2050,
|
yearRange = currentYear..2050,
|
||||||
selectableDates =
|
selectableDates =
|
||||||
object : SelectableDates {
|
object : SelectableDates {
|
||||||
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
|
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
|
||||||
// Only allow today and future dates
|
// Only allow today and future dates
|
||||||
return utcTimeMillis >= System.currentTimeMillis() - 86400000 // minus 24h buffer
|
return utcTimeMillis >= currentTimeMillis() - 86400000 // minus 24h buffer
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
val context = LocalContext.current
|
|
||||||
val timePickerState =
|
val timePickerState =
|
||||||
rememberTimePickerState(
|
rememberTimePickerState(
|
||||||
initialHour = currentTime.hour,
|
initialHour = localSecondOfDay / TimeUtils.ONE_HOUR,
|
||||||
initialMinute = currentTime.minute,
|
initialMinute = (localSecondOfDay % TimeUtils.ONE_HOUR) / TimeUtils.ONE_MINUTE,
|
||||||
is24Hour = DateFormat.is24HourFormat(context),
|
is24Hour = rememberIs24HourClock(),
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedCard(
|
OutlinedCard(
|
||||||
@@ -148,10 +149,7 @@ fun ZapPollDeadlinePicker(model: ShortNotePostViewModel) {
|
|||||||
(timePickerState.minute * TimeUtils.ONE_MINUTE)
|
(timePickerState.minute * TimeUtils.ONE_MINUTE)
|
||||||
} ?: TimeUtils.oneDayAhead()
|
} ?: TimeUtils.oneDayAhead()
|
||||||
|
|
||||||
// Get the offset from UTC for the current instant in the local time zone
|
model.zapPollClosedAt = datetimeLocalTimeZone - LocalClock.utcOffsetSeconds(TimeUtils.now())
|
||||||
val offset: ZoneOffset = ZoneId.systemDefault().rules.getOffset(Instant.now())
|
|
||||||
|
|
||||||
model.zapPollClosedAt = datetimeLocalTimeZone - offset.totalSeconds
|
|
||||||
|
|
||||||
showTimePicker = false
|
showTimePicker = false
|
||||||
},
|
},
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
package com.vitorpamplona.amethyst.commons.ui.note.creators.zappolls
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
+2
-2
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
package com.vitorpamplona.amethyst.commons.ui.note.creators.zappolls
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
@@ -67,7 +67,7 @@ fun ZapPollOption(
|
|||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(Res.string.poll_option_index).format(optionIndex + 1),
|
text = stringRes(Res.string.poll_option_index, (optionIndex + 1).toString()),
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
package com.vitorpamplona.amethyst.commons.ui.note.creators.zappolls
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.share
|
package com.vitorpamplona.amethyst.commons.ui.note.share
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.commons.model.Note
|
import com.vitorpamplona.amethyst.commons.model.Note
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.externalLinkForNote
|
import com.vitorpamplona.amethyst.commons.ui.note.externalLinkForNote
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.share
|
package com.vitorpamplona.amethyst.commons.ui.note.share
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
+3
-3
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.share
|
package com.vitorpamplona.amethyst.commons.ui.note.share
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -57,10 +57,10 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.components.collectContentWarningReasons
|
import com.vitorpamplona.amethyst.commons.ui.components.collectContentWarningReasons
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.NoteAuthorPicture
|
import com.vitorpamplona.amethyst.commons.ui.note.NoteAuthorPicture
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery.GalleryThumbnail
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery.UrlImageView
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.GalleryThumbnail
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.UrlImageView
|
|
||||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent
|
import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent
|
||||||
+2
-2
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -32,8 +32,8 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded
|
import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar
|
import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level screen for browsing NIP-52 kind-31924 calendars (collections of appointments). Reuses
|
* Top-level screen for browsing NIP-52 kind-31924 calendars (collections of appointments). Reuses
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
+4
-4
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -42,7 +42,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -62,6 +61,7 @@ import com.vitorpamplona.amethyst.commons.resources.calendar_empty_collections_s
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.calendar_empty_collections_title
|
import com.vitorpamplona.amethyst.commons.resources.calendar_empty_collections_title
|
||||||
import com.vitorpamplona.amethyst.commons.resources.calendar_export_event
|
import com.vitorpamplona.amethyst.commons.resources.calendar_export_event
|
||||||
import com.vitorpamplona.amethyst.commons.resources.calendar_export_share_title
|
import com.vitorpamplona.amethyst.commons.resources.calendar_export_share_title
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberFileSharer
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.commons.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.ScrollStateKeys
|
import com.vitorpamplona.amethyst.commons.ui.feeds.ScrollStateKeys
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.WatchScrollToTop
|
import com.vitorpamplona.amethyst.commons.ui.feeds.WatchScrollToTop
|
||||||
@@ -157,7 +157,7 @@ fun CalendarCollectionCard(
|
|||||||
val title = remember(note.idHex) { event.title() }
|
val title = remember(note.idHex) { event.title() }
|
||||||
val description = remember(note.idHex) { event.content.take(180) }
|
val description = remember(note.idHex) { event.content.take(180) }
|
||||||
val count = remember(note.idHex) { event.calendarEventAddresses().size }
|
val count = remember(note.idHex) { event.calendarEventAddresses().size }
|
||||||
val context = LocalContext.current
|
val fileSharer = rememberFileSharer()
|
||||||
val icsChooserTitle = stringRes(Res.string.calendar_export_share_title)
|
val icsChooserTitle = stringRes(Res.string.calendar_export_share_title)
|
||||||
|
|
||||||
// Calendar collections are addressable (kind 31924); route to the dedicated
|
// Calendar collections are addressable (kind 31924); route to the dedicated
|
||||||
@@ -223,7 +223,7 @@ fun CalendarCollectionCard(
|
|||||||
val members = collectMembers(event)
|
val members = collectMembers(event)
|
||||||
val ics = IcsExport.calendarToIcs(event, members, TimeUtils.now())
|
val ics = IcsExport.calendarToIcs(event, members, TimeUtils.now())
|
||||||
val filename = IcsExport.calendarFilename(event)
|
val filename = IcsExport.calendarFilename(event)
|
||||||
shareIcs(context, filename, ics, icsChooserTitle)
|
fileSharer.shareTextFile(filename, "text/calendar", ics, icsChooserTitle)
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
symbol = MaterialSymbols.Share,
|
symbol = MaterialSymbols.Share,
|
||||||
+6
-26
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
@@ -29,6 +28,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.calendar_relative_ongoing
|
import com.vitorpamplona.amethyst.commons.resources.calendar_relative_ongoing
|
||||||
import com.vitorpamplona.amethyst.commons.resources.calendar_relative_ongoing_with_end
|
import com.vitorpamplona.amethyst.commons.resources.calendar_relative_ongoing_with_end
|
||||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.relativeTimeSpan
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,14 +36,14 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
|||||||
* label for an appointment. Returns null when the event has no parseable start (nothing to
|
* label for an appointment. Returns null when the event has no parseable start (nothing to
|
||||||
* anchor a relative phrase to).
|
* anchor a relative phrase to).
|
||||||
*
|
*
|
||||||
* Uses [DateUtils.getRelativeTimeSpanString] for the underlying minute/hour/day phrasing — that
|
* Uses the platform's relative-time formatter ([relativeTimeSpan]) for the underlying minute/hour/day phrasing — that
|
||||||
* helper is locale-aware and ages from "just now" through "in N days" to absolute date for
|
* helper is locale-aware and ages from "just now" through "in N days" to absolute date for
|
||||||
* far-out events. For all-day events we extend the resolution to DAY so we get "tomorrow",
|
* far-out events. For all-day events we extend the resolution to DAY so we get "tomorrow",
|
||||||
* "in 3 days" instead of an hour-precision phrase that would lie about the start moment.
|
* "in 3 days" instead of an hour-precision phrase that would lie about the start moment.
|
||||||
*
|
*
|
||||||
* Ongoing events (start ≤ now ≤ end) get a composite "Happening now · ends in X" so a user
|
* Ongoing events (start ≤ now ≤ end) get a composite "Happening now · ends in X" so a user
|
||||||
* mid-event sees how much time is left rather than the misleading "started X minutes ago" that
|
* mid-event sees how much time is left rather than the misleading "started X minutes ago" that
|
||||||
* DateUtils would produce on its own.
|
* the formatter would produce on its own.
|
||||||
*/
|
*/
|
||||||
suspend fun relativeTimeLabel(
|
suspend fun relativeTimeLabel(
|
||||||
view: CalendarAppointmentView,
|
view: CalendarAppointmentView,
|
||||||
@@ -54,31 +54,11 @@ suspend fun relativeTimeLabel(
|
|||||||
|
|
||||||
if (end != null && start <= nowSeconds && nowSeconds <= end) {
|
if (end != null && start <= nowSeconds && nowSeconds <= end) {
|
||||||
val ongoing = loadStringRes(Res.string.calendar_relative_ongoing)
|
val ongoing = loadStringRes(Res.string.calendar_relative_ongoing)
|
||||||
val endsIn =
|
val endsIn = relativeTimeSpan(end, nowSeconds, view.isAllDay)
|
||||||
DateUtils
|
|
||||||
.getRelativeTimeSpanString(
|
|
||||||
end * 1000L,
|
|
||||||
nowSeconds * 1000L,
|
|
||||||
if (view.isAllDay) DateUtils.DAY_IN_MILLIS else DateUtils.MINUTE_IN_MILLIS,
|
|
||||||
DateUtils.FORMAT_ABBREV_RELATIVE,
|
|
||||||
).toString()
|
|
||||||
return loadStringRes(Res.string.calendar_relative_ongoing_with_end, ongoing, endsIn)
|
return loadStringRes(Res.string.calendar_relative_ongoing_with_end, ongoing, endsIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
val minResolution =
|
return relativeTimeSpan(start, nowSeconds, view.isAllDay)
|
||||||
if (view.isAllDay) {
|
|
||||||
DateUtils.DAY_IN_MILLIS
|
|
||||||
} else {
|
|
||||||
DateUtils.MINUTE_IN_MILLIS
|
|
||||||
}
|
|
||||||
|
|
||||||
return DateUtils
|
|
||||||
.getRelativeTimeSpanString(
|
|
||||||
start * 1000L,
|
|
||||||
nowSeconds * 1000L,
|
|
||||||
minResolution,
|
|
||||||
DateUtils.FORMAT_ABBREV_RELATIVE,
|
|
||||||
).toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
|
||||||
|
/** Hands a NIP-52 appointment to the device's own calendar app. */
|
||||||
|
fun interface PhoneCalendar {
|
||||||
|
/**
|
||||||
|
* Opens the calendar's "New Event" composer pre-filled with [event]. Returns false when there
|
||||||
|
* is no calendar app to take it (or [event] is not an appointment), so the caller can fall
|
||||||
|
* back to sharing an .ics file.
|
||||||
|
*/
|
||||||
|
fun add(event: Event): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android opens the system calendar composer. Desktop and iOS have no calendar composer wired
|
||||||
|
* yet and always report false.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
expect fun rememberPhoneCalendar(): PhoneCalendar
|
||||||
+10
-12
@@ -18,18 +18,16 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
|
||||||
import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where Android keeps the dismissed-chess-games store.
|
* The platform's own phrase for how far [targetSeconds] is from [nowSeconds] ("in 2 hr.",
|
||||||
*
|
* "5 min. ago", "tomorrow"), no finer than a day when [dayResolution] is set. Android asks
|
||||||
* A new file rather than a migration of `chess_dismissed_games`: the dismissed
|
* `DateUtils`, iOS `NSRelativeDateTimeFormatter`; Desktop has no such formatter and writes a
|
||||||
* list is a convenience, and chess has few enough users that carrying the old
|
* compact signed span.
|
||||||
* data over is not worth the code. Anyone who had dismissed a game sees it once
|
|
||||||
* more and dismisses it again.
|
|
||||||
*/
|
*/
|
||||||
internal fun chessDismissedGamesData(stores: AppPreferenceStores): DataStore<Preferences> = stores.getDataStore("chess_dismissed_games_v2")
|
expect suspend fun relativeTimeSpan(
|
||||||
|
targetSeconds: Long,
|
||||||
|
nowSeconds: Long,
|
||||||
|
dayResolution: Boolean,
|
||||||
|
): String
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.datasource
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.detail
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.detail
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
+33
-34
@@ -18,10 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.detail
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.detail
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -52,11 +50,10 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
@@ -91,6 +88,8 @@ import com.vitorpamplona.amethyst.commons.resources.calendar_untitled
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.edit_calendar_event
|
import com.vitorpamplona.amethyst.commons.resources.edit_calendar_event
|
||||||
import com.vitorpamplona.amethyst.commons.resources.route_calendar_event_detail
|
import com.vitorpamplona.amethyst.commons.resources.route_calendar_event_detail
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage
|
import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberFileSharer
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer
|
||||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture
|
import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture
|
||||||
@@ -100,16 +99,15 @@ import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter
|
import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.types.CalendarRsvpRow
|
import com.vitorpamplona.amethyst.commons.ui.note.types.CalendarRsvpRow
|
||||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.formatCalendarRange
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.formatCalendarRange
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.formatLongDate
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.formatLongDate
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.rememberPhoneCalendar
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.calendars.rememberRelativeTimeLabel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size30dp
|
import com.vitorpamplona.amethyst.commons.ui.theme.Size30dp
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp
|
import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.addToPhoneCalendar
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.rememberRelativeTimeLabel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.shareIcs
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||||
@@ -177,7 +175,9 @@ fun CalendarEventDetailScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
val context = LocalContext.current
|
val phoneCalendar = rememberPhoneCalendar()
|
||||||
|
val fileSharer = rememberFileSharer()
|
||||||
|
val textSharer = rememberTextSharer()
|
||||||
val icsChooserTitle = stringRes(Res.string.calendar_export_share_title)
|
val icsChooserTitle = stringRes(Res.string.calendar_export_share_title)
|
||||||
// Two share modes:
|
// Two share modes:
|
||||||
// - .ics for non-nostr calendar apps (Google Calendar / iOS / Outlook)
|
// - .ics for non-nostr calendar apps (Google Calendar / iOS / Outlook)
|
||||||
@@ -190,10 +190,10 @@ fun CalendarEventDetailScreen(
|
|||||||
// has no calendar app registered for ACTION_INSERT (rare; Wear OS, some
|
// has no calendar app registered for ACTION_INSERT (rare; Wear OS, some
|
||||||
// GrapheneOS profiles).
|
// GrapheneOS profiles).
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if (!addToPhoneCalendar(context, event)) {
|
if (!phoneCalendar.add(event)) {
|
||||||
val ics = IcsExport.appointmentToIcs(event, targetAddress, TimeUtils.now())
|
val ics = IcsExport.appointmentToIcs(event, targetAddress, TimeUtils.now())
|
||||||
val filename = IcsExport.appointmentFilename(event, targetAddress)
|
val filename = IcsExport.appointmentFilename(event, targetAddress)
|
||||||
shareIcs(context, filename, ics, icsChooserTitle)
|
fileSharer.shareTextFile(filename, "text/calendar", ics, icsChooserTitle)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -206,7 +206,7 @@ fun CalendarEventDetailScreen(
|
|||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
val ics = IcsExport.appointmentToIcs(event, targetAddress, TimeUtils.now())
|
val ics = IcsExport.appointmentToIcs(event, targetAddress, TimeUtils.now())
|
||||||
val filename = IcsExport.appointmentFilename(event, targetAddress)
|
val filename = IcsExport.appointmentFilename(event, targetAddress)
|
||||||
shareIcs(context, filename, ics, icsChooserTitle)
|
fileSharer.shareTextFile(filename, "text/calendar", ics, icsChooserTitle)
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
symbol = MaterialSymbols.Share,
|
symbol = MaterialSymbols.Share,
|
||||||
@@ -224,15 +224,7 @@ fun CalendarEventDetailScreen(
|
|||||||
targetAddress.dTag,
|
targetAddress.dTag,
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
val intent =
|
textSharer.share("nostr:$naddr", null, shareTitle)
|
||||||
Intent(Intent.ACTION_SEND)
|
|
||||||
.setType("text/plain")
|
|
||||||
.putExtra(Intent.EXTRA_TEXT, "nostr:$naddr")
|
|
||||||
context.startActivity(
|
|
||||||
Intent
|
|
||||||
.createChooser(intent, shareTitle)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
|
||||||
)
|
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
symbol = MaterialSymbols.AutoMirrored.Send,
|
symbol = MaterialSymbols.AutoMirrored.Send,
|
||||||
@@ -347,7 +339,6 @@ private fun EventBody(
|
|||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val context = LocalContext.current
|
|
||||||
val timeFormatter = rememberTimeOfDayFormatter()
|
val timeFormatter = rememberTimeOfDayFormatter()
|
||||||
formatCalendarRange(note, timeFormatter)?.let { range ->
|
formatCalendarRange(note, timeFormatter)?.let { range ->
|
||||||
Text(
|
Text(
|
||||||
@@ -595,7 +586,7 @@ private fun HeroImage(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LocationRow(location: String) {
|
private fun LocationRow(location: String) {
|
||||||
val context = LocalContext.current
|
val uriHandler = LocalUriHandler.current
|
||||||
// NIP-52 `location` is free-text — a place name, an address, OR a meeting URL (Zoom, Jitsi,
|
// NIP-52 `location` is free-text — a place name, an address, OR a meeting URL (Zoom, Jitsi,
|
||||||
// a livestream link). Treat http(s) URLs as web links so they open in the browser; otherwise
|
// a livestream link). Treat http(s) URLs as web links so they open in the browser; otherwise
|
||||||
// hand to the maps app via the geo: intent.
|
// hand to the maps app via the geo: intent.
|
||||||
@@ -607,17 +598,10 @@ private fun LocationRow(location: String) {
|
|||||||
.clickable {
|
.clickable {
|
||||||
runCatching {
|
runCatching {
|
||||||
val trimmed = location.trim()
|
val trimmed = location.trim()
|
||||||
val intent =
|
// `geo:0,0?q=<location>` is the Android geo URI; the user's installed maps
|
||||||
if (isUrl) {
|
// app handles it. runCatching swallows the failure when no handler is
|
||||||
Intent(Intent.ACTION_VIEW, trimmed.toUri())
|
|
||||||
} else {
|
|
||||||
// `geo:0,0?q=<location>` is the Android geo intent; the user's
|
|
||||||
// installed maps app handles it.
|
|
||||||
Intent(Intent.ACTION_VIEW, "geo:0,0?q=${Uri.encode(trimmed)}".toUri())
|
|
||||||
}
|
|
||||||
// runCatching swallows ActivityNotFoundException when no handler is
|
|
||||||
// installed — we don't have anywhere useful to fall back to.
|
// installed — we don't have anywhere useful to fall back to.
|
||||||
context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
uriHandler.openUri(if (isUrl) trimmed else "geo:0,0?q=${encodeUriComponent(trimmed)}")
|
||||||
}
|
}
|
||||||
}.padding(vertical = 4.dp),
|
}.padding(vertical = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -905,3 +889,18 @@ private fun rememberCalendarsContaining(targetAddress: Address): State<List<Cale
|
|||||||
|
|
||||||
return calendars.collectAsStateWithLifecycle(emptyList())
|
return calendars.collectAsStateWithLifecycle(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Percent-encodes everything but the unreserved characters, as Android's `Uri.encode` does. */
|
||||||
|
private fun encodeUriComponent(text: String): String {
|
||||||
|
val sb = StringBuilder(text.length)
|
||||||
|
for (b in text.encodeToByteArray()) {
|
||||||
|
val c = (b.toInt() and 0xFF).toChar()
|
||||||
|
if (c in 'a'..'z' || c in 'A'..'Z' || c in '0'..'9' || c in "_-!.~'()*") {
|
||||||
|
sb.append(c)
|
||||||
|
} else {
|
||||||
|
sb.append('%')
|
||||||
|
sb.append((b.toInt() and 0xFF).toString(16).padStart(2, '0').uppercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
+5
-10
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup
|
||||||
|
|
||||||
import androidx.activity.compose.LocalActivity
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -48,9 +47,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.commons.model.User
|
import com.vitorpamplona.amethyst.commons.model.User
|
||||||
@@ -75,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_info_has_key_package
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_member
|
import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_member
|
||||||
import com.vitorpamplona.amethyst.commons.resources.cordn_member_count
|
import com.vitorpamplona.amethyst.commons.resources.cordn_member_count
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
|
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberWindowViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.UserPicture
|
import com.vitorpamplona.amethyst.commons.ui.note.UserPicture
|
||||||
@@ -367,13 +365,10 @@ private fun MemberRow(
|
|||||||
* a roster built under another key.
|
* a roster built under another key.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberCordnGroupDraft(accountViewModel: AccountViewModel): CordnGroupDraft {
|
fun rememberCordnGroupDraft(accountViewModel: AccountViewModel): CordnGroupDraft =
|
||||||
val activity = LocalActivity.current as FragmentActivity
|
rememberWindowViewModel(
|
||||||
return viewModel(
|
|
||||||
viewModelStoreOwner = activity,
|
|
||||||
key = "CordnGroupDraft-${accountViewModel.account.signer.pubKey}",
|
key = "CordnGroupDraft-${accountViewModel.account.signer.pubKey}",
|
||||||
)
|
) { CordnGroupDraft() }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What each coordinator this account uses could do with [roster].
|
* What each coordinator this account uses could do with [roster].
|
||||||
+6
-6
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -28,9 +28,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.model.Note
|
import com.vitorpamplona.amethyst.commons.model.Note
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel
|
import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadLiveActivityChannel
|
import com.vitorpamplona.amethyst.commons.ui.note.LoadLiveActivityChannel
|
||||||
@@ -38,13 +38,13 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.feed.Refreshi
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.dal.ChannelFeedViewModel
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.dal.ChannelFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ShowVideoStreaming
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ShowVideoStreaming
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveStreamGoalHeader
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveStreamTopZappers
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.send.ChannelNewMessageViewModel
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.send.ChannelNewMessageViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.send.EditFieldRow
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.send.EditFieldRow
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.nests.room.lobby.NestJoinCard
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.nests.room.lobby.NestJoinCard
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveStreamGoalHeader
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveStreamTopZappers
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -77,7 +77,7 @@ fun PrepareChannelViewModels(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val feedViewModel: ChannelFeedViewModel =
|
val feedViewModel: ChannelFeedViewModel =
|
||||||
viewModel(
|
rememberViewModel(
|
||||||
key = baseChannel.address.toValue() + "ChannelFeedViewModel",
|
key = baseChannel.address.toValue() + "ChannelFeedViewModel",
|
||||||
factory =
|
factory =
|
||||||
ChannelFeedViewModel.Factory(
|
ChannelFeedViewModel.Factory(
|
||||||
@@ -86,7 +86,7 @@ fun PrepareChannelViewModels(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val channelScreenModel: ChannelNewMessageViewModel = viewModel()
|
val channelScreenModel: ChannelNewMessageViewModel = rememberViewModel { ChannelNewMessageViewModel() }
|
||||||
channelScreenModel.init(accountViewModel)
|
channelScreenModel.init(accountViewModel)
|
||||||
channelScreenModel.load(baseChannel)
|
channelScreenModel.load(baseChannel)
|
||||||
|
|
||||||
+4
-4
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -27,12 +27,12 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||||
import com.vitorpamplona.amethyst.commons.service.OnlineChecker
|
import com.vitorpamplona.amethyst.commons.service.OnlineStatusCache
|
||||||
import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold
|
import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadLiveActivityChannel
|
import com.vitorpamplona.amethyst.commons.ui.note.LoadLiveActivityChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveActivityTopBar
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveActivityTopBar
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ fun LiveActivityChannelScreen(
|
|||||||
LoadLiveActivityChannel(channelId) {
|
LoadLiveActivityChannel(channelId) {
|
||||||
LaunchedEffect(it.info) {
|
LaunchedEffect(it.info) {
|
||||||
it.info?.streaming()?.let {
|
it.info?.streaming()?.let {
|
||||||
OnlineChecker.resetIfOfflineToRetry(it)
|
OnlineStatusCache.resetIfOfflineToRetry(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LiveActivityTopBar(it, accountViewModel, nav)
|
LiveActivityTopBar(it, accountViewModel, nav)
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
+11
-6
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -47,13 +47,14 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.commons.nip53LiveActivities.TopZapperEntry
|
import com.vitorpamplona.amethyst.commons.nip53LiveActivities.TopZapperEntry
|
||||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||||
import com.vitorpamplona.amethyst.commons.resources.chat_zap_anonymous
|
import com.vitorpamplona.amethyst.commons.resources.chat_zap_anonymous
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.UserPicture
|
import com.vitorpamplona.amethyst.commons.ui.note.UserPicture
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon
|
import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon
|
||||||
@@ -64,7 +65,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size24dp
|
|||||||
import com.vitorpamplona.amethyst.commons.util.showAmountInteger
|
import com.vitorpamplona.amethyst.commons.util.showAmountInteger
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.LiveStreamTopZappersViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.LiveStreamTopZappersViewModel
|
||||||
import java.math.BigDecimal
|
import com.vitorpamplona.quartz.utils.BigDecimal
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Horizontally-scrollable leaderboard of the top zappers on a live stream. Matches
|
* Horizontally-scrollable leaderboard of the top zappers on a live stream. Matches
|
||||||
@@ -80,12 +82,15 @@ fun LiveStreamTopZappers(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val topZappersVm: LiveStreamTopZappersViewModel =
|
val topZappersVm: LiveStreamTopZappersViewModel =
|
||||||
viewModel(
|
rememberViewModel(
|
||||||
key = "TopZappers-${channel.address.toValue()}",
|
key = "TopZappers-${channel.address.toValue()}",
|
||||||
factory =
|
factory =
|
||||||
object : ViewModelProvider.Factory {
|
object : ViewModelProvider.Factory {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = LiveStreamTopZappersViewModel(channel) as T
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: KClass<T>,
|
||||||
|
extras: CreationExtras,
|
||||||
|
): T = LiveStreamTopZappersViewModel(channel) as T
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -164,7 +169,7 @@ private fun ZapperPill(
|
|||||||
}
|
}
|
||||||
ZapIcon(Size16Modifier, BitcoinOrange)
|
ZapIcon(Size16Modifier, BitcoinOrange)
|
||||||
Text(
|
Text(
|
||||||
text = showAmountInteger(BigDecimal.valueOf(entry.totalSats)),
|
text = showAmountInteger(BigDecimal(entry.totalSats)),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
)
|
||||||
Spacer(Modifier.padding(start = 2.dp))
|
Spacer(Modifier.padding(start = 2.dp))
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.commons.model.Account
|
import com.vitorpamplona.amethyst.commons.model.Account
|
||||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||||
+4
-8
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess
|
||||||
|
|
||||||
import androidx.activity.compose.LocalActivity
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -56,8 +55,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||||
@@ -75,12 +72,13 @@ import com.vitorpamplona.amethyst.commons.resources.chess_game_waiting
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.chess_loading_game
|
import com.vitorpamplona.amethyst.commons.resources.chess_loading_game
|
||||||
import com.vitorpamplona.amethyst.commons.resources.go_back
|
import com.vitorpamplona.amethyst.commons.resources.go_back
|
||||||
import com.vitorpamplona.amethyst.commons.resources.relay_settings
|
import com.vitorpamplona.amethyst.commons.resources.relay_settings
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberWindowViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices
|
import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess.datasource.ChessSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Height4dpModifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.Height4dpModifier
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource.ChessSubscription
|
|
||||||
import com.vitorpamplona.quartz.nip64Chess.ChessGameNameGenerator
|
import com.vitorpamplona.quartz.nip64Chess.ChessGameNameGenerator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,10 +98,8 @@ fun ChessGameScreen(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
// Scope ViewModel to Activity so it's shared between lobby and game screens
|
// Scope ViewModel to Activity so it's shared between lobby and game screens
|
||||||
val activity = LocalActivity.current as FragmentActivity
|
|
||||||
val chessViewModel: ChessViewModel =
|
val chessViewModel: ChessViewModel =
|
||||||
viewModel(
|
rememberWindowViewModel(
|
||||||
viewModelStoreOwner = activity,
|
|
||||||
key = "ChessViewModel-${accountViewModel.account.userProfile().pubkeyHex}",
|
key = "ChessViewModel-${accountViewModel.account.userProfile().pubkeyHex}",
|
||||||
factory = ChessViewModelFactory(accountViewModel.account, LocalAppServices.current.appStores),
|
factory = ChessViewModelFactory(accountViewModel.account, LocalAppServices.current.appStores),
|
||||||
)
|
)
|
||||||
+4
-8
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess
|
||||||
|
|
||||||
import androidx.activity.compose.LocalActivity
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -56,8 +55,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||||
@@ -79,13 +76,14 @@ import com.vitorpamplona.amethyst.commons.resources.back
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.dismiss
|
import com.vitorpamplona.amethyst.commons.resources.dismiss
|
||||||
import com.vitorpamplona.amethyst.commons.resources.relay_settings
|
import com.vitorpamplona.amethyst.commons.resources.relay_settings
|
||||||
import com.vitorpamplona.amethyst.commons.resources.route_chess
|
import com.vitorpamplona.amethyst.commons.resources.route_chess
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberWindowViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.commons.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded
|
import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices
|
import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess.datasource.ChessSubscription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource.ChessSubscription
|
|
||||||
import com.vitorpamplona.quartz.nip64Chess.Color as ChessColor
|
import com.vitorpamplona.quartz.nip64Chess.Color as ChessColor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,10 +96,8 @@ fun ChessLobbyScreen(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
// Scope ViewModel to Activity so it's shared between lobby and game screens
|
// Scope ViewModel to Activity so it's shared between lobby and game screens
|
||||||
val activity = LocalActivity.current as FragmentActivity
|
|
||||||
val chessViewModel: ChessViewModel =
|
val chessViewModel: ChessViewModel =
|
||||||
viewModel(
|
rememberWindowViewModel(
|
||||||
viewModelStoreOwner = activity,
|
|
||||||
key = "ChessViewModel-${accountViewModel.account.userProfile().pubkeyHex}",
|
key = "ChessViewModel-${accountViewModel.account.userProfile().pubkeyHex}",
|
||||||
factory = ChessViewModelFactory(accountViewModel.account, LocalAppServices.current.appStores),
|
factory = ChessViewModelFactory(accountViewModel.account, LocalAppServices.current.appStores),
|
||||||
)
|
)
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
+3
-3
@@ -18,16 +18,16 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewmodel.CreationExtras
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import com.vitorpamplona.amethyst.commons.model.Account
|
import com.vitorpamplona.amethyst.commons.model.Account
|
||||||
import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores
|
import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores
|
||||||
import com.vitorpamplona.amethyst.commons.nip64Chess.ChessDismissedGamesStore
|
|
||||||
import com.vitorpamplona.amethyst.commons.nip64Chess.ChessPollingDefaults
|
import com.vitorpamplona.amethyst.commons.nip64Chess.ChessPollingDefaults
|
||||||
import com.vitorpamplona.amethyst.commons.nip64Chess.ChessViewModel
|
import com.vitorpamplona.amethyst.commons.nip64Chess.ChessViewModel
|
||||||
|
import com.vitorpamplona.amethyst.commons.nip64Chess.chessDismissedGamesStore
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +49,7 @@ class ChessViewModelFactory(
|
|||||||
fetcher = AndroidRelayFetcher(account),
|
fetcher = AndroidRelayFetcher(account),
|
||||||
metadataProvider = AndroidMetadataProvider(),
|
metadataProvider = AndroidMetadataProvider(),
|
||||||
pollingConfig = ChessPollingDefaults.android,
|
pollingConfig = ChessPollingDefaults.android,
|
||||||
dismissedStorage = ChessDismissedGamesStore(chessDismissedGamesData(appStores)),
|
dismissedStorage = appStores.chessDismissedGamesStore(),
|
||||||
) as T
|
) as T
|
||||||
}
|
}
|
||||||
throw IllegalArgumentException("Unknown ViewModel class")
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chess.datasource
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
+12
-10
@@ -18,11 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.create
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.create
|
||||||
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.PickVisualMediaRequest
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -62,6 +59,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -70,7 +68,6 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||||
@@ -99,7 +96,9 @@ import com.vitorpamplona.amethyst.commons.resources.geocache_owner_qr_warning
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.geocache_photo_remove
|
import com.vitorpamplona.amethyst.commons.resources.geocache_photo_remove
|
||||||
import com.vitorpamplona.amethyst.commons.resources.route_edit_geocache
|
import com.vitorpamplona.amethyst.commons.resources.route_edit_geocache
|
||||||
import com.vitorpamplona.amethyst.commons.resources.route_new_geocache
|
import com.vitorpamplona.amethyst.commons.resources.route_new_geocache
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage
|
import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.geocacheEmoji
|
import com.vitorpamplona.amethyst.commons.ui.note.geocacheEmoji
|
||||||
@@ -139,7 +138,7 @@ fun NewGeocacheScreen(
|
|||||||
editPubKeyHex: String? = null,
|
editPubKeyHex: String? = null,
|
||||||
editDTag: String? = null,
|
editDTag: String? = null,
|
||||||
) {
|
) {
|
||||||
val model: NewGeocacheViewModel = viewModel()
|
val model: NewGeocacheViewModel = rememberViewModel { NewGeocacheViewModel() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
remember(editKind, editPubKeyHex, editDTag, prefillGeohash) {
|
remember(editKind, editPubKeyHex, editDTag, prefillGeohash) {
|
||||||
@@ -154,9 +153,12 @@ fun NewGeocacheScreen(
|
|||||||
|
|
||||||
var pickingLocation by remember { mutableStateOf(false) }
|
var pickingLocation by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val photoPicker =
|
var pickingPhoto by rememberSaveable { mutableStateOf(false) }
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
|
if (pickingPhoto) {
|
||||||
if (uri != null) scope.launch { model.uploadImage(uri, null, accountViewModel.host.mediaUploader) }
|
GallerySelectSingle(imagesOnly = true) { media ->
|
||||||
|
pickingPhoto = false
|
||||||
|
if (media != null) scope.launch { model.uploadImage(media.uri, media.mimeType, accountViewModel.host.mediaUploader) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -285,7 +287,7 @@ fun NewGeocacheScreen(
|
|||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
enabled = !model.isUploading.value,
|
enabled = !model.isUploading.value,
|
||||||
onClick = { photoPicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) },
|
onClick = { pickingPhoto = true },
|
||||||
) {
|
) {
|
||||||
if (model.isUploading.value) {
|
if (model.isUploading.value) {
|
||||||
CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp)
|
CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp)
|
||||||
+3
-3
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.create
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.create
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@@ -28,6 +27,7 @@ import com.vitorpamplona.amethyst.commons.model.Account
|
|||||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||||
import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality
|
import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality
|
||||||
import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader
|
import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader
|
||||||
|
import com.vitorpamplona.amethyst.commons.service.uploads.MediaUri
|
||||||
import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator
|
import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator
|
||||||
import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState
|
import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
@@ -175,7 +175,7 @@ class NewGeocacheViewModel : ViewModel() {
|
|||||||
fun isValid() = hasName() && hasLocation()
|
fun isValid() = hasName() && hasLocation()
|
||||||
|
|
||||||
suspend fun uploadImage(
|
suspend fun uploadImage(
|
||||||
uri: Uri,
|
uri: MediaUri,
|
||||||
mimeType: String?,
|
mimeType: String?,
|
||||||
uploader: MediaUploader,
|
uploader: MediaUploader,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.detail
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
+6
-9
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.detail
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -40,7 +39,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
@@ -57,6 +55,8 @@ import com.vitorpamplona.amethyst.commons.resources.geocache_log_type_dnf
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.geocache_log_type_maintenance
|
import com.vitorpamplona.amethyst.commons.resources.geocache_log_type_maintenance
|
||||||
import com.vitorpamplona.amethyst.commons.resources.geocache_log_type_note
|
import com.vitorpamplona.amethyst.commons.resources.geocache_log_type_note
|
||||||
import com.vitorpamplona.amethyst.commons.resources.geocache_verified_find
|
import com.vitorpamplona.amethyst.commons.resources.geocache_verified_find
|
||||||
|
import com.vitorpamplona.amethyst.commons.resources.quick_action_share
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture
|
import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.GeocacheChip
|
import com.vitorpamplona.amethyst.commons.ui.note.GeocacheChip
|
||||||
@@ -162,14 +162,11 @@ fun GeocacheWinnerStrip(
|
|||||||
/** Shares the cache as `nostr:naddr…`, which is how it opens in another NIP-CC client. */
|
/** Shares the cache as `nostr:naddr…`, which is how it opens in another NIP-CC client. */
|
||||||
@Composable
|
@Composable
|
||||||
fun GeocacheShareAction(address: Address) {
|
fun GeocacheShareAction(address: Address) {
|
||||||
val context = LocalContext.current
|
val sharer = rememberTextSharer()
|
||||||
|
val shareStr = stringRes(Res.string.quick_action_share)
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
val naddr = NAddress.create(address.kind, address.pubKeyHex, address.dTag, null)
|
val naddr = NAddress.create(address.kind, address.pubKeyHex, address.dTag, null)
|
||||||
val intent =
|
sharer.share("nostr:$naddr", null, shareStr)
|
||||||
Intent(Intent.ACTION_SEND)
|
|
||||||
.setType("text/plain")
|
|
||||||
.putExtra(Intent.EXTRA_TEXT, "nostr:$naddr")
|
|
||||||
context.startActivity(Intent.createChooser(intent, null).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
symbol = MaterialSymbols.AutoMirrored.Send,
|
symbol = MaterialSymbols.AutoMirrored.Send,
|
||||||
+4
-7
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.detail
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -64,10 +63,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||||
@@ -427,12 +425,11 @@ private fun GeocacheNavigateRow(
|
|||||||
longitude: Double,
|
longitude: Double,
|
||||||
name: String,
|
name: String,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val uriHandler = LocalUriHandler.current
|
||||||
OutlinedButton(onClick = {
|
OutlinedButton(onClick = {
|
||||||
val label = name.ifBlank { "Geocache" }
|
val label = name.ifBlank { "Geocache" }
|
||||||
val uri = "geo:$latitude,$longitude?q=$latitude,$longitude($label)".toUri()
|
|
||||||
runCatching {
|
runCatching {
|
||||||
context.startActivity(Intent(Intent.ACTION_VIEW, uri).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
uriHandler.openUri("geo:$latitude,$longitude?q=$latitude,$longitude($label)")
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.detail
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.detail
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
+14
-13
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
@@ -68,7 +68,6 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||||
@@ -102,14 +101,18 @@ import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind
|
|||||||
import com.vitorpamplona.amethyst.commons.resources.zapraiser
|
import com.vitorpamplona.amethyst.commons.resources.zapraiser
|
||||||
import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog
|
import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog
|
||||||
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.actions.mediaServers.FileServerSelectionRow
|
||||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.OnIncomingShare
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.OnIncomingShare
|
||||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles
|
||||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery
|
||||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakePictureButton
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakePictureButton
|
||||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakeVideoButton
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.TakeVideoButton
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.UploadProgressIndicator
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.VoiceAnonymizationSection
|
||||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberSharedMediaResolver
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberSharedMediaResolver
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler
|
import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.ThinPaddingTextField
|
import com.vitorpamplona.amethyst.commons.ui.components.ThinPaddingTextField
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar
|
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar
|
||||||
@@ -129,13 +132,17 @@ import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.MessageF
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.AudienceFlap
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.AudienceFlap
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.AudienceSheet
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.AudienceSheet
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.rememberAudienceLists
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.rememberAudienceLists
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.polls.PollOptionsField
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.pow.PowOverrideButton
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.pow.PowOverrideButton
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.previews.DisplayPreviews
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.previews.DisplayPreviews
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.scheduling.ScheduleAtButton
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.scheduling.ScheduleAtButton
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.scheduling.ScheduleAtPicker
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.scheduling.roundUpToNextQuarterHour
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.secretEmoji.AddSecretEmojiButton
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.secretEmoji.AddSecretEmojiButton
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.secretEmoji.SecretEmojiRequest
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.secretEmoji.SecretEmojiRequest
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.uploads.ImageVideoDescription
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.uploads.ImageVideoDescription
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.zappolls.ZapPollField
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.AddZapraiserButton
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.AddZapraiserButton
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.ZapRaiserRequest
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.ZapRaiserRequest
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.zapsplits.ForwardZapTo
|
import com.vitorpamplona.amethyst.commons.ui.note.creators.zapsplits.ForwardZapTo
|
||||||
@@ -161,14 +168,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceAnonymizationSection
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.polls.PollOptionsField
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.scheduling.ScheduleAtPicker
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.scheduling.roundUpToNextQuarterHour
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.zappolls.ZapPollField
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
@@ -189,7 +190,7 @@ fun ShortNotePostScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val postViewModel: ShortNotePostViewModel = viewModel()
|
val postViewModel: ShortNotePostViewModel = rememberViewModel { ShortNotePostViewModel() }
|
||||||
postViewModel.init(accountViewModel)
|
postViewModel.init(accountViewModel)
|
||||||
postViewModel.setGroupThread(groupThreadId, groupThreadRelayUrl)
|
postViewModel.setGroupThread(groupThreadId, groupThreadRelayUrl)
|
||||||
|
|
||||||
@@ -739,7 +740,7 @@ private fun NewPostScreenBody(
|
|||||||
showAlwaysOnPrompt = true
|
showAlwaysOnPrompt = true
|
||||||
} else {
|
} else {
|
||||||
postViewModel.scheduledForSec =
|
postViewModel.scheduledForSec =
|
||||||
roundUpToNextQuarterHour((System.currentTimeMillis() / 1000) + 60 * 60)
|
roundUpToNextQuarterHour(TimeUtils.now() + 60 * 60)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -761,7 +762,7 @@ private fun NewPostScreenBody(
|
|||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
showAlwaysOnPrompt = false
|
showAlwaysOnPrompt = false
|
||||||
postViewModel.scheduledForSec =
|
postViewModel.scheduledForSec =
|
||||||
roundUpToNextQuarterHour((System.currentTimeMillis() / 1000) + 60 * 60)
|
roundUpToNextQuarterHour(TimeUtils.now() + 60 * 60)
|
||||||
}) {
|
}) {
|
||||||
Text(stringRes(Res.string.schedule_post_always_on_prompt_continue))
|
Text(stringRes(Res.string.schedule_post_always_on_prompt_continue))
|
||||||
}
|
}
|
||||||
@@ -779,7 +780,7 @@ private fun BottomRowActions(
|
|||||||
if (postViewModel.scheduledForSec != null) {
|
if (postViewModel.scheduledForSec != null) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
roundUpToNextQuarterHour((System.currentTimeMillis() / 1000) + 60 * 60)
|
roundUpToNextQuarterHour(TimeUtils.now() + 60 * 60)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
+6
-6
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -38,8 +38,11 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import com.vitorpamplona.amethyst.commons.ui.actions.mediaServers.FileServerSelectionRow
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.UploadProgressIndicator
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.VoiceAnonymizationSection
|
||||||
import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler
|
import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar
|
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar
|
||||||
import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose
|
||||||
@@ -49,9 +52,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceAnonymizationSection
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -64,7 +64,7 @@ fun VoiceReplyScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val viewModel: VoiceReplyViewModel = viewModel()
|
val viewModel: VoiceReplyViewModel = rememberViewModel { VoiceReplyViewModel() }
|
||||||
|
|
||||||
LaunchedEffect(replyToNoteId, recordingFilePath, accountViewModel) {
|
LaunchedEffect(replyToNoteId, recordingFilePath, accountViewModel) {
|
||||||
viewModel.init(accountViewModel)
|
viewModel.init(accountViewModel)
|
||||||
+2
-4
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -26,7 +26,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
|
||||||
import com.vitorpamplona.amethyst.commons.audio.RecordingResult
|
import com.vitorpamplona.amethyst.commons.audio.RecordingResult
|
||||||
import com.vitorpamplona.amethyst.commons.audio.VoiceAnonymizationController
|
import com.vitorpamplona.amethyst.commons.audio.VoiceAnonymizationController
|
||||||
import com.vitorpamplona.amethyst.commons.audio.VoicePreset
|
import com.vitorpamplona.amethyst.commons.audio.VoicePreset
|
||||||
@@ -58,6 +57,7 @@ import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent
|
|||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.IO
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -215,7 +215,6 @@ class VoiceReplyViewModel : ViewModel() {
|
|||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
Log.w("VoiceReplyViewModel", "User canceled, or ViewModel cleared", e)
|
Log.w("VoiceReplyViewModel", "User canceled, or ViewModel cleared", e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val appContext = Amethyst.instance.appContext
|
|
||||||
accountViewModel.toastManager.toast(
|
accountViewModel.toastManager.toast(
|
||||||
loadStringRes(Res.string.upload_error_title),
|
loadStringRes(Res.string.upload_error_title),
|
||||||
loadStringRes(
|
loadStringRes(
|
||||||
@@ -239,7 +238,6 @@ class VoiceReplyViewModel : ViewModel() {
|
|||||||
result: UploadingState,
|
result: UploadingState,
|
||||||
onSuccess: () -> Unit,
|
onSuccess: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val appContext = Amethyst.instance.appContext
|
|
||||||
val uploadErrorTitle = loadStringRes(Res.string.upload_error_title)
|
val uploadErrorTitle = loadStringRes(Res.string.upload_error_title)
|
||||||
val uploadVoiceNip95NotSupported = loadStringRes(Res.string.upload_error_voice_message_nip95_not_supported)
|
val uploadVoiceNip95NotSupported = loadStringRes(Res.string.upload_error_voice_message_nip95_not_supported)
|
||||||
val uploadVoiceFailed = loadStringRes(Res.string.upload_error_voice_message_failed)
|
val uploadVoiceFailed = loadStringRes(Res.string.upload_error_voice_message_failed)
|
||||||
+4
-4
@@ -18,18 +18,18 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.polls
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls
|
||||||
|
|
||||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.NewPostScreenInner
|
||||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.ShortNotePostViewModel
|
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.home.ShortNotePostViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.NewPostScreenInner
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ fun PollPostScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val postViewModel: ShortNotePostViewModel = viewModel()
|
val postViewModel: ShortNotePostViewModel = rememberViewModel { ShortNotePostViewModel() }
|
||||||
postViewModel.init(accountViewModel)
|
postViewModel.init(accountViewModel)
|
||||||
|
|
||||||
LaunchedEffect(postViewModel, accountViewModel) {
|
LaunchedEffect(postViewModel, accountViewModel) {
|
||||||
+2
-5
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery
|
||||||
|
|
||||||
import androidx.annotation.OptIn
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -35,7 +34,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import coil3.compose.AsyncImagePainter
|
import coil3.compose.AsyncImagePainter
|
||||||
import coil3.compose.SubcomposeAsyncImage
|
import coil3.compose.SubcomposeAsyncImage
|
||||||
import coil3.compose.SubcomposeAsyncImageContent
|
import coil3.compose.SubcomposeAsyncImageContent
|
||||||
@@ -61,8 +59,8 @@ import com.vitorpamplona.amethyst.commons.ui.note.elements.BannerImage
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.QuoteBorder
|
import com.vitorpamplona.amethyst.commons.ui.theme.QuoteBorder
|
||||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size50Modifier
|
import com.vitorpamplona.amethyst.commons.ui.theme.Size50Modifier
|
||||||
|
import com.vitorpamplona.amethyst.commons.video.isHlsMedia
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.isHlsMedia
|
|
||||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.clip.LiveActivitiesClipEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.clip.LiveActivitiesClipEvent
|
||||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||||
@@ -216,7 +214,6 @@ fun DisplayGalleryAuthorBanner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GalleryContentView(
|
fun GalleryContentView(
|
||||||
contentList: List<MediaUrlContent>,
|
contentList: List<MediaUrlContent>,
|
||||||
+2
-5
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -39,7 +39,6 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
@@ -109,7 +108,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
|
|||||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.service.ai.WritingAssistantFactory
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.jetbrains.compose.resources.StringResource
|
import org.jetbrains.compose.resources.StringResource
|
||||||
|
|
||||||
@@ -153,7 +151,7 @@ fun ComposeSettingsContent(
|
|||||||
)
|
)
|
||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
// The F-Droid build ships no on-device model, so the toggle would do nothing there.
|
// The F-Droid build ships no on-device model, so the toggle would do nothing there.
|
||||||
if (WritingAssistantFactory.IS_SUPPORTED) {
|
if (accountViewModel.host.supportsWritingAssistant) {
|
||||||
BooleanSwitchTile(
|
BooleanSwitchTile(
|
||||||
flow = sharedPrefs.automaticallyProposeAiImprovements,
|
flow = sharedPrefs.automaticallyProposeAiImprovements,
|
||||||
icon = MaterialSymbols.AutoAwesome,
|
icon = MaterialSymbols.AutoAwesome,
|
||||||
@@ -246,7 +244,6 @@ private fun PowDifficultyTile(accountViewModel: AccountViewModel) {
|
|||||||
private fun PowTimeEstimate(difficulty: Int) {
|
private fun PowTimeEstimate(difficulty: Int) {
|
||||||
if (difficulty <= 0) return
|
if (difficulty <= 0) return
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val estimate by
|
val estimate by
|
||||||
produceState<String?>(initialValue = null, difficulty) {
|
produceState<String?>(initialValue = null, difficulty) {
|
||||||
val rate = deviceHashesPerSecond()
|
val rate = deviceHashesPerSecond()
|
||||||
+14
-1
@@ -22,7 +22,20 @@ package com.vitorpamplona.amethyst.commons.ui.components
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
private val NoFileSharer = FileSharer { _, _, _ -> }
|
private object NoFileSharer : FileSharer {
|
||||||
|
override fun shareTextFilesAsZip(
|
||||||
|
zipName: String,
|
||||||
|
files: List<Pair<String, String>>,
|
||||||
|
title: String,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
override fun shareTextFile(
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String,
|
||||||
|
content: String,
|
||||||
|
title: String,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
/** No share sheet wired yet. */
|
/** No share sheet wired yet. */
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
private val NoPhoneCalendar = PhoneCalendar { false }
|
||||||
|
|
||||||
|
/** No calendar composer wired yet: callers fall back to the .ics share. */
|
||||||
|
@Composable
|
||||||
|
actual fun rememberPhoneCalendar(): PhoneCalendar = NoPhoneCalendar
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
|
import platform.Foundation.NSDate
|
||||||
|
import platform.Foundation.NSRelativeDateTimeFormatter
|
||||||
|
import platform.Foundation.NSRelativeDateTimeFormatterUnitsStyleAbbreviated
|
||||||
|
import platform.Foundation.dateWithTimeIntervalSince1970
|
||||||
|
|
||||||
|
actual suspend fun relativeTimeSpan(
|
||||||
|
targetSeconds: Long,
|
||||||
|
nowSeconds: Long,
|
||||||
|
dayResolution: Boolean,
|
||||||
|
): String {
|
||||||
|
val formatter = NSRelativeDateTimeFormatter()
|
||||||
|
formatter.unitsStyle = NSRelativeDateTimeFormatterUnitsStyleAbbreviated
|
||||||
|
val day = 86_400L
|
||||||
|
val (target, now) =
|
||||||
|
if (dayResolution) {
|
||||||
|
Pair(targetSeconds.floorDiv(day) * day, nowSeconds.floorDiv(day) * day)
|
||||||
|
} else {
|
||||||
|
Pair(targetSeconds, nowSeconds)
|
||||||
|
}
|
||||||
|
return formatter.localizedStringForDate(
|
||||||
|
NSDate.dateWithTimeIntervalSince1970(target.toDouble()),
|
||||||
|
relativeToDate = NSDate.dateWithTimeIntervalSince1970(now.toDouble()),
|
||||||
|
)
|
||||||
|
}
|
||||||
+14
-1
@@ -22,7 +22,20 @@ package com.vitorpamplona.amethyst.commons.ui.components
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
private val NoFileSharer = FileSharer { _, _, _ -> }
|
private object NoFileSharer : FileSharer {
|
||||||
|
override fun shareTextFilesAsZip(
|
||||||
|
zipName: String,
|
||||||
|
files: List<Pair<String, String>>,
|
||||||
|
title: String,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
override fun shareTextFile(
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String,
|
||||||
|
content: String,
|
||||||
|
title: String,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
/** No share sheet wired yet. */
|
/** No share sheet wired yet. */
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
private val NoPhoneCalendar = PhoneCalendar { false }
|
||||||
|
|
||||||
|
/** No calendar composer wired yet: callers fall back to the .ics share. */
|
||||||
|
@Composable
|
||||||
|
actual fun rememberPhoneCalendar(): PhoneCalendar = NoPhoneCalendar
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.commons.ui.screen.loggedIn.calendars
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||||
|
import com.vitorpamplona.amethyst.commons.resources.d
|
||||||
|
import com.vitorpamplona.amethyst.commons.resources.h
|
||||||
|
import com.vitorpamplona.amethyst.commons.resources.m
|
||||||
|
import com.vitorpamplona.amethyst.commons.resources.now
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
/** The JDK has no relative-time formatter: a compact signed span ("+2h", "-5m"). */
|
||||||
|
actual suspend fun relativeTimeSpan(
|
||||||
|
targetSeconds: Long,
|
||||||
|
nowSeconds: Long,
|
||||||
|
dayResolution: Boolean,
|
||||||
|
): String {
|
||||||
|
val diff = targetSeconds - nowSeconds
|
||||||
|
val span = abs(diff)
|
||||||
|
val sign = if (diff < 0) "-" else "+"
|
||||||
|
return when {
|
||||||
|
span >= TimeUtils.ONE_DAY -> sign + (span / TimeUtils.ONE_DAY) + loadStringRes(Res.string.d)
|
||||||
|
dayResolution -> loadStringRes(Res.string.now)
|
||||||
|
span >= TimeUtils.ONE_HOUR -> sign + (span / TimeUtils.ONE_HOUR) + loadStringRes(Res.string.h)
|
||||||
|
span >= TimeUtils.ONE_MINUTE -> sign + (span / TimeUtils.ONE_MINUTE) + loadStringRes(Res.string.m)
|
||||||
|
else -> loadStringRes(Res.string.now)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.share
|
package com.vitorpamplona.amethyst.commons.ui.note.share
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||||
+1
-2
@@ -18,9 +18,8 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst
|
package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.disambiguate
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
Reference in New Issue
Block a user