commit 5f8c8a46753eadcfa08320ae826922325ef5bf38
parent 29dc79f8f0d0135646c43cbb8e010136c17fdd2f
Author: t3sserakt <t3sserakt@posteo.de>
Date: Thu, 19 Mar 2026 19:49:01 +0100
UI Waiting for first account list refresh. Fixed wrong enum for message kind.
Diffstat:
7 files changed, 406 insertions(+), 321 deletions(-)
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt
@@ -26,6 +26,8 @@ package org.gnunet.gnunetmessenger
import android.os.Bundle
import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
@@ -36,6 +38,8 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.gnunet.gnunetmessenger.model.ChatAccount
@@ -62,6 +66,8 @@ class MainActivity : AppCompatActivity() {
private lateinit var handle: ChatHandle
private val chatReady = CompletableDeferred<ChatHandle>()
+ private val initialRefreshReady = CompletableDeferred<Unit>()
+ private val accountRefreshEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
private val chatOverviewViewModel: ChatOverviewViewModel by viewModels()
val contactListViewModel: ContactListViewModel by viewModels()
@@ -95,12 +101,15 @@ class MainActivity : AppCompatActivity() {
if (!chatReady.isCompleted) {
chatReady.complete(handle)
}
- Log.d(TAG, "Chat is ready with handle=${handle.pointer}")
+ Log.d(TAG, "Chat handle ready: ${handle.pointer}")
} catch (t: Throwable) {
- Log.e(TAG, "Chat initialization failed", t)
+ Log.e(TAG, "Chat initialization failed before first refresh", t)
if (!chatReady.isCompleted) {
chatReady.completeExceptionally(t)
}
+ if (!initialRefreshReady.isCompleted) {
+ initialRefreshReady.completeExceptionally(t)
+ }
}
}
@@ -131,10 +140,22 @@ class MainActivity : AppCompatActivity() {
return chatReady.await()
}
+ suspend fun awaitInitialDataReady(): ChatHandle {
+ val readyHandle = chatReady.await()
+ initialRefreshReady.await()
+ return readyHandle
+ }
+
+ fun accountRefreshFlow(): SharedFlow<Unit> = accountRefreshEvents
+
fun isChatReady(): Boolean {
return chatReady.isCompleted && handle.pointer != 0L
}
+ fun hasInitialRefresh(): Boolean {
+ return initialRefreshReady.isCompleted
+ }
+
private fun processChatMessage(chatContext: ChatContext, chatMessage: ChatMessage) {
requireNotNull(chatMessage)
@@ -143,6 +164,12 @@ class MainActivity : AppCompatActivity() {
}
MessageKind.REFRESH -> {
+ Log.d(TAG, "Received REFRESH")
+ if (!initialRefreshReady.isCompleted) {
+ initialRefreshReady.complete(Unit)
+ Log.d(TAG, "Initial refresh barrier released")
+ }
+ accountRefreshEvents.tryEmit(Unit)
}
MessageKind.LOGIN -> {
@@ -217,10 +244,7 @@ class MainActivity : AppCompatActivity() {
val uuid = gnunetChat.getUserPointerForContext(chatContext)
val localChatContext = chats[uuid]
chatMessage.type =
- if (gnunetChat.getContactKey(chatMessage.sender!!) == gnunetChat.getProfileKey(
- handle
- )
- ) {
+ if (gnunetChat.getContactKey(chatMessage.sender!!) == gnunetChat.getProfileKey(handle)) {
ChatMessageType.OWN
} else {
ChatMessageType.OTHER
@@ -317,7 +341,7 @@ class MainActivity : AppCompatActivity() {
chatOverviewViewModel.setChats(summaries)
}
- override fun onCreateOptionsMenu(menu: android.view.Menu?): Boolean {
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
val current = currentAccount
menu?.findItem(R.id.menu_current_account)?.title =
@@ -325,14 +349,14 @@ class MainActivity : AppCompatActivity() {
return true
}
- override fun onPrepareOptionsMenu(menu: android.view.Menu?): Boolean {
+ override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
val current = currentAccount
menu?.findItem(R.id.menu_current_account)?.title =
"Account: ${current?.name ?: "No active account!"}"
return super.onPrepareOptionsMenu(menu)
}
- override fun onOptionsItemSelected(item: android.view.MenuItem): Boolean {
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_current_account -> {
if (currentAccount != null) {
@@ -355,13 +379,15 @@ class MainActivity : AppCompatActivity() {
}
R.id.menu_new_group -> {
- val action = NavGraphDirections.actionGlobalCreateGroupOrPlatformFragment(isGroup = true)
+ val action =
+ NavGraphDirections.actionGlobalCreateGroupOrPlatformFragment(isGroup = true)
navController.navigate(action)
true
}
R.id.menu_new_platform -> {
- val action = NavGraphDirections.actionGlobalCreateGroupOrPlatformFragment(isGroup = false)
+ val action =
+ NavGraphDirections.actionGlobalCreateGroupOrPlatformFragment(isGroup = false)
navController.navigate(action)
true
}
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/MessageKind.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/MessageKind.kt
@@ -25,13 +25,13 @@
package org.gnunet.gnunetmessenger.model
enum class MessageKind(val code: Int) {
- WARNING(0), REFRESH(1), LOGIN(2), LOGOUT(3),
- CREATED_ACCOUNT(4), UPDATE_ACCOUNT(5),
- UPDATE_CONTEXT(6), JOIN(7), LEAVE(8),
- CONTACT(9), SHARED_ATTRIBUTES(10),
- INVITATION(11), TEXT(12), FILE(13),
- DELETION(14), TAG(15), ATTRIBUTES(16),
- DISCOURSE(17), DATA(18);
+ UNKOWN (code = 0),WARNING(1), REFRESH(2), LOGIN(3), LOGOUT(4),
+ CREATED_ACCOUNT(5), DELETE_ACCOUNT(code = 6), UPDATE_ACCOUNT(7),
+ UPDATE_CONTEXT(8), JOIN(9), LEAVE(10),
+ CONTACT(11),
+ INVITATION(12), TEXT(13), FILE(14),
+ DELETION(15), TAG(16), ATTRIBUTES(17), SHARED_ATTRIBUTES(18),
+ DISCOURSE(19), DATA(20);
companion object {
fun fromCode(code: Int): MessageKind =
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt
@@ -1,30 +1,5 @@
-/*
- This file is part of GNUnet.
- Copyright (C) 2021--2025 GNUnet e.V.
-
- GNUnet is free software: you can redistribute it and/or modify it
- under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License,
- or (at your option) any later version.
-
- GNUnet is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- SPDX-License-Identifier: AGPL3.0-or-later
- */
-/*
- * @author t3sserakt
- * @file GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt
- */
-
package org.gnunet.gnunetmessenger.service
-
import org.gnunet.gnunetmessenger.model.ChatAccount
import org.gnunet.gnunetmessenger.model.ChatContact
import org.gnunet.gnunetmessenger.model.ChatContext
@@ -37,53 +12,72 @@ import org.gnunet.gnunetmessenger.model.MessageKind
import org.gnunet.gnunetmessenger.model.MessengerApp
interface GnunetChat {
+ fun startChat(
+ messengerApp: MessengerApp,
+ callback: (ChatContext, ChatMessage) -> Unit
+ ): ChatHandle
+
suspend fun awaitReady(handle: ChatHandle)
- fun startChat(messengerApp: MessengerApp, callback: (ChatContext, ChatMessage) -> Unit): ChatHandle
+
suspend fun reset()
+
fun iterateAccounts(handle: ChatHandle, callback: (ChatAccount) -> Unit)
+ suspend fun listAccounts(handle: ChatHandle): List<ChatAccount>
+
suspend fun createAccount(handle: ChatHandle, name: String): GnunetReturnValue
suspend fun connect(handle: ChatHandle, account: ChatAccount)
suspend fun disconnect(handle: ChatHandle)
suspend fun getProfileName(handle: ChatHandle): String
suspend fun setProfileName(handle: ChatHandle, name: String)
+
fun getProfileKey(handle: ChatHandle): String
- fun setContactBlocked(contact: ChatContact, isBlocked: Boolean)
fun isContactBlocked(contact: ChatContact): Boolean
- fun setAttribute(handle: ChatHandle,key: String, value: String)
+ fun setContactBlocked(contact: ChatContact, isBlocked: Boolean)
+ fun setAttribute(handle: ChatHandle, key: String, value: String)
fun getAttributes(handle: ChatHandle, callback: (String, String) -> Unit)
+
fun lobbyOpen(handle: ChatHandle, callback: (String) -> Unit)
fun lobbyJoin(handle: ChatHandle, uri: String)
+
fun setGroupName(group: ChatGroup, name: String)
fun createGroup(handle: ChatHandle, topic: String): ChatGroup
+
fun parseUri(uri: String): ChatUri
fun destroyUri(uri: ChatUri)
+
fun inviteContactToGroup(group: ChatGroup, contact: ChatContact)
- fun getUserPointerForContext(context: ChatContext) : String?
+ fun getUserPointerForContext(context: ChatContext): String?
fun setUserPointerForContext(context: ChatContext, userPointer: String)
- fun getSenderFromMessage(message: ChatMessage) : ChatContact
+
+ fun getSenderFromMessage(message: ChatMessage): ChatContact
fun getGroupFromContext(context: ChatContext): ChatGroup?
- fun getMessageForGroupContact(group: ChatGroup, contact: ChatContact) : ChatMessage
- fun getMessageKind(message: ChatMessage) : MessageKind
- fun isMessageRecent(message: ChatMessage) : GnunetReturnValue
- fun getMessageTimestamp(message: ChatMessage) : Long
+ fun getMessageForGroupContact(group: ChatGroup, contact: ChatContact): ChatMessage
+ fun getMessageKind(message: ChatMessage): MessageKind
+ fun isMessageRecent(message: ChatMessage): GnunetReturnValue
+ fun getMessageTimestamp(message: ChatMessage): Long
fun setMessageForGroupContact(group: ChatGroup, contact: ChatContact, message: ChatMessage)
+
fun iterateContacts(handle: ChatHandle, callback: (ChatContact) -> Int)
fun iterateGroups(handle: ChatHandle, callback: (ChatGroup) -> Int)
+
fun getContactContext(chatContact: ChatContact): ChatContext
- fun getGroupContext(chatGroup: ChatGroup): ChatContext
- fun getContactUserPointer(chatContact: ChatContact) : String
+ fun getGroupContext(chatGroup: ChatGroup): ChatContext
+ fun getContactUserPointer(chatContact: ChatContact): String
fun setContactUserPointer(chatContact: ChatContact, userPointer: String)
- fun getGroupUserPointer(chatGroup: ChatGroup) : String
+ fun getGroupUserPointer(chatGroup: ChatGroup): String
fun setGroupUserPointer(chatGroup: ChatGroup, userPointer: String)
+
fun sendText(chatContext: ChatContext, text: String)
fun getContactKey(chatContact: ChatContact): String
fun getContextContact(context: ChatContext): ChatContact
fun deleteContact(chatContact: ChatContact)
fun isGroup(context: ChatContext): Boolean
fun isPlatform(context: ChatContext): Boolean
+
fun iterateGroupContacts(chatGroup: ChatGroup, callback: (ChatGroup, ChatContact) -> Int)
+
fun randomUUID(): String
fun getContactAttributes(contact: ChatContact, callback: (String, String) -> Unit)
- fun shareAttributes (handle: ChatHandle, contact: ChatContact, key: String)
- fun unshareAttributes (handle: ChatHandle, contact: ChatContact, key: String)
-}
+ fun shareAttributes(handle: ChatHandle, contact: ChatContact, key: String)
+ fun unshareAttributes(handle: ChatHandle, contact: ChatContact, key: String)
+}
+\ No newline at end of file
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt
@@ -4,8 +4,8 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
-import android.os.IBinder
import android.os.DeadObjectException
+import android.os.IBinder
import android.os.RemoteException
import android.util.Log
import kotlinx.coroutines.CompletableDeferred
@@ -16,7 +16,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
-import kotlinx.coroutines.withTimeout
import org.gnunet.gnunetmessenger.ipc.*
import org.gnunet.gnunetmessenger.model.*
import org.gnunet.gnunetmessenger.service.GnunetChat
@@ -29,49 +28,41 @@ class GnunetChatBoundService(
private var uuidCounter: Long = 0
- // --- Scopes ---
private val mainScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private val ioScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.IO)
- // --- Binder state ---
private val remoteRef = AtomicReference<IGnunetChat?>()
private var deathRecipient: IBinder.DeathRecipient? = null
- @Volatile private var lastHandle: Long = 0L // echter Server-Handle
- @Volatile private lateinit var messageCallback: ((ChatContext, ChatMessage) -> Unit)
-
- // Aufgaben, die erst NACH erfolgreichem startChat(handle!=0) ausgeführt werden dürfen
- private val pendingAfterHandle = mutableListOf<(IGnunetChat, Long) -> Unit>()
-
- private data class PendingStart(val appName: String, val ch: ChatHandle)
+ @Volatile
+ private var lastHandle: Long = 0L
@Volatile
- private var pendingStart: PendingStart? = null
+ private lateinit var messageCallback: ((ChatContext, ChatMessage) -> Unit)
+
+ private val pendingAfterHandle = mutableListOf<(IGnunetChat, Long) -> Unit>()
private val handleReady = ConcurrentHashMap<ChatHandle, CompletableDeferred<Long>>()
- // ----- Callback vom Server (AIDL) -> App-Callback -----
private val binderCallback = object : IChatCallback.Stub() {
override fun onMessage(context: ChatContextDto, message: ChatMessageDto) {
try {
val ctxLocal = context.toLocal()
val msgLocal = message.toLocal(ctxLocal)
- mainScope.launch { messageCallback?.invoke(ctxLocal, msgLocal) }
+ mainScope.launch { messageCallback.invoke(ctxLocal, msgLocal) }
} catch (t: Throwable) {
Log.e(TAG, "onMessage mapping failed", t)
}
}
}
- // ----- ServiceConnection -----
private val conn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val remote = IGnunetChat.Stub.asInterface(service)
remoteRef.set(remote)
- // Death-Handling
val dr = IBinder.DeathRecipient {
Log.w(TAG, "Remote binder died")
remoteRef.set(null)
@@ -81,24 +72,6 @@ class GnunetChatBoundService(
deathRecipient = dr
runCatching { service.linkToDeath(dr, 0) }
.onFailure { Log.e(TAG, "linkToDeath failed", it) }
-
- // Falls der App-Callback schon gesetzt wurde, aber noch kein Handle existiert:
- if (::messageCallback.isInitialized && lastHandle == 0L) {
- ioScope.launch {
- try {
- val h = remote.startChat(DEFAULT_APP_NAME, binderCallback)
- Log.d(TAG, "startChat after bind -> handle=$h")
- lastHandle = h
- // Alle auf Handle wartenden Aufgaben ausführen
- drainPending(remote, h)
- // Falls wir dem Aufrufer schon einen Platzhalter-ChatHandle gegeben haben,
- // aktualisiert er dessen pointer in startChat() (s.u.)
- // -> hier nichts weiter nötig.
- } catch (e: RemoteException) {
- Log.e(TAG, "startChat after bind failed", e)
- }
- }
- }
}
override fun onServiceDisconnected(name: ComponentName) {
@@ -109,7 +82,6 @@ class GnunetChatBoundService(
}
init {
- // proaktiv binden (damit es später schneller geht)
bind()
}
@@ -130,30 +102,30 @@ class GnunetChatBoundService(
retries: Int = 1,
crossinline block: suspend (IGnunetChat, Long) -> T
): T {
- // stellt sicher: gültiger Handle + verbundener Remote
awaitReady(handle)
var attempt = 0
var lastError: Throwable? = null
while (attempt <= retries) {
- val remote = try { getOrBindRemote() } catch (t: Throwable) {
- lastError = t; null
+ val remote = try {
+ getOrBindRemote()
+ } catch (t: Throwable) {
+ lastError = t
+ null
}
+
if (remote != null) {
try {
return block(remote, handle.pointer)
- } catch (dead: android.os.DeadObjectException) {
- // Binder tot -> rebind und retry
+ } catch (dead: DeadObjectException) {
Log.w(TAG, "Binder died, rebinding… (attempt=$attempt)")
bind()
lastError = dead
} catch (re: RemoteException) {
- // manche RemoteExceptions bedeuten auch: Binder tot
Log.w(TAG, "RemoteException, retry if possible (attempt=$attempt)", re)
bind()
lastError = re
}
} else {
- // keine Verbindung -> kurze Pause vorm Retry
delay(150)
}
attempt++
@@ -181,60 +153,38 @@ class GnunetChatBoundService(
}
override suspend fun awaitReady(handle: ChatHandle) {
- // Falls der echte Pointer schon gesetzt ist: sofort fertig.
if (handle.pointer != 0L) return
- // Gibt es ein Deferred für genau DIESEN Handle? (wird in startChat angelegt)
handleReady[handle]?.let { deferred ->
try {
- val h = deferred.await() // suspendiert, blockiert NICHT den Main-Thread
+ val h = deferred.await()
if (handle.pointer == 0L) {
- handle.pointer = h // zur Sicherheit setzen, falls Racing
+ handle.pointer = h
}
return
} finally {
- // Aufräumen: Eintrag entfernen (verhindert Leaks).
handleReady.remove(handle)
}
}
- // Kein Deferred gefunden. War der Remote evtl. noch nicht verbunden?
- // Warte kurz auf die Verbindung (max. ~2s).
repeat(20) {
if (remoteRef.get() != null) return@repeat
delay(100)
}
+
val remote = remoteRef.get()
- ?: throw IllegalStateException("Remote not connected; startChat/bind() noch nicht durch")
-
- // Wenn wir bis hier kamen und der Pointer immer noch 0 ist:
- // Prüfe, ob ein ausstehender startChat für GENAU diesen Handle vormerkt ist.
- pendingStart?.let { ps ->
- val (appName, ch) = ps
- if (ch === handle) {
- // Wir waren gebunden, aber startChat wurde noch nicht abgesetzt – hole das nach.
- val real = remote.startChat(appName, binderCallback)
- handle.pointer = real
- handleReady[handle]?.complete(real)
- pendingStart = null
- handleReady.remove(handle)
- return
- }
- }
+ ?: throw IllegalStateException("Remote not connected; startChat/bind() not completed")
- // Letzter Versuch: vielleicht wurde kurz vorher doch noch ein Deferred angelegt.
- handleReady[handle]?.let { deferred ->
- try {
- val h = deferred.await()
- if (handle.pointer == 0L) handle.pointer = h
- return
- } finally {
- handleReady.remove(handle)
- }
+ if (handle.pointer == 0L) {
+ val real = remote.startChat(DEFAULT_APP_NAME, binderCallback)
+ handle.pointer = real
+ lastHandle = real
+ handleReady[handle]?.complete(real)
+ handleReady.remove(handle)
+ drainPending(remote, real)
}
- // Wenn wir hier landen, ist der Handle immer noch 0 → sauber fehlschlagen.
- throw IllegalStateException("Handle not ready (pointer==0); startChat() wurde evtl. nie erfolgreich abgeschlossen")
+ check(handle.pointer != 0L) { "Handle not ready (pointer==0)" }
}
override fun startChat(
@@ -243,36 +193,22 @@ class GnunetChatBoundService(
): ChatHandle {
messageCallback = callback
- // Platzhalter-Handle sofort zurückgeben (UI blockiert nicht)
val ch = ChatHandle(0L)
+ val deferred = CompletableDeferred<Long>()
+ handleReady[ch] = deferred
- val remote = remoteRef.get()
- if (remote != null) {
- // Wir sind gebunden -> Session starten (auch wenn lastHandle == 0L nach reset)
- ioScope.launch {
- runCatching { remote.startChat("messengerApp", binderCallback) }
- .onSuccess { h ->
- lastHandle = h
- ch.pointer = h // << echten Handle in deinen ChatHandle schreiben
- Log.d(TAG, "startChat -> handle=$h")
- drainPending(remote, h) // << aufgestaute Calls abarbeiten
- }
- .onFailure { t ->
- Log.e(TAG, "startChat failed", t)
- }
- }
- } else {
- // Noch nicht gebunden: Bind anstoßen; onServiceConnected ruft startChat
- bind()
-
- // Wir müssen den Platzhalter später noch mit dem echten Handle füllen:
- // Das erledigen wir, indem wir eine "No-Op"-Pending-Task eintragen,
- // die NUR den ChatHandle aktualisiert, sobald der echte Handle da ist.
- synchronized(pendingAfterHandle) {
- pendingAfterHandle += { _, handleReal ->
- ch.pointer = handleReal
- Log.d(TAG, "late handle propagation -> ${ch.pointer}")
- }
+ ioScope.launch {
+ try {
+ val remote = getOrBindRemote()
+ val h = remote.startChat("messengerApp", binderCallback)
+ lastHandle = h
+ ch.pointer = h
+ deferred.complete(h)
+ drainPending(remote, h)
+ Log.d(TAG, "startChat -> handle=$h")
+ } catch (t: Throwable) {
+ Log.e(TAG, "startChat failed", t)
+ deferred.completeExceptionally(t)
}
}
@@ -280,26 +216,14 @@ class GnunetChatBoundService(
}
override suspend fun reset() = withContext(Dispatchers.IO) {
- val remote = try {
- getOrBindRemote()
- } catch (t: Throwable) {
- Log.e(TAG, "reset: failed to bind remote", t)
- throw t
- }
-
- try {
- remote.reset()
- Log.i(TAG, "reset: successfully reset remote service")
-
- // Reset local state
- lastHandle = 0L
- handleReady.clear()
- synchronized(pendingAfterHandle) {
- pendingAfterHandle.clear()
- }
- } catch (e: RemoteException) {
- Log.e(TAG, "reset: remote call failed", e)
- throw e
+ val remote = getOrBindRemote()
+ remote.reset()
+ Log.i(TAG, "reset: successfully reset remote service")
+
+ lastHandle = 0L
+ handleReady.clear()
+ synchronized(pendingAfterHandle) {
+ pendingAfterHandle.clear()
}
}
@@ -309,7 +233,11 @@ class GnunetChatBoundService(
val acc = accountDto.toLocal()
mainScope.launch { callback(acc) }
}
- override fun onDone() { Log.d(TAG, "iterateAccounts: done") }
+
+ override fun onDone() {
+ Log.d(TAG, "iterateAccounts: done")
+ }
+
override fun onError(code: Int, message: String?) {
Log.e(TAG, "iterateAccounts: error $code $message")
}
@@ -323,23 +251,25 @@ class GnunetChatBoundService(
ioScope.launch {
try {
remote.iterateAccounts(h, bridge)
- } catch (dead: android.os.DeadObjectException) {
- // Binder tot -> re-queue und rebind
+ } catch (dead: DeadObjectException) {
Log.w(TAG, "iterateAccounts: binder died, queue & rebind")
synchronized(pendingAfterHandle) {
pendingAfterHandle += { r, real ->
runCatching { r.iterateAccounts(real, bridge) }
- .onFailure { Log.e(TAG, "iterateAccounts (deferred) failed", it) }
+ .onFailure {
+ Log.e(TAG, "iterateAccounts (deferred) failed", it)
+ }
}
}
bind()
} catch (e: RemoteException) {
Log.e(TAG, "iterateAccounts remote failed", e)
- // sicherheitshalber auch re-queue
synchronized(pendingAfterHandle) {
pendingAfterHandle += { r, real ->
runCatching { r.iterateAccounts(real, bridge) }
- .onFailure { Log.e(TAG, "iterateAccounts (deferred) failed", it) }
+ .onFailure {
+ Log.e(TAG, "iterateAccounts (deferred) failed", it)
+ }
}
}
bind()
@@ -347,7 +277,6 @@ class GnunetChatBoundService(
}
}
- // Verbunden, aber Handle noch nicht bereit → aufschieben bis handle da ist
remote != null && h == 0L -> {
synchronized(pendingAfterHandle) {
pendingAfterHandle += { r, real ->
@@ -357,7 +286,6 @@ class GnunetChatBoundService(
}
}
- // Noch nicht verbunden → binden & aufschieben
else -> {
synchronized(pendingAfterHandle) {
pendingAfterHandle += { r, real ->
@@ -370,7 +298,36 @@ class GnunetChatBoundService(
}
}
- // --- Helpers ---
+ override suspend fun listAccounts(handle: ChatHandle): List<ChatAccount> {
+ return withReadyRemote(handle) { remote, h ->
+ val result = mutableListOf<ChatAccount>()
+ val done = CompletableDeferred<Unit>()
+
+ val bridge = object : IAccountCallback.Stub() {
+ override fun onAccount(accountDto: ChatAccountDto) {
+ result.add(accountDto.toLocal())
+ }
+
+ override fun onDone() {
+ if (!done.isCompleted) {
+ done.complete(Unit)
+ }
+ }
+
+ override fun onError(code: Int, message: String?) {
+ if (!done.isCompleted) {
+ done.completeExceptionally(
+ IllegalStateException("iterateAccounts failed: $code ${message ?: ""}".trim())
+ )
+ }
+ }
+ }
+
+ remote.iterateAccounts(h, bridge)
+ done.await()
+ result.toList()
+ }
+ }
private fun drainPending(remote: IGnunetChat, handle: Long) {
val tasks = synchronized(pendingAfterHandle) {
@@ -387,26 +344,12 @@ class GnunetChatBoundService(
}
}
- private suspend fun resolveHandle(h: ChatHandle): Long {
- if (h.pointer != 0L) return h.pointer
- handleReady[h]?.let { return it.await() }
- if (lastHandle != 0L) return lastHandle
- throw IllegalStateException("No handle available yet (startChat not completed)")
- }
-
- // int <-> enum mapping
private fun Int.toGnunetReturn(): GnunetReturnValue =
when (this) {
0 -> GnunetReturnValue.OK
else -> GnunetReturnValue.NO
}
- private fun Int.toReturnValue(): GnunetReturnValue =
- when (this) {
- 0 -> GnunetReturnValue.OK
- else -> GnunetReturnValue.NO // oder ein feineres Mapping, falls vorhanden
- }
-
override suspend fun createAccount(handle: ChatHandle, name: String): GnunetReturnValue {
val code = withReadyRemote(handle) { remote, h ->
withContext(Dispatchers.IO) { remote.createAccount(h, name) }
@@ -441,8 +384,6 @@ class GnunetChatBoundService(
}
}
- // --- Remote Call Implementations ---
-
override fun getProfileKey(handle: ChatHandle): String {
return runBlocking {
withReadyRemote(handle) { remote, h ->
@@ -453,7 +394,7 @@ class GnunetChatBoundService(
override fun isContactBlocked(contact: ChatContact): Boolean {
return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.isContactBlocked(contact.toDto())
}
}
@@ -461,7 +402,7 @@ class GnunetChatBoundService(
override fun setContactBlocked(contact: ChatContact, isBlocked: Boolean) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.setContactBlocked(contact.toDto(), isBlocked)
}
}
@@ -480,7 +421,11 @@ class GnunetChatBoundService(
override fun onAttribute(key: String, value: String) {
mainScope.launch { callback(key, value) }
}
- override fun onDone() { Log.d(TAG, "getAttributes: done") }
+
+ override fun onDone() {
+ Log.d(TAG, "getAttributes: done")
+ }
+
override fun onError(code: Int, message: String?) {
Log.e(TAG, "getAttributes: error $code $message")
}
@@ -511,6 +456,7 @@ class GnunetChatBoundService(
override fun onLobbyUri(uri: String) {
mainScope.launch { callback(uri) }
}
+
override fun onError(code: Int, message: String?) {
Log.e(TAG, "lobbyOpen: error $code $message")
}
@@ -546,7 +492,7 @@ class GnunetChatBoundService(
override fun setGroupName(group: ChatGroup, name: String) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.setGroupName(group.toDto(), name)
}
}
@@ -563,7 +509,7 @@ class GnunetChatBoundService(
override fun parseUri(uri: String): ChatUri {
return runBlocking {
- val uriDto = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val uriDto = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.parseUri(uri)
}
uriDto.toLocal()
@@ -572,7 +518,7 @@ class GnunetChatBoundService(
override fun destroyUri(uri: ChatUri) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.destroyUri(uri.toDto())
}
}
@@ -580,7 +526,7 @@ class GnunetChatBoundService(
override fun inviteContactToGroup(group: ChatGroup, contact: ChatContact) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.inviteContactToGroup(group.toDto(), contact.toDto())
}
}
@@ -588,7 +534,7 @@ class GnunetChatBoundService(
override fun getUserPointerForContext(context: ChatContext): String? {
return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getUserPointerForContext(context.toDto())
}
}
@@ -596,7 +542,7 @@ class GnunetChatBoundService(
override fun setUserPointerForContext(context: ChatContext, userPointer: String) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.setUserPointerForContext(context.toDto(), userPointer)
}
}
@@ -604,7 +550,7 @@ class GnunetChatBoundService(
override fun getSenderFromMessage(message: ChatMessage): ChatContact {
return runBlocking {
- val contactDto = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val contactDto = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getSenderFromMessage(message.toDto())
}
contactDto.toLocal()
@@ -613,7 +559,7 @@ class GnunetChatBoundService(
override fun getGroupFromContext(context: ChatContext): ChatGroup? {
return runBlocking {
- val groupDto = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val groupDto = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getGroupFromContext(context.toDto())
}
groupDto.toLocal()
@@ -622,16 +568,16 @@ class GnunetChatBoundService(
override fun getMessageForGroupContact(group: ChatGroup, contact: ChatContact): ChatMessage {
return runBlocking {
- val messageDto = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val messageDto = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getMessageForGroupContact(group.toDto(), contact.toDto())
}
- messageDto.toLocal(ChatContext(ChatContextType.UNKNOWN, null, false, false))
+ messageDto.toLocal(ChatContext(null, null, false, false))
}
}
override fun getMessageKind(message: ChatMessage): MessageKind {
return runBlocking {
- val kind = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val kind = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getMessageKind(message.toDto())
}
MessageKind.fromCode(kind)
@@ -640,7 +586,7 @@ class GnunetChatBoundService(
override fun isMessageRecent(message: ChatMessage): GnunetReturnValue {
return runBlocking {
- val result = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val result = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.isMessageRecent(message.toDto())
}
result.toGnunetReturn()
@@ -649,15 +595,19 @@ class GnunetChatBoundService(
override fun getMessageTimestamp(message: ChatMessage): Long {
return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getMessageTimestamp(message.toDto())
}
}
}
- override fun setMessageForGroupContact(group: ChatGroup, contact: ChatContact, message: ChatMessage) {
+ override fun setMessageForGroupContact(
+ group: ChatGroup,
+ contact: ChatContact,
+ message: ChatMessage
+ ) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.setMessageForGroupContact(group.toDto(), contact.toDto(), message.toDto())
}
}
@@ -669,7 +619,11 @@ class GnunetChatBoundService(
val contact = contactDto.toLocal()
mainScope.launch { callback(contact) }
}
- override fun onDone() { Log.d(TAG, "iterateContacts: done") }
+
+ override fun onDone() {
+ Log.d(TAG, "iterateContacts: done")
+ }
+
override fun onError(code: Int, message: String?) {
Log.e(TAG, "iterateContacts: error $code $message")
}
@@ -701,7 +655,11 @@ class GnunetChatBoundService(
val group = groupDto.toLocal()
mainScope.launch { callback(group) }
}
- override fun onDone() { Log.d(TAG, "iterateGroups: done") }
+
+ override fun onDone() {
+ Log.d(TAG, "iterateGroups: done")
+ }
+
override fun onError(code: Int, message: String?) {
Log.e(TAG, "iterateGroups: error $code $message")
}
@@ -729,7 +687,7 @@ class GnunetChatBoundService(
override fun getContactContext(chatContact: ChatContact): ChatContext {
return runBlocking {
- val contextDto = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val contextDto = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getContactContext(chatContact.toDto())
}
contextDto.toLocal()
@@ -738,7 +696,7 @@ class GnunetChatBoundService(
override fun getGroupContext(chatGroup: ChatGroup): ChatContext {
return runBlocking {
- val contextDto = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val contextDto = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getGroupContext(chatGroup.toDto())
}
contextDto.toLocal()
@@ -747,7 +705,7 @@ class GnunetChatBoundService(
override fun getContactUserPointer(chatContact: ChatContact): String {
return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getContactUserPointer(chatContact.toDto())
}
}
@@ -755,7 +713,7 @@ class GnunetChatBoundService(
override fun setContactUserPointer(chatContact: ChatContact, userPointer: String) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.setContactUserPointer(chatContact.toDto(), userPointer)
}
}
@@ -763,7 +721,7 @@ class GnunetChatBoundService(
override fun getGroupUserPointer(chatGroup: ChatGroup): String {
return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getGroupUserPointer(chatGroup.toDto())
}
}
@@ -771,7 +729,7 @@ class GnunetChatBoundService(
override fun setGroupUserPointer(chatGroup: ChatGroup, userPointer: String) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.setGroupUserPointer(chatGroup.toDto(), userPointer)
}
}
@@ -779,7 +737,7 @@ class GnunetChatBoundService(
override fun sendText(chatContext: ChatContext, text: String) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.sendText(chatContext.toDto(), text)
}
}
@@ -787,7 +745,7 @@ class GnunetChatBoundService(
override fun getContactKey(chatContact: ChatContact): String {
return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getContactKey(chatContact.toDto())
}
}
@@ -795,7 +753,7 @@ class GnunetChatBoundService(
override fun getContextContact(context: ChatContext): ChatContact {
return runBlocking {
- val contactDto = withReadyRemote(ChatHandle(0)) { remote, h ->
+ val contactDto = withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.getContextContact(context.toDto())
}
contactDto.toLocal()
@@ -804,7 +762,7 @@ class GnunetChatBoundService(
override fun deleteContact(chatContact: ChatContact) {
runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.deleteContact(chatContact.toDto())
}
}
@@ -812,7 +770,7 @@ class GnunetChatBoundService(
override fun isGroup(context: ChatContext): Boolean {
return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.isGroup(context.toDto())
}
}
@@ -820,20 +778,27 @@ class GnunetChatBoundService(
override fun isPlatform(context: ChatContext): Boolean {
return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
+ withReadyRemote(ChatHandle(0)) { remote, _ ->
remote.isPlatform(context.toDto())
}
}
}
- override fun iterateGroupContacts(chatGroup: ChatGroup, callback: (ChatGroup, ChatContact) -> Int) {
+ override fun iterateGroupContacts(
+ chatGroup: ChatGroup,
+ callback: (ChatGroup, ChatContact) -> Int
+ ) {
val bridge = object : IGroupContactCallback.Stub() {
override fun onGroupContact(groupDto: ChatGroupDto, contactDto: ChatContactDto) {
val group = groupDto.toLocal()
val contact = contactDto.toLocal()
mainScope.launch { callback(group, contact) }
}
- override fun onDone() { Log.d(TAG, "iterateGroupContacts: done") }
+
+ override fun onDone() {
+ Log.d(TAG, "iterateGroupContacts: done")
+ }
+
override fun onError(code: Int, message: String?) {
Log.e(TAG, "iterateGroupContacts: error $code $message")
}
@@ -851,20 +816,18 @@ class GnunetChatBoundService(
} else {
bind()
synchronized(pendingAfterHandle) {
- pendingAfterHandle += { r, real ->
+ pendingAfterHandle += { r, _ ->
runCatching { r.iterateGroupContacts(chatGroup.toDto(), bridge) }
- .onFailure { Log.e(TAG, "iterateGroupContacts (deferred) failed", it) }
+ .onFailure {
+ Log.e(TAG, "iterateGroupContacts (deferred) failed", it)
+ }
}
}
}
}
override fun randomUUID(): String {
- return runBlocking {
- withReadyRemote(ChatHandle(0)) { remote, h ->
- remote.randomUUID()
- }
- }
+ return "uuid_${System.currentTimeMillis()}_${uuidCounter++}"
}
override fun getContactAttributes(contact: ChatContact, callback: (String, String) -> Unit) {
@@ -872,7 +835,11 @@ class GnunetChatBoundService(
override fun onAttribute(key: String, value: String) {
mainScope.launch { callback(key, value) }
}
- override fun onDone() { Log.d(TAG, "getContactAttributes: done") }
+
+ override fun onDone() {
+ Log.d(TAG, "getContactAttributes: done")
+ }
+
override fun onError(code: Int, message: String?) {
Log.e(TAG, "getContactAttributes: error $code $message")
}
@@ -890,9 +857,11 @@ class GnunetChatBoundService(
} else {
bind()
synchronized(pendingAfterHandle) {
- pendingAfterHandle += { r, real ->
+ pendingAfterHandle += { r, _ ->
runCatching { r.getContactAttributes(contact.toDto(), bridge) }
- .onFailure { Log.e(TAG, "getContactAttributes (deferred) failed", it) }
+ .onFailure {
+ Log.e(TAG, "getContactAttributes (deferred) failed", it)
+ }
}
}
}
@@ -921,5 +890,4 @@ class GnunetChatBoundService(
private const val SERVER_PACKAGE = "org.gnu.gnunet"
private const val DEFAULT_APP_NAME = "Default"
}
-
-}
+}
+\ No newline at end of file
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt
@@ -24,7 +24,6 @@
package org.gnunet.gnunetmessenger.service.mock
-
import org.gnunet.gnunetmessenger.model.ChatAccount
import org.gnunet.gnunetmessenger.model.ChatContact
import org.gnunet.gnunetmessenger.model.ChatContext
@@ -70,9 +69,9 @@ class GnunetChatMock : GnunetChat {
override fun iterateAccounts(handle: ChatHandle, callback: (ChatAccount) -> Unit) {
val mockAccounts = listOf(
- ChatAccount(1, "Alice",""),
- ChatAccount(2, "Bob",""),
- ChatAccount(3, "Charlie","")
+ ChatAccount(1, "Alice", ""),
+ ChatAccount(2, "Bob", ""),
+ ChatAccount(3, "Charlie", "")
)
for (account in mockAccounts) {
@@ -80,6 +79,14 @@ class GnunetChatMock : GnunetChat {
}
}
+ override suspend fun listAccounts(handle: ChatHandle): List<ChatAccount> {
+ return listOf(
+ ChatAccount(1, "Alice", ""),
+ ChatAccount(2, "Bob", ""),
+ ChatAccount(3, "Charlie", "")
+ )
+ }
+
override suspend fun createAccount(handle: ChatHandle, name: String): GnunetReturnValue {
println("create account")
return GnunetReturnValue.OK
@@ -87,16 +94,32 @@ class GnunetChatMock : GnunetChat {
override suspend fun connect(handle: ChatHandle, account: ChatAccount) {
println("connect")
- messageCallback(ChatContext(ChatContextType.UNKNOWN,UUID.randomUUID().toString(), false, false),
- ChatMessage(ChatContext(ChatContextType.UNKNOWN,null, true, false),"",0,null,MessageKind.LOGIN,null)
+ messageCallback(
+ ChatContext(ChatContextType.UNKNOWN, UUID.randomUUID().toString(), false, false),
+ ChatMessage(
+ ChatContext(ChatContextType.UNKNOWN, null, true, false),
+ "",
+ 0,
+ null,
+ MessageKind.LOGIN,
+ null
+ )
)
}
override suspend fun disconnect(handle: ChatHandle) {
println("disconnect")
uuidCounter = 0
- messageCallback(ChatContext(ChatContextType.UNKNOWN,UUID.randomUUID().toString(), false, false),
- ChatMessage(ChatContext(ChatContextType.UNKNOWN,null, false, false),"",0,null,MessageKind.LOGOUT,null)
+ messageCallback(
+ ChatContext(ChatContextType.UNKNOWN, UUID.randomUUID().toString(), false, false),
+ ChatMessage(
+ ChatContext(ChatContextType.UNKNOWN, null, false, false),
+ "",
+ 0,
+ null,
+ MessageKind.LOGOUT,
+ null
+ )
)
}
@@ -117,7 +140,7 @@ class GnunetChatMock : GnunetChat {
}
override fun setContactBlocked(contact: ChatContact, isBlocked: Boolean) {
- println("isblocked:" + isBlocked)
+ println("isblocked:$isBlocked")
}
override fun setAttribute(handle: ChatHandle, key: String, value: String) {
@@ -140,10 +163,7 @@ class GnunetChatMock : GnunetChat {
callback("000G006K2TJNMD9VTCYRX7BRVV3HAEPS15E6NHDXKPJA1KAJJEG9AFF884")
}
- override fun lobbyJoin(
- handle: ChatHandle,
- uri: String
- ) {
+ override fun lobbyJoin(handle: ChatHandle, uri: String) {
println("join lobby")
}
@@ -152,18 +172,17 @@ class GnunetChatMock : GnunetChat {
}
override fun createGroup(handle: ChatHandle, topic: String): ChatGroup {
- return ChatGroup(ChatContext(ChatContextType.GROUP, null,
- true, false), topic)
+ return ChatGroup(
+ ChatContext(ChatContextType.GROUP, null, true, false),
+ topic
+ )
}
override fun parseUri(uri: String): ChatUri {
- // Return a fake, hard-coded ChatUri.
- // We'll just put the URI we received inside it.
return ChatUri(uri)
}
override fun destroyUri(uri: ChatUri) {
- // This is our "side effect" that the test can check
lastDestroyedUri = uri
}
@@ -180,11 +199,14 @@ class GnunetChatMock : GnunetChat {
}
override fun getSenderFromMessage(message: ChatMessage): ChatContact {
- return ChatContact(ChatContext(ChatContextType.CONTACT, null, true, false), message.sender?.name ?: "")
+ return ChatContact(
+ ChatContext(ChatContextType.CONTACT, null, true, false),
+ message.sender?.name ?: ""
+ )
}
override fun getGroupFromContext(context: ChatContext): ChatGroup? {
- if ("3" == context.userPointer){
+ if ("3" == context.userPointer) {
val contextDev = ChatContext(ChatContextType.GROUP, null, true, false)
return ChatGroup(contextDev, name = "Dev Team")
}
@@ -193,7 +215,6 @@ class GnunetChatMock : GnunetChat {
}
override fun getMessageForGroupContact(group: ChatGroup, contact: ChatContact): ChatMessage {
- // Return a fixed, hard-coded ChatMessage for testing
return ChatMessage(
ChatContext(null, null, false, false),
"fake-message-for-group",
@@ -205,17 +226,14 @@ class GnunetChatMock : GnunetChat {
}
override fun getMessageKind(message: ChatMessage): MessageKind {
- // Always return TEXT for our mock
return MessageKind.TEXT
}
override fun isMessageRecent(message: ChatMessage): GnunetReturnValue {
- // Always return OK for our mock
return GnunetReturnValue.OK
}
override fun getMessageTimestamp(message: ChatMessage): Long {
- // Return a fixed, hard-coded timestamp for testing
return 123456789L
}
@@ -231,7 +249,6 @@ class GnunetChatMock : GnunetChat {
val contextAlice = ChatContext(ChatContextType.CONTACT, null, false, false)
val contextBob = ChatContext(ChatContextType.CONTACT, null, false, false)
val contacts = listOf(
-
ChatContact(contextAlice, name = "Alice"),
ChatContact(contextBob, name = "Bob")
)
@@ -252,36 +269,77 @@ class GnunetChatMock : GnunetChat {
callback(group)
}
- messageCallback(ChatContext(ChatContextType.CONTACT, null, true, false),
- ChatMessage(ChatContext(ChatContextType.CONTACT,null, true, false),"",0,
- ChatContact(ChatContext(ChatContextType.CONTACT, null, true, false), "Mallory"),MessageKind.JOIN, null)
+ messageCallback(
+ ChatContext(ChatContextType.CONTACT, null, true, false),
+ ChatMessage(
+ ChatContext(ChatContextType.CONTACT, null, true, false),
+ "",
+ 0,
+ ChatContact(ChatContext(ChatContextType.CONTACT, null, true, false), "Mallory"),
+ MessageKind.JOIN,
+ null
+ )
)
- messageCallback(ChatContext(ChatContextType.CONTACT,"5", true, false),
- ChatMessage(ChatContext(ChatContextType.CONTACT,null, true, false),"Hi, I am Mallory!",0,
- ChatContact(ChatContext(ChatContextType.CONTACT, "6", true, false), "Mallory"),MessageKind.TEXT, null)
+ messageCallback(
+ ChatContext(ChatContextType.CONTACT, "5", true, false),
+ ChatMessage(
+ ChatContext(ChatContextType.CONTACT, null, true, false),
+ "Hi, I am Mallory!",
+ 0,
+ ChatContact(ChatContext(ChatContextType.CONTACT, "6", true, false), "Mallory"),
+ MessageKind.TEXT,
+ null
+ )
)
- messageCallback(ChatContext(ChatContextType.GROUP,"3", true, false),
- ChatMessage(ChatContext(ChatContextType.GROUP,null, true, false),"",0,
- ChatContact(ChatContext(ChatContextType.CONTACT, null, true, false), "Flo"),MessageKind.JOIN, null)
+ messageCallback(
+ ChatContext(ChatContextType.GROUP, "3", true, false),
+ ChatMessage(
+ ChatContext(ChatContextType.GROUP, null, true, false),
+ "",
+ 0,
+ ChatContact(ChatContext(ChatContextType.CONTACT, null, true, false), "Flo"),
+ MessageKind.JOIN,
+ null
+ )
)
- messageCallback(ChatContext(ChatContextType.GROUP,"3", true, false),
- ChatMessage(ChatContext(ChatContextType.GROUP,null, true, false),"Hi, I am Flo!",0,
- ChatContact(ChatContext(ChatContextType.CONTACT, "7", true, false), "Flo"),MessageKind.TEXT, null)
+ messageCallback(
+ ChatContext(ChatContextType.GROUP, "3", true, false),
+ ChatMessage(
+ ChatContext(ChatContextType.GROUP, null, true, false),
+ "Hi, I am Flo!",
+ 0,
+ ChatContact(ChatContext(ChatContextType.CONTACT, "7", true, false), "Flo"),
+ MessageKind.TEXT,
+ null
+ )
)
- messageCallback(ChatContext(ChatContextType.GROUP,"3", true, false),
- ChatMessage(ChatContext(ChatContextType.GROUP,null, true, false),"",0,
- ChatContact(ChatContext(ChatContextType.CONTACT, "1", true, false), "Alice"),MessageKind.JOIN, null)
+ messageCallback(
+ ChatContext(ChatContextType.GROUP, "3", true, false),
+ ChatMessage(
+ ChatContext(ChatContextType.GROUP, null, true, false),
+ "",
+ 0,
+ ChatContact(ChatContext(ChatContextType.CONTACT, "1", true, false), "Alice"),
+ MessageKind.JOIN,
+ null
+ )
)
- messageCallback(ChatContext(ChatContextType.GROUP,"3", true, false),
- ChatMessage(ChatContext(ChatContextType.GROUP,null, true, false),"Hi, I am Alice!",0,
- ChatContact(ChatContext(ChatContextType.CONTACT, "1", true, false), "Alice"),MessageKind.TEXT, null)
+ messageCallback(
+ ChatContext(ChatContextType.GROUP, "3", true, false),
+ ChatMessage(
+ ChatContext(ChatContextType.GROUP, null, true, false),
+ "Hi, I am Alice!",
+ 0,
+ ChatContact(ChatContext(ChatContextType.CONTACT, "1", true, false), "Alice"),
+ MessageKind.TEXT,
+ null
+ )
)
-
}
override fun getContactContext(chatContact: ChatContact): ChatContext {
@@ -293,7 +351,6 @@ class GnunetChatMock : GnunetChat {
}
override fun getContactUserPointer(chatContact: ChatContact): String {
- // Return a fixed, hard-coded string for testing
return "fake-user-pointer-123"
}
@@ -302,7 +359,6 @@ class GnunetChatMock : GnunetChat {
}
override fun getGroupUserPointer(chatGroup: ChatGroup): String {
- // Return a fixed, hard-coded string for testing
return "fake-group-pointer-789"
}
@@ -320,7 +376,7 @@ class GnunetChatMock : GnunetChat {
override fun getContextContact(context: ChatContext): ChatContact {
println("get contact for context")
- return ChatContact(context,"test")
+ return ChatContact(context, "test")
}
override fun deleteContact(chatContact: ChatContact) {
@@ -341,7 +397,6 @@ class GnunetChatMock : GnunetChat {
) {
val contextCharlie = ChatContext(ChatContextType.CONTACT, null, true, false)
val contacts = listOf(
-
ChatContact(contextCharlie, name = "Charlie")
)
for (contact in contacts) {
@@ -377,12 +432,11 @@ class GnunetChatMock : GnunetChat {
}
override suspend fun reset() {
- // SAFETY CHECK: Only allow reset in debug builds
if (!org.gnunet.gnunetmessenger.BuildConfig.ALLOW_RESET) {
println("reset: BLOCKED - Reset not allowed in production builds")
throw SecurityException("Reset operation is not allowed in production")
}
-
+
println("reset mock service - clearing all state")
uuidCounter = 0
lastDestroyedUri = null
@@ -391,4 +445,4 @@ class GnunetChatMock : GnunetChat {
lastSetMessageForGroupContact = null
messageCallback = { _, _ -> }
}
-}
+}
+\ No newline at end of file
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/AccountListFragment.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/AccountListFragment.kt
@@ -39,6 +39,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.gnunet.gnunetmessenger.MainActivity
import org.gnunet.gnunetmessenger.R
@@ -54,6 +55,7 @@ class AccountListFragment : Fragment() {
private lateinit var adapter: AccountAdapter
private val accounts = mutableListOf<ChatAccount>()
+ private var refreshCollectorJob: Job? = null
companion object {
private const val TAG = "AccountListFragment"
@@ -84,6 +86,7 @@ class AccountListFragment : Fragment() {
Log.w(TAG, "disconnect before connect failed", it)
}
}
+
gnunetChat.connect(handle, selectedAccount)
selectedAccount.key = gnunetChat.getProfileKey(handle)
activity.setCurrentAccount(selectedAccount)
@@ -118,29 +121,21 @@ class AccountListFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
val activity = requireActivity() as MainActivity
- val gnunetChat = activity.getGnunetChatInstance()
viewLifecycleOwner.lifecycleScope.launch {
try {
- val handle = activity.awaitChatReady()
- Log.d(TAG, "Chat ready, loading accounts for handle=${handle.pointer}")
+ val handle = activity.awaitInitialDataReady()
+ Log.d(TAG, "Initial refresh received, loading accounts for handle=${handle.pointer}")
- showLoading(getString(R.string.loading_accounts))
createButton.isEnabled = true
- recycler.isVisible = true
- loadingIndicator.isGone = true
-
- gnunetChat.iterateAccounts(handle) { account ->
- account.key = gnunetChat.getProfileKey(handle)
- accounts.add(account)
- adapter.submitList(accounts.toList())
- statusText.isGone = true
- recycler.isVisible = true
- }
+ reloadAccounts(showLoading = true)
- if (accounts.isEmpty()) {
- statusText.text = getString(R.string.no_accounts_available)
- statusText.isVisible = true
+ refreshCollectorJob?.cancel()
+ refreshCollectorJob = viewLifecycleOwner.lifecycleScope.launch {
+ activity.accountRefreshFlow().collect {
+ Log.d(TAG, "Account refresh event received")
+ reloadAccounts(showLoading = false)
+ }
}
} catch (t: Throwable) {
Log.e(TAG, "Failed to initialize account list", t)
@@ -149,6 +144,49 @@ class AccountListFragment : Fragment() {
}
}
+ override fun onDestroyView() {
+ refreshCollectorJob?.cancel()
+ refreshCollectorJob = null
+ super.onDestroyView()
+ }
+
+ private suspend fun reloadAccounts(showLoading: Boolean) {
+ val activity = requireActivity() as MainActivity
+ val gnunetChat = activity.getGnunetChatInstance()
+ val handle = activity.getChatHandle()
+
+ if (showLoading) {
+ showLoading(getString(R.string.loading_accounts))
+ }
+
+ try {
+ Log.d(TAG, "listAccounts(): handle=${handle.pointer}")
+ val refreshedAccounts = gnunetChat.listAccounts(handle).map { account ->
+ account.key = gnunetChat.getProfileKey(handle)
+ account
+ }
+
+ accounts.clear()
+ accounts.addAll(refreshedAccounts)
+ adapter.submitList(accounts.toList())
+
+ loadingIndicator.isGone = true
+ createButton.isEnabled = true
+
+ if (accounts.isEmpty()) {
+ recycler.isGone = true
+ statusText.isVisible = true
+ statusText.text = getString(R.string.no_accounts_available)
+ } else {
+ statusText.isGone = true
+ recycler.isVisible = true
+ }
+ } catch (t: Throwable) {
+ Log.e(TAG, "reloadAccounts failed", t)
+ showError(getString(R.string.account_list_load_failed))
+ }
+ }
+
private fun showLoading(message: String) {
loadingIndicator.isVisible = true
statusText.isVisible = true
diff --git a/GNUnetMessenger/app/src/main/res/values/strings.xml b/GNUnetMessenger/app/src/main/res/values/strings.xml
@@ -30,9 +30,11 @@
<string name="connecting_to_gnunet">Connecting to GNUnet…</string>
<string name="loading_accounts">Loading accounts…</string>
+ <string name="waiting_for_initial_refresh">Waiting for initial refresh…</string>
<string name="no_accounts_available">No accounts available yet.</string>
<string name="gnunet_connection_failed">Could not connect to GNUnet.</string>
<string name="account_connect_failed">Could not activate the selected account.</string>
+ <string name="account_list_load_failed">Could not load the account list.</string>
<string-array name="lobby_lifetimes">
<item>Off</item>