refactor: rename Audio Room → Nest project-wide
Aligns class names, package paths, string resource keys, UI text and intent actions with the Nests branding used by the EGG specs in nestsClient/specs/. Mechanical rename — no behavior change. - Folders: audiorooms/ → nests/ (5 paths across amethyst, quartz) - 30+ class renames (AudioRoom* → Nest*, AudioRooms* → Nests*) - String resource keys audio_room_* → nest_* - UI strings "Audio Room"/"Audio Rooms" → "Nest"/"Nests" (incl. all locales) - Intent extras AUDIO_ROOM_* → NEST_* - Compose route Route.AudioRooms → Route.Nests Spec-aligned identifiers (MeetingSpaceEvent, meetingSpaces/, the nip53* packages) are intentionally untouched — those are NIP-53 protocol names, not "audio room" branding. https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
This commit is contained in:
@@ -219,7 +219,7 @@
|
|||||||
tools:ignore="DiscouragedApi" />
|
tools:ignore="DiscouragedApi" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.screen.loggedIn.audiorooms.room.AudioRoomActivity"
|
android:name=".ui.screen.loggedIn.nests.room.NestActivity"
|
||||||
android:autoRemoveFromRecents="true"
|
android:autoRemoveFromRecents="true"
|
||||||
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize|keyboardHidden|keyboard|uiMode|navigation|fontScale|density"
|
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize|keyboardHidden|keyboard|uiMode|navigation|fontScale|density"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
@@ -271,7 +271,7 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.audiorooms.AudioRoomForegroundService"
|
android:name=".service.nests.NestForegroundService"
|
||||||
android:foregroundServiceType="mediaPlayback|microphone"
|
android:foregroundServiceType="mediaPlayback|microphone"
|
||||||
android:stopWithTask="true"
|
android:stopWithTask="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
* - [saveNestsServersList] — build + sign a new replaceable kind 10112
|
* - [saveNestsServersList] — build + sign a new replaceable kind 10112
|
||||||
* event (preserving prior tags' alt etc.)
|
* event (preserving prior tags' alt etc.)
|
||||||
*
|
*
|
||||||
* The list is consumed by `CreateAudioRoomViewModel` to default the
|
* The list is consumed by `CreateNestViewModel` to default the
|
||||||
* "MoQ service URL" / "MoQ endpoint URL" fields when starting a new
|
* "MoQ service URL" / "MoQ endpoint URL" fields when starting a new
|
||||||
* space, and by the Settings screen for edit / add / remove.
|
* space, and by the Settings screen for edit / add / remove.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+16
-16
@@ -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.service.audiorooms
|
package com.vitorpamplona.amethyst.service.nests
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
@@ -43,7 +43,7 @@ import com.vitorpamplona.amethyst.ui.MainActivity
|
|||||||
*
|
*
|
||||||
* Scope decisions:
|
* Scope decisions:
|
||||||
* - The service does NOT own the MoQ session / decoder / player. Those
|
* - The service does NOT own the MoQ session / decoder / player. Those
|
||||||
* live in `AudioRoomViewModel`. This service exists to keep the process
|
* live in `NestViewModel`. This service exists to keep the process
|
||||||
* alive while audio is in flight; the VM still drives all wire activity.
|
* alive while audio is in flight; the VM still drives all wire activity.
|
||||||
* - Foreground type is `mediaPlayback`; if the user starts broadcasting,
|
* - Foreground type is `mediaPlayback`; if the user starts broadcasting,
|
||||||
* the screen calls [promoteToMicrophone] which re-`startForeground`s
|
* the screen calls [promoteToMicrophone] which re-`startForeground`s
|
||||||
@@ -54,7 +54,7 @@ import com.vitorpamplona.amethyst.ui.MainActivity
|
|||||||
* full-screen activity). The service uses a shared notification id so
|
* full-screen activity). The service uses a shared notification id so
|
||||||
* start/promote/stop is idempotent.
|
* start/promote/stop is idempotent.
|
||||||
*/
|
*/
|
||||||
class AudioRoomForegroundService : Service() {
|
class NestForegroundService : Service() {
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
private var promoted = false
|
private var promoted = false
|
||||||
private var audioFocusRequest: android.media.AudioFocusRequest? = null
|
private var audioFocusRequest: android.media.AudioFocusRequest? = null
|
||||||
@@ -169,25 +169,25 @@ class AudioRoomForegroundService : Service() {
|
|||||||
PendingIntent.getService(
|
PendingIntent.getService(
|
||||||
this,
|
this,
|
||||||
1,
|
1,
|
||||||
Intent(this, AudioRoomForegroundService::class.java).apply { action = ACTION_STOP },
|
Intent(this, NestForegroundService::class.java).apply { action = ACTION_STOP },
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
val title =
|
val title =
|
||||||
if (promoted) {
|
if (promoted) {
|
||||||
getString(R.string.audio_room_notification_broadcasting)
|
getString(R.string.nest_notification_broadcasting)
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.audio_room_notification_listening)
|
getString(R.string.nest_notification_listening)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotificationCompat
|
return NotificationCompat
|
||||||
.Builder(this, CHANNEL_ID)
|
.Builder(this, CHANNEL_ID)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(getString(R.string.audio_room_notification_text))
|
.setContentText(getString(R.string.nest_notification_text))
|
||||||
.setSmallIcon(R.drawable.amethyst)
|
.setSmallIcon(R.drawable.amethyst)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setContentIntent(openIntent)
|
.setContentIntent(openIntent)
|
||||||
.addAction(0, getString(R.string.audio_room_notification_stop), stopIntent)
|
.addAction(0, getString(R.string.nest_notification_stop), stopIntent)
|
||||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -198,10 +198,10 @@ class AudioRoomForegroundService : Service() {
|
|||||||
mgr.createNotificationChannel(
|
mgr.createNotificationChannel(
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_ID,
|
CHANNEL_ID,
|
||||||
getString(R.string.audio_room_notification_channel),
|
getString(R.string.nest_notification_channel),
|
||||||
NotificationManager.IMPORTANCE_LOW,
|
NotificationManager.IMPORTANCE_LOW,
|
||||||
).apply {
|
).apply {
|
||||||
description = getString(R.string.audio_room_notification_channel_description)
|
description = getString(R.string.nest_notification_channel_description)
|
||||||
setShowBadge(false)
|
setShowBadge(false)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -218,10 +218,10 @@ class AudioRoomForegroundService : Service() {
|
|||||||
override fun onBind(intent: Intent?): IBinder? = null
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CHANNEL_ID = "audio_room_foreground"
|
private const val CHANNEL_ID = "nest_foreground"
|
||||||
private const val NOTIFICATION_ID = 0xA0D10
|
private const val NOTIFICATION_ID = 0xA0D10
|
||||||
private const val ACTION_PROMOTE_TO_MIC = "com.vitorpamplona.amethyst.audio_room.PROMOTE_MIC"
|
private const val ACTION_PROMOTE_TO_MIC = "com.vitorpamplona.amethyst.nest.PROMOTE_MIC"
|
||||||
private const val ACTION_STOP = "com.vitorpamplona.amethyst.audio_room.STOP"
|
private const val ACTION_STOP = "com.vitorpamplona.amethyst.nest.STOP"
|
||||||
|
|
||||||
// 4-hour hard cap — long enough for typical audio-room sessions
|
// 4-hour hard cap — long enough for typical audio-room sessions
|
||||||
// (2-3 h podcasts / panels) plus headroom, short enough to release
|
// (2-3 h podcasts / panels) plus headroom, short enough to release
|
||||||
@@ -233,7 +233,7 @@ class AudioRoomForegroundService : Service() {
|
|||||||
fun startListening(context: Context) {
|
fun startListening(context: Context) {
|
||||||
ContextCompat.startForegroundService(
|
ContextCompat.startForegroundService(
|
||||||
context,
|
context,
|
||||||
Intent(context, AudioRoomForegroundService::class.java),
|
Intent(context, NestForegroundService::class.java),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ class AudioRoomForegroundService : Service() {
|
|||||||
fun promoteToMicrophone(context: Context) {
|
fun promoteToMicrophone(context: Context) {
|
||||||
ContextCompat.startForegroundService(
|
ContextCompat.startForegroundService(
|
||||||
context,
|
context,
|
||||||
Intent(context, AudioRoomForegroundService::class.java).apply {
|
Intent(context, NestForegroundService::class.java).apply {
|
||||||
action = ACTION_PROMOTE_TO_MIC
|
action = ACTION_PROMOTE_TO_MIC
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -253,7 +253,7 @@ class AudioRoomForegroundService : Service() {
|
|||||||
|
|
||||||
/** Stop and remove the foreground notification. Idempotent. */
|
/** Stop and remove the foreground notification. Idempotent. */
|
||||||
fun stop(context: Context) {
|
fun stop(context: Context) {
|
||||||
context.stopService(Intent(context, AudioRoomForegroundService::class.java))
|
context.stopService(Intent(context, NestForegroundService::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-7
@@ -28,11 +28,6 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentF
|
|||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchFilterAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.AudioRoomsFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.RoomAdminCommandsFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.RoomChatFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.RoomPresenceFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.RoomReactionsFilterAssembler
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.datasource.BadgesFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.datasource.BadgesFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.profile.datasource.ProfileBadgesFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.profile.datasource.ProfileBadgesFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.datasource.ChatroomFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.datasource.ChatroomFilterAssembler
|
||||||
@@ -50,6 +45,11 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.datasource.HashtagF
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.datasource.LiveStreamsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.datasource.LiveStreamsFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.LongsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.LongsFilterAssembler
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssembler
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomAdminCommandsFilterAssembler
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomChatFilterAssembler
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomPresenceFilterAssembler
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomReactionsFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.datasource.PicturesFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.datasource.PicturesFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.datasource.PollsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.datasource.PollsFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.datasource.ProductsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.datasource.ProductsFilterAssembler
|
||||||
@@ -110,7 +110,7 @@ class RelaySubscriptionsCoordinator(
|
|||||||
val shorts = ShortsFilterAssembler(client)
|
val shorts = ShortsFilterAssembler(client)
|
||||||
val publicChats = PublicChatsFilterAssembler(client)
|
val publicChats = PublicChatsFilterAssembler(client)
|
||||||
val liveStreams = LiveStreamsFilterAssembler(client)
|
val liveStreams = LiveStreamsFilterAssembler(client)
|
||||||
val audioRooms = AudioRoomsFilterAssembler(client)
|
val nests = NestsFilterAssembler(client)
|
||||||
val roomPresence = RoomPresenceFilterAssembler(client)
|
val roomPresence = RoomPresenceFilterAssembler(client)
|
||||||
val roomChat = RoomChatFilterAssembler(client)
|
val roomChat = RoomChatFilterAssembler(client)
|
||||||
val roomReactions = RoomReactionsFilterAssembler(client)
|
val roomReactions = RoomReactionsFilterAssembler(client)
|
||||||
@@ -139,7 +139,7 @@ class RelaySubscriptionsCoordinator(
|
|||||||
publicChats,
|
publicChats,
|
||||||
followPacksList,
|
followPacksList,
|
||||||
liveStreams,
|
liveStreams,
|
||||||
audioRooms,
|
nests,
|
||||||
longs,
|
longs,
|
||||||
articles,
|
articles,
|
||||||
badges,
|
badges,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ object ScrollStateKeys {
|
|||||||
const val PUBLIC_CHATS_SCREEN = "PublicChatsFeed"
|
const val PUBLIC_CHATS_SCREEN = "PublicChatsFeed"
|
||||||
const val FOLLOW_PACKS_SCREEN = "FollowPacksFeed"
|
const val FOLLOW_PACKS_SCREEN = "FollowPacksFeed"
|
||||||
const val LIVE_STREAMS_SCREEN = "LiveStreamsFeed"
|
const val LIVE_STREAMS_SCREEN = "LiveStreamsFeed"
|
||||||
const val AUDIO_ROOMS_SCREEN = "AudioRoomsFeed"
|
const val NESTS_SCREEN = "NestsFeed"
|
||||||
const val LONGS_SCREEN = "LongsFeed"
|
const val LONGS_SCREEN = "LongsFeed"
|
||||||
const val ARTICLES_SCREEN = "ArticlesFeed"
|
const val ARTICLES_SCREEN = "ArticlesFeed"
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ 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.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.ArticlesScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.ArticlesScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.AudioRoomsScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.BadgesScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.BadgesScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.award.AwardBadgeScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.award.AwardBadgeScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.profile.ProfileBadgesScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.profile.ProfileBadgesScreen
|
||||||
@@ -128,6 +127,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleL
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.LiveStreamsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.LiveStreamsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.LongsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.LongsScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListPickFollowsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListPickFollowsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListSelectUserScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListSelectUserScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
||||||
@@ -245,7 +245,7 @@ fun BuildNavigation(
|
|||||||
composableFromEnd<Route.PublicChats> { PublicChatsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.PublicChats> { PublicChatsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.FollowPacks> { FollowPacksScreen(accountViewModel, nav) }
|
composableFromEnd<Route.FollowPacks> { FollowPacksScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.LiveStreams> { LiveStreamsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.LiveStreams> { LiveStreamsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.AudioRooms> { AudioRoomsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Nests> { NestsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.Longs> { LongsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Longs> { LongsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.Articles> { ArticlesScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Articles> { ArticlesScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.NewHlsVideo> { NewHlsVideoScreen(accountViewModel, nav) }
|
composableFromEnd<Route.NewHlsVideo> { NewHlsVideoScreen(accountViewModel, nav) }
|
||||||
|
|||||||
+6
-6
@@ -54,7 +54,7 @@ enum class NavBarItem {
|
|||||||
PUBLIC_CHATS,
|
PUBLIC_CHATS,
|
||||||
FOLLOW_PACKS,
|
FOLLOW_PACKS,
|
||||||
LIVE_STREAMS,
|
LIVE_STREAMS,
|
||||||
AUDIO_ROOMS,
|
NESTS,
|
||||||
LONGS,
|
LONGS,
|
||||||
POLLS,
|
POLLS,
|
||||||
BADGES,
|
BADGES,
|
||||||
@@ -212,12 +212,12 @@ val NavBarCatalog: Map<NavBarItem, NavBarItemDef> =
|
|||||||
icon = MaterialSymbols.Sensors,
|
icon = MaterialSymbols.Sensors,
|
||||||
resolveRoute = { Route.LiveStreams },
|
resolveRoute = { Route.LiveStreams },
|
||||||
),
|
),
|
||||||
NavBarItem.AUDIO_ROOMS to
|
NavBarItem.NESTS to
|
||||||
NavBarItemDef(
|
NavBarItemDef(
|
||||||
id = NavBarItem.AUDIO_ROOMS,
|
id = NavBarItem.NESTS,
|
||||||
labelRes = R.string.audio_rooms,
|
labelRes = R.string.nests,
|
||||||
icon = MaterialSymbols.Mic,
|
icon = MaterialSymbols.Mic,
|
||||||
resolveRoute = { Route.AudioRooms },
|
resolveRoute = { Route.Nests },
|
||||||
),
|
),
|
||||||
NavBarItem.LONGS to
|
NavBarItem.LONGS to
|
||||||
NavBarItemDef(
|
NavBarItemDef(
|
||||||
@@ -305,7 +305,7 @@ val DrawerFeedsItems: List<NavBarItem> =
|
|||||||
NavBarItem.PUBLIC_CHATS,
|
NavBarItem.PUBLIC_CHATS,
|
||||||
NavBarItem.FOLLOW_PACKS,
|
NavBarItem.FOLLOW_PACKS,
|
||||||
NavBarItem.LIVE_STREAMS,
|
NavBarItem.LIVE_STREAMS,
|
||||||
if (isDebug) NavBarItem.AUDIO_ROOMS else null,
|
if (isDebug) NavBarItem.NESTS else null,
|
||||||
NavBarItem.LONGS,
|
NavBarItem.LONGS,
|
||||||
NavBarItem.POLLS,
|
NavBarItem.POLLS,
|
||||||
NavBarItem.BADGES,
|
NavBarItem.BADGES,
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable object LiveStreams : Route()
|
@Serializable object LiveStreams : Route()
|
||||||
|
|
||||||
@Serializable object AudioRooms : Route()
|
@Serializable object Nests : Route()
|
||||||
|
|
||||||
@Serializable object Longs : Route()
|
@Serializable object Longs : Route()
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ fun RenderMeetingSpaceEventInner(
|
|||||||
ListenToRecordingButton(url = it, accountViewModel = accountViewModel)
|
ListenToRecordingButton(url = it, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.JoinAudioRoomButton(
|
com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.JoinNestButton(
|
||||||
event = noteEvent,
|
event = noteEvent,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
@@ -196,7 +196,7 @@ private fun ListenToRecordingButton(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val context = androidx.compose.ui.platform.LocalContext.current
|
val context = androidx.compose.ui.platform.LocalContext.current
|
||||||
val noAppMessage = stringRes(R.string.audio_room_no_app_to_open_link)
|
val noAppMessage = stringRes(R.string.nest_no_app_to_open_link)
|
||||||
androidx.compose.material3.OutlinedButton(onClick = {
|
androidx.compose.material3.OutlinedButton(onClick = {
|
||||||
val launched =
|
val launched =
|
||||||
runCatching {
|
runCatching {
|
||||||
@@ -208,13 +208,13 @@ private fun ListenToRecordingButton(
|
|||||||
}.isSuccess
|
}.isSuccess
|
||||||
if (!launched) {
|
if (!launched) {
|
||||||
accountViewModel.toastManager.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.audio_room_chat_send_failed_title,
|
R.string.nest_chat_send_failed_title,
|
||||||
noAppMessage,
|
noAppMessage,
|
||||||
user = null,
|
user = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(stringRes(R.string.audio_room_listen_to_recording))
|
Text(stringRes(R.string.nest_listen_to_recording))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -28,7 +28,6 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
|||||||
import com.vitorpamplona.amethyst.ui.feeds.ChannelFeedContentState
|
import com.vitorpamplona.amethyst.ui.feeds.ChannelFeedContentState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState
|
import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.dal.ArticlesFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.dal.ArticlesFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.dal.AudioRoomsFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.dal.BadgesFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.dal.BadgesFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListKnownFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListKnownFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListNewFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListNewFeedFilter
|
||||||
@@ -48,6 +47,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeLiveFilter
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeNewThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeNewThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.dal.LiveStreamsFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.dal.LiveStreamsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.dal.LongsFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.dal.LongsFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.dal.NestsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CardFeedContentState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CardFeedContentState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationSummaryState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationSummaryState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.OpenPollsState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.OpenPollsState
|
||||||
@@ -101,7 +101,7 @@ class AccountFeedContentStates(
|
|||||||
val publicChatsFeed = FeedContentState(PublicChatsFeedFilter(account), scope, LocalCache)
|
val publicChatsFeed = FeedContentState(PublicChatsFeedFilter(account), scope, LocalCache)
|
||||||
val followPacksFeed = FeedContentState(FollowPacksFeedFilter(account), scope, LocalCache)
|
val followPacksFeed = FeedContentState(FollowPacksFeedFilter(account), scope, LocalCache)
|
||||||
val liveStreamsFeed = FeedContentState(LiveStreamsFeedFilter(account), scope, LocalCache)
|
val liveStreamsFeed = FeedContentState(LiveStreamsFeedFilter(account), scope, LocalCache)
|
||||||
val audioRoomsFeed = FeedContentState(AudioRoomsFeedFilter(account), scope, LocalCache)
|
val nestsFeed = FeedContentState(NestsFeedFilter(account), scope, LocalCache)
|
||||||
val longsFeed = FeedContentState(LongsFeedFilter(account), scope, LocalCache)
|
val longsFeed = FeedContentState(LongsFeedFilter(account), scope, LocalCache)
|
||||||
val articlesFeed = FeedContentState(ArticlesFeedFilter(account), scope, LocalCache)
|
val articlesFeed = FeedContentState(ArticlesFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ class AccountFeedContentStates(
|
|||||||
publicChatsFeed.updateFeedWith(newNotes)
|
publicChatsFeed.updateFeedWith(newNotes)
|
||||||
followPacksFeed.updateFeedWith(newNotes)
|
followPacksFeed.updateFeedWith(newNotes)
|
||||||
liveStreamsFeed.updateFeedWith(newNotes)
|
liveStreamsFeed.updateFeedWith(newNotes)
|
||||||
audioRoomsFeed.updateFeedWith(newNotes)
|
nestsFeed.updateFeedWith(newNotes)
|
||||||
longsFeed.updateFeedWith(newNotes)
|
longsFeed.updateFeedWith(newNotes)
|
||||||
articlesFeed.updateFeedWith(newNotes)
|
articlesFeed.updateFeedWith(newNotes)
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ class AccountFeedContentStates(
|
|||||||
publicChatsFeed.deleteFromFeed(newNotes)
|
publicChatsFeed.deleteFromFeed(newNotes)
|
||||||
followPacksFeed.deleteFromFeed(newNotes)
|
followPacksFeed.deleteFromFeed(newNotes)
|
||||||
liveStreamsFeed.deleteFromFeed(newNotes)
|
liveStreamsFeed.deleteFromFeed(newNotes)
|
||||||
audioRoomsFeed.deleteFromFeed(newNotes)
|
nestsFeed.deleteFromFeed(newNotes)
|
||||||
longsFeed.deleteFromFeed(newNotes)
|
longsFeed.deleteFromFeed(newNotes)
|
||||||
articlesFeed.deleteFromFeed(newNotes)
|
articlesFeed.deleteFromFeed(newNotes)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1574,7 +1574,7 @@ class AccountViewModel(
|
|||||||
callManager.dispose()
|
callManager.dispose()
|
||||||
com.vitorpamplona.amethyst.service.call.CallSessionBridge
|
com.vitorpamplona.amethyst.service.call.CallSessionBridge
|
||||||
.clear()
|
.clear()
|
||||||
com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomBridge
|
com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestBridge
|
||||||
.clear()
|
.clear()
|
||||||
feedStates.destroy()
|
feedStates.destroy()
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
|
|||||||
+2
-2
@@ -35,7 +35,6 @@ import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
|||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadLiveActivityChannel
|
import com.vitorpamplona.amethyst.ui.note.LoadLiveActivityChannel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomJoinCard
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.dal.ChannelFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.dal.ChannelFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelFilterAssemblerSubscription
|
||||||
@@ -43,6 +42,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53L
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveStreamTopZappers
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveStreamTopZappers
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send.ChannelNewMessageViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send.ChannelNewMessageViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send.EditFieldRow
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send.EditFieldRow
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestJoinCard
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ fun LiveActivityChannelView(
|
|||||||
.weight(1f, true),
|
.weight(1f, true),
|
||||||
) {
|
) {
|
||||||
ShowVideoStreaming(channel, accountViewModel)
|
ShowVideoStreaming(channel, accountViewModel)
|
||||||
AudioRoomJoinCard(channel, accountViewModel)
|
NestJoinCard(channel, accountViewModel)
|
||||||
LiveStreamTopZappers(channel, accountViewModel, nav)
|
LiveStreamTopZappers(channel, accountViewModel, nav)
|
||||||
LiveStreamGoalHeader(channel, accountViewModel, nav)
|
LiveStreamGoalHeader(channel, accountViewModel, nav)
|
||||||
RefreshingChatroomFeedView(
|
RefreshingChatroomFeedView(
|
||||||
|
|||||||
+5
-5
@@ -38,8 +38,8 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
|||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.note.Gallery
|
import com.vitorpamplona.amethyst.ui.note.Gallery
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomActivity
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestActivity
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomBridge
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestBridge
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ fun RenderLiveActivityBubble(
|
|||||||
// LiveActivitiesEvent), so toBestDisplayName() would fall back
|
// LiveActivitiesEvent), so toBestDisplayName() would fall back
|
||||||
// to the truncated bech32. Read the kind-30312 addressable
|
// to the truncated bech32. Read the kind-30312 addressable
|
||||||
// directly when the channel's address points to one and use the
|
// directly when the channel's address points to one and use the
|
||||||
// room name + a launch path that goes straight to AudioRoomActivity.
|
// room name + a launch path that goes straight to NestActivity.
|
||||||
val meetingEvent =
|
val meetingEvent =
|
||||||
remember(channel.address) {
|
remember(channel.address) {
|
||||||
if (channel.address.kind == MeetingSpaceEvent.KIND) {
|
if (channel.address.kind == MeetingSpaceEvent.KIND) {
|
||||||
@@ -73,8 +73,8 @@ fun RenderLiveActivityBubble(
|
|||||||
val endpoint = meetingEvent.endpoint()
|
val endpoint = meetingEvent.endpoint()
|
||||||
val dTag = meetingEvent.address().dTag
|
val dTag = meetingEvent.address().dTag
|
||||||
if (!service.isNullOrBlank() && !endpoint.isNullOrBlank() && dTag.isNotBlank()) {
|
if (!service.isNullOrBlank() && !endpoint.isNullOrBlank() && dTag.isNotBlank()) {
|
||||||
AudioRoomBridge.set(accountViewModel)
|
NestBridge.set(accountViewModel)
|
||||||
AudioRoomActivity.launch(
|
NestActivity.launch(
|
||||||
context = context,
|
context = context,
|
||||||
addressValue = meetingEvent.address().toValue(),
|
addressValue = meetingEvent.address().toValue(),
|
||||||
authBaseUrl = service,
|
authBaseUrl = service,
|
||||||
|
|||||||
+9
-9
@@ -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.audiorooms
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -41,9 +41,9 @@ import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding
|
|||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomActivity
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomBridge
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.RenderLiveActivityThumb
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.RenderLiveActivityThumb
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestActivity
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestBridge
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
@@ -51,7 +51,7 @@ import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEv
|
|||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioRoomsFeedLoaded(
|
fun NestsFeedLoaded(
|
||||||
loaded: FeedState.Loaded,
|
loaded: FeedState.Loaded,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@@ -65,7 +65,7 @@ fun AudioRoomsFeedLoaded(
|
|||||||
) {
|
) {
|
||||||
itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item ->
|
itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item ->
|
||||||
Row(Modifier.fillMaxWidth().animateItem()) {
|
Row(Modifier.fillMaxWidth().animateItem()) {
|
||||||
AudioRoomFeedCard(
|
NestFeedCard(
|
||||||
baseNote = item,
|
baseNote = item,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
@@ -82,12 +82,12 @@ fun AudioRoomsFeedLoaded(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio-rooms list card. Mirrors [RenderLiveActivityThumb] visually but
|
* Audio-rooms list card. Mirrors [RenderLiveActivityThumb] visually but
|
||||||
* routes a tap straight into [AudioRoomActivity] when the underlying event
|
* routes a tap straight into [NestActivity] when the underlying event
|
||||||
* is a [MeetingSpaceEvent], instead of the thread view that the generic
|
* is a [MeetingSpaceEvent], instead of the thread view that the generic
|
||||||
* `ChannelCardCompose` → `ClickableNote` chain would otherwise open.
|
* `ChannelCardCompose` → `ClickableNote` chain would otherwise open.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun AudioRoomFeedCard(
|
private fun NestFeedCard(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@@ -103,8 +103,8 @@ private fun AudioRoomFeedCard(
|
|||||||
val endpoint = meetingEvent.endpoint()
|
val endpoint = meetingEvent.endpoint()
|
||||||
val dTag = meetingEvent.address().dTag
|
val dTag = meetingEvent.address().dTag
|
||||||
if (!service.isNullOrBlank() && !endpoint.isNullOrBlank() && dTag.isNotBlank()) {
|
if (!service.isNullOrBlank() && !endpoint.isNullOrBlank() && dTag.isNotBlank()) {
|
||||||
AudioRoomBridge.set(accountViewModel)
|
NestBridge.set(accountViewModel)
|
||||||
AudioRoomActivity.launch(
|
NestActivity.launch(
|
||||||
context = context,
|
context = context,
|
||||||
addressValue = meetingEvent.address().toValue(),
|
addressValue = meetingEvent.address().toValue(),
|
||||||
authBaseUrl = service,
|
authBaseUrl = service,
|
||||||
+34
-34
@@ -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.audiorooms
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests
|
||||||
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
@@ -46,32 +46,32 @@ import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
|||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.create.CreateAudioRoomSheet
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.create.CreateNestSheet
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.create.CreateAudioRoomViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.create.CreateNestViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.AudioRoomsFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioRoomsScreen(
|
fun NestsScreen(
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
AudioRoomsScreen(
|
NestsScreen(
|
||||||
audioRoomsFeedContentState = accountViewModel.feedStates.audioRoomsFeed,
|
nestsFeedContentState = accountViewModel.feedStates.nestsFeed,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioRoomsScreen(
|
fun NestsScreen(
|
||||||
audioRoomsFeedContentState: FeedContentState,
|
nestsFeedContentState: FeedContentState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
WatchLifecycleAndUpdateModel(audioRoomsFeedContentState)
|
WatchLifecycleAndUpdateModel(nestsFeedContentState)
|
||||||
WatchAccountForAudioRoomsScreen(audioRoomsFeedState = audioRoomsFeedContentState, accountViewModel = accountViewModel)
|
WatchAccountForNestsScreen(nestsFeedState = nestsFeedContentState, accountViewModel = accountViewModel)
|
||||||
AudioRoomsFilterAssemblerSubscription(accountViewModel)
|
NestsFilterAssemblerSubscription(accountViewModel)
|
||||||
|
|
||||||
val nestsServers by accountViewModel.account.nestsServers.flow
|
val nestsServers by accountViewModel.account.nestsServers.flow
|
||||||
.collectAsStateWithLifecycle()
|
.collectAsStateWithLifecycle()
|
||||||
@@ -81,12 +81,12 @@ fun AudioRoomsScreen(
|
|||||||
DisappearingScaffold(
|
DisappearingScaffold(
|
||||||
isInvertedLayout = false,
|
isInvertedLayout = false,
|
||||||
topBar = {
|
topBar = {
|
||||||
AudioRoomsTopBar(accountViewModel, nav)
|
NestsTopBar(accountViewModel, nav)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
AppBottomBar(Route.AudioRooms, accountViewModel) { route ->
|
AppBottomBar(Route.Nests, accountViewModel) { route ->
|
||||||
if (route == Route.AudioRooms) {
|
if (route == Route.Nests) {
|
||||||
audioRoomsFeedContentState.sendToTop()
|
nestsFeedContentState.sendToTop()
|
||||||
} else {
|
} else {
|
||||||
nav.navBottomBar(route)
|
nav.navBottomBar(route)
|
||||||
}
|
}
|
||||||
@@ -105,22 +105,22 @@ fun AudioRoomsScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
symbol = MaterialSymbols.Add,
|
symbol = MaterialSymbols.Add,
|
||||||
contentDescription = stringRes(R.string.audio_room_create_fab),
|
contentDescription = stringRes(R.string.nest_create_fab),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
) {
|
) {
|
||||||
RefresheableBox(audioRoomsFeedContentState, true) {
|
RefresheableBox(nestsFeedContentState, true) {
|
||||||
SaveableFeedContentState(audioRoomsFeedContentState, scrollStateKey = ScrollStateKeys.AUDIO_ROOMS_SCREEN) { listState ->
|
SaveableFeedContentState(nestsFeedContentState, scrollStateKey = ScrollStateKeys.NESTS_SCREEN) { listState ->
|
||||||
RenderFeedContentState(
|
RenderFeedContentState(
|
||||||
feedContentState = audioRoomsFeedContentState,
|
feedContentState = nestsFeedContentState,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
routeForLastRead = "AudioRoomsFeed",
|
routeForLastRead = "NestsFeed",
|
||||||
onLoaded = { loaded ->
|
onLoaded = { loaded ->
|
||||||
AudioRoomsFeedLoaded(
|
NestsFeedLoaded(
|
||||||
loaded = loaded,
|
loaded = loaded,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
@@ -134,20 +134,20 @@ fun AudioRoomsScreen(
|
|||||||
|
|
||||||
if (showSetupDialog) {
|
if (showSetupDialog) {
|
||||||
SetUpAudioServerDialog(
|
SetUpAudioServerDialog(
|
||||||
defaultUrl = CreateAudioRoomViewModel.DEFAULT_SERVICE_URL,
|
defaultUrl = CreateNestViewModel.DEFAULT_SERVICE_URL,
|
||||||
onDismiss = { showSetupDialog = false },
|
onDismiss = { showSetupDialog = false },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
showSetupDialog = false
|
showSetupDialog = false
|
||||||
accountViewModel.launchSigner {
|
accountViewModel.launchSigner {
|
||||||
try {
|
try {
|
||||||
accountViewModel.account.sendNestsServersList(
|
accountViewModel.account.sendNestsServersList(
|
||||||
listOf(CreateAudioRoomViewModel.DEFAULT_SERVICE_URL),
|
listOf(CreateNestViewModel.DEFAULT_SERVICE_URL),
|
||||||
)
|
)
|
||||||
showCreateSheet = true
|
showCreateSheet = true
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
accountViewModel.toastManager.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.audio_rooms,
|
R.string.nests,
|
||||||
R.string.audio_room_no_server_save_failed,
|
R.string.nest_no_server_save_failed,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ fun AudioRoomsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showCreateSheet) {
|
if (showCreateSheet) {
|
||||||
CreateAudioRoomSheet(
|
CreateNestSheet(
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
onDismiss = { showCreateSheet = false },
|
onDismiss = { showCreateSheet = false },
|
||||||
)
|
)
|
||||||
@@ -171,24 +171,24 @@ private fun SetUpAudioServerDialog(
|
|||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text(stringRes(R.string.audio_room_no_server_title)) },
|
title = { Text(stringRes(R.string.nest_no_server_title)) },
|
||||||
text = { Text(stringRes(R.string.audio_room_no_server_body, defaultUrl)) },
|
text = { Text(stringRes(R.string.nest_no_server_body, defaultUrl)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onConfirm) {
|
TextButton(onClick = onConfirm) {
|
||||||
Text(stringRes(R.string.audio_room_no_server_use_default))
|
Text(stringRes(R.string.nest_no_server_use_default))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismiss) {
|
TextButton(onClick = onDismiss) {
|
||||||
Text(stringRes(R.string.audio_room_no_server_cancel))
|
Text(stringRes(R.string.nest_no_server_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WatchAccountForAudioRoomsScreen(
|
fun WatchAccountForNestsScreen(
|
||||||
audioRoomsFeedState: FeedContentState,
|
nestsFeedState: FeedContentState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val listState by accountViewModel.account.liveLiveStreamsFollowLists.collectAsStateWithLifecycle()
|
val listState by accountViewModel.account.liveLiveStreamsFollowLists.collectAsStateWithLifecycle()
|
||||||
@@ -201,6 +201,6 @@ fun WatchAccountForAudioRoomsScreen(
|
|||||||
.collectAsStateWithLifecycle()
|
.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
LaunchedEffect(accountViewModel, listState, hiddenUsers) {
|
LaunchedEffect(accountViewModel, listState, hiddenUsers) {
|
||||||
audioRoomsFeedState.checkKeysInvalidateDataAndSendToTop()
|
nestsFeedState.checkKeysInvalidateDataAndSendToTop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+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.audiorooms
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -34,7 +34,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioRoomsTopBar(
|
fun NestsTopBar(
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
@@ -42,7 +42,7 @@ fun AudioRoomsTopBar(
|
|||||||
val list by accountViewModel.account.settings.defaultLiveStreamsFollowList
|
val list by accountViewModel.account.settings.defaultLiveStreamsFollowList
|
||||||
.collectAsStateWithLifecycle()
|
.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
AudioRoomsTopNavFilterBar(
|
NestsTopNavFilterBar(
|
||||||
followListsModel = accountViewModel.feedStates.feedListOptions,
|
followListsModel = accountViewModel.feedStates.feedListOptions,
|
||||||
listName = list,
|
listName = list,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
@@ -52,7 +52,7 @@ fun AudioRoomsTopBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AudioRoomsTopNavFilterBar(
|
private fun NestsTopNavFilterBar(
|
||||||
followListsModel: TopNavFilterState,
|
followListsModel: TopNavFilterState,
|
||||||
listName: TopFilter,
|
listName: TopFilter,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
+29
-29
@@ -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.audiorooms.create
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.create
|
||||||
|
|
||||||
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,32 +54,32 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.AudioRoomsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomActivity
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestActivity
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomBridge
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestBridge
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bottom sheet that lets the logged-in user start a new NIP-53 kind 30312
|
* Bottom sheet that lets the logged-in user start a new NIP-53 kind 30312
|
||||||
* audio room. On submit, [CreateAudioRoomViewModel] builds and signs the
|
* audio room. On submit, [CreateNestViewModel] builds and signs the
|
||||||
* MeetingSpaceEvent (with the user as `host`), broadcasts it to the
|
* MeetingSpaceEvent (with the user as `host`), broadcasts it to the
|
||||||
* account's relays, and then launches [AudioRoomActivity] against the
|
* account's relays, and then launches [NestActivity] against the
|
||||||
* fresh address — the user lands inside the room as host with the
|
* fresh address — the user lands inside the room as host with the
|
||||||
* Talk button enabled.
|
* Talk button enabled.
|
||||||
*
|
*
|
||||||
* Hidden by default; surfaced by the "Start space" FAB on
|
* Hidden by default; surfaced by the "Start space" FAB on
|
||||||
* [AudioRoomsScreen].
|
* [NestsScreen].
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateAudioRoomSheet(
|
fun CreateNestSheet(
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val viewModelKey = remember(accountViewModel) { accountViewModel.account.userProfile().pubkeyHex }
|
val viewModelKey = remember(accountViewModel) { accountViewModel.account.userProfile().pubkeyHex }
|
||||||
val viewModel: CreateAudioRoomViewModel =
|
val viewModel: CreateNestViewModel =
|
||||||
viewModel(key = "CreateAudioRoom-$viewModelKey")
|
viewModel(key = "CreateNest-$viewModelKey")
|
||||||
LaunchedEffect(viewModel) { viewModel.bindAccountIfMissing(accountViewModel) }
|
LaunchedEffect(viewModel) { viewModel.bindAccountIfMissing(accountViewModel) }
|
||||||
|
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
@@ -96,14 +96,14 @@ fun CreateAudioRoomSheet(
|
|||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.audio_room_create_title),
|
text = stringRes(R.string.nest_create_title),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.roomName,
|
value = state.roomName,
|
||||||
onValueChange = viewModel::onRoomNameChange,
|
onValueChange = viewModel::onRoomNameChange,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_room)) },
|
label = { Text(stringRes(R.string.nest_create_field_room)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = state.error != null && state.roomName.isBlank(),
|
isError = state.error != null && state.roomName.isBlank(),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -112,7 +112,7 @@ fun CreateAudioRoomSheet(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.summary,
|
value = state.summary,
|
||||||
onValueChange = viewModel::onSummaryChange,
|
onValueChange = viewModel::onSummaryChange,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_summary)) },
|
label = { Text(stringRes(R.string.nest_create_field_summary)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
||||||
)
|
)
|
||||||
@@ -120,27 +120,27 @@ fun CreateAudioRoomSheet(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.serviceUrl,
|
value = state.serviceUrl,
|
||||||
onValueChange = viewModel::onServiceUrlChange,
|
onValueChange = viewModel::onServiceUrlChange,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_service)) },
|
label = { Text(stringRes(R.string.nest_create_field_service)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = state.error != null && state.serviceUrl.isBlank(),
|
isError = state.error != null && state.serviceUrl.isBlank(),
|
||||||
supportingText = { Text(stringRes(R.string.audio_room_create_field_service_hint)) },
|
supportingText = { Text(stringRes(R.string.nest_create_field_service_hint)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.endpointUrl,
|
value = state.endpointUrl,
|
||||||
onValueChange = viewModel::onEndpointUrlChange,
|
onValueChange = viewModel::onEndpointUrlChange,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_endpoint)) },
|
label = { Text(stringRes(R.string.nest_create_field_endpoint)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = state.error != null && state.endpointUrl.isBlank(),
|
isError = state.error != null && state.endpointUrl.isBlank(),
|
||||||
supportingText = { Text(stringRes(R.string.audio_room_create_field_endpoint_hint)) },
|
supportingText = { Text(stringRes(R.string.nest_create_field_endpoint_hint)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.imageUrl,
|
value = state.imageUrl,
|
||||||
onValueChange = viewModel::onImageUrlChange,
|
onValueChange = viewModel::onImageUrlChange,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_image)) },
|
label = { Text(stringRes(R.string.nest_create_field_image)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
@@ -157,7 +157,7 @@ fun CreateAudioRoomSheet(
|
|||||||
onCheckedChange = viewModel::onScheduledToggle,
|
onCheckedChange = viewModel::onScheduledToggle,
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text(stringRes(R.string.audio_room_create_schedule_toggle))
|
Text(stringRes(R.string.nest_create_schedule_toggle))
|
||||||
}
|
}
|
||||||
if (state.scheduled) {
|
if (state.scheduled) {
|
||||||
ScheduleStartPicker(
|
ScheduleStartPicker(
|
||||||
@@ -181,7 +181,7 @@ fun CreateAudioRoomSheet(
|
|||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
) {
|
) {
|
||||||
TextButton(onClick = onDismiss, enabled = !state.isPublishing) {
|
TextButton(onClick = onDismiss, enabled = !state.isPublishing) {
|
||||||
Text(stringRes(R.string.audio_room_create_cancel))
|
Text(stringRes(R.string.nest_create_cancel))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Button(
|
Button(
|
||||||
@@ -189,10 +189,10 @@ fun CreateAudioRoomSheet(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
val launchInfo = viewModel.publishAndBuildLaunchInfo() ?: return@launch
|
val launchInfo = viewModel.publishAndBuildLaunchInfo() ?: return@launch
|
||||||
// Hand the active AccountViewModel to the
|
// Hand the active AccountViewModel to the
|
||||||
// separately-tasked AudioRoomActivity (mirrors
|
// separately-tasked NestActivity (mirrors
|
||||||
// the join-card flow).
|
// the join-card flow).
|
||||||
AudioRoomBridge.set(accountViewModel)
|
NestBridge.set(accountViewModel)
|
||||||
AudioRoomActivity.launch(
|
NestActivity.launch(
|
||||||
context = context,
|
context = context,
|
||||||
addressValue = launchInfo.addressValue,
|
addressValue = launchInfo.addressValue,
|
||||||
authBaseUrl = launchInfo.authBaseUrl,
|
authBaseUrl = launchInfo.authBaseUrl,
|
||||||
@@ -212,7 +212,7 @@ fun CreateAudioRoomSheet(
|
|||||||
strokeWidth = 2.dp,
|
strokeWidth = 2.dp,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(stringRes(R.string.audio_room_create_submit))
|
Text(stringRes(R.string.nest_create_submit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ private fun ScheduleStartPicker(
|
|||||||
|
|
||||||
val pretty =
|
val pretty =
|
||||||
if (unixSeconds <= 0L) {
|
if (unixSeconds <= 0L) {
|
||||||
stringRes(R.string.audio_room_create_when)
|
stringRes(R.string.nest_create_when)
|
||||||
} else {
|
} else {
|
||||||
val instant = java.util.Date(unixSeconds * 1000L)
|
val instant = java.util.Date(unixSeconds * 1000L)
|
||||||
java.text.DateFormat
|
java.text.DateFormat
|
||||||
@@ -302,7 +302,7 @@ private fun ScheduleStartPicker(
|
|||||||
resetPickersToCommitted()
|
resetPickersToCommitted()
|
||||||
showDate = false
|
showDate = false
|
||||||
}) {
|
}) {
|
||||||
Text(stringRes(R.string.audio_room_create_cancel))
|
Text(stringRes(R.string.nest_create_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@@ -312,7 +312,7 @@ private fun ScheduleStartPicker(
|
|||||||
|
|
||||||
if (showTime) {
|
if (showTime) {
|
||||||
androidx.compose.material3.TimePickerDialog(
|
androidx.compose.material3.TimePickerDialog(
|
||||||
title = { Text(stringRes(R.string.audio_room_create_when)) },
|
title = { Text(stringRes(R.string.nest_create_when)) },
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
resetPickersToCommitted()
|
resetPickersToCommitted()
|
||||||
showTime = false
|
showTime = false
|
||||||
@@ -340,14 +340,14 @@ private fun ScheduleStartPicker(
|
|||||||
onChange(localSeconds - offsetSec)
|
onChange(localSeconds - offsetSec)
|
||||||
}
|
}
|
||||||
showTime = false
|
showTime = false
|
||||||
}) { Text(stringRes(R.string.audio_room_create_submit)) }
|
}) { Text(stringRes(R.string.nest_create_submit)) }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
resetPickersToCommitted()
|
resetPickersToCommitted()
|
||||||
showTime = false
|
showTime = false
|
||||||
}) {
|
}) {
|
||||||
Text(stringRes(R.string.audio_room_create_cancel))
|
Text(stringRes(R.string.nest_create_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
+7
-7
@@ -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.ui.screen.loggedIn.audiorooms.create
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.create
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.AudioRoomActivity
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.NestActivity
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.endpoint
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.endpoint
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.image
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.image
|
||||||
@@ -37,16 +37,16 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backing ViewModel for [CreateAudioRoomSheet]. Holds form state, runs
|
* Backing ViewModel for [CreateNestSheet]. Holds form state, runs
|
||||||
* [publishAndBuildLaunchInfo] which signs and broadcasts a NIP-53 kind
|
* [publishAndBuildLaunchInfo] which signs and broadcasts a NIP-53 kind
|
||||||
* 30312 [MeetingSpaceEvent] tagging the user as `host`, then returns
|
* 30312 [MeetingSpaceEvent] tagging the user as `host`, then returns
|
||||||
* the launch parameters for [AudioRoomActivity].
|
* the launch parameters for [NestActivity].
|
||||||
*
|
*
|
||||||
* The defaults point at `nostrnests.com`'s public moq-rs deployment so a
|
* The defaults point at `nostrnests.com`'s public moq-rs deployment so a
|
||||||
* blank form produces a working room. The user can edit them to point at
|
* blank form produces a working room. The user can edit them to point at
|
||||||
* their own moq-rs / moq-auth pair.
|
* their own moq-rs / moq-auth pair.
|
||||||
*/
|
*/
|
||||||
class CreateAudioRoomViewModel : ViewModel() {
|
class CreateNestViewModel : ViewModel() {
|
||||||
/** Lazily bound on first composition; the sheet calls [bindAccountIfMissing]. */
|
/** Lazily bound on first composition; the sheet calls [bindAccountIfMissing]. */
|
||||||
@Volatile private var account: AccountViewModel? = null
|
@Volatile private var account: AccountViewModel? = null
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ class CreateAudioRoomViewModel : ViewModel() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the kind-30312 event, sign + broadcast it, and return the
|
* Build the kind-30312 event, sign + broadcast it, and return the
|
||||||
* launch info the sheet needs to start [AudioRoomActivity]. Returns
|
* launch info the sheet needs to start [NestActivity]. Returns
|
||||||
* null on validation or network failure (with [FormState.error]
|
* null on validation or network failure (with [FormState.error]
|
||||||
* set so the UI can render it).
|
* set so the UI can render it).
|
||||||
*/
|
*/
|
||||||
@@ -222,7 +222,7 @@ class CreateAudioRoomViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Captured fields needed to launch [AudioRoomActivity]. Mirrors the
|
* Captured fields needed to launch [NestActivity]. Mirrors the
|
||||||
* `EXTRA_*` set the activity expects; pulled out here so the sheet
|
* `EXTRA_*` set the activity expects; pulled out here so the sheet
|
||||||
* is a thin renderer.
|
* is a thin renderer.
|
||||||
*/
|
*/
|
||||||
+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.audiorooms.dal
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.dal
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
@@ -45,7 +45,7 @@ import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag
|
|||||||
* to MeetingSpaceEvent (30312) and MeetingRoomEvent (30313) so that the
|
* to MeetingSpaceEvent (30312) and MeetingRoomEvent (30313) so that the
|
||||||
* Clubhouse-style audio-room surface is independent of video live streams.
|
* Clubhouse-style audio-room surface is independent of video live streams.
|
||||||
*/
|
*/
|
||||||
class AudioRoomsFeedFilter(
|
class NestsFeedFilter(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
) : AdditiveFeedFilter<Note>() {
|
) : AdditiveFeedFilter<Note>() {
|
||||||
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code
|
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code
|
||||||
+5
-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.audiorooms.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||||
@@ -27,19 +27,19 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
class AudioRoomsQueryState(
|
class NestsQueryState(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
val feedStates: AccountFeedContentStates,
|
val feedStates: AccountFeedContentStates,
|
||||||
val scope: CoroutineScope,
|
val scope: CoroutineScope,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class AudioRoomsFilterAssembler(
|
class NestsFilterAssembler(
|
||||||
client: INostrClient,
|
client: INostrClient,
|
||||||
) : ComposeSubscriptionManager<AudioRoomsQueryState>() {
|
) : ComposeSubscriptionManager<NestsQueryState>() {
|
||||||
val group =
|
val group =
|
||||||
listOf(
|
listOf(
|
||||||
AudioRoomsSubAssembler(client, ::allKeys),
|
NestsSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun invalidateKeys() = invalidateFilters()
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
+7
-7
@@ -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.audiorooms.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -27,21 +27,21 @@ import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourc
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioRoomsFilterAssemblerSubscription(accountViewModel: AccountViewModel) {
|
fun NestsFilterAssemblerSubscription(accountViewModel: AccountViewModel) {
|
||||||
AudioRoomsFilterAssemblerSubscription(
|
NestsFilterAssemblerSubscription(
|
||||||
accountViewModel.dataSources().audioRooms,
|
accountViewModel.dataSources().nests,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioRoomsFilterAssemblerSubscription(
|
fun NestsFilterAssemblerSubscription(
|
||||||
dataSource: AudioRoomsFilterAssembler,
|
dataSource: NestsFilterAssembler,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val state =
|
val state =
|
||||||
remember(accountViewModel.account) {
|
remember(accountViewModel.account) {
|
||||||
AudioRoomsQueryState(accountViewModel.account, accountViewModel.feedStates, accountViewModel.viewModelScope)
|
NestsQueryState(accountViewModel.account, accountViewModel.feedStates, accountViewModel.viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyDataSourceSubscription(state, dataSource)
|
KeyDataSourceSubscription(state, dataSource)
|
||||||
+14
-14
@@ -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.audiorooms.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.TopFilter
|
import com.vitorpamplona.amethyst.model.TopFilter
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
@@ -41,34 +41,34 @@ import kotlinx.coroutines.launch
|
|||||||
* sharing the wire filter avoids duplicate REQs on relays when both the Live
|
* sharing the wire filter avoids duplicate REQs on relays when both the Live
|
||||||
* Streams and Audio Rooms screens are open for the same user.
|
* Streams and Audio Rooms screens are open for the same user.
|
||||||
*/
|
*/
|
||||||
class AudioRoomsSubAssembler(
|
class NestsSubAssembler(
|
||||||
client: INostrClient,
|
client: INostrClient,
|
||||||
allKeys: () -> Set<AudioRoomsQueryState>,
|
allKeys: () -> Set<NestsQueryState>,
|
||||||
) : PerUserAndFollowListEoseManager<AudioRoomsQueryState, TopFilter>(client, allKeys) {
|
) : PerUserAndFollowListEoseManager<NestsQueryState, TopFilter>(client, allKeys) {
|
||||||
override fun updateFilter(
|
override fun updateFilter(
|
||||||
key: AudioRoomsQueryState,
|
key: NestsQueryState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter> {
|
): List<RelayBasedFilter> {
|
||||||
val feedSettings = key.followsPerRelay()
|
val feedSettings = key.followsPerRelay()
|
||||||
return makeLiveActivitiesFilter(feedSettings, since, key.feedStates.audioRoomsFeed.lastNoteCreatedAtIfFilled())
|
return makeLiveActivitiesFilter(feedSettings, since, key.feedStates.nestsFeed.lastNoteCreatedAtIfFilled())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun user(key: AudioRoomsQueryState) = key.account.userProfile()
|
override fun user(key: NestsQueryState) = key.account.userProfile()
|
||||||
|
|
||||||
override fun list(key: AudioRoomsQueryState) = key.listName()
|
override fun list(key: NestsQueryState) = key.listName()
|
||||||
|
|
||||||
fun AudioRoomsQueryState.listNameFlow() = account.settings.defaultLiveStreamsFollowList
|
fun NestsQueryState.listNameFlow() = account.settings.defaultLiveStreamsFollowList
|
||||||
|
|
||||||
fun AudioRoomsQueryState.listName() = listNameFlow().value
|
fun NestsQueryState.listName() = listNameFlow().value
|
||||||
|
|
||||||
fun AudioRoomsQueryState.followsPerRelayFlow() = account.liveLiveStreamsFollowListsPerRelay
|
fun NestsQueryState.followsPerRelayFlow() = account.liveLiveStreamsFollowListsPerRelay
|
||||||
|
|
||||||
fun AudioRoomsQueryState.followsPerRelay() = followsPerRelayFlow().value
|
fun NestsQueryState.followsPerRelay() = followsPerRelayFlow().value
|
||||||
|
|
||||||
private val userJobMap = mutableMapOf<User, List<Job>>()
|
private val userJobMap = mutableMapOf<User, List<Job>>()
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
override fun newSub(key: AudioRoomsQueryState): Subscription {
|
override fun newSub(key: NestsQueryState): Subscription {
|
||||||
val user = user(key)
|
val user = user(key)
|
||||||
userJobMap[user]?.forEach { it.cancel() }
|
userJobMap[user]?.forEach { it.cancel() }
|
||||||
userJobMap[user] =
|
userJobMap[user] =
|
||||||
@@ -84,7 +84,7 @@ class AudioRoomsSubAssembler(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
key.account.scope.launch(Dispatchers.IO) {
|
key.account.scope.launch(Dispatchers.IO) {
|
||||||
key.feedStates.audioRoomsFeed.lastNoteCreatedAtWhenFullyLoaded.sample(5000).collectLatest {
|
key.feedStates.nestsFeed.lastNoteCreatedAtWhenFullyLoaded.sample(5000).collectLatest {
|
||||||
invalidateFilters()
|
invalidateFilters()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
+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.audiorooms.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.experimental.audiorooms.admin.AdminCommandEvent
|
import com.vitorpamplona.quartz.experimental.nests.admin.AdminCommandEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
+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.audiorooms.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
@@ -49,7 +49,7 @@ class RoomChatQueryState(
|
|||||||
* REQs `kinds=[1311], #a=[roomATag]` against the user's outbox
|
* REQs `kinds=[1311], #a=[roomATag]` against the user's outbox
|
||||||
* relays so the chat panel sees every message tagged for this
|
* relays so the chat panel sees every message tagged for this
|
||||||
* room. Events arrive in [com.vitorpamplona.amethyst.model.LocalCache];
|
* room. Events arrive in [com.vitorpamplona.amethyst.model.LocalCache];
|
||||||
* the room screen pipes them into `AudioRoomViewModel.onChatEvent`.
|
* the room screen pipes them into `NestViewModel.onChatEvent`.
|
||||||
*/
|
*/
|
||||||
class RoomChatFilterSubAssembler(
|
class RoomChatFilterSubAssembler(
|
||||||
client: INostrClient,
|
client: INostrClient,
|
||||||
+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.audiorooms.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||||
@@ -47,7 +47,7 @@ class RoomPresenceQueryState(
|
|||||||
* already talks to. The events arrive in [com.vitorpamplona.amethyst.model.LocalCache]
|
* already talks to. The events arrive in [com.vitorpamplona.amethyst.model.LocalCache]
|
||||||
* via the normal client pipeline; the room screen observes them
|
* via the normal client pipeline; the room screen observes them
|
||||||
* with `LocalCache.observeEvents(...)` and feeds each event into
|
* with `LocalCache.observeEvents(...)` and feeds each event into
|
||||||
* `AudioRoomViewModel.onPresenceEvent`.
|
* `NestViewModel.onPresenceEvent`.
|
||||||
*
|
*
|
||||||
* One assembler instance services every audio-room screen — the
|
* One assembler instance services every audio-room screen — the
|
||||||
* `key` (= room a-tag) keeps overlapping screens (in unlikely
|
* `key` (= room a-tag) keeps overlapping screens (in unlikely
|
||||||
+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.audiorooms.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.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.audiorooms.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
+18
-18
@@ -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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -63,18 +63,18 @@ import kotlinx.coroutines.launch
|
|||||||
* original), or closes the room with a destructive button.
|
* original), or closes the room with a destructive button.
|
||||||
*
|
*
|
||||||
* Visibility gating (host-only) is the caller's job;
|
* Visibility gating (host-only) is the caller's job;
|
||||||
* [EditAudioRoomViewModel] preserves every promoted participant
|
* [EditNestViewModel] preserves every promoted participant
|
||||||
* verbatim, so a save MUST NOT silently demote anyone.
|
* verbatim, so a save MUST NOT silently demote anyone.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun EditAudioRoomSheet(
|
fun EditNestSheet(
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
event: MeetingSpaceEvent,
|
event: MeetingSpaceEvent,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val key = remember(event) { "EditAudioRoom-${event.dTag()}" }
|
val key = remember(event) { "EditNest-${event.dTag()}" }
|
||||||
val viewModel: EditAudioRoomViewModel = viewModel(key = key)
|
val viewModel: EditNestViewModel = viewModel(key = key)
|
||||||
LaunchedEffect(viewModel, event) { viewModel.bind(accountViewModel, event) }
|
LaunchedEffect(viewModel, event) { viewModel.bind(accountViewModel, event) }
|
||||||
|
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
@@ -85,8 +85,8 @@ fun EditAudioRoomSheet(
|
|||||||
if (confirmCloseOpen) {
|
if (confirmCloseOpen) {
|
||||||
androidx.compose.material3.AlertDialog(
|
androidx.compose.material3.AlertDialog(
|
||||||
onDismissRequest = { confirmCloseOpen = false },
|
onDismissRequest = { confirmCloseOpen = false },
|
||||||
title = { Text(stringRes(R.string.audio_room_close_room_confirm_title)) },
|
title = { Text(stringRes(R.string.nest_close_room_confirm_title)) },
|
||||||
text = { Text(stringRes(R.string.audio_room_close_room_confirm_body)) },
|
text = { Text(stringRes(R.string.nest_close_room_confirm_body)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
||||||
@@ -97,12 +97,12 @@ fun EditAudioRoomSheet(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(stringRes(R.string.audio_room_close_room_confirm_action))
|
Text(stringRes(R.string.nest_close_room_confirm_action))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { confirmCloseOpen = false }) {
|
TextButton(onClick = { confirmCloseOpen = false }) {
|
||||||
Text(stringRes(R.string.audio_room_create_cancel))
|
Text(stringRes(R.string.nest_create_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -113,41 +113,41 @@ fun EditAudioRoomSheet(
|
|||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.audio_room_edit_title),
|
text = stringRes(R.string.nest_edit_title),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.roomName,
|
value = state.roomName,
|
||||||
onValueChange = viewModel::setRoomName,
|
onValueChange = viewModel::setRoomName,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_room)) },
|
label = { Text(stringRes(R.string.nest_create_field_room)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.summary,
|
value = state.summary,
|
||||||
onValueChange = viewModel::setSummary,
|
onValueChange = viewModel::setSummary,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_summary)) },
|
label = { Text(stringRes(R.string.nest_create_field_summary)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.serviceUrl,
|
value = state.serviceUrl,
|
||||||
onValueChange = viewModel::setServiceUrl,
|
onValueChange = viewModel::setServiceUrl,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_service)) },
|
label = { Text(stringRes(R.string.nest_create_field_service)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.endpointUrl,
|
value = state.endpointUrl,
|
||||||
onValueChange = viewModel::setEndpointUrl,
|
onValueChange = viewModel::setEndpointUrl,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_endpoint)) },
|
label = { Text(stringRes(R.string.nest_create_field_endpoint)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.imageUrl,
|
value = state.imageUrl,
|
||||||
onValueChange = viewModel::setImageUrl,
|
onValueChange = viewModel::setImageUrl,
|
||||||
label = { Text(stringRes(R.string.audio_room_create_field_image)) },
|
label = { Text(stringRes(R.string.nest_create_field_image)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
@@ -171,11 +171,11 @@ fun EditAudioRoomSheet(
|
|||||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
||||||
onClick = { confirmCloseOpen = true },
|
onClick = { confirmCloseOpen = true },
|
||||||
) {
|
) {
|
||||||
Text(stringRes(R.string.audio_room_close_action))
|
Text(stringRes(R.string.nest_close_action))
|
||||||
}
|
}
|
||||||
Row {
|
Row {
|
||||||
TextButton(onClick = onDismiss, enabled = !state.isPublishing) {
|
TextButton(onClick = onDismiss, enabled = !state.isPublishing) {
|
||||||
Text(stringRes(R.string.audio_room_create_cancel))
|
Text(stringRes(R.string.nest_create_cancel))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Button(
|
Button(
|
||||||
@@ -189,7 +189,7 @@ fun EditAudioRoomSheet(
|
|||||||
if (state.isPublishing) {
|
if (state.isPublishing) {
|
||||||
CircularProgressIndicator(modifier = Modifier.size(18.dp), strokeWidth = 2.dp)
|
CircularProgressIndicator(modifier = Modifier.size(18.dp), strokeWidth = 2.dp)
|
||||||
} else {
|
} else {
|
||||||
Text(stringRes(R.string.audio_room_edit_save))
|
Text(stringRes(R.string.nest_edit_save))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+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.screen.loggedIn.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -36,7 +36,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backing ViewModel for [EditAudioRoomSheet]. Loads the existing
|
* Backing ViewModel for [EditNestSheet]. Loads the existing
|
||||||
* [MeetingSpaceEvent], lets the host edit room name / summary /
|
* [MeetingSpaceEvent], lets the host edit room name / summary /
|
||||||
* image / endpoint, then republishes kind-30312 with the SAME `d`
|
* image / endpoint, then republishes kind-30312 with the SAME `d`
|
||||||
* tag so the relay treats it as a replacement of the original.
|
* tag so the relay treats it as a replacement of the original.
|
||||||
@@ -49,7 +49,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
* speakers or admins who were promoted in a previous version of
|
* speakers or admins who were promoted in a previous version of
|
||||||
* the event (audit risk note in the Tier-1 plan).
|
* the event (audit risk note in the Tier-1 plan).
|
||||||
*/
|
*/
|
||||||
class EditAudioRoomViewModel : ViewModel() {
|
class EditNestViewModel : ViewModel() {
|
||||||
@Volatile private var account: AccountViewModel? = null
|
@Volatile private var account: AccountViewModel? = null
|
||||||
|
|
||||||
@Volatile private var original: MeetingSpaceEvent? = null
|
@Volatile private var original: MeetingSpaceEvent? = null
|
||||||
+5
-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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -38,7 +38,7 @@ 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 com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
@@ -62,7 +62,7 @@ import kotlinx.coroutines.launch
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun HandRaiseQueueSection(
|
internal fun HandRaiseQueueSection(
|
||||||
event: MeetingSpaceEvent,
|
event: MeetingSpaceEvent,
|
||||||
viewModel: AudioRoomViewModel,
|
viewModel: NestViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@@ -83,7 +83,7 @@ internal fun HandRaiseQueueSection(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
Column(modifier = modifier.fillMaxWidth().padding(top = 12.dp)) {
|
Column(modifier = modifier.fillMaxWidth().padding(top = 12.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.audio_room_hand_raise_queue_title),
|
text = stringRes(R.string.nest_hand_raise_queue_title),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@@ -112,7 +112,7 @@ internal fun HandRaiseQueueSection(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(stringRes(R.string.audio_room_hand_raise_approve))
|
Text(stringRes(R.string.nest_hand_raise_approve))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+19
-19
@@ -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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PictureInPictureParams
|
import android.app.PictureInPictureParams
|
||||||
@@ -43,7 +43,7 @@ import kotlinx.coroutines.flow.asSharedFlow
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Standalone activity that owns the lifetime of an audio-room session. The
|
* Standalone activity that owns the lifetime of an audio-room session. The
|
||||||
* lobby ([AudioRoomJoinCard]) launches this activity when the user taps
|
* lobby ([NestJoinCard]) launches this activity when the user taps
|
||||||
* "Join audio room"; finishing it tears down the MoQ session, the
|
* "Join audio room"; finishing it tears down the MoQ session, the
|
||||||
* broadcaster (if any), and the foreground notification.
|
* broadcaster (if any), and the foreground notification.
|
||||||
*
|
*
|
||||||
@@ -52,11 +52,11 @@ import kotlinx.coroutines.flow.asSharedFlow
|
|||||||
* keep the speakers visible while doing other things. Mute / leave are
|
* keep the speakers visible while doing other things. Mute / leave are
|
||||||
* exposed via the system PIP overlay as [RemoteAction]s.
|
* exposed via the system PIP overlay as [RemoteAction]s.
|
||||||
*
|
*
|
||||||
* AccountViewModel arrives via [AudioRoomBridge] — same pattern as
|
* AccountViewModel arrives via [NestBridge] — same pattern as
|
||||||
* [com.vitorpamplona.amethyst.ui.call.CallActivity] uses for
|
* [com.vitorpamplona.amethyst.ui.call.CallActivity] uses for
|
||||||
* `CallSessionBridge`.
|
* `CallSessionBridge`.
|
||||||
*/
|
*/
|
||||||
class AudioRoomActivity : AppCompatActivity() {
|
class NestActivity : AppCompatActivity() {
|
||||||
private val isInPipMode = mutableStateOf(false)
|
private val isInPipMode = mutableStateOf(false)
|
||||||
private val isMuted = mutableStateOf(false)
|
private val isMuted = mutableStateOf(false)
|
||||||
private val isConnected = mutableStateOf(false)
|
private val isConnected = mutableStateOf(false)
|
||||||
@@ -96,7 +96,7 @@ class AudioRoomActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val accountViewModel = AudioRoomBridge.accountViewModel
|
val accountViewModel = NestBridge.accountViewModel
|
||||||
val addressValue = intent.getStringExtra(EXTRA_ADDRESS)
|
val addressValue = intent.getStringExtra(EXTRA_ADDRESS)
|
||||||
val authBaseUrl = intent.getStringExtra(EXTRA_AUTH_BASE_URL)
|
val authBaseUrl = intent.getStringExtra(EXTRA_AUTH_BASE_URL)
|
||||||
val endpoint = intent.getStringExtra(EXTRA_ENDPOINT)
|
val endpoint = intent.getStringExtra(EXTRA_ENDPOINT)
|
||||||
@@ -145,7 +145,7 @@ class AudioRoomActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
AmethystTheme {
|
AmethystTheme {
|
||||||
AudioRoomActivityContent(
|
NestActivityContent(
|
||||||
addressValue = addressValue,
|
addressValue = addressValue,
|
||||||
room =
|
room =
|
||||||
com.vitorpamplona.nestsclient.NestsRoomConfig(
|
com.vitorpamplona.nestsclient.NestsRoomConfig(
|
||||||
@@ -211,7 +211,7 @@ class AudioRoomActivity : AppCompatActivity() {
|
|||||||
PictureInPictureParams
|
PictureInPictureParams
|
||||||
.Builder()
|
.Builder()
|
||||||
// Landscape ratio — the PIP layout is a horizontal row of
|
// Landscape ratio — the PIP layout is a horizontal row of
|
||||||
// avatars under a title (see AudioRoomPipScreen), which 9:16
|
// avatars under a title (see NestPipScreen), which 9:16
|
||||||
// squeezed into a sliver. 16:9 fits the row and the room name.
|
// squeezed into a sliver. 16:9 fits the row and the room name.
|
||||||
.setAspectRatio(Rational(16, 9))
|
.setAspectRatio(Rational(16, 9))
|
||||||
.setActions(buildPipActions())
|
.setActions(buildPipActions())
|
||||||
@@ -233,13 +233,13 @@ class AudioRoomActivity : AppCompatActivity() {
|
|||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
)
|
)
|
||||||
val muteIconRes = if (isMuted.value) R.drawable.ic_mic_off else R.drawable.ic_mic_on
|
val muteIconRes = if (isMuted.value) R.drawable.ic_mic_off else R.drawable.ic_mic_on
|
||||||
val muteLabel = getString(if (isMuted.value) R.string.audio_room_unmute else R.string.audio_room_mute)
|
val muteLabel = getString(if (isMuted.value) R.string.nest_unmute else R.string.nest_mute)
|
||||||
return listOf(
|
return listOf(
|
||||||
RemoteAction(Icon.createWithResource(this, muteIconRes), muteLabel, muteLabel, muteIntent),
|
RemoteAction(Icon.createWithResource(this, muteIconRes), muteLabel, muteLabel, muteIntent),
|
||||||
RemoteAction(
|
RemoteAction(
|
||||||
Icon.createWithResource(this, R.drawable.ic_call_end),
|
Icon.createWithResource(this, R.drawable.ic_call_end),
|
||||||
getString(R.string.audio_room_leave),
|
getString(R.string.nest_leave),
|
||||||
getString(R.string.audio_room_leave),
|
getString(R.string.nest_leave),
|
||||||
leaveIntent,
|
leaveIntent,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -251,14 +251,14 @@ class AudioRoomActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_ADDRESS = "com.vitorpamplona.amethyst.AUDIO_ROOM_ADDRESS"
|
const val EXTRA_ADDRESS = "com.vitorpamplona.amethyst.NEST_ADDRESS"
|
||||||
const val EXTRA_AUTH_BASE_URL = "com.vitorpamplona.amethyst.AUDIO_ROOM_AUTH_BASE_URL"
|
const val EXTRA_AUTH_BASE_URL = "com.vitorpamplona.amethyst.NEST_AUTH_BASE_URL"
|
||||||
const val EXTRA_ENDPOINT = "com.vitorpamplona.amethyst.AUDIO_ROOM_ENDPOINT"
|
const val EXTRA_ENDPOINT = "com.vitorpamplona.amethyst.NEST_ENDPOINT"
|
||||||
const val EXTRA_HOST_PUBKEY = "com.vitorpamplona.amethyst.AUDIO_ROOM_HOST_PUBKEY"
|
const val EXTRA_HOST_PUBKEY = "com.vitorpamplona.amethyst.NEST_HOST_PUBKEY"
|
||||||
const val EXTRA_ROOM_ID = "com.vitorpamplona.amethyst.AUDIO_ROOM_ROOM_ID"
|
const val EXTRA_ROOM_ID = "com.vitorpamplona.amethyst.NEST_ROOM_ID"
|
||||||
const val EXTRA_KIND = "com.vitorpamplona.amethyst.AUDIO_ROOM_KIND"
|
const val EXTRA_KIND = "com.vitorpamplona.amethyst.NEST_KIND"
|
||||||
private const val ACTION_PIP_TOGGLE_MUTE = "com.vitorpamplona.amethyst.AUDIO_ROOM_PIP_MUTE"
|
private const val ACTION_PIP_TOGGLE_MUTE = "com.vitorpamplona.amethyst.NEST_PIP_MUTE"
|
||||||
private const val ACTION_PIP_LEAVE = "com.vitorpamplona.amethyst.AUDIO_ROOM_PIP_LEAVE"
|
private const val ACTION_PIP_LEAVE = "com.vitorpamplona.amethyst.NEST_PIP_LEAVE"
|
||||||
private const val PIP_MUTE_REQ = 0x6A001
|
private const val PIP_MUTE_REQ = 0x6A001
|
||||||
private const val PIP_LEAVE_REQ = 0x6A002
|
private const val PIP_LEAVE_REQ = 0x6A002
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ class AudioRoomActivity : AppCompatActivity() {
|
|||||||
kind: Int,
|
kind: Int,
|
||||||
) {
|
) {
|
||||||
context.startActivity(
|
context.startActivity(
|
||||||
Intent(context, AudioRoomActivity::class.java).apply {
|
Intent(context, NestActivity::class.java).apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
putExtra(EXTRA_ADDRESS, addressValue)
|
putExtra(EXTRA_ADDRESS, addressValue)
|
||||||
putExtra(EXTRA_AUTH_BASE_URL, authBaseUrl)
|
putExtra(EXTRA_AUTH_BASE_URL, authBaseUrl)
|
||||||
+21
-21
@@ -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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -32,18 +32,18 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomViewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.BroadcastUiState
|
import com.vitorpamplona.amethyst.commons.viewmodels.BroadcastUiState
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.ConnectionUiState
|
import com.vitorpamplona.amethyst.commons.viewmodels.ConnectionUiState
|
||||||
|
import com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.audiorooms.AudioRoomForegroundService
|
import com.vitorpamplona.amethyst.service.nests.NestForegroundService
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.RoomAdminCommandsFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomAdminCommandsFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.RoomChatFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomChatFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.RoomPresenceFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomPresenceFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.datasource.RoomReactionsFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.RoomReactionsFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.quartz.experimental.audiorooms.admin.AdminCommandEvent
|
import com.vitorpamplona.quartz.experimental.nests.admin.AdminCommandEvent
|
||||||
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.nip25Reactions.ReactionEvent
|
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||||
@@ -58,16 +58,16 @@ import kotlinx.coroutines.isActive
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level composable for [AudioRoomActivity]. Observes the room event +
|
* Top-level composable for [NestActivity]. Observes the room event +
|
||||||
* VM, auto-connects on entry, runs the kind-10312 presence loop, drives
|
* VM, auto-connects on entry, runs the kind-10312 presence loop, drives
|
||||||
* the foreground service, and flips between full-screen and PIP layouts.
|
* the foreground service, and flips between full-screen and PIP layouts.
|
||||||
*
|
*
|
||||||
* The [AudioRoomActivity] supplies [isInPipMode] (a Compose state observed
|
* The [NestActivity] supplies [isInPipMode] (a Compose state observed
|
||||||
* via the activity's `mutableStateOf`) and [onLeave] which finishes the
|
* via the activity's `mutableStateOf`) and [onLeave] which finishes the
|
||||||
* activity. Everything else is local to this composable's scope.
|
* activity. Everything else is local to this composable's scope.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AudioRoomActivityContent(
|
internal fun NestActivityContent(
|
||||||
addressValue: String,
|
addressValue: String,
|
||||||
room: com.vitorpamplona.nestsclient.NestsRoomConfig,
|
room: com.vitorpamplona.nestsclient.NestsRoomConfig,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@@ -88,7 +88,7 @@ internal fun AudioRoomActivityContent(
|
|||||||
LoadAddressableNote(parsedAddress, accountViewModel) { addressableNote ->
|
LoadAddressableNote(parsedAddress, accountViewModel) { addressableNote ->
|
||||||
addressableNote ?: return@LoadAddressableNote
|
addressableNote ?: return@LoadAddressableNote
|
||||||
val event = addressableNote.event as? MeetingSpaceEvent ?: return@LoadAddressableNote
|
val event = addressableNote.event as? MeetingSpaceEvent ?: return@LoadAddressableNote
|
||||||
AudioRoomActivityBody(
|
NestActivityBody(
|
||||||
event = event,
|
event = event,
|
||||||
roomNote = addressableNote,
|
roomNote = addressableNote,
|
||||||
room = room,
|
room = room,
|
||||||
@@ -103,7 +103,7 @@ internal fun AudioRoomActivityContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AudioRoomActivityBody(
|
private fun NestActivityBody(
|
||||||
event: MeetingSpaceEvent,
|
event: MeetingSpaceEvent,
|
||||||
roomNote: com.vitorpamplona.amethyst.model.AddressableNote,
|
roomNote: com.vitorpamplona.amethyst.model.AddressableNote,
|
||||||
room: com.vitorpamplona.nestsclient.NestsRoomConfig,
|
room: com.vitorpamplona.nestsclient.NestsRoomConfig,
|
||||||
@@ -127,12 +127,12 @@ private fun AudioRoomActivityBody(
|
|||||||
val account = accountViewModel.account
|
val account = accountViewModel.account
|
||||||
val signer = account.signer
|
val signer = account.signer
|
||||||
|
|
||||||
val viewModel: AudioRoomViewModel =
|
val viewModel: NestViewModel =
|
||||||
viewModel(
|
viewModel(
|
||||||
key = "${room.authBaseUrl}|${room.roomId}",
|
key = "${room.authBaseUrl}|${room.roomId}",
|
||||||
factory =
|
factory =
|
||||||
remember(room, signer) {
|
remember(room, signer) {
|
||||||
AudioRoomViewModelFactory(
|
NestViewModelFactory(
|
||||||
signer = signer,
|
signer = signer,
|
||||||
room = room,
|
room = room,
|
||||||
)
|
)
|
||||||
@@ -343,22 +343,22 @@ private fun AudioRoomActivityBody(
|
|||||||
val isBroadcasting = ui.broadcast is BroadcastUiState.Broadcasting
|
val isBroadcasting = ui.broadcast is BroadcastUiState.Broadcasting
|
||||||
LaunchedEffect(isLive, isBroadcasting) {
|
LaunchedEffect(isLive, isBroadcasting) {
|
||||||
when {
|
when {
|
||||||
isLive && isBroadcasting -> AudioRoomForegroundService.promoteToMicrophone(context)
|
isLive && isBroadcasting -> NestForegroundService.promoteToMicrophone(context)
|
||||||
isLive -> AudioRoomForegroundService.startListening(context)
|
isLive -> NestForegroundService.startListening(context)
|
||||||
else -> AudioRoomForegroundService.stop(context)
|
else -> NestForegroundService.stop(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DisposableEffect(Unit) { onDispose { AudioRoomForegroundService.stop(context) } }
|
DisposableEffect(Unit) { onDispose { NestForegroundService.stop(context) } }
|
||||||
|
|
||||||
if (isInPipMode) {
|
if (isInPipMode) {
|
||||||
AudioRoomPipScreen(
|
NestPipScreen(
|
||||||
title = event.room(),
|
title = event.room(),
|
||||||
onStage = onStage,
|
onStage = onStage,
|
||||||
ui = ui,
|
ui = ui,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
AudioRoomFullScreen(
|
NestFullScreen(
|
||||||
event = event,
|
event = event,
|
||||||
roomNote = roomNote,
|
roomNote = roomNote,
|
||||||
onStage = onStage,
|
onStage = onStage,
|
||||||
+4
-4
@@ -18,21 +18,21 @@
|
|||||||
* 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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process-level singleton that hands the active [AccountViewModel] from the
|
* Process-level singleton that hands the active [AccountViewModel] from the
|
||||||
* main activity to [AudioRoomActivity]. Mirrors the
|
* main activity to [NestActivity]. Mirrors the
|
||||||
* [com.vitorpamplona.amethyst.service.call.CallSessionBridge] pattern so a
|
* [com.vitorpamplona.amethyst.service.call.CallSessionBridge] pattern so a
|
||||||
* separately-tasked Activity in the same process can reach the logged-in
|
* separately-tasked Activity in the same process can reach the logged-in
|
||||||
* account without serialising a `NostrSigner` through an Intent extra.
|
* account without serialising a `NostrSigner` through an Intent extra.
|
||||||
*
|
*
|
||||||
* Set immediately before `startActivity(Intent(..., AudioRoomActivity::class))`
|
* Set immediately before `startActivity(Intent(..., NestActivity::class))`
|
||||||
* is called and cleared on logout / account switch.
|
* is called and cleared on logout / account switch.
|
||||||
*/
|
*/
|
||||||
object AudioRoomBridge {
|
object NestBridge {
|
||||||
@Volatile
|
@Volatile
|
||||||
var accountViewModel: AccountViewModel? = null
|
var accountViewModel: AccountViewModel? = null
|
||||||
private set
|
private set
|
||||||
+10
-10
@@ -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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -49,7 +49,7 @@ 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 com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -63,15 +63,15 @@ import kotlinx.coroutines.launch
|
|||||||
/**
|
/**
|
||||||
* Live-chat panel (T1 #2) — renders the kind-1311 transcript for
|
* Live-chat panel (T1 #2) — renders the kind-1311 transcript for
|
||||||
* the room and provides a send field. Messages flow from
|
* the room and provides a send field. Messages flow from
|
||||||
* [AudioRoomViewModel.chat] (populated by the LocalCache observer
|
* [NestViewModel.chat] (populated by the LocalCache observer
|
||||||
* in [AudioRoomActivityContent]); send routes through
|
* in [NestActivityContent]); send routes through
|
||||||
* `account.signAndComputeBroadcast` directly so the VM stays free
|
* `account.signAndComputeBroadcast` directly so the VM stays free
|
||||||
* of platform / signing dependencies.
|
* of platform / signing dependencies.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AudioRoomChatPanel(
|
internal fun NestChatPanel(
|
||||||
roomATag: ATag,
|
roomATag: ATag,
|
||||||
viewModel: AudioRoomViewModel,
|
viewModel: NestViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@@ -100,7 +100,7 @@ internal fun AudioRoomChatPanel(
|
|||||||
Column(modifier = modifier.fillMaxWidth()) {
|
Column(modifier = modifier.fillMaxWidth()) {
|
||||||
if (messages.isEmpty()) {
|
if (messages.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.audio_room_chat_empty),
|
text = stringRes(R.string.nest_chat_empty),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(vertical = 12.dp),
|
modifier = Modifier.padding(vertical = 12.dp),
|
||||||
@@ -178,7 +178,7 @@ private fun ChatComposer(
|
|||||||
value = draft,
|
value = draft,
|
||||||
onValueChange = { draft = it },
|
onValueChange = { draft = it },
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
placeholder = { Text(stringRes(R.string.audio_room_chat_placeholder)) },
|
placeholder = { Text(stringRes(R.string.nest_chat_placeholder)) },
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
maxLines = 4,
|
maxLines = 4,
|
||||||
enabled = !isSending,
|
enabled = !isSending,
|
||||||
@@ -212,7 +212,7 @@ private fun ChatComposer(
|
|||||||
?: result.exceptionOrNull()?.let { it::class.simpleName }
|
?: result.exceptionOrNull()?.let { it::class.simpleName }
|
||||||
?: "unknown error"
|
?: "unknown error"
|
||||||
accountViewModel.toastManager.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.audio_room_chat_send_failed_title,
|
R.string.nest_chat_send_failed_title,
|
||||||
why,
|
why,
|
||||||
user = null,
|
user = null,
|
||||||
)
|
)
|
||||||
@@ -220,7 +220,7 @@ private fun ChatComposer(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(stringRes(R.string.audio_room_chat_send))
|
Text(stringRes(R.string.nest_chat_send))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -99,7 +99,7 @@ internal fun StagePeopleRow(
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun connectingLabel(connection: ConnectionUiState.Connecting): String =
|
internal fun connectingLabel(connection: ConnectionUiState.Connecting): String =
|
||||||
when (connection.step) {
|
when (connection.step) {
|
||||||
ConnectionUiState.Step.ResolvingRoom -> stringRes(R.string.audio_room_connecting_resolving)
|
ConnectionUiState.Step.ResolvingRoom -> stringRes(R.string.nest_connecting_resolving)
|
||||||
ConnectionUiState.Step.OpeningTransport -> stringRes(R.string.audio_room_connecting_transport)
|
ConnectionUiState.Step.OpeningTransport -> stringRes(R.string.nest_connecting_transport)
|
||||||
ConnectionUiState.Step.MoqHandshake -> stringRes(R.string.audio_room_connecting_handshake)
|
ConnectionUiState.Step.MoqHandshake -> stringRes(R.string.nest_connecting_handshake)
|
||||||
}
|
}
|
||||||
+49
-49
@@ -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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@@ -58,10 +58,10 @@ import androidx.core.content.ContextCompat
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
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.viewmodels.AudioRoomUiState
|
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomViewModel
|
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.BroadcastUiState
|
import com.vitorpamplona.amethyst.commons.viewmodels.BroadcastUiState
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.ConnectionUiState
|
import com.vitorpamplona.amethyst.commons.viewmodels.ConnectionUiState
|
||||||
|
import com.vitorpamplona.amethyst.commons.viewmodels.NestUiState
|
||||||
|
import com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.RoomTheme
|
import com.vitorpamplona.amethyst.commons.viewmodels.RoomTheme
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
@@ -73,28 +73,28 @@ import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTa
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Full-screen layout for [AudioRoomActivity]. Renders title + summary,
|
* Full-screen layout for [NestActivity]. Renders title + summary,
|
||||||
* host/speaker/audience rows (with active-speaker rings), connection chip
|
* host/speaker/audience rows (with active-speaker rings), connection chip
|
||||||
* + mute, talk row (when allowed), hand-raise, and a Leave button that
|
* + mute, talk row (when allowed), hand-raise, and a Leave button that
|
||||||
* finishes the activity.
|
* finishes the activity.
|
||||||
*
|
*
|
||||||
* The PIP variant lives in [AudioRoomPipScreen]; the activity flips
|
* The PIP variant lives in [NestPipScreen]; the activity flips
|
||||||
* between them based on `isInPipMode`.
|
* between them based on `isInPipMode`.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AudioRoomFullScreen(
|
internal fun NestFullScreen(
|
||||||
event: MeetingSpaceEvent,
|
event: MeetingSpaceEvent,
|
||||||
roomNote: com.vitorpamplona.amethyst.model.AddressableNote,
|
roomNote: com.vitorpamplona.amethyst.model.AddressableNote,
|
||||||
onStage: List<ParticipantTag>,
|
onStage: List<ParticipantTag>,
|
||||||
viewModel: AudioRoomViewModel,
|
viewModel: NestViewModel,
|
||||||
ui: AudioRoomUiState,
|
ui: NestUiState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
handRaised: Boolean,
|
handRaised: Boolean,
|
||||||
onHandRaisedChange: (Boolean) -> Unit,
|
onHandRaisedChange: (Boolean) -> Unit,
|
||||||
onLeave: () -> Unit,
|
onLeave: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val roomTheme = androidx.compose.runtime.remember(event) { RoomTheme.from(event) }
|
val roomTheme = androidx.compose.runtime.remember(event) { RoomTheme.from(event) }
|
||||||
AudioRoomThemedScope(theme = roomTheme, accountViewModel = accountViewModel) {
|
NestThemedScope(theme = roomTheme, accountViewModel = accountViewModel) {
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
@@ -126,7 +126,7 @@ internal fun AudioRoomFullScreen(
|
|||||||
androidx.compose.material3.IconButton(onClick = { showHostMenu = true }) {
|
androidx.compose.material3.IconButton(onClick = { showHostMenu = true }) {
|
||||||
Icon(
|
Icon(
|
||||||
symbol = MaterialSymbols.MoreVert,
|
symbol = MaterialSymbols.MoreVert,
|
||||||
contentDescription = stringRes(R.string.audio_room_overflow_menu),
|
contentDescription = stringRes(R.string.nest_overflow_menu),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val context = androidx.compose.ui.platform.LocalContext.current
|
val context = androidx.compose.ui.platform.LocalContext.current
|
||||||
@@ -135,7 +135,7 @@ internal fun AudioRoomFullScreen(
|
|||||||
onDismissRequest = { showHostMenu = false },
|
onDismissRequest = { showHostMenu = false },
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.DropdownMenuItem(
|
androidx.compose.material3.DropdownMenuItem(
|
||||||
text = { Text(stringRes(R.string.audio_room_share_action)) },
|
text = { Text(stringRes(R.string.nest_share_action)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
showHostMenu = false
|
showHostMenu = false
|
||||||
shareRoomNaddr(context, event)
|
shareRoomNaddr(context, event)
|
||||||
@@ -143,7 +143,7 @@ internal fun AudioRoomFullScreen(
|
|||||||
)
|
)
|
||||||
if (isHost) {
|
if (isHost) {
|
||||||
androidx.compose.material3.DropdownMenuItem(
|
androidx.compose.material3.DropdownMenuItem(
|
||||||
text = { Text(stringRes(R.string.audio_room_edit_title)) },
|
text = { Text(stringRes(R.string.nest_edit_title)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
showHostMenu = false
|
showHostMenu = false
|
||||||
showEditSheet = true
|
showEditSheet = true
|
||||||
@@ -154,7 +154,7 @@ internal fun AudioRoomFullScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showEditSheet) {
|
if (showEditSheet) {
|
||||||
EditAudioRoomSheet(
|
EditNestSheet(
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
event = event,
|
event = event,
|
||||||
onDismiss = { showEditSheet = false },
|
onDismiss = { showEditSheet = false },
|
||||||
@@ -178,7 +178,7 @@ internal fun AudioRoomFullScreen(
|
|||||||
Text(
|
Text(
|
||||||
text =
|
text =
|
||||||
androidx.compose.ui.res.pluralStringResource(
|
androidx.compose.ui.res.pluralStringResource(
|
||||||
R.plurals.audio_room_listener_count,
|
R.plurals.nest_listener_count,
|
||||||
listenerCount,
|
listenerCount,
|
||||||
listenerCount,
|
listenerCount,
|
||||||
),
|
),
|
||||||
@@ -211,8 +211,8 @@ internal fun AudioRoomFullScreen(
|
|||||||
grid = participantGrid,
|
grid = participantGrid,
|
||||||
speakingNow = ui.speakingNow,
|
speakingNow = ui.speakingNow,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
onStageLabel = stringRes(R.string.audio_room_stage),
|
onStageLabel = stringRes(R.string.nest_stage),
|
||||||
audienceLabel = stringRes(R.string.audio_room_audience),
|
audienceLabel = stringRes(R.string.nest_audience),
|
||||||
reactionsByPubkey = reactionsByPubkey,
|
reactionsByPubkey = reactionsByPubkey,
|
||||||
connectingSpeakers = ui.connectingSpeakers,
|
connectingSpeakers = ui.connectingSpeakers,
|
||||||
onLongPressParticipant = onLongPressParticipant,
|
onLongPressParticipant = onLongPressParticipant,
|
||||||
@@ -257,12 +257,12 @@ internal fun AudioRoomFullScreen(
|
|||||||
symbol = MaterialSymbols.PanTool,
|
symbol = MaterialSymbols.PanTool,
|
||||||
contentDescription =
|
contentDescription =
|
||||||
stringRes(
|
stringRes(
|
||||||
if (handRaised) R.string.audio_room_lower_hand else R.string.audio_room_raise_hand,
|
if (handRaised) R.string.nest_lower_hand else R.string.nest_raise_hand,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
OutlinedButton(onClick = { showReactionPicker = true }) {
|
OutlinedButton(onClick = { showReactionPicker = true }) {
|
||||||
Text(stringRes(R.string.audio_room_reactions_button))
|
Text(stringRes(R.string.nest_reactions_button))
|
||||||
}
|
}
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -273,7 +273,7 @@ internal fun AudioRoomFullScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(stringRes(R.string.audio_room_leave))
|
Text(stringRes(R.string.nest_leave))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showReactionPicker) {
|
if (showReactionPicker) {
|
||||||
@@ -288,8 +288,8 @@ internal fun AudioRoomFullScreen(
|
|||||||
if (showHostLeaveConfirm) {
|
if (showHostLeaveConfirm) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showHostLeaveConfirm = false },
|
onDismissRequest = { showHostLeaveConfirm = false },
|
||||||
title = { Text(stringRes(R.string.audio_room_leave_host_title)) },
|
title = { Text(stringRes(R.string.nest_leave_host_title)) },
|
||||||
text = { Text(stringRes(R.string.audio_room_leave_host_body)) },
|
text = { Text(stringRes(R.string.nest_leave_host_body)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
||||||
@@ -299,15 +299,15 @@ internal fun AudioRoomFullScreen(
|
|||||||
val ok = closeMeetingSpace(accountViewModel, event)
|
val ok = closeMeetingSpace(accountViewModel, event)
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
accountViewModel.toastManager.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.audio_rooms,
|
R.string.nests,
|
||||||
R.string.audio_room_leave_host_close_failed,
|
R.string.nest_leave_host_close_failed,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onLeave()
|
onLeave()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(stringRes(R.string.audio_room_leave_host_close))
|
Text(stringRes(R.string.nest_leave_host_close))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
@@ -317,13 +317,13 @@ internal fun AudioRoomFullScreen(
|
|||||||
onLeave()
|
onLeave()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(stringRes(R.string.audio_room_leave_host_just_leave))
|
Text(stringRes(R.string.nest_leave_host_just_leave))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioRoomChatPanel(
|
NestChatPanel(
|
||||||
roomATag =
|
roomATag =
|
||||||
ATag(
|
ATag(
|
||||||
kind = event.kind,
|
kind = event.kind,
|
||||||
@@ -341,8 +341,8 @@ internal fun AudioRoomFullScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ConnectionRow(
|
private fun ConnectionRow(
|
||||||
viewModel: AudioRoomViewModel,
|
viewModel: NestViewModel,
|
||||||
ui: AudioRoomUiState,
|
ui: NestUiState,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 12.dp),
|
modifier = Modifier.fillMaxWidth().padding(top = 12.dp),
|
||||||
@@ -352,7 +352,7 @@ private fun ConnectionRow(
|
|||||||
when (val connection = ui.connection) {
|
when (val connection = ui.connection) {
|
||||||
is ConnectionUiState.Idle, is ConnectionUiState.Closed -> {
|
is ConnectionUiState.Idle, is ConnectionUiState.Closed -> {
|
||||||
Button(onClick = { viewModel.connect() }) {
|
Button(onClick = { viewModel.connect() }) {
|
||||||
Text(stringRes(R.string.audio_room_connect))
|
Text(stringRes(R.string.nest_connect))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ private fun ConnectionRow(
|
|||||||
AssistChip(
|
AssistChip(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
enabled = false,
|
enabled = false,
|
||||||
label = { Text(stringRes(R.string.audio_room_reconnecting)) },
|
label = { Text(stringRes(R.string.nest_reconnecting)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,7 +381,7 @@ private fun ConnectionRow(
|
|||||||
AssistChip(
|
AssistChip(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
enabled = false,
|
enabled = false,
|
||||||
label = { Text(stringRes(R.string.audio_room_connected)) },
|
label = { Text(stringRes(R.string.nest_connected)) },
|
||||||
colors =
|
colors =
|
||||||
AssistChipDefaults.assistChipColors(
|
AssistChipDefaults.assistChipColors(
|
||||||
disabledLabelColor = MaterialTheme.colorScheme.primary,
|
disabledLabelColor = MaterialTheme.colorScheme.primary,
|
||||||
@@ -395,7 +395,7 @@ private fun ConnectionRow(
|
|||||||
symbol = if (ui.isMuted) MaterialSymbols.AutoMirrored.VolumeOff else MaterialSymbols.AutoMirrored.VolumeUp,
|
symbol = if (ui.isMuted) MaterialSymbols.AutoMirrored.VolumeOff else MaterialSymbols.AutoMirrored.VolumeUp,
|
||||||
contentDescription =
|
contentDescription =
|
||||||
stringRes(
|
stringRes(
|
||||||
if (ui.isMuted) R.string.audio_room_unmute else R.string.audio_room_mute,
|
if (ui.isMuted) R.string.nest_unmute else R.string.nest_mute,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -403,13 +403,13 @@ private fun ConnectionRow(
|
|||||||
|
|
||||||
is ConnectionUiState.Failed -> {
|
is ConnectionUiState.Failed -> {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.audio_room_audio_failed, connection.reason),
|
text = stringRes(R.string.nest_audio_failed, connection.reason),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
)
|
)
|
||||||
Button(onClick = { viewModel.connect() }) {
|
Button(onClick = { viewModel.connect() }) {
|
||||||
Text(stringRes(R.string.audio_room_connect))
|
Text(stringRes(R.string.nest_connect))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,8 +418,8 @@ private fun ConnectionRow(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TalkRow(
|
private fun TalkRow(
|
||||||
viewModel: AudioRoomViewModel,
|
viewModel: NestViewModel,
|
||||||
ui: AudioRoomUiState,
|
ui: NestUiState,
|
||||||
speakerPubkeyHex: String,
|
speakerPubkeyHex: String,
|
||||||
) {
|
) {
|
||||||
// Render whenever the user can act on broadcast state. The
|
// Render whenever the user can act on broadcast state. The
|
||||||
@@ -472,11 +472,11 @@ private fun TalkRow(
|
|||||||
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(stringRes(R.string.audio_room_talk))
|
Text(stringRes(R.string.nest_talk))
|
||||||
}
|
}
|
||||||
if (showDenialWarning) {
|
if (showDenialWarning) {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.audio_room_record_permission_required),
|
text = stringRes(R.string.nest_record_permission_required),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
)
|
)
|
||||||
@@ -497,7 +497,7 @@ private fun TalkRow(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(stringRes(R.string.audio_room_open_settings))
|
Text(stringRes(R.string.nest_open_settings))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,7 +506,7 @@ private fun TalkRow(
|
|||||||
AssistChip(
|
AssistChip(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
enabled = false,
|
enabled = false,
|
||||||
label = { Text(stringRes(R.string.audio_room_broadcast_connecting)) },
|
label = { Text(stringRes(R.string.nest_broadcast_connecting)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +514,7 @@ private fun TalkRow(
|
|||||||
AssistChip(
|
AssistChip(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
enabled = false,
|
enabled = false,
|
||||||
label = { Text(stringRes(R.string.audio_room_broadcasting)) },
|
label = { Text(stringRes(R.string.nest_broadcasting)) },
|
||||||
colors =
|
colors =
|
||||||
AssistChipDefaults.assistChipColors(
|
AssistChipDefaults.assistChipColors(
|
||||||
disabledLabelColor = MaterialTheme.colorScheme.error,
|
disabledLabelColor = MaterialTheme.colorScheme.error,
|
||||||
@@ -528,24 +528,24 @@ private fun TalkRow(
|
|||||||
symbol = if (broadcast.isMuted) MaterialSymbols.AutoMirrored.VolumeOff else MaterialSymbols.AutoMirrored.VolumeUp,
|
symbol = if (broadcast.isMuted) MaterialSymbols.AutoMirrored.VolumeOff else MaterialSymbols.AutoMirrored.VolumeUp,
|
||||||
contentDescription =
|
contentDescription =
|
||||||
stringRes(
|
stringRes(
|
||||||
if (broadcast.isMuted) R.string.audio_room_mic_unmute else R.string.audio_room_mic_mute,
|
if (broadcast.isMuted) R.string.nest_mic_unmute else R.string.nest_mic_mute,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
OutlinedButton(onClick = { viewModel.stopBroadcast() }) {
|
OutlinedButton(onClick = { viewModel.stopBroadcast() }) {
|
||||||
Text(stringRes(R.string.audio_room_stop_talking))
|
Text(stringRes(R.string.nest_stop_talking))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is BroadcastUiState.Failed -> {
|
is BroadcastUiState.Failed -> {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.audio_room_broadcast_failed, broadcast.reason),
|
text = stringRes(R.string.nest_broadcast_failed, broadcast.reason),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
)
|
)
|
||||||
Button(onClick = { viewModel.startBroadcast(speakerPubkeyHex) }) {
|
Button(onClick = { viewModel.startBroadcast(speakerPubkeyHex) }) {
|
||||||
Text(stringRes(R.string.audio_room_talk))
|
Text(stringRes(R.string.nest_talk))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,9 +557,9 @@ private fun TalkRow(
|
|||||||
* other field verbatim (room name, summary, image, service, endpoint,
|
* other field verbatim (room name, summary, image, service, endpoint,
|
||||||
* participants). Returns true on success.
|
* participants). Returns true on success.
|
||||||
*
|
*
|
||||||
* Reuses [EditAudioRoomViewModel.buildEditTemplate] so the close-on-leave
|
* Reuses [EditNestViewModel.buildEditTemplate] so the close-on-leave
|
||||||
* path emits the same shape as the explicit Edit → Close room action.
|
* path emits the same shape as the explicit Edit → Close room action.
|
||||||
* Decoupled from [EditAudioRoomViewModel] state so the host's unsaved
|
* Decoupled from [EditNestViewModel] state so the host's unsaved
|
||||||
* edits in the Edit sheet (if any) cannot accidentally leak into the
|
* edits in the Edit sheet (if any) cannot accidentally leak into the
|
||||||
* close payload.
|
* close payload.
|
||||||
*/
|
*/
|
||||||
@@ -568,7 +568,7 @@ private suspend fun closeMeetingSpace(
|
|||||||
event: MeetingSpaceEvent,
|
event: MeetingSpaceEvent,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val verbatim =
|
val verbatim =
|
||||||
EditAudioRoomViewModel.FormState(
|
EditNestViewModel.FormState(
|
||||||
dTag = event.dTag(),
|
dTag = event.dTag(),
|
||||||
roomName = event.room().orEmpty(),
|
roomName = event.room().orEmpty(),
|
||||||
summary = event.summary().orEmpty(),
|
summary = event.summary().orEmpty(),
|
||||||
@@ -580,7 +580,7 @@ private suspend fun closeMeetingSpace(
|
|||||||
)
|
)
|
||||||
return try {
|
return try {
|
||||||
val template =
|
val template =
|
||||||
EditAudioRoomViewModel.buildEditTemplate(event, verbatim, StatusTag.STATUS.CLOSED)
|
EditNestViewModel.buildEditTemplate(event, verbatim, StatusTag.STATUS.CLOSED)
|
||||||
accountViewModel.account.signAndComputeBroadcast(template)
|
accountViewModel.account.signAndComputeBroadcast(template)
|
||||||
true
|
true
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
+10
-10
@@ -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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -47,7 +47,7 @@ import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEv
|
|||||||
/**
|
/**
|
||||||
* Lobby card rendered in [com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ChannelView]
|
* Lobby card rendered in [com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ChannelView]
|
||||||
* for NIP-53 kind 30312 meeting-space events. Shows the room title +
|
* for NIP-53 kind 30312 meeting-space events. Shows the room title +
|
||||||
* summary + a "Join audio room" button that launches [AudioRoomActivity].
|
* summary + a "Join audio room" button that launches [NestActivity].
|
||||||
*
|
*
|
||||||
* The lobby is intentionally thin: no audio session, no presence event, no
|
* The lobby is intentionally thin: no audio session, no presence event, no
|
||||||
* hand-raise. All of that lives behind the Join button so the on-the-wire
|
* hand-raise. All of that lives behind the Join button so the on-the-wire
|
||||||
@@ -57,19 +57,19 @@ import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEv
|
|||||||
* non-nests servers we can't connect to.
|
* non-nests servers we can't connect to.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioRoomJoinCard(
|
fun NestJoinCard(
|
||||||
baseChannel: LiveActivitiesChannel,
|
baseChannel: LiveActivitiesChannel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
LoadAddressableNote(baseChannel.address, accountViewModel) { addressableNote ->
|
LoadAddressableNote(baseChannel.address, accountViewModel) { addressableNote ->
|
||||||
addressableNote ?: return@LoadAddressableNote
|
addressableNote ?: return@LoadAddressableNote
|
||||||
val event = addressableNote.event as? MeetingSpaceEvent ?: return@LoadAddressableNote
|
val event = addressableNote.event as? MeetingSpaceEvent ?: return@LoadAddressableNote
|
||||||
AudioRoomJoinCardContent(event, accountViewModel)
|
NestJoinCardContent(event, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AudioRoomJoinCardContent(
|
private fun NestJoinCardContent(
|
||||||
event: MeetingSpaceEvent,
|
event: MeetingSpaceEvent,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
@@ -107,7 +107,7 @@ private fun AudioRoomJoinCardContent(
|
|||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
JoinAudioRoomButton(event = event, accountViewModel = accountViewModel)
|
JoinNestButton(event = event, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ private fun AudioRoomJoinCardContent(
|
|||||||
* the audio plane.
|
* the audio plane.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun JoinAudioRoomButton(
|
fun JoinNestButton(
|
||||||
event: MeetingSpaceEvent,
|
event: MeetingSpaceEvent,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
@@ -138,8 +138,8 @@ fun JoinAudioRoomButton(
|
|||||||
val kind = event.kind
|
val kind = event.kind
|
||||||
|
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
AudioRoomBridge.set(accountViewModel)
|
NestBridge.set(accountViewModel)
|
||||||
AudioRoomActivity.launch(
|
NestActivity.launch(
|
||||||
context = context,
|
context = context,
|
||||||
addressValue = addressValue,
|
addressValue = addressValue,
|
||||||
authBaseUrl = serviceBase,
|
authBaseUrl = serviceBase,
|
||||||
@@ -149,6 +149,6 @@ fun JoinAudioRoomButton(
|
|||||||
kind = kind,
|
kind = kind,
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
Text(stringRes(R.string.audio_room_join))
|
Text(stringRes(R.string.nest_join))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
-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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
@@ -36,7 +36,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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 com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomUiState
|
import com.vitorpamplona.amethyst.commons.viewmodels.NestUiState
|
||||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||||
@@ -46,13 +46,13 @@ import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTa
|
|||||||
* Compact PIP layout. Shows up to four "on stage" speakers with a ring on
|
* Compact PIP layout. Shows up to four "on stage" speakers with a ring on
|
||||||
* whoever is actively delivering audio, plus the room title. The mute /
|
* whoever is actively delivering audio, plus the room title. The mute /
|
||||||
* leave actions live in the system PIP overlay as RemoteActions — see
|
* leave actions live in the system PIP overlay as RemoteActions — see
|
||||||
* [AudioRoomActivity.buildPipActions].
|
* [NestActivity.buildPipActions].
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AudioRoomPipScreen(
|
internal fun NestPipScreen(
|
||||||
title: String?,
|
title: String?,
|
||||||
onStage: List<ParticipantTag>,
|
onStage: List<ParticipantTag>,
|
||||||
ui: AudioRoomUiState,
|
ui: NestUiState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val ringColor = MaterialTheme.colorScheme.primary
|
val ringColor = MaterialTheme.colorScheme.primary
|
||||||
+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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -55,7 +55,7 @@ import kotlinx.coroutines.withContext
|
|||||||
* passthrough Box.
|
* passthrough Box.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AudioRoomThemedScope(
|
internal fun NestThemedScope(
|
||||||
theme: RoomTheme,
|
theme: RoomTheme,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
+5
-5
@@ -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.ui.screen.loggedIn.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomViewModel
|
import com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel
|
||||||
import com.vitorpamplona.nestsclient.NestsRoomConfig
|
import com.vitorpamplona.nestsclient.NestsRoomConfig
|
||||||
import com.vitorpamplona.nestsclient.OkHttpNestsClient
|
import com.vitorpamplona.nestsclient.OkHttpNestsClient
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioRecordCapture
|
import com.vitorpamplona.nestsclient.audio.AudioRecordCapture
|
||||||
@@ -33,19 +33,19 @@ import com.vitorpamplona.nestsclient.transport.QuicWebTransportFactory
|
|||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Android-side Factory for [AudioRoomViewModel]. The ViewModel itself lives
|
* Android-side Factory for [NestViewModel]. The ViewModel itself lives
|
||||||
* in `commons/` so a future desktop port can reuse the orchestration once
|
* in `commons/` so a future desktop port can reuse the orchestration once
|
||||||
* Compose Desktop has WebTransport; this factory binds it to the Android
|
* Compose Desktop has WebTransport; this factory binds it to the Android
|
||||||
* actuals (OkHttp HTTP, pure-Kotlin QUIC, MediaCodec Opus, AudioTrack +
|
* actuals (OkHttp HTTP, pure-Kotlin QUIC, MediaCodec Opus, AudioTrack +
|
||||||
* AudioRecord on the speaker side).
|
* AudioRecord on the speaker side).
|
||||||
*/
|
*/
|
||||||
internal class AudioRoomViewModelFactory(
|
internal class NestViewModelFactory(
|
||||||
private val signer: NostrSigner,
|
private val signer: NostrSigner,
|
||||||
private val room: NestsRoomConfig,
|
private val room: NestsRoomConfig,
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
||||||
AudioRoomViewModel(
|
NestViewModel(
|
||||||
httpClient = OkHttpNestsClient(),
|
httpClient = OkHttpNestsClient(),
|
||||||
transport = QuicWebTransportFactory(),
|
transport = QuicWebTransportFactory(),
|
||||||
decoderFactory = { MediaCodecOpusDecoder() },
|
decoderFactory = { MediaCodecOpusDecoder() },
|
||||||
+15
-15
@@ -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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -49,7 +49,7 @@ import com.vitorpamplona.amethyst.ui.note.ZapCustomDialog
|
|||||||
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.experimental.audiorooms.admin.AdminCommandEvent
|
import com.vitorpamplona.quartz.experimental.nests.admin.AdminCommandEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||||
@@ -134,7 +134,7 @@ internal fun ParticipantHostActionsSheet(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
// View Profile — AudioRoomActivity is a separate Android
|
// View Profile — NestActivity is a separate Android
|
||||||
// Activity without its own nav stack, so the deep-link
|
// Activity without its own nav stack, so the deep-link
|
||||||
// path goes through MainActivity. Launching `nostr:npub1...`
|
// path goes through MainActivity. Launching `nostr:npub1...`
|
||||||
// via ACTION_VIEW lets the existing MainActivity URI
|
// via ACTION_VIEW lets the existing MainActivity URI
|
||||||
@@ -145,8 +145,8 @@ internal fun ParticipantHostActionsSheet(
|
|||||||
// audio-room foreground service keeps audio alive while
|
// audio-room foreground service keeps audio alive while
|
||||||
// the user is on the profile screen.
|
// the user is on the profile screen.
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val noAppMessage = stringRes(R.string.audio_room_no_app_to_open_link)
|
val noAppMessage = stringRes(R.string.nest_no_app_to_open_link)
|
||||||
ActionRow(stringRes(R.string.audio_room_participant_view_profile)) {
|
ActionRow(stringRes(R.string.nest_participant_view_profile)) {
|
||||||
val npub = NPub.create(target)
|
val npub = NPub.create(target)
|
||||||
val launched =
|
val launched =
|
||||||
runCatching {
|
runCatching {
|
||||||
@@ -165,7 +165,7 @@ internal fun ParticipantHostActionsSheet(
|
|||||||
}.isSuccess
|
}.isSuccess
|
||||||
if (!launched) {
|
if (!launched) {
|
||||||
accountViewModel.toastManager.toast(
|
accountViewModel.toastManager.toast(
|
||||||
R.string.audio_room_chat_send_failed_title,
|
R.string.nest_chat_send_failed_title,
|
||||||
noAppMessage,
|
noAppMessage,
|
||||||
user = null,
|
user = null,
|
||||||
)
|
)
|
||||||
@@ -180,7 +180,7 @@ internal fun ParticipantHostActionsSheet(
|
|||||||
// the profile screen rather than wired into a non-nav-host
|
// the profile screen rather than wired into a non-nav-host
|
||||||
// ModalBottomSheet.
|
// ModalBottomSheet.
|
||||||
var showZapDialog by rememberSaveable { mutableStateOf(false) }
|
var showZapDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
ActionRow(stringRes(R.string.audio_room_participant_zap)) {
|
ActionRow(stringRes(R.string.nest_participant_zap)) {
|
||||||
showZapDialog = true
|
showZapDialog = true
|
||||||
}
|
}
|
||||||
if (showZapDialog) {
|
if (showZapDialog) {
|
||||||
@@ -191,7 +191,7 @@ internal fun ParticipantHostActionsSheet(
|
|||||||
}
|
}
|
||||||
// Pre-resolved at composition because callbacks below run
|
// Pre-resolved at composition because callbacks below run
|
||||||
// outside a @Composable scope (stringRes is composable-only).
|
// outside a @Composable scope (stringRes is composable-only).
|
||||||
val splitUnsupportedMsg = stringRes(R.string.audio_room_participant_zap_split_unsupported)
|
val splitUnsupportedMsg = stringRes(R.string.nest_participant_zap_split_unsupported)
|
||||||
ZapCustomDialog(
|
ZapCustomDialog(
|
||||||
onZapStarts = {},
|
onZapStarts = {},
|
||||||
onClose = {
|
onClose = {
|
||||||
@@ -229,9 +229,9 @@ internal fun ParticipantHostActionsSheet(
|
|||||||
text =
|
text =
|
||||||
stringRes(
|
stringRes(
|
||||||
if (isFollowing) {
|
if (isFollowing) {
|
||||||
R.string.audio_room_participant_unfollow
|
R.string.nest_participant_unfollow
|
||||||
} else {
|
} else {
|
||||||
R.string.audio_room_participant_follow
|
R.string.nest_participant_follow
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
@@ -242,9 +242,9 @@ internal fun ParticipantHostActionsSheet(
|
|||||||
text =
|
text =
|
||||||
stringRes(
|
stringRes(
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
R.string.audio_room_participant_unmute
|
R.string.nest_participant_unmute
|
||||||
} else {
|
} else {
|
||||||
R.string.audio_room_participant_mute
|
R.string.nest_participant_mute
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
@@ -255,16 +255,16 @@ internal fun ParticipantHostActionsSheet(
|
|||||||
// Host-only rows.
|
// Host-only rows.
|
||||||
if (isLocalUserHost && target != event.pubKey) {
|
if (isLocalUserHost && target != event.pubKey) {
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
ActionRow(stringRes(R.string.audio_room_promote_speaker)) {
|
ActionRow(stringRes(R.string.nest_promote_speaker)) {
|
||||||
broadcast(RoomParticipantActions.setRole(event, target, ROLE.SPEAKER))
|
broadcast(RoomParticipantActions.setRole(event, target, ROLE.SPEAKER))
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
ActionRow(stringRes(R.string.audio_room_demote_listener)) {
|
ActionRow(stringRes(R.string.nest_demote_listener)) {
|
||||||
broadcast(RoomParticipantActions.demoteToListener(event, target))
|
broadcast(RoomParticipantActions.demoteToListener(event, target))
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
ActionRow(
|
ActionRow(
|
||||||
text = stringRes(R.string.audio_room_kick_action),
|
text = stringRes(R.string.nest_kick_action),
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
) {
|
) {
|
||||||
broadcast(AdminCommandEvent.kick(roomATag, target))
|
broadcast(AdminCommandEvent.kick(roomATag, target))
|
||||||
+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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
+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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
+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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||||
@@ -37,7 +37,7 @@ import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ROLE
|
|||||||
*
|
*
|
||||||
* Extracted out of the screen Composable so they can be unit-tested
|
* Extracted out of the screen Composable so they can be unit-tested
|
||||||
* without an AccountViewModel / signer (the sign+broadcast path is
|
* without an AccountViewModel / signer (the sign+broadcast path is
|
||||||
* the only side effect, mirroring [EditAudioRoomViewModel.buildEditTemplate]).
|
* the only side effect, mirroring [EditNestViewModel.buildEditTemplate]).
|
||||||
*
|
*
|
||||||
* Risk-mitigation: each builder reads ALL participants from
|
* Risk-mitigation: each builder reads ALL participants from
|
||||||
* [original] and rebuilds the list with one row mutated; never
|
* [original] and rebuilds the list with one row mutated; never
|
||||||
+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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -66,7 +66,7 @@ internal fun RoomReactionPickerSheet(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp)) {
|
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(R.string.audio_room_reactions_title),
|
text = stringRes(R.string.nest_reactions_title),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
+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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -38,7 +38,7 @@ import com.vitorpamplona.amethyst.commons.viewmodels.RoomReaction
|
|||||||
*
|
*
|
||||||
* Hides itself when there are no reactions in the window — the
|
* Hides itself when there are no reactions in the window — the
|
||||||
* 30-s sliding-window aggregator in
|
* 30-s sliding-window aggregator in
|
||||||
* [com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomViewModel.recentReactions]
|
* [com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel.recentReactions]
|
||||||
* drops stale entries on the 1-s tick.
|
* drops stale entries on the 1-s tick.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
@@ -456,11 +456,11 @@
|
|||||||
<string name="public_chats">Veřejné chaty</string>
|
<string name="public_chats">Veřejné chaty</string>
|
||||||
<string name="follow_packs">Seznamy sledování</string>
|
<string name="follow_packs">Seznamy sledování</string>
|
||||||
<string name="live_streams">Živé vysílání</string>
|
<string name="live_streams">Živé vysílání</string>
|
||||||
<string name="audio_rooms">Audio místnosti</string>
|
<string name="nests">Audio místnosti</string>
|
||||||
<string name="audio_room_stage">Pódium</string>
|
<string name="nest_stage">Pódium</string>
|
||||||
<string name="audio_room_audience">Publikum</string>
|
<string name="nest_audience">Publikum</string>
|
||||||
<string name="audio_room_raise_hand">Přihlásit se</string>
|
<string name="nest_raise_hand">Přihlásit se</string>
|
||||||
<string name="audio_room_lower_hand">Dát ruku dolů</string>
|
<string name="nest_lower_hand">Dát ruku dolů</string>
|
||||||
<string name="longs">Videa</string>
|
<string name="longs">Videa</string>
|
||||||
<string name="articles">Články</string>
|
<string name="articles">Články</string>
|
||||||
<string name="private_bookmarks">Soukromé záložky</string>
|
<string name="private_bookmarks">Soukromé záložky</string>
|
||||||
|
|||||||
@@ -462,11 +462,11 @@ anz der Bedingungen ist erforderlich</string>
|
|||||||
<string name="public_chats">Öffentliche Chats</string>
|
<string name="public_chats">Öffentliche Chats</string>
|
||||||
<string name="follow_packs">Follow-Pakete</string>
|
<string name="follow_packs">Follow-Pakete</string>
|
||||||
<string name="live_streams">Live-Streams</string>
|
<string name="live_streams">Live-Streams</string>
|
||||||
<string name="audio_rooms">Audioräume</string>
|
<string name="nests">Audioräume</string>
|
||||||
<string name="audio_room_stage">Bühne</string>
|
<string name="nest_stage">Bühne</string>
|
||||||
<string name="audio_room_audience">Publikum</string>
|
<string name="nest_audience">Publikum</string>
|
||||||
<string name="audio_room_raise_hand">Hand heben</string>
|
<string name="nest_raise_hand">Hand heben</string>
|
||||||
<string name="audio_room_lower_hand">Hand senken</string>
|
<string name="nest_lower_hand">Hand senken</string>
|
||||||
<string name="longs">Videos</string>
|
<string name="longs">Videos</string>
|
||||||
<string name="articles">Artikel</string>
|
<string name="articles">Artikel</string>
|
||||||
<string name="private_bookmarks">Private Lesezeichen</string>
|
<string name="private_bookmarks">Private Lesezeichen</string>
|
||||||
|
|||||||
@@ -458,11 +458,11 @@
|
|||||||
<string name="public_chats">सार्वजनिक चर्चा</string>
|
<string name="public_chats">सार्वजनिक चर्चा</string>
|
||||||
<string name="follow_packs">अनुचरण पोटलियाँ</string>
|
<string name="follow_packs">अनुचरण पोटलियाँ</string>
|
||||||
<string name="live_streams">सीधा प्रसारण</string>
|
<string name="live_streams">सीधा प्रसारण</string>
|
||||||
<string name="audio_rooms">ध्वनि शाला</string>
|
<string name="nests">ध्वनि शाला</string>
|
||||||
<string name="audio_room_stage">मंच</string>
|
<string name="nest_stage">मंच</string>
|
||||||
<string name="audio_room_audience">श्रोतागण</string>
|
<string name="nest_audience">श्रोतागण</string>
|
||||||
<string name="audio_room_raise_hand">हाथ उठाएँ</string>
|
<string name="nest_raise_hand">हाथ उठाएँ</string>
|
||||||
<string name="audio_room_lower_hand">हाथ नीचे करें</string>
|
<string name="nest_lower_hand">हाथ नीचे करें</string>
|
||||||
<string name="longs">चलचित्र</string>
|
<string name="longs">चलचित्र</string>
|
||||||
<string name="articles">लेख</string>
|
<string name="articles">लेख</string>
|
||||||
<string name="private_bookmarks">निजी स्मर्तव्य</string>
|
<string name="private_bookmarks">निजी स्मर्तव्य</string>
|
||||||
|
|||||||
@@ -458,11 +458,11 @@
|
|||||||
<string name="public_chats">Nyilvános csevegések</string>
|
<string name="public_chats">Nyilvános csevegések</string>
|
||||||
<string name="follow_packs">Követési csomagok</string>
|
<string name="follow_packs">Követési csomagok</string>
|
||||||
<string name="live_streams">Élő közvetítések</string>
|
<string name="live_streams">Élő közvetítések</string>
|
||||||
<string name="audio_rooms">Hangszobák</string>
|
<string name="nests">Hangszobák</string>
|
||||||
<string name="audio_room_stage">Állapot</string>
|
<string name="nest_stage">Állapot</string>
|
||||||
<string name="audio_room_audience">Közönség</string>
|
<string name="nest_audience">Közönség</string>
|
||||||
<string name="audio_room_raise_hand">Kéz felemelése</string>
|
<string name="nest_raise_hand">Kéz felemelése</string>
|
||||||
<string name="audio_room_lower_hand">Kéz leengedése</string>
|
<string name="nest_lower_hand">Kéz leengedése</string>
|
||||||
<string name="longs">Videók</string>
|
<string name="longs">Videók</string>
|
||||||
<string name="articles">Cikkek</string>
|
<string name="articles">Cikkek</string>
|
||||||
<string name="private_bookmarks">Privát könyvjelzők</string>
|
<string name="private_bookmarks">Privát könyvjelzők</string>
|
||||||
|
|||||||
@@ -455,11 +455,11 @@
|
|||||||
<string name="public_chats">Czaty publiczne</string>
|
<string name="public_chats">Czaty publiczne</string>
|
||||||
<string name="follow_packs">Pakiety subskrybentów</string>
|
<string name="follow_packs">Pakiety subskrybentów</string>
|
||||||
<string name="live_streams">Transmisje na żywo</string>
|
<string name="live_streams">Transmisje na żywo</string>
|
||||||
<string name="audio_rooms">Pokoje audio</string>
|
<string name="nests">Pokoje audio</string>
|
||||||
<string name="audio_room_stage">Scena</string>
|
<string name="nest_stage">Scena</string>
|
||||||
<string name="audio_room_audience">Publiczność</string>
|
<string name="nest_audience">Publiczność</string>
|
||||||
<string name="audio_room_raise_hand">Podnieś rękę</string>
|
<string name="nest_raise_hand">Podnieś rękę</string>
|
||||||
<string name="audio_room_lower_hand">Opuść rękę</string>
|
<string name="nest_lower_hand">Opuść rękę</string>
|
||||||
<string name="longs">Filmy wideo</string>
|
<string name="longs">Filmy wideo</string>
|
||||||
<string name="articles">Artykuły</string>
|
<string name="articles">Artykuły</string>
|
||||||
<string name="private_bookmarks">Prywatne Zakładki</string>
|
<string name="private_bookmarks">Prywatne Zakładki</string>
|
||||||
|
|||||||
@@ -456,11 +456,11 @@
|
|||||||
<string name="public_chats">Chats públicos</string>
|
<string name="public_chats">Chats públicos</string>
|
||||||
<string name="follow_packs">Pacotes de seguidos</string>
|
<string name="follow_packs">Pacotes de seguidos</string>
|
||||||
<string name="live_streams">Transmissões ao vivo</string>
|
<string name="live_streams">Transmissões ao vivo</string>
|
||||||
<string name="audio_rooms">Salas de áudio</string>
|
<string name="nests">Salas de áudio</string>
|
||||||
<string name="audio_room_stage">Palco</string>
|
<string name="nest_stage">Palco</string>
|
||||||
<string name="audio_room_audience">Plateia</string>
|
<string name="nest_audience">Plateia</string>
|
||||||
<string name="audio_room_raise_hand">Levantar mão</string>
|
<string name="nest_raise_hand">Levantar mão</string>
|
||||||
<string name="audio_room_lower_hand">Abaixar mão</string>
|
<string name="nest_lower_hand">Abaixar mão</string>
|
||||||
<string name="longs">Vídeos</string>
|
<string name="longs">Vídeos</string>
|
||||||
<string name="articles">Artigos</string>
|
<string name="articles">Artigos</string>
|
||||||
<string name="private_bookmarks">Itens Salvos Privados</string>
|
<string name="private_bookmarks">Itens Salvos Privados</string>
|
||||||
|
|||||||
@@ -469,11 +469,11 @@ Za podpisovanje se je potrebno prijaviti s privatnim ključem</string>
|
|||||||
<string name="public_chats">Javni klepeti</string>
|
<string name="public_chats">Javni klepeti</string>
|
||||||
<string name="follow_packs">Paketi sledenih</string>
|
<string name="follow_packs">Paketi sledenih</string>
|
||||||
<string name="live_streams">Posnetki v živo</string>
|
<string name="live_streams">Posnetki v živo</string>
|
||||||
<string name="audio_rooms">Zvočne sobe</string>
|
<string name="nests">Zvočne sobe</string>
|
||||||
<string name="audio_room_stage">Prizorišče</string>
|
<string name="nest_stage">Prizorišče</string>
|
||||||
<string name="audio_room_audience">Občinstvo</string>
|
<string name="nest_audience">Občinstvo</string>
|
||||||
<string name="audio_room_raise_hand">Dvigni roko</string>
|
<string name="nest_raise_hand">Dvigni roko</string>
|
||||||
<string name="audio_room_lower_hand">Spusti roko</string>
|
<string name="nest_lower_hand">Spusti roko</string>
|
||||||
<string name="longs">Videoposnetki</string>
|
<string name="longs">Videoposnetki</string>
|
||||||
<string name="articles">Članki</string>
|
<string name="articles">Članki</string>
|
||||||
<string name="private_bookmarks">Privatni zaznamki</string>
|
<string name="private_bookmarks">Privatni zaznamki</string>
|
||||||
|
|||||||
@@ -456,11 +456,11 @@
|
|||||||
<string name="public_chats">Offentliga chattar</string>
|
<string name="public_chats">Offentliga chattar</string>
|
||||||
<string name="follow_packs">Följpaket</string>
|
<string name="follow_packs">Följpaket</string>
|
||||||
<string name="live_streams">Livesändningar</string>
|
<string name="live_streams">Livesändningar</string>
|
||||||
<string name="audio_rooms">Ljudrum</string>
|
<string name="nests">Ljudrum</string>
|
||||||
<string name="audio_room_stage">Scen</string>
|
<string name="nest_stage">Scen</string>
|
||||||
<string name="audio_room_audience">Publik</string>
|
<string name="nest_audience">Publik</string>
|
||||||
<string name="audio_room_raise_hand">Räck upp handen</string>
|
<string name="nest_raise_hand">Räck upp handen</string>
|
||||||
<string name="audio_room_lower_hand">Sänk handen</string>
|
<string name="nest_lower_hand">Sänk handen</string>
|
||||||
<string name="longs">Videor</string>
|
<string name="longs">Videor</string>
|
||||||
<string name="articles">Artiklar</string>
|
<string name="articles">Artiklar</string>
|
||||||
<string name="private_bookmarks">Privata Bokmärken</string>
|
<string name="private_bookmarks">Privata Bokmärken</string>
|
||||||
|
|||||||
@@ -458,11 +458,11 @@
|
|||||||
<string name="public_chats">公共聊天</string>
|
<string name="public_chats">公共聊天</string>
|
||||||
<string name="follow_packs">关注包</string>
|
<string name="follow_packs">关注包</string>
|
||||||
<string name="live_streams">直播</string>
|
<string name="live_streams">直播</string>
|
||||||
<string name="audio_rooms">音频室</string>
|
<string name="nests">音频室</string>
|
||||||
<string name="audio_room_stage">阶段</string>
|
<string name="nest_stage">阶段</string>
|
||||||
<string name="audio_room_audience">观众</string>
|
<string name="nest_audience">观众</string>
|
||||||
<string name="audio_room_raise_hand">举手</string>
|
<string name="nest_raise_hand">举手</string>
|
||||||
<string name="audio_room_lower_hand">放下手</string>
|
<string name="nest_lower_hand">放下手</string>
|
||||||
<string name="longs">视频</string>
|
<string name="longs">视频</string>
|
||||||
<string name="articles">文章</string>
|
<string name="articles">文章</string>
|
||||||
<string name="private_bookmarks">私人书签</string>
|
<string name="private_bookmarks">私人书签</string>
|
||||||
|
|||||||
@@ -488,107 +488,107 @@
|
|||||||
<string name="public_chats">Public Chats</string>
|
<string name="public_chats">Public Chats</string>
|
||||||
<string name="follow_packs">Follow Packs</string>
|
<string name="follow_packs">Follow Packs</string>
|
||||||
<string name="live_streams">Live Streams</string>
|
<string name="live_streams">Live Streams</string>
|
||||||
<string name="audio_rooms">Audio Rooms</string>
|
<string name="nests">Nests</string>
|
||||||
<string name="audio_room_stage">Stage</string>
|
<string name="nest_stage">Stage</string>
|
||||||
<string name="audio_room_audience">Audience</string>
|
<string name="nest_audience">Audience</string>
|
||||||
<string name="audio_room_raise_hand">Raise hand</string>
|
<string name="nest_raise_hand">Raise hand</string>
|
||||||
<string name="audio_room_lower_hand">Lower hand</string>
|
<string name="nest_lower_hand">Lower hand</string>
|
||||||
<string name="audio_room_connect">Connect audio</string>
|
<string name="nest_connect">Connect audio</string>
|
||||||
<string name="audio_room_disconnect">Disconnect</string>
|
<string name="nest_disconnect">Disconnect</string>
|
||||||
<string name="audio_room_mute">Mute</string>
|
<string name="nest_mute">Mute</string>
|
||||||
<string name="audio_room_unmute">Unmute</string>
|
<string name="nest_unmute">Unmute</string>
|
||||||
<string name="audio_room_connecting">Connecting…</string>
|
<string name="nest_connecting">Connecting…</string>
|
||||||
<string name="audio_room_connecting_resolving">Resolving room</string>
|
<string name="nest_connecting_resolving">Resolving room</string>
|
||||||
<string name="audio_room_connecting_transport">Opening transport</string>
|
<string name="nest_connecting_transport">Opening transport</string>
|
||||||
<string name="audio_room_connecting_handshake">Negotiating audio</string>
|
<string name="nest_connecting_handshake">Negotiating audio</string>
|
||||||
<string name="audio_room_connected">Audio connected</string>
|
<string name="nest_connected">Audio connected</string>
|
||||||
<string name="audio_room_reconnecting">Reconnecting…</string>
|
<string name="nest_reconnecting">Reconnecting…</string>
|
||||||
<string name="audio_room_listen_to_recording">Listen to recording</string>
|
<string name="nest_listen_to_recording">Listen to recording</string>
|
||||||
<string name="audio_room_chat_send_failed_title">Couldn\'t send message</string>
|
<string name="nest_chat_send_failed_title">Couldn\'t send message</string>
|
||||||
<string name="audio_room_no_app_to_open_link">No app installed to open this link.</string>
|
<string name="nest_no_app_to_open_link">No app installed to open this link.</string>
|
||||||
<string name="audio_room_close_room_confirm_title">Close this room?</string>
|
<string name="nest_close_room_confirm_title">Close this room?</string>
|
||||||
<string name="audio_room_close_room_confirm_body">All attendees will be disconnected. The room will show as CLOSED in the feed.</string>
|
<string name="nest_close_room_confirm_body">All attendees will be disconnected. The room will show as CLOSED in the feed.</string>
|
||||||
<string name="audio_room_close_room_confirm_action">Close room</string>
|
<string name="nest_close_room_confirm_action">Close room</string>
|
||||||
<string name="audio_room_create_when_in_past">Pick a future start time.</string>
|
<string name="nest_create_when_in_past">Pick a future start time.</string>
|
||||||
<string name="audio_room_audio_failed">Audio failed: %1$s</string>
|
<string name="nest_audio_failed">Audio failed: %1$s</string>
|
||||||
<string name="audio_room_audio_unavailable">Audio is not available for this room</string>
|
<string name="nest_audio_unavailable">Audio is not available for this room</string>
|
||||||
<string name="audio_room_talk">Talk</string>
|
<string name="nest_talk">Talk</string>
|
||||||
<string name="audio_room_stop_talking">Stop talking</string>
|
<string name="nest_stop_talking">Stop talking</string>
|
||||||
<string name="audio_room_mic_mute">Mute mic</string>
|
<string name="nest_mic_mute">Mute mic</string>
|
||||||
<string name="audio_room_mic_unmute">Unmute mic</string>
|
<string name="nest_mic_unmute">Unmute mic</string>
|
||||||
<string name="audio_room_broadcast_connecting">Going live…</string>
|
<string name="nest_broadcast_connecting">Going live…</string>
|
||||||
<string name="audio_room_broadcasting">Live</string>
|
<string name="nest_broadcasting">Live</string>
|
||||||
<string name="audio_room_broadcast_failed">Broadcast failed: %1$s</string>
|
<string name="nest_broadcast_failed">Broadcast failed: %1$s</string>
|
||||||
<string name="audio_room_record_permission_required">Microphone access is required to talk in this room.</string>
|
<string name="nest_record_permission_required">Microphone access is required to talk in this room.</string>
|
||||||
<string name="audio_room_open_settings">Open settings</string>
|
<string name="nest_open_settings">Open settings</string>
|
||||||
<string name="audio_room_notification_channel">Audio rooms</string>
|
<string name="nest_notification_channel">Nests</string>
|
||||||
<string name="audio_room_notification_channel_description">Keeps audio playing while a room is open.</string>
|
<string name="nest_notification_channel_description">Keeps audio playing while a room is open.</string>
|
||||||
<string name="audio_room_notification_listening">Audio room connected</string>
|
<string name="nest_notification_listening">Nest connected</string>
|
||||||
<string name="audio_room_notification_broadcasting">Audio room — Live</string>
|
<string name="nest_notification_broadcasting">Nest — Live</string>
|
||||||
<string name="audio_room_notification_text">Tap to return.</string>
|
<string name="nest_notification_text">Tap to return.</string>
|
||||||
<string name="audio_room_notification_stop">Stop</string>
|
<string name="nest_notification_stop">Stop</string>
|
||||||
<string name="audio_room_join">Join audio room</string>
|
<string name="nest_join">Join nest</string>
|
||||||
<string name="audio_room_leave">Leave</string>
|
<string name="nest_leave">Leave</string>
|
||||||
<string name="audio_room_leave_host_title">End this audio room?</string>
|
<string name="nest_leave_host_title">End this nest?</string>
|
||||||
<string name="audio_room_leave_host_body">You\'re the host. Closing the room will disconnect everyone. Choose \"Just leave\" if you want to come back later — the room will auto-close after 8 hours of inactivity.</string>
|
<string name="nest_leave_host_body">You\'re the host. Closing the room will disconnect everyone. Choose \"Just leave\" if you want to come back later — the room will auto-close after 8 hours of inactivity.</string>
|
||||||
<string name="audio_room_leave_host_close">Close room</string>
|
<string name="nest_leave_host_close">Close room</string>
|
||||||
<string name="audio_room_leave_host_just_leave">Just leave</string>
|
<string name="nest_leave_host_just_leave">Just leave</string>
|
||||||
<string name="audio_room_leave_host_close_failed">Couldn\'t mark room as closed. Leaving anyway — room will auto-close.</string>
|
<string name="nest_leave_host_close_failed">Couldn\'t mark room as closed. Leaving anyway — room will auto-close.</string>
|
||||||
<plurals name="audio_room_listener_count">
|
<plurals name="nest_listener_count">
|
||||||
<item quantity="one">%1$d listener</item>
|
<item quantity="one">%1$d listener</item>
|
||||||
<item quantity="other">%1$d listeners</item>
|
<item quantity="other">%1$d listeners</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="audio_room_chat_send">Send</string>
|
<string name="nest_chat_send">Send</string>
|
||||||
<string name="audio_room_chat_placeholder">Say something…</string>
|
<string name="nest_chat_placeholder">Say something…</string>
|
||||||
<string name="audio_room_chat_empty">No messages yet. Be the first to chat.</string>
|
<string name="nest_chat_empty">No messages yet. Be the first to chat.</string>
|
||||||
<string name="audio_room_reactions_title">React</string>
|
<string name="nest_reactions_title">React</string>
|
||||||
<string name="audio_room_reactions_button">React</string>
|
<string name="nest_reactions_button">React</string>
|
||||||
<string name="audio_room_edit_title">Edit room</string>
|
<string name="nest_edit_title">Edit room</string>
|
||||||
<string name="audio_room_edit_save">Save</string>
|
<string name="nest_edit_save">Save</string>
|
||||||
<string name="audio_room_close_action">Close room</string>
|
<string name="nest_close_action">Close room</string>
|
||||||
<string name="audio_room_overflow_menu">Room actions</string>
|
<string name="nest_overflow_menu">Room actions</string>
|
||||||
<string name="audio_room_hand_raise_queue_title">Raised hands</string>
|
<string name="nest_hand_raise_queue_title">Raised hands</string>
|
||||||
<string name="audio_room_hand_raise_approve">Approve</string>
|
<string name="nest_hand_raise_approve">Approve</string>
|
||||||
<string name="audio_room_promote_speaker">Promote to speaker</string>
|
<string name="nest_promote_speaker">Promote to speaker</string>
|
||||||
<string name="audio_room_demote_listener">Demote to listener</string>
|
<string name="nest_demote_listener">Demote to listener</string>
|
||||||
<string name="audio_room_kick_action">Kick</string>
|
<string name="nest_kick_action">Kick</string>
|
||||||
<string name="audio_room_participant_view_profile">View profile</string>
|
<string name="nest_participant_view_profile">View profile</string>
|
||||||
<string name="audio_room_participant_zap">Send zap</string>
|
<string name="nest_participant_zap">Send zap</string>
|
||||||
<string name="audio_room_participant_zap_split_unsupported">Split zaps are not supported from inside an audio room. Open the profile screen to send.</string>
|
<string name="nest_participant_zap_split_unsupported">Split zaps are not supported from inside a nest. Open the profile screen to send.</string>
|
||||||
<string name="audio_room_participant_follow">Follow</string>
|
<string name="nest_participant_follow">Follow</string>
|
||||||
<string name="audio_room_participant_unfollow">Unfollow</string>
|
<string name="nest_participant_unfollow">Unfollow</string>
|
||||||
<string name="audio_room_participant_mute">Mute</string>
|
<string name="nest_participant_mute">Mute</string>
|
||||||
<string name="audio_room_participant_unmute">Unmute</string>
|
<string name="nest_participant_unmute">Unmute</string>
|
||||||
<string name="audio_room_share_action">Share room</string>
|
<string name="nest_share_action">Share room</string>
|
||||||
<string name="audio_room_create_fab">Start space</string>
|
<string name="nest_create_fab">Start space</string>
|
||||||
<string name="audio_room_no_server_title">Set up an audio server</string>
|
<string name="nest_no_server_title">Set up a nest server</string>
|
||||||
<string name="audio_room_no_server_body">You haven\'t picked an audio server yet. Add %1$s to your server list and continue?\n\nYou can change this later in Settings.</string>
|
<string name="nest_no_server_body">You haven\'t picked a nest server yet. Add %1$s to your server list and continue?\n\nYou can change this later in Settings.</string>
|
||||||
<string name="audio_room_no_server_use_default">Use default</string>
|
<string name="nest_no_server_use_default">Use default</string>
|
||||||
<string name="audio_room_no_server_cancel">Cancel</string>
|
<string name="nest_no_server_cancel">Cancel</string>
|
||||||
<string name="audio_room_no_server_save_failed">Couldn\'t save your audio server list. Try again.</string>
|
<string name="nest_no_server_save_failed">Couldn\'t save your nest server list. Try again.</string>
|
||||||
<string name="audio_room_create_title">Start a new audio room</string>
|
<string name="nest_create_title">Start a new nest</string>
|
||||||
<string name="audio_room_create_field_room">Room name</string>
|
<string name="nest_create_field_room">Room name</string>
|
||||||
<string name="audio_room_create_field_summary">What\'s it about?</string>
|
<string name="nest_create_field_summary">What\'s it about?</string>
|
||||||
<string name="audio_room_create_field_service">MoQ service URL</string>
|
<string name="nest_create_field_service">MoQ service URL</string>
|
||||||
<string name="audio_room_create_field_service_hint">Auth sidecar — defaults to nostrnests.com</string>
|
<string name="nest_create_field_service_hint">Auth sidecar — defaults to nostrnests.com</string>
|
||||||
<string name="audio_room_create_field_endpoint">MoQ relay endpoint</string>
|
<string name="nest_create_field_endpoint">MoQ relay endpoint</string>
|
||||||
<string name="audio_room_create_field_endpoint_hint">WebTransport URL — usually the same host</string>
|
<string name="nest_create_field_endpoint_hint">WebTransport URL — usually the same host</string>
|
||||||
<string name="audio_room_create_field_image">Cover image URL (optional)</string>
|
<string name="nest_create_field_image">Cover image URL (optional)</string>
|
||||||
<string name="audio_room_create_cancel">Cancel</string>
|
<string name="nest_create_cancel">Cancel</string>
|
||||||
<string name="audio_room_create_submit">Start space</string>
|
<string name="nest_create_submit">Start space</string>
|
||||||
<string name="audio_room_create_schedule_toggle">Schedule for later</string>
|
<string name="nest_create_schedule_toggle">Schedule for later</string>
|
||||||
<string name="audio_room_create_when">Pick a start time</string>
|
<string name="nest_create_when">Pick a start time</string>
|
||||||
<string name="nests_servers_title">Audio-room servers</string>
|
<string name="nests_servers_title">Nest servers</string>
|
||||||
<string name="nests_servers_explainer">Choose which MoQ host servers Amethyst publishes your audio rooms to. The first entry is used by default when you start a new space.</string>
|
<string name="nests_servers_explainer">Choose which MoQ host servers Amethyst publishes your nests to. The first entry is used by default when you start a new space.</string>
|
||||||
<string name="nests_servers_my_section">Your servers</string>
|
<string name="nests_servers_my_section">Your servers</string>
|
||||||
<string name="nests_servers_my_explainer">Saved as a kind-10112 replaceable event so other clients can read your preference.</string>
|
<string name="nests_servers_my_explainer">Saved as a kind-10112 replaceable event so other clients can read your preference.</string>
|
||||||
<string name="nests_servers_add_field">Add an audio-room server</string>
|
<string name="nests_servers_add_field">Add a nest server</string>
|
||||||
<string name="nests_servers_recommended_section">Recommended servers</string>
|
<string name="nests_servers_recommended_section">Recommended servers</string>
|
||||||
<string name="nests_servers_recommended_explainer">Built-in suggestions you can add to your list with one tap.</string>
|
<string name="nests_servers_recommended_explainer">Built-in suggestions you can add to your list with one tap.</string>
|
||||||
<string name="nests_servers_use_defaults">Use Amethyst defaults</string>
|
<string name="nests_servers_use_defaults">Use Amethyst defaults</string>
|
||||||
<string name="nests_servers_add_recommended">Add server</string>
|
<string name="nests_servers_add_recommended">Add server</string>
|
||||||
<string name="nests_servers_remove">Remove server</string>
|
<string name="nests_servers_remove">Remove server</string>
|
||||||
<string name="nests_servers_empty">No audio-room servers yet. Add one below or pick a recommended server.</string>
|
<string name="nests_servers_empty">No nest servers yet. Add one below or pick a recommended server.</string>
|
||||||
<string name="longs">Videos</string>
|
<string name="longs">Videos</string>
|
||||||
<string name="articles">Articles</string>
|
<string name="articles">Articles</string>
|
||||||
<string name="private_bookmarks">Private Bookmarks</string>
|
<string name="private_bookmarks">Private Bookmarks</string>
|
||||||
@@ -2141,7 +2141,7 @@
|
|||||||
<string name="kind_profile_badges">Profile Badges</string>
|
<string name="kind_profile_badges">Profile Badges</string>
|
||||||
<string name="kind_blocked_relays">Blocked Relays</string>
|
<string name="kind_blocked_relays">Blocked Relays</string>
|
||||||
<string name="kind_blossom_servers">Blossom Servers</string>
|
<string name="kind_blossom_servers">Blossom Servers</string>
|
||||||
<string name="kind_nests_servers">Audio-room (Nests) Servers</string>
|
<string name="kind_nests_servers">Nests Servers</string>
|
||||||
<string name="kind_blossom_auth">Blossom Auth</string>
|
<string name="kind_blossom_auth">Blossom Auth</string>
|
||||||
<string name="kind_broadcast_relays">Broadcast Relays</string>
|
<string name="kind_broadcast_relays">Broadcast Relays</string>
|
||||||
<string name="kind_bookmark_list">Bookmark List</string>
|
<string name="kind_bookmark_list">Bookmark List</string>
|
||||||
|
|||||||
+7
-7
@@ -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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag
|
||||||
@@ -27,7 +27,7 @@ import org.junit.Assert.assertNull
|
|||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class EditAudioRoomViewModelTest {
|
class EditNestViewModelTest {
|
||||||
private val host = "a".repeat(64)
|
private val host = "a".repeat(64)
|
||||||
private val speakerA = "b".repeat(64)
|
private val speakerA = "b".repeat(64)
|
||||||
private val speakerB = "c".repeat(64)
|
private val speakerB = "c".repeat(64)
|
||||||
@@ -68,7 +68,7 @@ class EditAudioRoomViewModelTest {
|
|||||||
imageUrl: String = "",
|
imageUrl: String = "",
|
||||||
endpointUrl: String = "https://moq.example.com",
|
endpointUrl: String = "https://moq.example.com",
|
||||||
serviceUrl: String = "https://auth.example.com",
|
serviceUrl: String = "https://auth.example.com",
|
||||||
) = EditAudioRoomViewModel.FormState(
|
) = EditNestViewModel.FormState(
|
||||||
dTag = dTag,
|
dTag = dTag,
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
summary = summary,
|
summary = summary,
|
||||||
@@ -85,7 +85,7 @@ class EditAudioRoomViewModelTest {
|
|||||||
val newForm = form(dTag = "rt-42", roomName = "New name", summary = "New summary")
|
val newForm = form(dTag = "rt-42", roomName = "New name", summary = "New summary")
|
||||||
|
|
||||||
val template =
|
val template =
|
||||||
EditAudioRoomViewModel.buildEditTemplate(src, newForm, StatusTag.STATUS.OPEN)
|
EditNestViewModel.buildEditTemplate(src, newForm, StatusTag.STATUS.OPEN)
|
||||||
|
|
||||||
// Same d-tag → same address → relay treats as replacement.
|
// Same d-tag → same address → relay treats as replacement.
|
||||||
val dTag = template.tags.firstOrNull { it.firstOrNull() == "d" }?.getOrNull(1)
|
val dTag = template.tags.firstOrNull { it.firstOrNull() == "d" }?.getOrNull(1)
|
||||||
@@ -113,7 +113,7 @@ class EditAudioRoomViewModelTest {
|
|||||||
val newForm = form(dTag = "rt-1", roomName = "Renamed")
|
val newForm = form(dTag = "rt-1", roomName = "Renamed")
|
||||||
|
|
||||||
val template =
|
val template =
|
||||||
EditAudioRoomViewModel.buildEditTemplate(src, newForm, StatusTag.STATUS.OPEN)
|
EditNestViewModel.buildEditTemplate(src, newForm, StatusTag.STATUS.OPEN)
|
||||||
|
|
||||||
val pTagPubkeys = template.tags.filter { it.firstOrNull() == "p" }.map { it[1] }
|
val pTagPubkeys = template.tags.filter { it.firstOrNull() == "p" }.map { it[1] }
|
||||||
// Host plus two speakers — none can be lost on a rename.
|
// Host plus two speakers — none can be lost on a rename.
|
||||||
@@ -129,7 +129,7 @@ class EditAudioRoomViewModelTest {
|
|||||||
val sameForm = form(dTag = "rt-99", roomName = src.room().orEmpty())
|
val sameForm = form(dTag = "rt-99", roomName = src.room().orEmpty())
|
||||||
|
|
||||||
val template =
|
val template =
|
||||||
EditAudioRoomViewModel.buildEditTemplate(src, sameForm, StatusTag.STATUS.CLOSED)
|
EditNestViewModel.buildEditTemplate(src, sameForm, StatusTag.STATUS.CLOSED)
|
||||||
|
|
||||||
val statusTag = template.tags.firstOrNull { it.firstOrNull() == "status" }?.getOrNull(1)
|
val statusTag = template.tags.firstOrNull { it.firstOrNull() == "status" }?.getOrNull(1)
|
||||||
assertEquals(StatusTag.STATUS.CLOSED.code, statusTag)
|
assertEquals(StatusTag.STATUS.CLOSED.code, statusTag)
|
||||||
@@ -143,7 +143,7 @@ class EditAudioRoomViewModelTest {
|
|||||||
val emptied = form(dTag = "rt-1", roomName = "X", summary = " ", imageUrl = "")
|
val emptied = form(dTag = "rt-1", roomName = "X", summary = " ", imageUrl = "")
|
||||||
|
|
||||||
val template =
|
val template =
|
||||||
EditAudioRoomViewModel.buildEditTemplate(src, emptied, StatusTag.STATUS.OPEN)
|
EditNestViewModel.buildEditTemplate(src, emptied, StatusTag.STATUS.OPEN)
|
||||||
|
|
||||||
// Blank inputs MUST NOT carry over from the original; otherwise
|
// Blank inputs MUST NOT carry over from the original; otherwise
|
||||||
// a "delete the summary" edit would silently fail.
|
// a "delete the summary" edit would silently fail.
|
||||||
+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.audiorooms.room
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ROLE
|
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ROLE
|
||||||
+18
-18
@@ -33,7 +33,7 @@ import com.vitorpamplona.nestsclient.NestsSpeaker
|
|||||||
import com.vitorpamplona.nestsclient.NestsSpeakerState
|
import com.vitorpamplona.nestsclient.NestsSpeakerState
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioCapture
|
import com.vitorpamplona.nestsclient.audio.AudioCapture
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioPlayer
|
import com.vitorpamplona.nestsclient.audio.AudioPlayer
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioRoomPlayer
|
import com.vitorpamplona.nestsclient.audio.NestPlayer
|
||||||
import com.vitorpamplona.nestsclient.audio.OpusDecoder
|
import com.vitorpamplona.nestsclient.audio.OpusDecoder
|
||||||
import com.vitorpamplona.nestsclient.audio.OpusEncoder
|
import com.vitorpamplona.nestsclient.audio.OpusEncoder
|
||||||
import com.vitorpamplona.nestsclient.connectNestsSpeaker
|
import com.vitorpamplona.nestsclient.connectNestsSpeaker
|
||||||
@@ -60,7 +60,7 @@ import kotlin.coroutines.cancellation.CancellationException
|
|||||||
* Per-screen state holder for a NIP-53 audio-room.
|
* Per-screen state holder for a NIP-53 audio-room.
|
||||||
*
|
*
|
||||||
* Owns one [NestsListener] for the lifetime of the screen and one
|
* Owns one [NestsListener] for the lifetime of the screen and one
|
||||||
* [AudioRoomPlayer] per active speaker subscription. The screen tells the
|
* [NestPlayer] per active speaker subscription. The screen tells the
|
||||||
* VM which speakers it cares about via [updateSpeakers]; subscribe / play
|
* VM which speakers it cares about via [updateSpeakers]; subscribe / play
|
||||||
* happen automatically once the listener is [NestsListenerState.Connected].
|
* happen automatically once the listener is [NestsListenerState.Connected].
|
||||||
*
|
*
|
||||||
@@ -90,7 +90,7 @@ import kotlin.coroutines.cancellation.CancellationException
|
|||||||
* via `viewModelScope.launch(Dispatchers.Main.immediate) { ... }` first.
|
* via `viewModelScope.launch(Dispatchers.Main.immediate) { ... }` first.
|
||||||
*/
|
*/
|
||||||
@Stable
|
@Stable
|
||||||
class AudioRoomViewModel(
|
class NestViewModel(
|
||||||
private val httpClient: NestsClient,
|
private val httpClient: NestsClient,
|
||||||
private val transport: WebTransportFactory,
|
private val transport: WebTransportFactory,
|
||||||
private val decoderFactory: () -> OpusDecoder,
|
private val decoderFactory: () -> OpusDecoder,
|
||||||
@@ -116,8 +116,8 @@ class AudioRoomViewModel(
|
|||||||
@OptIn(kotlinx.coroutines.DelicateCoroutinesApi::class)
|
@OptIn(kotlinx.coroutines.DelicateCoroutinesApi::class)
|
||||||
private val cleanupScope: CoroutineScope = GlobalScope,
|
private val cleanupScope: CoroutineScope = GlobalScope,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _uiState = MutableStateFlow(AudioRoomUiState())
|
private val _uiState = MutableStateFlow(NestUiState())
|
||||||
val uiState: StateFlow<AudioRoomUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<NestUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener-side aggregation of every peer's most recent kind-10312
|
* Listener-side aggregation of every peer's most recent kind-10312
|
||||||
@@ -292,7 +292,7 @@ class AudioRoomViewModel(
|
|||||||
/**
|
/**
|
||||||
* Toggle whether we hold a speaker slot. Tier-2's "leave the stage"
|
* Toggle whether we hold a speaker slot. Tier-2's "leave the stage"
|
||||||
* tap flips this to `false`; the next kind-10312 heartbeat picks up
|
* tap flips this to `false`; the next kind-10312 heartbeat picks up
|
||||||
* the change via [AudioRoomUiState.onStageNow]. Does NOT stop a
|
* the change via [NestUiState.onStageNow]. Does NOT stop a
|
||||||
* running broadcast — UI / caller is expected to call
|
* running broadcast — UI / caller is expected to call
|
||||||
* [BroadcastHandle.close] separately when leaving the stage.
|
* [BroadcastHandle.close] separately when leaving the stage.
|
||||||
*/
|
*/
|
||||||
@@ -674,7 +674,7 @@ class AudioRoomViewModel(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the per-speaker player + fire-and-forget UNSUBSCRIBE on the VM
|
* Stop the per-speaker player + fire-and-forget UNSUBSCRIBE on the VM
|
||||||
* scope. Both `AudioRoomPlayer.stop()` and `SubscribeHandle.unsubscribe()`
|
* scope. Both `NestPlayer.stop()` and `SubscribeHandle.unsubscribe()`
|
||||||
* are suspend, so we route them through one coroutine instead of two.
|
* are suspend, so we route them through one coroutine instead of two.
|
||||||
*/
|
*/
|
||||||
private fun closeSubscription(slot: ActiveSubscription) {
|
private fun closeSubscription(slot: ActiveSubscription) {
|
||||||
@@ -766,7 +766,7 @@ class AudioRoomViewModel(
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val isMuted = _uiState.value.isMuted
|
val isMuted = _uiState.value.isMuted
|
||||||
val roomPlayer = AudioRoomPlayer(decoder, player, viewModelScope)
|
val roomPlayer = NestPlayer(decoder, player, viewModelScope)
|
||||||
// Apply current mute state before play() opens the device so the
|
// Apply current mute state before play() opens the device so the
|
||||||
// first frame respects it.
|
// first frame respects it.
|
||||||
player.setMutedSafe(isMuted)
|
player.setMutedSafe(isMuted)
|
||||||
@@ -834,7 +834,7 @@ class AudioRoomViewModel(
|
|||||||
// listener.close() below tears down the MoQ session and drops
|
// listener.close() below tears down the MoQ session and drops
|
||||||
// every active subscription, so we don't need to call
|
// every active subscription, so we don't need to call
|
||||||
// unsubscribe() per-handle here — but we DO need to await each
|
// unsubscribe() per-handle here — but we DO need to await each
|
||||||
// AudioRoomPlayer.stop() so the native MediaCodec / AudioTrack
|
// NestPlayer.stop() so the native MediaCodec / AudioTrack
|
||||||
// is released after its decode loop has unwound.
|
// is released after its decode loop has unwound.
|
||||||
val scope = if (finalCleanup) cleanupScope else viewModelScope
|
val scope = if (finalCleanup) cleanupScope else viewModelScope
|
||||||
val players = activeSubscriptions.values.map { it.detach().first }
|
val players = activeSubscriptions.values.map { it.detach().first }
|
||||||
@@ -912,7 +912,7 @@ class AudioRoomViewModel(
|
|||||||
val pubkey: String,
|
val pubkey: String,
|
||||||
) {
|
) {
|
||||||
private var handle: SubscribeHandle? = null
|
private var handle: SubscribeHandle? = null
|
||||||
private var roomPlayer: AudioRoomPlayer? = null
|
private var roomPlayer: NestPlayer? = null
|
||||||
var player: AudioPlayer? = null
|
var player: AudioPlayer? = null
|
||||||
private set
|
private set
|
||||||
var isPlaying: Boolean = false
|
var isPlaying: Boolean = false
|
||||||
@@ -920,7 +920,7 @@ class AudioRoomViewModel(
|
|||||||
|
|
||||||
fun attach(
|
fun attach(
|
||||||
handle: SubscribeHandle,
|
handle: SubscribeHandle,
|
||||||
roomPlayer: AudioRoomPlayer,
|
roomPlayer: NestPlayer,
|
||||||
player: AudioPlayer,
|
player: AudioPlayer,
|
||||||
) {
|
) {
|
||||||
this.handle = handle
|
this.handle = handle
|
||||||
@@ -931,12 +931,12 @@ class AudioRoomViewModel(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hand the player + handle back to the caller's coroutine scope —
|
* Hand the player + handle back to the caller's coroutine scope —
|
||||||
* `AudioRoomPlayer.stop()` and `SubscribeHandle.unsubscribe()` are
|
* `NestPlayer.stop()` and `SubscribeHandle.unsubscribe()` are
|
||||||
* both suspend, and the caller has the right scope to await them
|
* both suspend, and the caller has the right scope to await them
|
||||||
* (so native MediaCodec/AudioTrack release runs after the decode
|
* (so native MediaCodec/AudioTrack release runs after the decode
|
||||||
* loop has unwound, per audit MoQ #11/#12).
|
* loop has unwound, per audit MoQ #11/#12).
|
||||||
*/
|
*/
|
||||||
fun detach(): Pair<AudioRoomPlayer?, SubscribeHandle?> {
|
fun detach(): Pair<NestPlayer?, SubscribeHandle?> {
|
||||||
isPlaying = false
|
isPlaying = false
|
||||||
val p = roomPlayer
|
val p = roomPlayer
|
||||||
val h = handle
|
val h = handle
|
||||||
@@ -951,7 +951,7 @@ class AudioRoomViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Platform-specific Factory lives in `amethyst/.../audiorooms/room/`,
|
// Platform-specific Factory lives in `amethyst/.../nests/room/`,
|
||||||
// not commonMain — the lifecycle KMP `ViewModelProvider.Factory`
|
// not commonMain — the lifecycle KMP `ViewModelProvider.Factory`
|
||||||
// signature has been migrating between releases; a thin Android-side
|
// signature has been migrating between releases; a thin Android-side
|
||||||
// factory keeps that churn out of shared code.
|
// factory keeps that churn out of shared code.
|
||||||
@@ -1006,7 +1006,7 @@ private fun AudioPlayer.setMutedSafe(muted: Boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class AudioRoomUiState(
|
data class NestUiState(
|
||||||
val connection: ConnectionUiState = ConnectionUiState.Idle,
|
val connection: ConnectionUiState = ConnectionUiState.Idle,
|
||||||
val isMuted: Boolean = false,
|
val isMuted: Boolean = false,
|
||||||
/** Pubkeys we have an open MoQ subscription for. */
|
/** Pubkeys we have an open MoQ subscription for. */
|
||||||
@@ -1025,11 +1025,11 @@ data class AudioRoomUiState(
|
|||||||
* speaker subsequently goes quiet. Cleared on speaker removal.
|
* speaker subsequently goes quiet. Cleared on speaker removal.
|
||||||
*/
|
*/
|
||||||
val connectingSpeakers: ImmutableSet<String> = persistentSetOf(),
|
val connectingSpeakers: ImmutableSet<String> = persistentSetOf(),
|
||||||
/** Speaker / publisher state — only relevant when [AudioRoomViewModel.canBroadcast]. */
|
/** Speaker / publisher state — only relevant when [NestViewModel.canBroadcast]. */
|
||||||
val broadcast: BroadcastUiState = BroadcastUiState.Idle,
|
val broadcast: BroadcastUiState = BroadcastUiState.Idle,
|
||||||
/**
|
/**
|
||||||
* `true` when we hold a speaker slot vs being pure audience. Defaults
|
* `true` when we hold a speaker slot vs being pure audience. Defaults
|
||||||
* to `true` for users with [AudioRoomViewModel.canBroadcast]; the
|
* to `true` for users with [NestViewModel.canBroadcast]; the
|
||||||
* "leave the stage" tap (Tier 2) flips it to `false`. Drives the
|
* "leave the stage" tap (Tier 2) flips it to `false`. Drives the
|
||||||
* `["onstage", "0|1"]` tag on emitted kind-10312 presence events.
|
* `["onstage", "0|1"]` tag on emitted kind-10312 presence events.
|
||||||
*/
|
*/
|
||||||
@@ -1076,7 +1076,7 @@ const val SPEAKING_TIMEOUT_MS: Long = 250L
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* How long a kind-7 reaction stays in
|
* How long a kind-7 reaction stays in
|
||||||
* [AudioRoomViewModel.recentReactions] before the eviction sweep
|
* [NestViewModel.recentReactions] before the eviction sweep
|
||||||
* drops it. Matches the duration of the floating-up animation in the
|
* drops it. Matches the duration of the floating-up animation in the
|
||||||
* SpeakerReactionOverlay.
|
* SpeakerReactionOverlay.
|
||||||
*/
|
*/
|
||||||
+12
-12
@@ -54,7 +54,7 @@ import kotlin.test.assertIs
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drives [AudioRoomViewModel] with a fake [NestsListenerConnector] and
|
* Drives [NestViewModel] with a fake [NestsListenerConnector] and
|
||||||
* verifies its state-flow transitions:
|
* verifies its state-flow transitions:
|
||||||
* - connect() shows Connecting before the underlying connector returns
|
* - connect() shows Connecting before the underlying connector returns
|
||||||
* - listener emits Connected → uiState collapses to Connected
|
* - listener emits Connected → uiState collapses to Connected
|
||||||
@@ -62,13 +62,13 @@ import kotlin.test.assertTrue
|
|||||||
* - setMuted updates uiState and propagates to active subscriptions
|
* - setMuted updates uiState and propagates to active subscriptions
|
||||||
* - disconnect() returns to Idle and tears down the listener
|
* - disconnect() returns to Idle and tears down the listener
|
||||||
*
|
*
|
||||||
* Subscribe-side (subscribeSpeaker → AudioRoomPlayer.play) is exercised via
|
* Subscribe-side (subscribeSpeaker → NestPlayer.play) is exercised via
|
||||||
* the existing nestsClient tests; the cross-module audio glue would need
|
* the existing nestsClient tests; the cross-module audio glue would need
|
||||||
* an internal SubscribeHandle constructor that's intentionally not visible
|
* an internal SubscribeHandle constructor that's intentionally not visible
|
||||||
* here.
|
* here.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class AudioRoomViewModelTest {
|
class NestViewModelTest {
|
||||||
@BeforeTest
|
@BeforeTest
|
||||||
fun setupMainDispatcher() {
|
fun setupMainDispatcher() {
|
||||||
Dispatchers.setMain(UnconfinedTestDispatcher())
|
Dispatchers.setMain(UnconfinedTestDispatcher())
|
||||||
@@ -143,7 +143,7 @@ class AudioRoomViewModelTest {
|
|||||||
|
|
||||||
// Defaults to true so a freshly-joined speaker advertises
|
// Defaults to true so a freshly-joined speaker advertises
|
||||||
// onstage=1 on the first heartbeat (the heartbeat in
|
// onstage=1 on the first heartbeat (the heartbeat in
|
||||||
// AudioRoomActivityContent reads this as the tag value).
|
// NestActivityContent reads this as the tag value).
|
||||||
assertTrue(vm.uiState.value.onStageNow)
|
assertTrue(vm.uiState.value.onStageNow)
|
||||||
vm.setOnStage(false)
|
vm.setOnStage(false)
|
||||||
assertFalse(vm.uiState.value.onStageNow)
|
assertFalse(vm.uiState.value.onStageNow)
|
||||||
@@ -332,20 +332,20 @@ class AudioRoomViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun publishingNowDerivesFromBroadcastStateAndMute() {
|
fun publishingNowDerivesFromBroadcastStateAndMute() {
|
||||||
// Idle / connecting / failed: never publishing.
|
// Idle / connecting / failed: never publishing.
|
||||||
assertFalse(AudioRoomUiState().publishingNow)
|
assertFalse(NestUiState().publishingNow)
|
||||||
assertFalse(AudioRoomUiState(broadcast = BroadcastUiState.Connecting).publishingNow)
|
assertFalse(NestUiState(broadcast = BroadcastUiState.Connecting).publishingNow)
|
||||||
assertFalse(AudioRoomUiState(broadcast = BroadcastUiState.Failed("boom")).publishingNow)
|
assertFalse(NestUiState(broadcast = BroadcastUiState.Failed("boom")).publishingNow)
|
||||||
|
|
||||||
// Broadcasting unmuted: publishing.
|
// Broadcasting unmuted: publishing.
|
||||||
assertTrue(
|
assertTrue(
|
||||||
AudioRoomUiState(broadcast = BroadcastUiState.Broadcasting(isMuted = false)).publishingNow,
|
NestUiState(broadcast = BroadcastUiState.Broadcasting(isMuted = false)).publishingNow,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Broadcasting muted: NOT publishing — matches the wire-tag
|
// Broadcasting muted: NOT publishing — matches the wire-tag
|
||||||
// semantics ("publishing=1" means actually pushing packets,
|
// semantics ("publishing=1" means actually pushing packets,
|
||||||
// not "hold a slot but silenced").
|
// not "hold a slot but silenced").
|
||||||
assertFalse(
|
assertFalse(
|
||||||
AudioRoomUiState(broadcast = BroadcastUiState.Broadcasting(isMuted = true)).publishingNow,
|
NestUiState(broadcast = BroadcastUiState.Broadcasting(isMuted = true)).publishingNow,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,8 +425,8 @@ class AudioRoomViewModelTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TestScope.newViewModel(connect: suspend (CoroutineScope) -> NestsListener): AudioRoomViewModel =
|
private fun TestScope.newViewModel(connect: suspend (CoroutineScope) -> NestsListener): NestViewModel =
|
||||||
AudioRoomViewModel(
|
NestViewModel(
|
||||||
httpClient = NoopNestsClient,
|
httpClient = NoopNestsClient,
|
||||||
transport = NoopWebTransportFactory,
|
transport = NoopWebTransportFactory,
|
||||||
decoderFactory = { NoopOpusDecoder },
|
decoderFactory = { NoopOpusDecoder },
|
||||||
@@ -452,7 +452,7 @@ class AudioRoomViewModelTest {
|
|||||||
mutable.value = s
|
mutable.value = s
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun subscribeSpeaker(speakerPubkeyHex: String): SubscribeHandle = error("subscribeSpeaker not exercised in these tests — see AudioRoomPlayerTest in :nestsClient")
|
override suspend fun subscribeSpeaker(speakerPubkeyHex: String): SubscribeHandle = error("subscribeSpeaker not exercised in these tests — see NestPlayerTest in :nestsClient")
|
||||||
|
|
||||||
override suspend fun close() {
|
override suspend fun close() {
|
||||||
closeCallCount++
|
closeCallCount++
|
||||||
+1
-1
@@ -38,7 +38,7 @@ import java.util.concurrent.atomic.AtomicLong
|
|||||||
* Moq-lite-backed [NestsListener]. Wraps a connected [MoqLiteSession]
|
* Moq-lite-backed [NestsListener]. Wraps a connected [MoqLiteSession]
|
||||||
* and exposes the same listener API the IETF [DefaultNestsListener]
|
* and exposes the same listener API the IETF [DefaultNestsListener]
|
||||||
* does, so [connectNestsListener] can swap the framing layer without
|
* does, so [connectNestsListener] can swap the framing layer without
|
||||||
* changing the public surface that [com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomViewModel]
|
* changing the public surface that [com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel]
|
||||||
* and downstream UI consume.
|
* and downstream UI consume.
|
||||||
*
|
*
|
||||||
* Subscription mapping per the audio-rooms NIP draft + nests JS
|
* Subscription mapping per the audio-rooms NIP draft + nests JS
|
||||||
|
|||||||
+3
-3
@@ -21,7 +21,7 @@
|
|||||||
package com.vitorpamplona.nestsclient
|
package com.vitorpamplona.nestsclient
|
||||||
|
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioCapture
|
import com.vitorpamplona.nestsclient.audio.AudioCapture
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioRoomMoqLiteBroadcaster
|
import com.vitorpamplona.nestsclient.audio.NestMoqLiteBroadcaster
|
||||||
import com.vitorpamplona.nestsclient.audio.OpusEncoder
|
import com.vitorpamplona.nestsclient.audio.OpusEncoder
|
||||||
import com.vitorpamplona.nestsclient.moq.lite.MoqLitePublisherHandle
|
import com.vitorpamplona.nestsclient.moq.lite.MoqLitePublisherHandle
|
||||||
import com.vitorpamplona.nestsclient.moq.lite.MoqLiteSession
|
import com.vitorpamplona.nestsclient.moq.lite.MoqLiteSession
|
||||||
@@ -76,7 +76,7 @@ class MoqLiteNestsSpeaker internal constructor(
|
|||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
val broadcaster =
|
val broadcaster =
|
||||||
AudioRoomMoqLiteBroadcaster(
|
NestMoqLiteBroadcaster(
|
||||||
capture = captureFactory(),
|
capture = captureFactory(),
|
||||||
encoder = encoderFactory(),
|
encoder = encoderFactory(),
|
||||||
publisher = publisher,
|
publisher = publisher,
|
||||||
@@ -139,7 +139,7 @@ class MoqLiteNestsSpeaker internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class MoqLiteBroadcastHandle(
|
internal class MoqLiteBroadcastHandle(
|
||||||
private val broadcaster: AudioRoomMoqLiteBroadcaster,
|
private val broadcaster: NestMoqLiteBroadcaster,
|
||||||
private val publisher: MoqLitePublisherHandle,
|
private val publisher: MoqLitePublisherHandle,
|
||||||
private val parent: MoqLiteNestsSpeaker,
|
private val parent: MoqLiteNestsSpeaker,
|
||||||
) : BroadcastHandle {
|
) : BroadcastHandle {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
package com.vitorpamplona.nestsclient
|
package com.vitorpamplona.nestsclient
|
||||||
|
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioCapture
|
import com.vitorpamplona.nestsclient.audio.AudioCapture
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioRoomBroadcaster
|
import com.vitorpamplona.nestsclient.audio.NestBroadcaster
|
||||||
import com.vitorpamplona.nestsclient.audio.OpusEncoder
|
import com.vitorpamplona.nestsclient.audio.OpusEncoder
|
||||||
import com.vitorpamplona.nestsclient.moq.MoqProtocolException
|
import com.vitorpamplona.nestsclient.moq.MoqProtocolException
|
||||||
import com.vitorpamplona.nestsclient.moq.MoqSession
|
import com.vitorpamplona.nestsclient.moq.MoqSession
|
||||||
@@ -133,7 +133,7 @@ sealed class NestsSpeakerState {
|
|||||||
* unit-test suite (`NestsSpeakerTest`) and for any future IETF
|
* unit-test suite (`NestsSpeakerTest`) and for any future IETF
|
||||||
* MoQ-transport target.
|
* MoQ-transport target.
|
||||||
*
|
*
|
||||||
* Wraps a connected [MoqSession] and plumbs an [AudioRoomBroadcaster]
|
* Wraps a connected [MoqSession] and plumbs an [NestBroadcaster]
|
||||||
* through it on [startBroadcasting]. Construction does NOT open the
|
* through it on [startBroadcasting]. Construction does NOT open the
|
||||||
* transport.
|
* transport.
|
||||||
*/
|
*/
|
||||||
@@ -168,7 +168,7 @@ class DefaultNestsSpeaker internal constructor(
|
|||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
val broadcaster =
|
val broadcaster =
|
||||||
AudioRoomBroadcaster(
|
NestBroadcaster(
|
||||||
capture = captureFactory(),
|
capture = captureFactory(),
|
||||||
encoder = encoderFactory(),
|
encoder = encoderFactory(),
|
||||||
publisher = publisher,
|
publisher = publisher,
|
||||||
@@ -236,7 +236,7 @@ class DefaultNestsSpeaker internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultBroadcastHandle(
|
internal class DefaultBroadcastHandle(
|
||||||
private val broadcaster: AudioRoomBroadcaster,
|
private val broadcaster: NestBroadcaster,
|
||||||
private val announce: MoqSession.AnnounceHandle,
|
private val announce: MoqSession.AnnounceHandle,
|
||||||
private val parent: DefaultNestsSpeaker,
|
private val parent: DefaultNestsSpeaker,
|
||||||
) : BroadcastHandle {
|
) : BroadcastHandle {
|
||||||
|
|||||||
+4
-4
@@ -28,7 +28,7 @@ import kotlinx.coroutines.cancelAndJoin
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inverse of [AudioRoomPlayer]: pulls PCM from an [AudioCapture], runs it
|
* Inverse of [NestPlayer]: pulls PCM from an [AudioCapture], runs it
|
||||||
* through an [OpusEncoder], and pushes the resulting Opus packets into a
|
* through an [OpusEncoder], and pushes the resulting Opus packets into a
|
||||||
* [MoqSession.TrackPublisher] as MoQ OBJECT_DATAGRAMs.
|
* [MoqSession.TrackPublisher] as MoQ OBJECT_DATAGRAMs.
|
||||||
*
|
*
|
||||||
@@ -46,7 +46,7 @@ import kotlinx.coroutines.launch
|
|||||||
* Encode failures are reported via [onError] but do NOT tear the loop down —
|
* Encode failures are reported via [onError] but do NOT tear the loop down —
|
||||||
* one bad frame shouldn't end the broadcast.
|
* one bad frame shouldn't end the broadcast.
|
||||||
*/
|
*/
|
||||||
class AudioRoomBroadcaster(
|
class NestBroadcaster(
|
||||||
private val capture: AudioCapture,
|
private val capture: AudioCapture,
|
||||||
private val encoder: OpusEncoder,
|
private val encoder: OpusEncoder,
|
||||||
private val publisher: MoqSession.TrackPublisher,
|
private val publisher: MoqSession.TrackPublisher,
|
||||||
@@ -66,8 +66,8 @@ class AudioRoomBroadcaster(
|
|||||||
* surface it to the user.
|
* surface it to the user.
|
||||||
*/
|
*/
|
||||||
fun start(onError: (AudioException) -> Unit = { /* swallow */ }) {
|
fun start(onError: (AudioException) -> Unit = { /* swallow */ }) {
|
||||||
check(!stopped) { "AudioRoomBroadcaster already stopped" }
|
check(!stopped) { "NestBroadcaster already stopped" }
|
||||||
check(job == null) { "AudioRoomBroadcaster.start already called" }
|
check(job == null) { "NestBroadcaster.start already called" }
|
||||||
|
|
||||||
// Audit round-2 MoQ #7: capture.start() can throw before we have
|
// Audit round-2 MoQ #7: capture.start() can throw before we have
|
||||||
// a job. Mark stopped + propagate so a half-started capture isn't
|
// a job. Mark stopped + propagate so a half-started capture isn't
|
||||||
+4
-4
@@ -28,7 +28,7 @@ import kotlinx.coroutines.cancelAndJoin
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirror of [AudioRoomBroadcaster] but driving a moq-lite
|
* Mirror of [NestBroadcaster] but driving a moq-lite
|
||||||
* [MoqLitePublisherHandle] instead of an IETF `MoqSession.TrackPublisher`.
|
* [MoqLitePublisherHandle] instead of an IETF `MoqSession.TrackPublisher`.
|
||||||
* Keeps the IETF broadcaster intact for the IETF unit-test suite while
|
* Keeps the IETF broadcaster intact for the IETF unit-test suite while
|
||||||
* letting the production speaker path use moq-lite.
|
* letting the production speaker path use moq-lite.
|
||||||
@@ -36,7 +36,7 @@ import kotlinx.coroutines.launch
|
|||||||
* Lifecycle and audit comments mirror the IETF version 1:1 — only the
|
* Lifecycle and audit comments mirror the IETF version 1:1 — only the
|
||||||
* sink type changes.
|
* sink type changes.
|
||||||
*/
|
*/
|
||||||
class AudioRoomMoqLiteBroadcaster(
|
class NestMoqLiteBroadcaster(
|
||||||
private val capture: AudioCapture,
|
private val capture: AudioCapture,
|
||||||
private val encoder: OpusEncoder,
|
private val encoder: OpusEncoder,
|
||||||
private val publisher: MoqLitePublisherHandle,
|
private val publisher: MoqLitePublisherHandle,
|
||||||
@@ -55,8 +55,8 @@ class AudioRoomMoqLiteBroadcaster(
|
|||||||
* the exception propagates.
|
* the exception propagates.
|
||||||
*/
|
*/
|
||||||
fun start(onError: (AudioException) -> Unit = { /* swallow */ }) {
|
fun start(onError: (AudioException) -> Unit = { /* swallow */ }) {
|
||||||
check(!stopped) { "AudioRoomMoqLiteBroadcaster already stopped" }
|
check(!stopped) { "NestMoqLiteBroadcaster already stopped" }
|
||||||
check(job == null) { "AudioRoomMoqLiteBroadcaster.start already called" }
|
check(job == null) { "NestMoqLiteBroadcaster.start already called" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
capture.start()
|
capture.start()
|
||||||
+4
-4
@@ -33,7 +33,7 @@ import kotlinx.coroutines.launch
|
|||||||
* through an [OpusDecoder] into an [AudioPlayer].
|
* through an [OpusDecoder] into an [AudioPlayer].
|
||||||
*
|
*
|
||||||
* Single-track. To play multiple speakers in a room, instantiate one
|
* Single-track. To play multiple speakers in a room, instantiate one
|
||||||
* [AudioRoomPlayer] per [com.vitorpamplona.nestsclient.moq.SubscribeHandle];
|
* [NestPlayer] per [com.vitorpamplona.nestsclient.moq.SubscribeHandle];
|
||||||
* each owns its own decoder (Opus state is per-track).
|
* each owns its own decoder (Opus state is per-track).
|
||||||
*
|
*
|
||||||
* Lifecycle:
|
* Lifecycle:
|
||||||
@@ -41,7 +41,7 @@ import kotlinx.coroutines.launch
|
|||||||
* - [stop] cancels the decode loop, stops the player, and releases the
|
* - [stop] cancels the decode loop, stops the player, and releases the
|
||||||
* decoder. Idempotent.
|
* decoder. Idempotent.
|
||||||
*/
|
*/
|
||||||
class AudioRoomPlayer(
|
class NestPlayer(
|
||||||
private val decoder: OpusDecoder,
|
private val decoder: OpusDecoder,
|
||||||
private val player: AudioPlayer,
|
private val player: AudioPlayer,
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
@@ -61,8 +61,8 @@ class AudioRoomPlayer(
|
|||||||
objects: Flow<MoqObject>,
|
objects: Flow<MoqObject>,
|
||||||
onError: (AudioException) -> Unit = { /* swallow */ },
|
onError: (AudioException) -> Unit = { /* swallow */ },
|
||||||
) {
|
) {
|
||||||
check(!stopped) { "AudioRoomPlayer already stopped" }
|
check(!stopped) { "NestPlayer already stopped" }
|
||||||
check(job == null) { "AudioRoomPlayer.play already called" }
|
check(job == null) { "NestPlayer.play already called" }
|
||||||
|
|
||||||
player.start()
|
player.start()
|
||||||
job =
|
job =
|
||||||
+6
-6
@@ -28,14 +28,14 @@ import kotlin.test.assertContentEquals
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class AudioRoomBroadcasterTest {
|
class NestBroadcasterTest {
|
||||||
@Test
|
@Test
|
||||||
fun pcm_frames_flow_through_encoder_into_publisher_in_order() =
|
fun pcm_frames_flow_through_encoder_into_publisher_in_order() =
|
||||||
runTest {
|
runTest {
|
||||||
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
|
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
|
||||||
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xAA.toByte()))
|
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xAA.toByte()))
|
||||||
val publisher = RecordingPublisher("track")
|
val publisher = RecordingPublisher("track")
|
||||||
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
|
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
|
||||||
|
|
||||||
broadcaster.start()
|
broadcaster.start()
|
||||||
// Capture exhausts after 3 frames (readFrame returns null) so the
|
// Capture exhausts after 3 frames (readFrame returns null) so the
|
||||||
@@ -61,7 +61,7 @@ class AudioRoomBroadcasterTest {
|
|||||||
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
|
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
|
||||||
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xBB.toByte()))
|
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xBB.toByte()))
|
||||||
val publisher = RecordingPublisher("track")
|
val publisher = RecordingPublisher("track")
|
||||||
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
|
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
|
||||||
|
|
||||||
broadcaster.setMuted(true)
|
broadcaster.setMuted(true)
|
||||||
broadcaster.start()
|
broadcaster.start()
|
||||||
@@ -81,7 +81,7 @@ class AudioRoomBroadcasterTest {
|
|||||||
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2)))
|
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2)))
|
||||||
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xCC.toByte()), warmupSkips = 1)
|
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xCC.toByte()), warmupSkips = 1)
|
||||||
val publisher = RecordingPublisher("track")
|
val publisher = RecordingPublisher("track")
|
||||||
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
|
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
|
||||||
|
|
||||||
broadcaster.start()
|
broadcaster.start()
|
||||||
capture.awaitDrained()
|
capture.awaitDrained()
|
||||||
@@ -100,7 +100,7 @@ class AudioRoomBroadcasterTest {
|
|||||||
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
|
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
|
||||||
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xDD.toByte()), failOnNthCall = 1)
|
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xDD.toByte()), failOnNthCall = 1)
|
||||||
val publisher = RecordingPublisher("track")
|
val publisher = RecordingPublisher("track")
|
||||||
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
|
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
|
||||||
val errors = mutableListOf<AudioException>()
|
val errors = mutableListOf<AudioException>()
|
||||||
|
|
||||||
broadcaster.start { errors.add(it) }
|
broadcaster.start { errors.add(it) }
|
||||||
@@ -117,7 +117,7 @@ class AudioRoomBroadcasterTest {
|
|||||||
val capture = ScriptedCapture(listOf(shortArrayOf(1)))
|
val capture = ScriptedCapture(listOf(shortArrayOf(1)))
|
||||||
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xEE.toByte()))
|
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xEE.toByte()))
|
||||||
val publisher = RecordingPublisher("track")
|
val publisher = RecordingPublisher("track")
|
||||||
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
|
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
|
||||||
|
|
||||||
broadcaster.start()
|
broadcaster.start()
|
||||||
capture.awaitDrained()
|
capture.awaitDrained()
|
||||||
+8
-8
@@ -31,7 +31,7 @@ import kotlin.test.assertEquals
|
|||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class AudioRoomPlayerTest {
|
class NestPlayerTest {
|
||||||
@Test
|
@Test
|
||||||
fun every_object_payload_is_decoded_and_enqueued_in_order() =
|
fun every_object_payload_is_decoded_and_enqueued_in_order() =
|
||||||
runTest {
|
runTest {
|
||||||
@@ -45,7 +45,7 @@ class AudioRoomPlayerTest {
|
|||||||
moqObject(byteArrayOf(0x04, 0x05, 0x06)),
|
moqObject(byteArrayOf(0x04, 0x05, 0x06)),
|
||||||
)
|
)
|
||||||
|
|
||||||
val sut = AudioRoomPlayer(decoder, player, this)
|
val sut = NestPlayer(decoder, player, this)
|
||||||
sut.play(objects)
|
sut.play(objects)
|
||||||
testScheduler.advanceUntilIdle()
|
testScheduler.advanceUntilIdle()
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class AudioRoomPlayerTest {
|
|||||||
moqObject(byteArrayOf(0x02)),
|
moqObject(byteArrayOf(0x02)),
|
||||||
)
|
)
|
||||||
|
|
||||||
val sut = AudioRoomPlayer(decoder, player, this)
|
val sut = NestPlayer(decoder, player, this)
|
||||||
sut.play(objects, onError = { errors.add(it) })
|
sut.play(objects, onError = { errors.add(it) })
|
||||||
testScheduler.advanceUntilIdle()
|
testScheduler.advanceUntilIdle()
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ class AudioRoomPlayerTest {
|
|||||||
runTest {
|
runTest {
|
||||||
val decoder = FakeOpusDecoder { byteToShorts(it) }
|
val decoder = FakeOpusDecoder { byteToShorts(it) }
|
||||||
val player = FakeAudioPlayer()
|
val player = FakeAudioPlayer()
|
||||||
val sut = AudioRoomPlayer(decoder, player, this)
|
val sut = NestPlayer(decoder, player, this)
|
||||||
sut.play(flowOf())
|
sut.play(flowOf())
|
||||||
testScheduler.advanceUntilIdle()
|
testScheduler.advanceUntilIdle()
|
||||||
sut.stop()
|
sut.stop()
|
||||||
@@ -115,7 +115,7 @@ class AudioRoomPlayerTest {
|
|||||||
fun play_cannot_be_called_twice_on_the_same_instance() =
|
fun play_cannot_be_called_twice_on_the_same_instance() =
|
||||||
runTest {
|
runTest {
|
||||||
val sut =
|
val sut =
|
||||||
AudioRoomPlayer(
|
NestPlayer(
|
||||||
FakeOpusDecoder { byteToShorts(it) },
|
FakeOpusDecoder { byteToShorts(it) },
|
||||||
FakeAudioPlayer(),
|
FakeAudioPlayer(),
|
||||||
this,
|
this,
|
||||||
@@ -129,7 +129,7 @@ class AudioRoomPlayerTest {
|
|||||||
fun play_after_stop_is_rejected() =
|
fun play_after_stop_is_rejected() =
|
||||||
runTest {
|
runTest {
|
||||||
val sut =
|
val sut =
|
||||||
AudioRoomPlayer(
|
NestPlayer(
|
||||||
FakeOpusDecoder { byteToShorts(it) },
|
FakeOpusDecoder { byteToShorts(it) },
|
||||||
FakeAudioPlayer(),
|
FakeAudioPlayer(),
|
||||||
this,
|
this,
|
||||||
@@ -143,7 +143,7 @@ class AudioRoomPlayerTest {
|
|||||||
runTest {
|
runTest {
|
||||||
val decoder = FakeOpusDecoder { ShortArray(0) }
|
val decoder = FakeOpusDecoder { ShortArray(0) }
|
||||||
val player = FakeAudioPlayer()
|
val player = FakeAudioPlayer()
|
||||||
val sut = AudioRoomPlayer(decoder, player, this)
|
val sut = NestPlayer(decoder, player, this)
|
||||||
sut.play(flowOf(moqObject(byteArrayOf(0x01))))
|
sut.play(flowOf(moqObject(byteArrayOf(0x01))))
|
||||||
testScheduler.advanceUntilIdle()
|
testScheduler.advanceUntilIdle()
|
||||||
assertEquals(0, player.queued.size)
|
assertEquals(0, player.queued.size)
|
||||||
@@ -157,7 +157,7 @@ class AudioRoomPlayerTest {
|
|||||||
val decoder = FakeOpusDecoder { byteToShorts(it) }
|
val decoder = FakeOpusDecoder { byteToShorts(it) }
|
||||||
val player = FakeAudioPlayer()
|
val player = FakeAudioPlayer()
|
||||||
|
|
||||||
val sut = AudioRoomPlayer(decoder, player, this)
|
val sut = NestPlayer(decoder, player, this)
|
||||||
sut.play(channel.receiveAsFlow())
|
sut.play(channel.receiveAsFlow())
|
||||||
testScheduler.runCurrent()
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
+1
-1
@@ -51,7 +51,7 @@ import kotlin.test.fail
|
|||||||
* - the broadcaster keeps the capture pump running while muted (so
|
* - the broadcaster keeps the capture pump running while muted (so
|
||||||
* unmute is sample-accurate)
|
* unmute is sample-accurate)
|
||||||
* - frames pushed during mute do NOT reach subscribers (`if (muted)
|
* - frames pushed during mute do NOT reach subscribers (`if (muted)
|
||||||
* continue` in [com.vitorpamplona.nestsclient.audio.AudioRoomMoqLiteBroadcaster])
|
* continue` in [com.vitorpamplona.nestsclient.audio.NestMoqLiteBroadcaster])
|
||||||
* - frames pushed before / after the muted window DO reach
|
* - frames pushed before / after the muted window DO reach
|
||||||
* subscribers, with the muted ones missing entirely (no silent
|
* subscribers, with the muted ones missing entirely (no silent
|
||||||
* placeholder frames)
|
* placeholder frames)
|
||||||
|
|||||||
+2
-2
@@ -23,7 +23,7 @@ package com.vitorpamplona.nestsclient.interop
|
|||||||
import com.vitorpamplona.nestsclient.NestsRoomConfig
|
import com.vitorpamplona.nestsclient.NestsRoomConfig
|
||||||
import com.vitorpamplona.nestsclient.OkHttpNestsClient
|
import com.vitorpamplona.nestsclient.OkHttpNestsClient
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioCapture
|
import com.vitorpamplona.nestsclient.audio.AudioCapture
|
||||||
import com.vitorpamplona.nestsclient.audio.AudioRoomMoqLiteBroadcaster
|
import com.vitorpamplona.nestsclient.audio.NestMoqLiteBroadcaster
|
||||||
import com.vitorpamplona.nestsclient.audio.OpusEncoder
|
import com.vitorpamplona.nestsclient.audio.OpusEncoder
|
||||||
import com.vitorpamplona.nestsclient.connectNestsListener
|
import com.vitorpamplona.nestsclient.connectNestsListener
|
||||||
import com.vitorpamplona.nestsclient.connectNestsSpeaker
|
import com.vitorpamplona.nestsclient.connectNestsSpeaker
|
||||||
@@ -57,7 +57,7 @@ import kotlin.test.fail
|
|||||||
* (`MoqLitePublisherHandle`), the listener subscribes by speaker
|
* (`MoqLitePublisherHandle`), the listener subscribes by speaker
|
||||||
* pubkey (`broadcast=pubkey`, `track="audio/data"`), the speaker
|
* pubkey (`broadcast=pubkey`, `track="audio/data"`), the speaker
|
||||||
* pushes deterministic Opus-shaped payloads through the
|
* pushes deterministic Opus-shaped payloads through the
|
||||||
* [AudioRoomMoqLiteBroadcaster] pipeline, and the listener collects
|
* [NestMoqLiteBroadcaster] pipeline, and the listener collects
|
||||||
* them via [SubscribeHandle.objects] (which the moq-lite adapter
|
* them via [SubscribeHandle.objects] (which the moq-lite adapter
|
||||||
* wraps over `MoqLiteFrame` so existing decoder / player code keeps
|
* wraps over `MoqLiteFrame` so existing decoder / player code keeps
|
||||||
* working).
|
* working).
|
||||||
|
|||||||
+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.quartz.experimental.audiorooms.admin
|
package com.vitorpamplona.quartz.experimental.nests.admin
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
+1
-1
@@ -50,7 +50,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* The `server` URL points at the moq-rs / moq-auth deployment's base
|
* The `server` URL points at the moq-rs / moq-auth deployment's base
|
||||||
* URL — Amethyst's `CreateAudioRoomSheet` uses it for both the
|
* URL — Amethyst's `CreateNestSheet` uses it for both the
|
||||||
* `service` (auth sidecar) and `endpoint` (WebTransport relay) tags
|
* `service` (auth sidecar) and `endpoint` (WebTransport relay) tags
|
||||||
* on the kind-30312 event, since nostrnests's reference deployment
|
* on the kind-30312 event, since nostrnests's reference deployment
|
||||||
* co-locates them. A future revision can split the two into separate
|
* co-locates them. A future revision can split the two into separate
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import com.vitorpamplona.quartz.experimental.attestations.recommendation.Attesto
|
|||||||
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||||
import com.vitorpamplona.quartz.experimental.audiorooms.admin.AdminCommandEvent
|
|
||||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent
|
import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent
|
||||||
@@ -36,6 +35,7 @@ import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStory
|
|||||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent
|
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent
|
||||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||||
|
import com.vitorpamplona.quartz.experimental.nests.admin.AdminCommandEvent
|
||||||
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent
|
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent
|
||||||
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.SoftwareAssetEvent
|
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.SoftwareAssetEvent
|
||||||
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
||||||
|
|||||||
+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.quartz.experimental.audiorooms.admin
|
package com.vitorpamplona.quartz.experimental.nests.admin
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
Reference in New Issue
Block a user