Merge branch 'mantra' into claude/african-dialect-convenience-de2f69
One file overlapped and git merged it cleanly: ChatRoomDetailScreen.kt, where mantra turned `parentChatRoomIds` into `parents` carrying each parent's room, and this branch added the "Add dialects from a list" button under the dialect list. Checked rather than assumed -- the merged file is mantra's version plus only this branch's additions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
4a11cc6ed9
12 files changed
+457
-244
No files matched your search
+2
-2
@@ -192,8 +192,8 @@ class DatabaseChatRepository(
|
||||
false
|
||||
}
|
||||
|
||||
override suspend fun parentsOf(chatRoomId: String): List<HexKey> = try {
|
||||
SubgroupManager.parentsOf(database, chatRoomId)
|
||||
override suspend fun parentsOf(chatRoomId: String): List<SubgroupManager.Parent> = try {
|
||||
SubgroupManager.parentGroupsOf(database, chatRoomId)
|
||||
} catch (e: Throwable) {
|
||||
logger.e("Error reading the parents of $chatRoomId", e)
|
||||
emptyList()
|
||||
|
||||
@@ -842,6 +842,35 @@ object SubgroupManager {
|
||||
database.groupKeyStateDao().getLocalByChatRoomId(chatRoomId)?.parentChatRoomIds()
|
||||
.orEmpty()
|
||||
|
||||
/**
|
||||
* A group this room is a child of, plus the room itself where this device
|
||||
* has it.
|
||||
*
|
||||
* [room] is the only place a parent's *name* can come from: a certificate
|
||||
* names the child, never the certifier, so a device that is not in the
|
||||
* parent room knows the lineage and not what to call it. Null is therefore
|
||||
* the ordinary case for a subgroup member who is not also a parent member,
|
||||
* and the id is all such a screen can honestly show.
|
||||
*/
|
||||
data class Parent(
|
||||
val chatRoomId: HexKey,
|
||||
val room: LocalChatRoom?,
|
||||
)
|
||||
|
||||
/**
|
||||
* [parentsOf], with each parent's room where this device holds one.
|
||||
*
|
||||
* The same verified set in the same order -- this only looks each id up, and
|
||||
* a parent whose room is missing stays in the list rather than being dropped.
|
||||
*/
|
||||
suspend fun parentGroupsOf(database: MantraDatabase, chatRoomId: String): List<Parent> =
|
||||
parentsOf(database, chatRoomId).map { parentChatRoomId ->
|
||||
Parent(
|
||||
chatRoomId = parentChatRoomId,
|
||||
room = database.chatRoomDao().findChatRoomById(parentChatRoomId)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* What a certificate arriving in a room is, or null if it is nothing this
|
||||
* room should believe.
|
||||
|
||||
+50
-47
@@ -19,7 +19,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
interface ChatRepository {
|
||||
suspend fun observeChatRoomListByPublicKey(publicKey: String): Flow<List<LocalChatRoom>>
|
||||
@@ -78,8 +78,12 @@ interface ChatRepository {
|
||||
* may be adopted by another later. A member welcomed in after the founding
|
||||
* holds no state and gets an empty list even when the room really is a
|
||||
* subgroup.
|
||||
*
|
||||
* Each carries the parent's room where this device has one, which is the
|
||||
* only place its name can come from -- a certificate names the child and
|
||||
* never the certifier.
|
||||
*/
|
||||
suspend fun parentsOf(chatRoomId: String): List<HexKey>
|
||||
suspend fun parentsOf(chatRoomId: String): List<SubgroupManager.Parent>
|
||||
|
||||
/**
|
||||
* One event the group signed, as the group signed it, or null when this
|
||||
@@ -308,7 +312,13 @@ interface ChatRepository {
|
||||
): MarmotReindexReport
|
||||
|
||||
companion object {
|
||||
val NO_OP_CHAT_REPOSITORY = object: ChatRepository {
|
||||
/**
|
||||
* A chat repository with no database behind it: every room is absent, every
|
||||
* membership change is refused with a reason, and no member throws. See
|
||||
* [NostrRepository.NO_OP_NOSTR_REPOSITORY] for why a `TODO()` here is a silent
|
||||
* misattribution rather than a loud failure, and for the rules a new member follows.
|
||||
*/
|
||||
val NO_OP_CHAT_REPOSITORY: ChatRepository = object : ChatRepository {
|
||||
override suspend fun requestGroupHistoryIfMissing(
|
||||
chatRoomId: String,
|
||||
userPublicKey: HexKey
|
||||
@@ -323,7 +333,9 @@ interface ChatRepository {
|
||||
override suspend fun subgroupsOf(chatRoomId: String): List<SubgroupManager.Subgroup> =
|
||||
emptyList()
|
||||
|
||||
override suspend fun parentsOf(chatRoomId: String): List<HexKey> = emptyList()
|
||||
override suspend fun parentsOf(
|
||||
chatRoomId: String
|
||||
): List<SubgroupManager.Parent> = emptyList()
|
||||
|
||||
override suspend fun groupSignedEvent(
|
||||
chatRoomId: String,
|
||||
@@ -338,9 +350,8 @@ interface ChatRepository {
|
||||
coordinatorPublicKey: HexKey,
|
||||
): String? = "No database"
|
||||
|
||||
override suspend fun observeChatRoomListByPublicKey(publicKey: String): Flow<List<LocalChatRoom>> {
|
||||
return flow { }
|
||||
}
|
||||
override suspend fun observeChatRoomListByPublicKey(publicKey: String): Flow<List<LocalChatRoom>> =
|
||||
flowOf(emptyList())
|
||||
|
||||
override suspend fun getChatRoomListByPublicKey(publicKey: String): List<LocalChatRoom> {
|
||||
return emptyList()
|
||||
@@ -375,22 +386,18 @@ interface ChatRepository {
|
||||
userPublicKey: HexKey
|
||||
): Boolean = false
|
||||
|
||||
override suspend fun getAllParticipantsWithPubKey(publicKey: HexKey): List<Participant> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun getAllParticipantsWithPubKey(publicKey: HexKey): List<Participant> =
|
||||
emptyList()
|
||||
|
||||
override suspend fun findDirectMessageRoom(userPublicKey: HexKey, peerPublicKey: HexKey): LocalChatRoom? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun findDirectMessageRoom(userPublicKey: HexKey, peerPublicKey: HexKey): LocalChatRoom? =
|
||||
null
|
||||
|
||||
override suspend fun getChatMessageListByChatRoomId(chatRoomId: String): List<LocalChatMessage> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override suspend fun observeChatMessageListByChatRoomId(chatRoomId: String): Flow<List<LocalChatMessage>> {
|
||||
return flow {
|
||||
}
|
||||
}
|
||||
override suspend fun observeChatMessageListByChatRoomId(chatRoomId: String): Flow<List<LocalChatMessage>> =
|
||||
flowOf(emptyList())
|
||||
|
||||
override suspend fun getOrCreateChatRoom(
|
||||
chatRoomId: String,
|
||||
@@ -407,13 +414,11 @@ interface ChatRepository {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun getMarmotKeyPackageForPublicKey(publicKey: HexKey): MarmotKeyPackage? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun getMarmotKeyPackageForPublicKey(publicKey: HexKey): MarmotKeyPackage? =
|
||||
null
|
||||
|
||||
override suspend fun observeMarmotKeyPackageForPublicKey(publicKey: HexKey): Flow<MarmotKeyPackage?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeMarmotKeyPackageForPublicKey(publicKey: HexKey): Flow<MarmotKeyPackage?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun createNip17ChatRoom(
|
||||
userPublicKey: HexKey,
|
||||
@@ -424,23 +429,30 @@ interface ChatRepository {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* The empty id, meaning no room was created.
|
||||
*
|
||||
* The only member here whose return type cannot say "nothing": it owes the id
|
||||
* of a room, and `String` has no absent value. Both creators beside it can say
|
||||
* they failed -- [createNip17ChatRoom] returns null, [createMarmotGroup] an
|
||||
* [MarmotGroupCreation.Outcome] -- so this one's signature is the odd one out
|
||||
* rather than this body being the odd answer. Nothing reaches it in practice:
|
||||
* `ChatRoomMessagingViewModel.initiateNewChat` calls it only once a profile and
|
||||
* a key package have both resolved, and against this object neither ever does.
|
||||
*/
|
||||
override suspend fun createMlsDirectMessage(
|
||||
name: String?,
|
||||
description: String?,
|
||||
userPublicKey: HexKey,
|
||||
peerPublicKey: HexKey,
|
||||
peerKeyPackage: MarmotKeyPackage
|
||||
): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
): String = ""
|
||||
|
||||
override suspend fun inviteMember(
|
||||
localChatRoom: LocalChatRoom,
|
||||
peerPublicKey: HexKey,
|
||||
peerKeyPackage: MarmotKeyPackage
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun addMembers(
|
||||
localChatRoom: LocalChatRoom,
|
||||
@@ -464,45 +476,36 @@ interface ChatRepository {
|
||||
localChatRoom: LocalChatRoom,
|
||||
messageType: Kind,
|
||||
directMessageRecipientPublicKey: HexKey?
|
||||
) {
|
||||
// TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override fun updateChatRoomSubject(publicKey: String, subject: String): ChatRoom? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun observeUnsealedGiftWrapPayloads(publicKey: HexKey): Flow<List<GiftWrapPayload>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeUnsealedGiftWrapPayloads(publicKey: HexKey): Flow<List<GiftWrapPayload>> =
|
||||
flowOf(emptyList())
|
||||
|
||||
override suspend fun sealGiftWrapPayload(
|
||||
giftWrapPayload: GiftWrapPayload,
|
||||
nostrSignerSync: NostrSignerSync
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
// None of the three calls onCompletion: nothing was left or deleted, and each
|
||||
// of these callbacks navigates away from a room that is still there.
|
||||
override suspend fun leaveChatRoom(
|
||||
localChatRoom: LocalChatRoom,
|
||||
onCompletion: () -> Unit
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun softDeleteChatRoom(
|
||||
chatRoom: ChatRoom,
|
||||
onCompletion: () -> Unit
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun deleteChatRoom(
|
||||
chatRoom: ChatRoom,
|
||||
onCompletion: () -> Unit
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun reindexMarmotGroupEvents(
|
||||
chatRoomId: String,
|
||||
|
||||
+19
-23
@@ -5,6 +5,7 @@ import press.mantra.compose.database.model.MarmotKeyPackageBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
interface MarmotRepository {
|
||||
|
||||
@@ -33,40 +34,35 @@ interface MarmotRepository {
|
||||
suspend fun encryptAndSendMarmotInnerEvent(marmotInnerEvent: MarmotInnerEvent, nostrSignerSync: NostrSignerSync)
|
||||
|
||||
companion object {
|
||||
val NO_OP_MARMOT_KEY_PACKAGE_BUNDLE = object: MarmotRepository {
|
||||
/**
|
||||
* A repository with no key packages behind it: inert, and no member throws. See
|
||||
* [NostrRepository.NO_OP_NOSTR_REPOSITORY] for why a `TODO()` here is a silent
|
||||
* misattribution rather than a loud failure, and for the rules a new member follows.
|
||||
*/
|
||||
val NO_OP_MARMOT_KEY_PACKAGE_BUNDLE: MarmotRepository = object : MarmotRepository {
|
||||
override suspend fun publishMarmotKeyPackageBundle(
|
||||
publicKey: HexKey,
|
||||
nsecPassword: String
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun rotateMarmotKeyPackageBundle(marmotKeyPackageBundle: MarmotKeyPackageBundle) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun rotateMarmotKeyPackageBundle(marmotKeyPackageBundle: MarmotKeyPackageBundle) = Unit
|
||||
|
||||
override suspend fun observeMarmotKeyPackageBundles(publicKey: HexKey): Flow<List<MarmotKeyPackageBundle>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeMarmotKeyPackageBundles(publicKey: HexKey): Flow<List<MarmotKeyPackageBundle>> =
|
||||
flowOf(emptyList())
|
||||
|
||||
override suspend fun getMarmotKeyPackageBundles(publicKey: HexKey): List<MarmotKeyPackageBundle> {
|
||||
return emptyList()
|
||||
}
|
||||
override suspend fun getMarmotKeyPackageBundles(publicKey: HexKey): List<MarmotKeyPackageBundle> =
|
||||
emptyList()
|
||||
|
||||
override suspend fun observeUnprocessedMarmotInnerEvents(publicKey: HexKey): Flow<List<MarmotInnerEvent>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeUnprocessedMarmotInnerEvents(publicKey: HexKey): Flow<List<MarmotInnerEvent>> =
|
||||
flowOf(emptyList())
|
||||
|
||||
override suspend fun observeActiveMarmotKeyPackageBundle(publicKey: HexKey): Flow<MarmotKeyPackageBundle?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeActiveMarmotKeyPackageBundle(publicKey: HexKey): Flow<MarmotKeyPackageBundle?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun encryptAndSendMarmotInnerEvent(
|
||||
marmotInnerEvent: MarmotInnerEvent,
|
||||
nostrSignerSync: NostrSignerSync
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
-133
@@ -20,6 +20,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
interface NostrRepository {
|
||||
suspend fun getLocalAccounts(): List<LocalAccount>
|
||||
@@ -209,78 +210,97 @@ interface NostrRepository {
|
||||
suspend fun rescheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List<BroadcastNostrEventRequest>)
|
||||
|
||||
companion object {
|
||||
val NO_OP_NOSTR_REPOSITORY = object : NostrRepository {
|
||||
override suspend fun getLocalAccounts(): List<LocalAccount> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
/**
|
||||
* A repository with nothing behind it: every question answers "nothing here",
|
||||
* every write goes nowhere, and **no member throws**.
|
||||
*
|
||||
* Previews and test fixtures are the only callers, and both want inertness. It is
|
||||
* written out here because these members were once `TODO()`, and a `TODO()` in a
|
||||
* no-op is not a loud failure -- it is a silent misattribution. Callers reach most
|
||||
* of them from a `viewModelScope.launch(Dispatchers.IO)` that nobody awaits, so the
|
||||
* `NotImplementedError` escapes to the platform handler, is stashed by
|
||||
* kotlinx-coroutines-test's `ExceptionCollector`, and is re-thrown as
|
||||
* `UncaughtExceptionsBeforeTest` inside *the next* `runTest` -- under that test's
|
||||
* name, with the real culprit buried as a suppressed cause. One `TODO()` here
|
||||
* (`queueNegentropySynchronizeRequest`, reached from
|
||||
* `ChatMessageListViewModel.scheduleSynchronization`) failed `NavigationRoutingTest`
|
||||
* four times a run for a call that test never made.
|
||||
*
|
||||
* So the rules for a member added here, which the other three no-op repositories
|
||||
* follow too:
|
||||
*
|
||||
* - **Answer the call, never stub it out.** `NoOpRepositoryTotalityJvmTest` reads
|
||||
* these four objects and fails if an unimplemented stub reappears in one.
|
||||
* - **A flow emits its empty value once** -- `flowOf(null)`, `flowOf(emptyList())` --
|
||||
* rather than `flow { }`. A screen collecting an empty flow waits in Loading
|
||||
* forever; one collecting `flowOf(null)` renders its empty state, which is what
|
||||
* a preview is for.
|
||||
* - **A callback is not invoked.** Nothing happened, and calling `onCompletion` or
|
||||
* `onSuccess` would say it did -- letting a test pass on the fixture rather than
|
||||
* on the code under test.
|
||||
* - **Throwing is never the way to be loud.** A member that a test must not call is
|
||||
* the test's assertion to make, not the fixture's: override it with a recorder and
|
||||
* assert it stayed untouched. An exception thrown here lands on whichever test
|
||||
* runs next.
|
||||
*
|
||||
* A test that needs a real answer from one member overrides just that one:
|
||||
* `object : NostrRepository by NO_OP_NOSTR_REPOSITORY { … }`.
|
||||
*/
|
||||
val NO_OP_NOSTR_REPOSITORY: NostrRepository = object : NostrRepository {
|
||||
override suspend fun getLocalAccounts(): List<LocalAccount> = emptyList()
|
||||
|
||||
override suspend fun observeLocalAccount(publicKey: HexKey): Flow<LocalAccount?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeLocalAccount(publicKey: HexKey): Flow<LocalAccount?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun getProfileWithPublicKey(publicKey: HexKey): Profile? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun getProfileWithPublicKey(publicKey: HexKey): Profile? = null
|
||||
|
||||
override suspend fun observeProfileWithPublicKey(publicKey: HexKey): Flow<Profile?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeProfileWithPublicKey(publicKey: HexKey): Flow<Profile?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun observeRelation(
|
||||
publicKey: HexKey,
|
||||
activePublicKey: HexKey
|
||||
): Flow<List<Connection>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
): Flow<List<Connection>> = flowOf(emptyList())
|
||||
|
||||
override suspend fun deleteConnection(connection: Connection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun deleteConnection(connection: Connection) = Unit
|
||||
|
||||
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<List<UnsignedNostrEvent>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<List<UnsignedNostrEvent>> =
|
||||
flowOf(emptyList())
|
||||
|
||||
override suspend fun observePendingBroadcastNostrEventRequests(): Flow<LocalBroadcastNostrEventRequest> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observePendingBroadcastNostrEventRequests(): Flow<LocalBroadcastNostrEventRequest?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun reopenInbox(activeKeyPair: KeyPair): Int = 0
|
||||
|
||||
override suspend fun observePendingSynchronizeNostrEventRequests(ownerPublicKey: HexKey): Flow<SynchronizeNostrEventRequest?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observePendingSynchronizeNostrEventRequests(ownerPublicKey: HexKey): Flow<SynchronizeNostrEventRequest?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun observePendingNegentropySynchronizeRequests(ownerPublicKey: HexKey): Flow<NegentropySynchronizeRequest?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observePendingNegentropySynchronizeRequests(ownerPublicKey: HexKey): Flow<NegentropySynchronizeRequest?> =
|
||||
flowOf(null)
|
||||
|
||||
// onCompletion is deliberately not called: no profile was created, and a
|
||||
// fixture that reports completion is a fixture a test can pass against.
|
||||
override suspend fun createNewProfile(
|
||||
publicKey: HexKey,
|
||||
name: String?,
|
||||
biography: String?,
|
||||
onCompletion: suspend () -> Unit
|
||||
) {
|
||||
TODO("")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun signInToProfile(publicKey: HexKey) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun signInToProfile(publicKey: HexKey) = Unit
|
||||
|
||||
override suspend fun setUpProfileForExistingKey(
|
||||
publicKey: HexKey,
|
||||
placeholderUnsignedNostrEventId: Long,
|
||||
name: String?,
|
||||
biography: String?,
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun forgetLocalAccount(publicKey: HexKey) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun forgetLocalAccount(publicKey: HexKey) = Unit
|
||||
|
||||
// Neither onSuccess nor onError: the note was not written, and it did not fail
|
||||
// to be written either -- there was nowhere to write it. A test that wants
|
||||
// either branch overrides this member and calls the one it is testing.
|
||||
override suspend fun createNewTextNote(
|
||||
publicKey: HexKey,
|
||||
mentionedPublicKeys: List<HexKey>,
|
||||
@@ -289,74 +309,50 @@ interface NostrRepository {
|
||||
quotedNostrEvent: LocalNostrEvent?,
|
||||
onError: () -> Unit,
|
||||
onSuccess: () -> Unit
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? {
|
||||
return null
|
||||
}
|
||||
override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? =
|
||||
null
|
||||
|
||||
override suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List<UnsignedNostrEvent>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List<UnsignedNostrEvent>) = Unit
|
||||
|
||||
override suspend fun wipeDatabase() {
|
||||
}
|
||||
override suspend fun wipeDatabase() = Unit
|
||||
|
||||
override suspend fun publishNostrEvent(
|
||||
unsignedNostrEvent: UnsignedNostrEvent,
|
||||
nostrEvent: NostrEvent,
|
||||
relayURLs: List<String>,
|
||||
activeKeyPair: KeyPair
|
||||
) {
|
||||
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun broadcastProcessed(
|
||||
broadcastNostrEventRequest: BroadcastNostrEventRequest,
|
||||
status: String
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun requeueStaleBroadcastNostrEventRequests(): Int {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun requeueStaleBroadcastNostrEventRequests(): Int = 0
|
||||
|
||||
override suspend fun synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest: SynchronizeNostrEventRequest) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest: SynchronizeNostrEventRequest) = Unit
|
||||
|
||||
override suspend fun synchronizeNostrEventRequestCompleted(synchronizeNostrEventRequest: SynchronizeNostrEventRequest) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun synchronizeNostrEventRequestCompleted(synchronizeNostrEventRequest: SynchronizeNostrEventRequest) = Unit
|
||||
|
||||
override suspend fun negentropySynchronizeRequestProcessed(negentropySynchronizeRequest: NegentropySynchronizeRequest) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun negentropySynchronizeRequestProcessed(negentropySynchronizeRequest: NegentropySynchronizeRequest) = Unit
|
||||
|
||||
override suspend fun saveBroadcastReceipt(broadcastNostrEventReceipt: BroadcastNostrEventReceipt) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun saveBroadcastReceipt(broadcastNostrEventReceipt: BroadcastNostrEventReceipt) = Unit
|
||||
|
||||
override suspend fun saveNostrEvent(
|
||||
nostrEvent: NostrEvent,
|
||||
synchronizeNostrEventRequest: SynchronizeNostrEventRequest,
|
||||
synchronizationRelayURLs: List<String>,
|
||||
activeKeyPair: KeyPair
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun saveNostrEvent(
|
||||
nostrEvent: NostrEvent,
|
||||
negentropySynchronizeRequest: NegentropySynchronizeRequest,
|
||||
synchronizationRelayURLs: List<String>,
|
||||
activeKeyPair: KeyPair
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun saveNostrEvent(
|
||||
nostrEvent: NostrEvent,
|
||||
@@ -364,82 +360,54 @@ interface NostrRepository {
|
||||
synchronizationRelayURLs: List<String>,
|
||||
level: Int,
|
||||
activeKeyPair: KeyPair
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun queueSynchronizeNostrEvent(synchronizeNostrEventRequests: List<SynchronizeNostrEventRequest>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun queueSynchronizeNostrEvent(synchronizeNostrEventRequests: List<SynchronizeNostrEventRequest>) = Unit
|
||||
|
||||
override suspend fun queueNegentropySynchronizeRequest(negentropySynchronizeRequests: List<NegentropySynchronizeRequest>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun queueNegentropySynchronizeRequest(negentropySynchronizeRequests: List<NegentropySynchronizeRequest>) = Unit
|
||||
|
||||
override suspend fun observeSynchronizeNostrEventRequestByPurposeAndStatusCount(purpose: String): Flow<Int> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeSynchronizeNostrEventRequestByPurposeAndStatusCount(purpose: String): Flow<Int> =
|
||||
flowOf(0)
|
||||
|
||||
override suspend fun getNostrEvent(
|
||||
publicKey: HexKey,
|
||||
kind: Kind
|
||||
): NostrEvent? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
): NostrEvent? = null
|
||||
|
||||
override suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>> = flowOf(emptyList())
|
||||
|
||||
override suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow<List<LocalNostrEvent>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow<List<LocalNostrEvent>> =
|
||||
flowOf(emptyList())
|
||||
|
||||
override suspend fun getNegentropicNostrFeedIds(
|
||||
synchronizationFilters: Array<SynchronizationFilter>,
|
||||
applyLimits: Boolean
|
||||
): List<NostrEvent> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
): List<NostrEvent> = emptyList()
|
||||
|
||||
override suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun searchableProfiles(excludingPublicKeys: Array<HexKey>): List<Profile> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun searchableProfiles(excludingPublicKeys: Array<HexKey>): List<Profile> =
|
||||
emptyList()
|
||||
|
||||
override suspend fun filterProfiles(searchTerm: String): List<LocalProfile> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun filterProfiles(searchTerm: String): List<LocalProfile> = emptyList()
|
||||
|
||||
override suspend fun observeProfileWithFollowers(publicKey: String): Flow<LocalProfileWithFollowers?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeProfileWithFollowers(publicKey: String): Flow<LocalProfileWithFollowers?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun observeProfileWithFollowing(publicKey: String): Flow<LocalProfileWithFollowing?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun observeProfileWithFollowing(publicKey: String): Flow<LocalProfileWithFollowing?> =
|
||||
flowOf(null)
|
||||
|
||||
override suspend fun getRecentSearches(): List<RecentSearch> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun getRecentSearches(): List<RecentSearch> = emptyList()
|
||||
|
||||
override suspend fun removeRecentSearch(recentSearch: RecentSearch) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun removeRecentSearch(recentSearch: RecentSearch) = Unit
|
||||
|
||||
override suspend fun saveSearchQuery(
|
||||
query: String,
|
||||
synchronizationFilterArray: press.mantra.compose.database.model.typealiases.SynchronizationFilterArray?
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
) = Unit
|
||||
|
||||
override suspend fun rescheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List<BroadcastNostrEventRequest>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override suspend fun rescheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List<BroadcastNostrEventRequest>) = Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -7,10 +7,15 @@ interface SearchRepository {
|
||||
)
|
||||
|
||||
companion object {
|
||||
val NO_OP_SEARCH_REPOSITORY = object: SearchRepository {
|
||||
override suspend fun submitSearch(synchronizeNostrEventRequests: List<press.mantra.compose.database.model.SynchronizeNostrEventRequest>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
/**
|
||||
* A search that reaches no relay: inert, and the one member does not throw. See
|
||||
* [NostrRepository.NO_OP_NOSTR_REPOSITORY] for why a `TODO()` here is a silent
|
||||
* misattribution rather than a loud failure.
|
||||
*/
|
||||
val NO_OP_SEARCH_REPOSITORY: SearchRepository = object : SearchRepository {
|
||||
override suspend fun submitSearch(
|
||||
synchronizeNostrEventRequests: List<press.mantra.compose.database.model.SynchronizeNostrEventRequest>
|
||||
) = Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
-14
@@ -285,15 +285,40 @@ fun ChatRoomDetailScreen(
|
||||
// later. They are a set rather than a path -- neither
|
||||
// is "the" parent and the order says nothing about
|
||||
// which came first -- so each gets an equal row.
|
||||
chatRoomDetailUIState.parentChatRoomIds.forEach { parentChatRoomId ->
|
||||
item(key = "parent-$parentChatRoomId") {
|
||||
chatRoomDetailUIState.parents.forEach { parent ->
|
||||
item(key = "parent-${parent.chatRoomId}") {
|
||||
// The parent's own name, which only its room
|
||||
// can give: a certificate names the child and
|
||||
// never the certifier, so a member of the
|
||||
// subgroup who is not also in the parent has
|
||||
// the id and nothing else to call it by.
|
||||
val name = parent.room
|
||||
?.chatRoom
|
||||
?.subject
|
||||
?.takeIf { it.isNotBlank() }
|
||||
|
||||
// Shown either way -- as the headline where
|
||||
// there is no name, and under it where there
|
||||
// is, since it is the part that can be
|
||||
// compared against what the lineage says.
|
||||
val identifier: @Composable () -> Unit = {
|
||||
Text(
|
||||
text = parent.chatRoomId
|
||||
.take(16)
|
||||
.inComparableGroups(),
|
||||
fontFamily = FontFamily.Monospace,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis
|
||||
)
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
onNavigateToRoute.invoke(
|
||||
ChatRoomDetailRoute(
|
||||
activeUserPublicKey = activeUserPublicKey,
|
||||
chatRoomId = parentChatRoomId,
|
||||
chatRoomId = parent.chatRoomId,
|
||||
relayHint = relayHint
|
||||
)
|
||||
)
|
||||
@@ -312,19 +337,25 @@ fun ChatRoomDetailScreen(
|
||||
contentDescription = Decorative
|
||||
)
|
||||
},
|
||||
headlineContent = {
|
||||
// The label keeps its place above the
|
||||
// name rather than being replaced by
|
||||
// it: a card carrying a group's name
|
||||
// and nothing else says nothing about
|
||||
// which way the lineage runs, and the
|
||||
// signing key's card -- same icon
|
||||
// column, same shape -- sits directly
|
||||
// above this one.
|
||||
overlineContent = {
|
||||
Text(text = stringResource(Res.string.parent_group))
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = parentChatRoomId
|
||||
.take(16)
|
||||
.inComparableGroups(),
|
||||
fontFamily = FontFamily.Monospace,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis
|
||||
)
|
||||
}
|
||||
headlineContent = {
|
||||
if (name != null) {
|
||||
Text(text = name)
|
||||
} else {
|
||||
identifier()
|
||||
}
|
||||
},
|
||||
supportingContent = identifier.takeIf { name != null }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ class ChatRoomDetailViewModel(
|
||||
groupKeyState = chatRepository.groupKeyState(chatRoomId),
|
||||
signedGroupKeyStateEvent = chatRepository.signedGroupKeyStateEvent(chatRoomId),
|
||||
subgroups = chatRepository.subgroupsOf(chatRoomId),
|
||||
parentChatRoomIds = chatRepository.parentsOf(chatRoomId),
|
||||
parents = chatRepository.parentsOf(chatRoomId),
|
||||
canAddSubgroup = localChatRoom.chatRoom.mlsGroupState != null &&
|
||||
chatRepository.canSign(chatRoomId),
|
||||
adoptionOffers = chatRepository.adoptionOffersFor(chatRoomId),
|
||||
|
||||
+5
-2
@@ -45,15 +45,18 @@ sealed interface ChatRoomDetailUIState {
|
||||
*/
|
||||
val subgroups: List<SubgroupManager.Subgroup> = emptyList(),
|
||||
/**
|
||||
* The group this room is a subgroup of, or null when it is nobody's
|
||||
* The groups this room is a subgroup of, empty when it is nobody's
|
||||
* child.
|
||||
*
|
||||
* Verified: it comes off the room's key state, which only carries a
|
||||
* parent whose certificate was checked. A member welcomed in after the
|
||||
* founding holds no state and so sees nothing here, even in a room that
|
||||
* really is a subgroup.
|
||||
*
|
||||
* Each carries the parent's room where this device holds one, so the
|
||||
* card can name the group rather than only identify it.
|
||||
*/
|
||||
val parentChatRoomIds: List<HexKey> = emptyList(),
|
||||
val parents: List<SubgroupManager.Parent> = emptyList(),
|
||||
/**
|
||||
* Whether this device could open a subgroup here at all.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user