messenger-android

Android graphical user interfaces for GNUnet Messenger
Log | Files | Refs | README | LICENSE

commit cdaecd24cb3e08147a0f88a3704a4a2dfdcc4e32
parent 7b44d07b0eec9580aef5c3edf6a40423ad16d4a2
Author: t3sserakt <t3sserakt@posteo.de>
Date:   Wed, 30 Apr 2025 17:46:07 +0200

Added one to one chat channels and QR scanning for entering the channel.

Diffstat:
MGNUnetMessenger/app/build.gradle.kts | 6+++++-
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/logic/ChatRegistry.kt | 18++++++++++++++++++
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatContact.kt | 5++++-
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatContext.kt | 4+++-
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatGroup.kt | 3++-
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatMessage.kt | 7++++++-
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatMessageType.kt | 7+++++++
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatSummary.kt | 8++++++++
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt | 23+++++++++++++++++++----
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt | 128++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt | 196++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/AccountListFragment.kt | 2++
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/AccountOverviewFragment.kt | 23+++++++++++++++++------
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/CreateAccountFragment.kt | 2++
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ChatListAdapter.kt | 17++++++++++++-----
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ChatMessageAdapter.kt | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ContactListAdapter.kt | 37+++++++++++++++++++++++++++++++++++++
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/contact/ContactListFragment.kt | 34++++++++++++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/contact/LobbyJoinFragment.kt | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatOverviewViewModel.kt | 48++++++++++++++++++++++++++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatViewModel.kt | 25+++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatViewModelFactory.kt | 17+++++++++++++++++
AGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ContactListViewModel.kt | 21+++++++++++++++++++++
AGNUnetMessenger/app/src/main/res/drawable/bubble_other.xml | 12++++++++++++
AGNUnetMessenger/app/src/main/res/drawable/bubble_own.xml | 12++++++++++++
AGNUnetMessenger/app/src/main/res/drawable/edittext_bg.xml | 6++++++
AGNUnetMessenger/app/src/main/res/layout/fragment_chat.xml | 35+++++++++++++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/res/layout/fragment_contact_list.xml | 12++++++++++++
AGNUnetMessenger/app/src/main/res/layout/fragment_join_lobby.xml | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MGNUnetMessenger/app/src/main/res/layout/item_chat.xml | 32++++++++++++++++++++++++--------
AGNUnetMessenger/app/src/main/res/layout/item_contact.xml | 18++++++++++++++++++
AGNUnetMessenger/app/src/main/res/layout/item_message_other.xml | 33+++++++++++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/res/layout/item_message_own.xml | 25+++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/res/layout/item_message_system.xml | 24++++++++++++++++++++++++
AGNUnetMessenger/app/src/main/res/menu/chat_menu.xml | 23+++++++++++++++++++++++
MGNUnetMessenger/app/src/main/res/menu/main_menu.xml | 14++++++++++++++
MGNUnetMessenger/app/src/main/res/navigation/nav_graph.xml | 33+++++++++++++++++++++++++++------
MGNUnetMessenger/app/src/main/res/values/strings.xml | 7+++++++
MGNUnetMessenger/gradle/libs.versions.toml | 10++++++++--
41 files changed, 1438 insertions(+), 51 deletions(-)

diff --git a/GNUnetMessenger/app/build.gradle.kts b/GNUnetMessenger/app/build.gradle.kts @@ -44,9 +44,13 @@ android { dependencies { + implementation(libs.camerax.core) + implementation(libs.camerax.camera2) + implementation(libs.camerax.lifecycle) + implementation(libs.camerax.view) + implementation(libs.mlkit.barcode) implementation(libs.zxingcore) implementation(libs.zxingandroidembedded) - implementation(libs.firebase.bom) implementation(libs.androidx.cardview) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt @@ -3,6 +3,7 @@ package org.gnunet.gnunetmessenger import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.navigation.NavController import androidx.navigation.ui.AppBarConfiguration @@ -10,14 +11,19 @@ import androidx.navigation.ui.NavigationUI import androidx.appcompat.widget.Toolbar import androidx.navigation.fragment.NavHostFragment import org.gnunet.gnunetmessenger.model.ChatAccount +import org.gnunet.gnunetmessenger.model.ChatContact import org.gnunet.gnunetmessenger.model.ChatContext import org.gnunet.gnunetmessenger.model.ChatHandle import org.gnunet.gnunetmessenger.model.ChatMessage -import org.gnunet.gnunetmessenger.model.EventType +import org.gnunet.gnunetmessenger.model.ChatMessageType +import org.gnunet.gnunetmessenger.model.ChatSummary import org.gnunet.gnunetmessenger.model.MessageKind import org.gnunet.gnunetmessenger.model.MessengerApp import org.gnunet.gnunetmessenger.service.GnunetChat import org.gnunet.gnunetmessenger.service.ServiceFactory +import org.gnunet.gnunetmessenger.viewmodel.ChatOverviewViewModel +import org.gnunet.gnunetmessenger.viewmodel.ChatViewModel +import org.gnunet.gnunetmessenger.viewmodel.ContactListViewModel class MainActivity : AppCompatActivity() { @@ -25,6 +31,10 @@ class MainActivity : AppCompatActivity() { private lateinit var navController: NavController private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var handle: ChatHandle + private val chatOverviewViewModel: ChatOverviewViewModel by viewModels() + val contactListViewModel: ContactListViewModel by viewModels() + private val chatViewModels = mutableMapOf<String, ChatViewModel>() + private val chats = mutableMapOf<String, ChatContext>() var currentAccount: ChatAccount? = null private set @@ -58,7 +68,7 @@ class MainActivity : AppCompatActivity() { NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration) } - fun processChatMessage(chatContext: ChatContext, chatMessage: ChatMessage){ + private fun processChatMessage(chatContext: ChatContext, chatMessage: ChatMessage){ requireNotNull(chatMessage) /*if (chatMessage.isDeleted()) { @@ -74,10 +84,15 @@ class MainActivity : AppCompatActivity() { //app.callEvent(EventType.REFRESH_ACCOUNTS) } MessageKind.LOGIN -> { - //app.callEvent(EventType.UPDATE_PROFILE) + loadChats() } MessageKind.LOGOUT -> { - //app.callSyncEvent(EventType.CLEANUP_PROFILE) + println("logout") + contactListViewModel.clearModel() + chatOverviewViewModel.clearModel() + contactListViewModel.clearModel() + chatViewModels.values.forEach { it.clearModel() } + chats.clear() } MessageKind.CREATED_ACCOUNT, MessageKind.UPDATE_ACCOUNT -> { @@ -88,12 +103,38 @@ class MainActivity : AppCompatActivity() { } MessageKind.JOIN, MessageKind.LEAVE -> { - /*val event = if (chatMessage.isSent()) { - EventType.UPDATE_CHATS + var uuid = gnunetChat.getUserPointerForContext(chatContext) + val chatContact = gnunetChat.getSenderFromMessage(chatMessage) + val lastMessagePreview = if (chatMessage.kind == MessageKind.JOIN) "${chatContact.name} joined the chat" else "${chatContact.name} left the chat" + val viewModel = getChatViewModel(chatContext) + + chatMessage.text = lastMessagePreview + chatMessage.type = ChatMessageType.SYSTEM + + + if (null == uuid) { + assert(chatMessage.kind == MessageKind.JOIN) + uuid = gnunetChat.randomUUID() + chats.put(uuid, chatContext) + chatOverviewViewModel.addOrUpdateChat(ChatSummary(chatContext,chatContact.name,lastMessagePreview)) } else { - EventType.PRESENCE_CONTACT - }*/ - //app.callMessageEvent(event, chatContext, chatMessage) + val localChatContext = chats[uuid] + val group = gnunetChat.getGroupFromContext(chatContext) + + if (null != localChatContext && null != group) { + if (MessageKind.JOIN == chatMessage.kind) { + chatOverviewViewModel.addOrUpdateChat( + ChatSummary( + localChatContext, + group.name, + chatContact.name+" "+lastMessagePreview + ) + ) + } + } + } + viewModel?.addMessage(chatMessage) + } MessageKind.CONTACT, MessageKind.SHARED_ATTRIBUTES -> { @@ -104,7 +145,17 @@ class MainActivity : AppCompatActivity() { } MessageKind.TEXT, MessageKind.FILE -> { - //app.callMessageEvent(EventType.RECEIVE_MESSAGE, chatContext, chatMessage) + val viewModel = getChatViewModel(chatContext) + val uuid = gnunetChat.getUserPointerForContext(chatContext) + val localChatContext = chats[uuid] + chatMessage.type = if (gnunetChat.getContactKey(chatMessage.sender!!) == gnunetChat.getProfileKey(handle)) + ChatMessageType.OWN + else ChatMessageType.OTHER + + if (localChatContext != null) { + viewModel?.addMessage(chatMessage) + chatOverviewViewModel.updateChatMessage(localChatContext, chatMessage) + } } MessageKind.DELETION -> { /*val target = chatMessage.getTarget() @@ -130,6 +181,56 @@ class MainActivity : AppCompatActivity() { } } + fun getChatViewModel(chatContext: ChatContext): ChatViewModel? { + val key = gnunetChat.getUserPointerForContext(chatContext) + var chatViewModel: ChatViewModel? = null + if (null != key) + chatViewModel = chatViewModels.getOrPut(key) { + ChatViewModel(chatContext) + } + return chatViewModel + } + + private fun loadChats() { + val summaries = mutableListOf<ChatSummary>() + + val contacts = mutableListOf<ChatContact>() + + gnunetChat.iterateContacts(handle) { contact -> + val chatContext = gnunetChat.getContactContext(contact) + val uuid = gnunetChat.randomUUID() + contacts.add(contact) + + gnunetChat.setUserPointerForContext(chatContext, uuid) + chats.put(uuid, chatContext) + summaries.add( + ChatSummary( + chatContext = chatContext, + displayName = contact.name + ) + ) + 0 // Rückgabewert (OK) + } + contactListViewModel.setContacts(contacts) + + gnunetChat.iterateGroups(handle) { group -> + val chatContext = gnunetChat.getGroupContext(group) + val uuid = gnunetChat.randomUUID() + + gnunetChat.setUserPointerForContext(chatContext, uuid) + chats.put(uuid, chatContext) + summaries.add( + ChatSummary( + chatContext = group.chatContext, + displayName = group.name + ) + ) + 0 + } + + chatOverviewViewModel.setChats(summaries) + } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.main_menu, menu) val current = currentAccount @@ -157,6 +258,16 @@ class MainActivity : AppCompatActivity() { navController.navigate(action) true } + R.id.menu_join_lobby -> { + val action = NavGraphDirections.actionGlobalLobbyJoinFragment() + navController.navigate(action) + true + } + R.id.menu_contact_list -> { + val action = NavGraphDirections.actionGlobalContactListFragment() + navController.navigate(action) + true + } R.id.menu_settings -> { // Beispiel: Navigation zu Einstellungen // startActivity(Intent(this, SettingsActivity::class.java)) diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/logic/ChatRegistry.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/logic/ChatRegistry.kt @@ -0,0 +1,17 @@ +package org.gnunet.gnunetmessenger.logic + +import org.gnunet.gnunetmessenger.viewmodel.ChatViewModel + +object ChatRegistry { + private val viewModels = mutableMapOf<String, ChatViewModel>() + + fun register(chatId: String, viewModel: ChatViewModel) { + viewModels[chatId] = viewModel + } + + fun unregister(chatId: String) { + viewModels.remove(chatId) + } + + fun getViewModel(chatId: String): ChatViewModel? = viewModels[chatId] +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatContact.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatContact.kt @@ -4,4 +4,7 @@ import android.os.Parcelable import kotlinx.parcelize.Parcelize @Parcelize -data class ChatContact(val chatContext: ChatContext) : Parcelable +data class ChatContact( + val chatContext: ChatContext, + val name: String +) : Parcelable diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatContext.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatContext.kt @@ -4,4 +4,6 @@ import android.os.Parcelable import kotlinx.parcelize.Parcelize @Parcelize -data class ChatContext (val chatContextType: ChatContextType): Parcelable +data class ChatContext ( + val chatContextType: ChatContextType, + var userPointer: String?): Parcelable diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatGroup.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatGroup.kt @@ -5,5 +5,6 @@ import kotlinx.parcelize.Parcelize @Parcelize data class ChatGroup ( - val chatContext: ChatContext + val chatContext: ChatContext, + val name: String ): Parcelable \ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatMessage.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatMessage.kt @@ -5,5 +5,10 @@ import kotlinx.parcelize.Parcelize @Parcelize data class ChatMessage( - val kind: MessageKind + val chatContext: ChatContext, + var text: String, + val timestamp: Long, + val sender: ChatContact?, + val kind: MessageKind, + var type: ChatMessageType? ): Parcelable diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatMessageType.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatMessageType.kt @@ -0,0 +1,7 @@ +package org.gnunet.gnunetmessenger.model + +enum class ChatMessageType { + OWN, + OTHER, + SYSTEM +} diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatSummary.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/model/ChatSummary.kt @@ -0,0 +1,7 @@ +package org.gnunet.gnunetmessenger.model + +data class ChatSummary( + val chatContext: ChatContext, + val displayName: String, + val lastMessagePreview: String? = null +) +\ No newline at end of file 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 @@ -17,10 +17,12 @@ interface GnunetChat { fun iterateAccounts(handle: ChatHandle, callback: (ChatAccount) -> Unit) fun createAccount(handle: ChatHandle, name: String): GnunetReturnValue fun connect(handle: ChatHandle, account: ChatAccount) + fun disconnect(handle: ChatHandle) fun getProfileName(handle: ChatHandle): String fun setProfileName(handle: ChatHandle, name: String) fun getProfileKey(handle: ChatHandle): String - fun setContactBlock(isBlocked: Boolean) + fun setContactBlocked(contact: ChatContact, isBlocked: Boolean) + fun isContactBlocked(contact: ChatContact): Boolean fun setAttribute(handle: ChatHandle,key: String, value: String) fun getAttributes(handle: ChatHandle, callback: (String, String) -> Unit) fun lobbyOpen(handle: ChatHandle, callback: (String) -> Unit) @@ -30,13 +32,26 @@ interface GnunetChat { 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 getGroupFromContext(context: ChatContext): ChatGroup + 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 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 setContactUserPointer(chatContact: ChatContact, userPointer: 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 randomUUID(): 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 @@ -2,12 +2,18 @@ package org.gnunet.gnunetmessenger.service.boundimpl import org.gnunet.gnunetmessenger.model.ChatAccount +import org.gnunet.gnunetmessenger.model.ChatContact import org.gnunet.gnunetmessenger.model.ChatContext +import org.gnunet.gnunetmessenger.model.ChatContextType +import org.gnunet.gnunetmessenger.model.ChatGroup import org.gnunet.gnunetmessenger.model.ChatHandle import org.gnunet.gnunetmessenger.model.ChatMessage +import org.gnunet.gnunetmessenger.model.ChatUri import org.gnunet.gnunetmessenger.model.GnunetReturnValue +import org.gnunet.gnunetmessenger.model.MessageKind import org.gnunet.gnunetmessenger.model.MessengerApp import org.gnunet.gnunetmessenger.service.GnunetChat +import java.util.UUID class GnunetChatBoundService : GnunetChat { override fun startChat( @@ -29,6 +35,10 @@ class GnunetChatBoundService : GnunetChat { TODO("Not yet implemented") } + override fun disconnect(handle: ChatHandle) { + TODO("Not yet implemented") + } + override fun getProfileName(handle: ChatHandle): String { TODO("Not yet implemented") } @@ -41,7 +51,11 @@ class GnunetChatBoundService : GnunetChat { TODO("Not yet implemented") } - override fun setContactBlock(isBlocked: Boolean) { + override fun setContactBlocked(contact: ChatContact, isBlocked: Boolean) { + TODO("Not yet implemented") + } + + override fun isContactBlocked(contact: ChatContact): Boolean { TODO("Not yet implemented") } @@ -63,4 +77,116 @@ class GnunetChatBoundService : GnunetChat { ) { TODO("Not yet implemented") } + + override fun setGroupName(group: ChatGroup, name: String) { + TODO("Not yet implemented") + } + + override fun createGroup(handle: ChatHandle, topic: String) { + TODO("Not yet implemented") + } + + override fun parseUri(uri: String): ChatUri { + TODO("Not yet implemented") + } + + override fun destroyUri(uri: ChatUri) { + TODO("Not yet implemented") + } + + override fun inviteContactToGroup(group: ChatGroup, contact: ChatContact) { + TODO("Not yet implemented") + } + + override fun getUserPointerForContext(context: ChatContext): String { + TODO("Not yet implemented") + } + + override fun setUserPointerForContext(context: ChatContext, userPointer: String) { + TODO("Not yet implemented") + } + + override fun getSenderFromMessage(message: ChatMessage): ChatContact { + TODO("Not yet implemented") + } + + override fun getGroupFromContext(context: ChatContext): ChatGroup { + TODO("Not yet implemented") + } + + override fun getMessageForGroupContact(group: ChatGroup, contact: ChatContact): ChatMessage { + TODO("Not yet implemented") + } + + override fun getMessageKind(message: ChatMessage): MessageKind { + TODO("Not yet implemented") + } + + override fun isMessageRecent(message: ChatMessage): GnunetReturnValue { + TODO("Not yet implemented") + } + + override fun getMessageTimestamp(message: ChatMessage): Long { + TODO("Not yet implemented") + } + + override fun setMessageForGroupContact( + group: ChatGroup, + contact: ChatContact, + message: ChatMessage + ) { + TODO("Not yet implemented") + } + + override fun iterateContacts(handle: ChatHandle, callback: (ChatContact) -> Int) { + TODO("Not yet implemented") + } + + override fun iterateGroups(handle: ChatHandle, callback: (ChatGroup) -> Int) { + TODO("Not yet implemented") + } + + override fun getContactContext(chatContact: ChatContact): ChatContext { + TODO("Not yet implemented") + } + + override fun getGroupContext(chatGroup: ChatGroup): ChatContext { + TODO("Not yet implemented") + } + + override fun getContactUserPointer(chatContact: ChatContact): String { + TODO("Not yet implemented") + } + + override fun setContactUserPointer(chatContact: ChatContact, userPointer: String) { + TODO("Not yet implemented") + } + + override fun getGroupUserPointer(chatGroup: ChatGroup): String { + TODO("Not yet implemented") + } + + override fun setGroupUserPointer(chatGroup: ChatGroup, userPointer: String) { + TODO("Not yet implemented") + } + + override fun sendText(chatContext: ChatContext, text: String) { + TODO("Not yet implemented") + } + + override fun getContactKey(chatContact: ChatContact): String { + TODO("Not yet implemented") + } + + override fun getContextContact(context: ChatContext): ChatContact { + TODO("Not yet implemented") + } + + override fun deleteContact(chatContact: ChatContact) { + TODO("Not yet implemented") + } + + override fun randomUUID(): String { + return UUID.randomUUID().toString() + } } \ 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 @@ -2,19 +2,31 @@ package org.gnunet.gnunetmessenger.service.mock import org.gnunet.gnunetmessenger.model.ChatAccount +import org.gnunet.gnunetmessenger.model.ChatContact import org.gnunet.gnunetmessenger.model.ChatContext +import org.gnunet.gnunetmessenger.model.ChatContextType +import org.gnunet.gnunetmessenger.model.ChatGroup import org.gnunet.gnunetmessenger.model.ChatHandle import org.gnunet.gnunetmessenger.model.ChatMessage +import org.gnunet.gnunetmessenger.model.ChatMessageType +import org.gnunet.gnunetmessenger.model.ChatUri import org.gnunet.gnunetmessenger.model.GnunetReturnValue +import org.gnunet.gnunetmessenger.model.MessageKind import org.gnunet.gnunetmessenger.model.MessengerApp import org.gnunet.gnunetmessenger.service.GnunetChat +import java.util.UUID class GnunetChatMock : GnunetChat { + + private lateinit var messageCallback: (ChatContext, ChatMessage) -> Unit + private var uuidCounter: Long = 0 + override fun startChat( messengerApp: MessengerApp, callback: (ChatContext, ChatMessage) -> Unit ): ChatHandle { println("start chat") + messageCallback = callback return ChatHandle(1) } @@ -37,6 +49,17 @@ class GnunetChatMock : GnunetChat { override fun connect(handle: ChatHandle, account: ChatAccount) { println("connect") + messageCallback(ChatContext(ChatContextType.UNKNOWN,UUID.randomUUID().toString()), + ChatMessage(ChatContext(ChatContextType.UNKNOWN,null),"",0,null,MessageKind.LOGIN,null) + ) + } + + override fun disconnect(handle: ChatHandle) { + println("disconnect") + uuidCounter = 0 + messageCallback(ChatContext(ChatContextType.UNKNOWN,UUID.randomUUID().toString()), + ChatMessage(ChatContext(ChatContextType.UNKNOWN,null),"",0,null,MessageKind.LOGOUT,null) + ) } override fun getProfileName(handle: ChatHandle): String { @@ -51,7 +74,12 @@ class GnunetChatMock : GnunetChat { return "somekey1337" } - override fun setContactBlock(isBlocked: Boolean) { + override fun isContactBlocked(contact: ChatContact): Boolean { + println("is contact blocked") + return false + } + + override fun setContactBlocked(contact: ChatContact, isBlocked: Boolean) { println("isblocked:" + isBlocked) } @@ -81,4 +109,170 @@ class GnunetChatMock : GnunetChat { ) { TODO("Not yet implemented") } + + override fun setGroupName(group: ChatGroup, name: String) { + TODO("Not yet implemented") + } + + override fun createGroup(handle: ChatHandle, topic: String) { + TODO("Not yet implemented") + } + + override fun parseUri(uri: String): ChatUri { + TODO("Not yet implemented") + } + + override fun destroyUri(uri: ChatUri) { + TODO("Not yet implemented") + } + + override fun inviteContactToGroup(group: ChatGroup, contact: ChatContact) { + TODO("Not yet implemented") + } + + override fun getUserPointerForContext(context: ChatContext): String? { + return context.userPointer + } + + override fun setUserPointerForContext(context: ChatContext, userPointer: String) { + context.userPointer = userPointer + } + + override fun getSenderFromMessage(message: ChatMessage): ChatContact { + return ChatContact(ChatContext(ChatContextType.CONTACT, null), message.sender?.name ?: "") + } + + override fun getGroupFromContext(context: ChatContext): ChatGroup? { + if ("3" == context.userPointer){ + val contextDev = ChatContext(ChatContextType.GROUP, null) + return ChatGroup(contextDev, name = "Dev Team") + } + return null + } + + override fun getMessageForGroupContact(group: ChatGroup, contact: ChatContact): ChatMessage { + TODO("Not yet implemented") + } + + override fun getMessageKind(message: ChatMessage): MessageKind { + TODO("Not yet implemented") + } + + override fun isMessageRecent(message: ChatMessage): GnunetReturnValue { + TODO("Not yet implemented") + } + + override fun getMessageTimestamp(message: ChatMessage): Long { + TODO("Not yet implemented") + } + + override fun setMessageForGroupContact( + group: ChatGroup, + contact: ChatContact, + message: ChatMessage + ) { + TODO("Not yet implemented") + } + + override fun iterateContacts(handle: ChatHandle, callback: (ChatContact) -> Int) { + val contextAlice = ChatContext(ChatContextType.CONTACT, null) + val contextBob = ChatContext(ChatContextType.CONTACT, null) + val contacts = listOf( + + ChatContact(contextAlice, name = "Alice"), + ChatContact(contextBob, name = "Bob") + ) + for (contact in contacts) { + callback(contact) + } + } + + override fun iterateGroups(handle: ChatHandle, callback: (ChatGroup) -> Int) { + println("iterate groups") + val contextDev = ChatContext(ChatContextType.GROUP, null) + val contextFriends = ChatContext(ChatContextType.GROUP, null) + val groups = listOf( + ChatGroup(contextDev, name = "Dev Team"), + ChatGroup(contextFriends, name = "Friends") + ) + for (group in groups) { + callback(group) + } + + messageCallback(ChatContext(ChatContextType.CONTACT, null), + ChatMessage(ChatContext(ChatContextType.CONTACT,null),"",0, + ChatContact(ChatContext(ChatContextType.CONTACT, null), "Mallory"),MessageKind.JOIN, null) + ) + + messageCallback(ChatContext(ChatContextType.CONTACT,"5"), + ChatMessage(ChatContext(ChatContextType.CONTACT,null),"Hi, I am Mallory!",0, + ChatContact(ChatContext(ChatContextType.CONTACT, "6"), "Mallory"),MessageKind.TEXT, null) + ) + + messageCallback(ChatContext(ChatContextType.GROUP,"3"), + ChatMessage(ChatContext(ChatContextType.GROUP,null),"",0, + ChatContact(ChatContext(ChatContextType.CONTACT, null), "Flo"),MessageKind.JOIN, null) + ) + + messageCallback(ChatContext(ChatContextType.GROUP,"3"), + ChatMessage(ChatContext(ChatContextType.GROUP,null),"Hi, I am Flo!",0, + ChatContact(ChatContext(ChatContextType.CONTACT, "7"), "Flo"),MessageKind.TEXT, null) + ) + + messageCallback(ChatContext(ChatContextType.GROUP,"3"), + ChatMessage(ChatContext(ChatContextType.GROUP,null),"",0, + ChatContact(ChatContext(ChatContextType.CONTACT, "1"), "Alice"),MessageKind.JOIN, null) + ) + + messageCallback(ChatContext(ChatContextType.GROUP,"3"), + ChatMessage(ChatContext(ChatContextType.GROUP,null),"Hi, I am Alice!",0, + ChatContact(ChatContext(ChatContextType.CONTACT, "1"), "Alice"),MessageKind.TEXT, null) + ) + + } + + override fun getContactContext(chatContact: ChatContact): ChatContext { + return chatContact.chatContext + } + + override fun getGroupContext(chatGroup: ChatGroup): ChatContext { + return chatGroup.chatContext + } + + override fun getContactUserPointer(chatContact: ChatContact): String { + TODO("Not yet implemented") + } + + override fun setContactUserPointer(chatContact: ChatContact, userPointer: String) { + TODO("Not yet implemented") + } + + override fun getGroupUserPointer(chatGroup: ChatGroup): String { + TODO("Not yet implemented") + } + + override fun setGroupUserPointer(chatGroup: ChatGroup, userPointer: String) { + TODO("Not yet implemented") + } + + override fun sendText(chatContext: ChatContext, text: String) { + println("send text: $text") + } + + override fun getContactKey(chatContact: ChatContact): String { + return "otherkey42" + } + + override fun getContextContact(context: ChatContext): ChatContact { + println("get contact for context") + return ChatContact(context,"test") + } + + override fun deleteContact(chatContact: ChatContact) { + println("delete contact") + } + + override fun randomUUID(): String { + return uuidCounter++.toString() + } } \ 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 @@ -37,6 +37,8 @@ class AccountListFragment : Fragment() { createButton = view.findViewById(R.id.btn_create_account) adapter = AccountAdapter { selectedAccount -> + if (null != activity.currentAccount) + gnunetChat.disconnect(handle) gnunetChat.connect(handle, selectedAccount) selectedAccount.key = gnunetChat.getProfileKey(handle) val action = AccountListFragmentDirections.actionAccountListFragmentToAccountOverviewFragment(account = selectedAccount) diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/AccountOverviewFragment.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/AccountOverviewFragment.kt @@ -4,11 +4,13 @@ import android.os.Bundle import android.view.* import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import org.gnunet.gnunetmessenger.MainActivity import org.gnunet.gnunetmessenger.databinding.FragmentAccountOverviewBinding import org.gnunet.gnunetmessenger.ui.adapters.ChatListAdapter +import org.gnunet.gnunetmessenger.viewmodel.ChatOverviewViewModel class AccountOverviewFragment : Fragment() { @@ -16,6 +18,10 @@ class AccountOverviewFragment : Fragment() { private val binding get() = _binding!! private lateinit var drawerLayout: DrawerLayout + private lateinit var adapter: ChatListAdapter + + // Hol dir das ViewModel direkt aus der MainActivity (shared scope) + private val viewModel: ChatOverviewViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -28,16 +34,20 @@ class AccountOverviewFragment : Fragment() { drawerLayout = binding.accountDrawerLayout - // Setup Chat list (dummy data vorerst) - val chatList = listOf("Chat mit Alex", "Projektgruppe", "Family", "Plattform XYZ") - val adapter = ChatListAdapter(chatList) { selectedChat -> - // Navigiere zum ChatFragment mit dem ausgewählten Chat - val action = AccountOverviewFragmentDirections.actionAccountOverviewFragmentToChatFragment(chatName = selectedChat) + adapter = ChatListAdapter(emptyList()) { selectedChat -> + val action = AccountOverviewFragmentDirections + .actionAccountOverviewFragmentToChatFragment(chatContext = selectedChat.chatContext) findNavController().navigate(action) } + binding.chatListRecyclerView.layoutManager = LinearLayoutManager(requireContext()) binding.chatListRecyclerView.adapter = adapter + // Observer auf LiveData aus dem ViewModel + viewModel.chats.observe(viewLifecycleOwner) { chatList -> + adapter.submitList(chatList) + } + return binding.root } @@ -45,4 +55,4 @@ class AccountOverviewFragment : Fragment() { super.onDestroyView() _binding = null } -} +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/CreateAccountFragment.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/CreateAccountFragment.kt @@ -78,6 +78,8 @@ class CreateAccountFragment : Fragment() { GnunetReturnValue.OK -> { gnunetChat.iterateAccounts(handle) { account -> if (account.name == accountName) { + if (null != mainActivity.currentAccount) + gnunetChat.disconnect(handle) gnunetChat.connect(handle, account) requireActivity().runOnUiThread { val action = diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ChatListAdapter.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ChatListAdapter.kt @@ -4,18 +4,20 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.gnunet.gnunetmessenger.databinding.ItemChatBinding +import org.gnunet.gnunetmessenger.model.ChatSummary class ChatListAdapter( - private val chats: List<String>, // später ersetzen durch ChatModel - private val onChatClick: (String) -> Unit + private var chats: List<ChatSummary>, + private val onChatClick: (ChatSummary) -> Unit ) : RecyclerView.Adapter<ChatListAdapter.ChatViewHolder>() { inner class ChatViewHolder(private val binding: ItemChatBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(chatName: String) { - binding.chatName.text = chatName + fun bind(chat: ChatSummary) { + binding.chatName.text = chat.displayName + binding.lastMessage.text = chat.lastMessagePreview ?: "" binding.root.setOnClickListener { - onChatClick(chatName) + onChatClick(chat) } } } @@ -30,4 +32,9 @@ class ChatListAdapter( } override fun getItemCount() = chats.size + + fun submitList(newChats: List<ChatSummary>) { + this.chats = newChats + notifyDataSetChanged() + } } diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ChatMessageAdapter.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ChatMessageAdapter.kt @@ -0,0 +1,104 @@ +package org.gnunet.gnunetmessenger.ui.adapters + + + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.gnunet.gnunetmessenger.R +import org.gnunet.gnunetmessenger.model.ChatMessage +import org.gnunet.gnunetmessenger.model.ChatMessageType +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.Date + +class ChatMessageAdapter : ListAdapter<ChatMessage, RecyclerView.ViewHolder>(DiffCallback()) { + + companion object { + private const val VIEW_TYPE_OWN = 1 + private const val VIEW_TYPE_OTHER = 2 + private const val VIEW_TYPE_SYSTEM = 3 + } + + override fun getItemViewType(position: Int): Int { + return when (getItem(position).type) { + ChatMessageType.OWN -> VIEW_TYPE_OWN + ChatMessageType.OTHER -> VIEW_TYPE_OTHER + ChatMessageType.SYSTEM -> VIEW_TYPE_SYSTEM + else -> VIEW_TYPE_SYSTEM + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + VIEW_TYPE_OWN -> { + val view = inflater.inflate(R.layout.item_message_own, parent, false) + OwnMessageViewHolder(view) + } + VIEW_TYPE_OTHER -> { + val view = inflater.inflate(R.layout.item_message_other, parent, false) + OtherMessageViewHolder(view) + } + VIEW_TYPE_SYSTEM -> { + val view = inflater.inflate(R.layout.item_message_system, parent, false) + SystemMessageViewHolder(view) + } + else -> throw IllegalArgumentException("Unknown view type $viewType") + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val message = getItem(position) + when (holder) { + is OwnMessageViewHolder -> holder.bind(message) + is OtherMessageViewHolder -> holder.bind(message) + is SystemMessageViewHolder -> holder.bind(message) + } + } + + class OwnMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(message: ChatMessage) { + itemView.findViewById<TextView>(R.id.messageText).text = message.text + itemView.findViewById<TextView>(R.id.timestamp).text = formatTime(message.timestamp) + } + } + + class OtherMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(message: ChatMessage) { + itemView.findViewById<TextView>(R.id.messageText).text = message.text + itemView.findViewById<TextView>(R.id.senderName).text = message.sender?.name + itemView.findViewById<TextView>(R.id.timestamp).text = formatTime(message.timestamp) + } + } + + class SystemMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(message: ChatMessage) { + itemView.findViewById<TextView>(R.id.messageText).text = message.text + itemView.findViewById<TextView>(R.id.timestamp).text = formatTime(message.timestamp) + } + } + + class DiffCallback : DiffUtil.ItemCallback<ChatMessage>() { + override fun areItemsTheSame(oldItem: ChatMessage, newItem: ChatMessage): Boolean { + // Zwei Nachrichten sind gleich, wenn Text, Absender und Zeit identisch sind + return oldItem.text == newItem.text && + oldItem.sender == newItem.sender && + oldItem.timestamp == newItem.timestamp + } + + override fun areContentsTheSame(oldItem: ChatMessage, newItem: ChatMessage): Boolean { + // Wenn alle Felder identisch sind, dann Inhalte gleich + return oldItem == newItem + } + } +} + +fun formatTime(timestamp: Long): String { + val sdf = SimpleDateFormat("HH:mm", Locale.getDefault()) + return sdf.format(Date(timestamp)) +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ContactListAdapter.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/adapters/ContactListAdapter.kt @@ -0,0 +1,36 @@ +package org.gnunet.gnunetmessenger.ui.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import org.gnunet.gnunetmessenger.R +import org.gnunet.gnunetmessenger.model.ChatContact + +class ContactListAdapter : RecyclerView.Adapter<ContactListAdapter.ViewHolder>() { + + private val contacts = mutableListOf<ChatContact>() + + fun submitList(list: List<ChatContact>) { + contacts.clear() + contacts.addAll(list) + notifyDataSetChanged() + } + + class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val nameText: TextView = view.findViewById(R.id.contactName) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_contact, parent, false) + return ViewHolder(view) + } + + override fun getItemCount(): Int = contacts.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.nameText.text = contacts[position].name + } +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt @@ -1,6 +1,135 @@ package org.gnunet.gnunetmessenger.ui.chat +import android.os.Bundle +import android.view.* +import android.widget.Button +import android.widget.EditText +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import org.gnunet.gnunetmessenger.MainActivity +import org.gnunet.gnunetmessenger.R +import org.gnunet.gnunetmessenger.model.ChatContact +import org.gnunet.gnunetmessenger.model.ChatContext +import org.gnunet.gnunetmessenger.model.ChatMessage +import org.gnunet.gnunetmessenger.model.ChatMessageType +import org.gnunet.gnunetmessenger.model.MessageKind +import org.gnunet.gnunetmessenger.ui.adapters.ChatMessageAdapter +import org.gnunet.gnunetmessenger.viewmodel.ChatViewModel -class ChatFragment : Fragment() { -} -\ No newline at end of file +class ChatFragment : Fragment(R.layout.fragment_chat) { + + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: ChatMessageAdapter + private val args: ChatFragmentArgs by navArgs() + private lateinit var chatViewModel: ChatViewModel + private lateinit var chatContext: ChatContext + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val menuHost = requireActivity() as MenuHost + menuHost.addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.chat_menu, menu) + } + + override fun onPrepareMenu(menu: Menu) { + val mainActivity = requireActivity() as MainActivity + val gnunetChat = mainActivity.getGnunetChatInstance() + val contact = gnunetChat.getContextContact(chatContext) + val isBlocked = gnunetChat.isContactBlocked(contact) + + val blockItem = menu.findItem(R.id.menu_block_contact) + blockItem.title = if (isBlocked) "Unblock Contact" else "Block Contact" + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + val mainActivity = requireActivity() as MainActivity + val gnunetChat = mainActivity.getGnunetChatInstance() + val handle = mainActivity.getChatHandle() + + return when (menuItem.itemId) { + R.id.menu_share_identity -> { + val action = ChatFragmentDirections.actionChatFragmentToLobbyDisplayFragment(lobbyId = gnunetChat.getProfileKey(handle), + // lifetime not used here + lifetime = "0") + findNavController().navigate(action) + true + } + R.id.menu_block_contact -> { + val contact = gnunetChat.getContextContact(chatContext) + val isBlocked = gnunetChat.isContactBlocked(contact) + gnunetChat.setContactBlocked(contact, !isBlocked) + requireActivity().invalidateOptionsMenu() + true + } + R.id.menu_leave_chat -> { + val contact = gnunetChat.getContextContact(chatContext) + gnunetChat.deleteContact(contact) + true + } + else -> false + } + } + }, viewLifecycleOwner) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_chat, container, false) + val activity = activity as MainActivity + val gnunetChat = activity.getGnunetChatInstance() + + chatContext = args.chatContext + chatViewModel = (requireActivity() as MainActivity).getChatViewModel(chatContext)!! + recyclerView = view.findViewById(R.id.chatRecyclerView) + recyclerView.layoutManager = LinearLayoutManager(requireContext()) + + // Adapter initialisieren + adapter = ChatMessageAdapter() + recyclerView.adapter = adapter + + adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + recyclerView.scrollToPosition(adapter.itemCount - 1) + } + }) + + // Beobachte das LiveData aus dem ViewModel + chatViewModel.messages.observe(viewLifecycleOwner, Observer { messages -> + // Wenn sich die Nachrichten im ViewModel ändern, aktualisiere den Adapter + adapter.submitList(messages) + recyclerView.scrollToPosition(messages.size - 1) + }) + + // Sende-Nachricht Logik + view.findViewById<Button>(R.id.sendButton).setOnClickListener { + val input = view.findViewById<EditText>(R.id.inputMessage) + val text = input.text.toString() + if (text.isNotBlank()) { + // Erstelle eine neue Nachricht und sende sie an das ViewModel + val newMessage = ChatMessage( + chatContext = chatContext, + text = text, + timestamp = System.currentTimeMillis(), + sender = ChatContact(chatContext, activity.currentAccount?.name ?: ""), + kind = MessageKind.TEXT, + type = ChatMessageType.OWN + ) + chatViewModel.addMessage(newMessage) + gnunetChat.sendText(chatContext, text) + input.text.clear() // Eingabefeld leeren + } + } + + return view + } +} diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/contact/ContactListFragment.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/contact/ContactListFragment.kt @@ -0,0 +1,33 @@ +package org.gnunet.gnunetmessenger.ui.contacts + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import org.gnunet.gnunetmessenger.MainActivity +import org.gnunet.gnunetmessenger.R +import org.gnunet.gnunetmessenger.ui.adapters.ContactListAdapter +import org.gnunet.gnunetmessenger.viewmodel.ContactListViewModel + +class ContactListFragment : Fragment(R.layout.fragment_contact_list) { + + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: ContactListAdapter + private val viewModel: ContactListViewModel by activityViewModels() // ViewModel teilen mit Activity + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + recyclerView = view.findViewById(R.id.contactRecyclerView) + recyclerView.layoutManager = LinearLayoutManager(requireContext()) + adapter = ContactListAdapter() + recyclerView.adapter = adapter + + viewModel.contacts.observe(viewLifecycleOwner, Observer { contactList -> + adapter.submitList(contactList) + }) + } +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/contact/LobbyJoinFragment.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/contact/LobbyJoinFragment.kt @@ -0,0 +1,134 @@ +package org.gnunet.gnunetmessenger.ui.lobby + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log +import android.view.* +import android.widget.Button +import android.widget.EditText +import androidx.camera.core.* +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import org.gnunet.gnunetmessenger.R +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.common.InputImage +import org.gnunet.gnunetmessenger.MainActivity +import java.util.concurrent.Executors + +class LobbyJoinFragment : Fragment() { + + private lateinit var previewView: PreviewView + private lateinit var qrText: EditText + private lateinit var joinButton: Button + private lateinit var cancelButton: Button + + private val cameraExecutor = Executors.newSingleThreadExecutor() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val view = inflater.inflate(R.layout.fragment_join_lobby, container, false) + + previewView = view.findViewById(R.id.camera_preview) + qrText = view.findViewById(R.id.qr_text) + joinButton = view.findViewById(R.id.join_button) + cancelButton = view.findViewById(R.id.cancel_button) + + joinButton.setOnClickListener { + val lobbyId = qrText.text.toString() + val activity = activity as MainActivity + val gnunetChat = activity.getGnunetChatInstance() + val handle = activity.getChatHandle() + gnunetChat.lobbyJoin(handle, lobbyId) + findNavController().popBackStack() + } + + cancelButton.setOnClickListener { + findNavController().popBackStack() + } + + if (allPermissionsGranted()) { + startCamera() + } else { + ActivityCompat.requestPermissions( + requireActivity(), + arrayOf(Manifest.permission.CAMERA), + 10 + ) + } + + return view + } + + private fun startCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) + + cameraProviderFuture.addListener({ + val cameraProvider = cameraProviderFuture.get() + val preview = Preview.Builder().build().also { + it.setSurfaceProvider(previewView.surfaceProvider) + } + + val barcodeScanner = BarcodeScanning.getClient() + + val analysis = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + + analysis.setAnalyzer(cameraExecutor) { imageProxy -> + processImageProxy(barcodeScanner, imageProxy) + } + + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + try { + cameraProvider.unbindAll() + cameraProvider.bindToLifecycle(this, cameraSelector, preview, analysis) + } catch (e: Exception) { + Log.e("LobbyJoinFragment", "Camera binding failed", e) + } + + }, ContextCompat.getMainExecutor(requireContext())) + } + + private fun processImageProxy(scanner: com.google.mlkit.vision.barcode.BarcodeScanner, imageProxy: ImageProxy) { + val mediaImage = imageProxy.image ?: run { + imageProxy.close() + return + } + + val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) + scanner.process(inputImage) + .addOnSuccessListener { barcodes -> + for (barcode in barcodes) { + val rawValue = barcode.rawValue + if (!rawValue.isNullOrBlank()) { + requireActivity().runOnUiThread { + qrText.setText(rawValue) + } + break // nur ersten Treffer nehmen + } + } + } + .addOnFailureListener { + Log.e("LobbyJoinFragment", "Barcode scan failed", it) + } + .addOnCompleteListener { + imageProxy.close() + } + } + + private fun allPermissionsGranted() = + ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED + + override fun onDestroy() { + super.onDestroy() + cameraExecutor.shutdown() + } +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatOverviewViewModel.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatOverviewViewModel.kt @@ -0,0 +1,47 @@ +package org.gnunet.gnunetmessenger.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.gnunet.gnunetmessenger.model.ChatContext +import org.gnunet.gnunetmessenger.model.ChatMessage +import org.gnunet.gnunetmessenger.model.ChatSummary +import org.gnunet.gnunetmessenger.model.MessageKind + +class ChatOverviewViewModel : ViewModel() { + + val chats = MutableLiveData<List<ChatSummary>>(emptyList()) + + + fun setChats(newChats: List<ChatSummary>) { + chats.value = newChats + } + + fun updateChatMessage(chatContext: ChatContext, chatMessage: ChatMessage) { + val updated = chats.value.orEmpty().map { + if (it.chatContext.userPointer == chatContext.userPointer) + it.copy(lastMessagePreview = chatMessage.text) + else + it + } + chats.value = updated + } + + fun addOrUpdateChat(chat: ChatSummary) { + val current = chats.value.orEmpty().toMutableList() + val index = current.indexOfFirst { it.chatContext.userPointer == chat.chatContext.userPointer } + if (index >= 0) { + current[index] = chat + } else { + current.add(chat) + } + chats.value = current + } + + fun clearModel(){ + chats.value = emptyList() + } + + fun removeChat(chat: ChatSummary) { + chats.value = chats.value?.filter { it.chatContext.userPointer != chat.chatContext.userPointer } + } +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatViewModel.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatViewModel.kt @@ -0,0 +1,24 @@ +package org.gnunet.gnunetmessenger.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.gnunet.gnunetmessenger.model.ChatContext +import org.gnunet.gnunetmessenger.model.ChatMessage + +class ChatViewModel(chatContext: ChatContext) : ViewModel() { + + private val _messages = MutableLiveData<List<ChatMessage>>(emptyList()) + val messages: LiveData<List<ChatMessage>> = _messages + + fun addMessage(msg: ChatMessage) { + val updated = _messages.value.orEmpty().toMutableList().apply { + add(msg) + } + _messages.value = updated + } + + fun clearModel() { + _messages.value = emptyList() + } +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatViewModelFactory.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ChatViewModelFactory.kt @@ -0,0 +1,16 @@ +// Datei: ChatViewModelFactory.kt + +package org.gnunet.gnunetmessenger.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import org.gnunet.gnunetmessenger.model.ChatContext + +class ChatViewModelFactory(private val chatContext: ChatContext) : ViewModelProvider.Factory { + override fun <T : ViewModel> create(modelClass: Class<T>): T { + if (modelClass.isAssignableFrom(ChatViewModel::class.java)) { + return ChatViewModel(chatContext) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ContactListViewModel.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/viewmodel/ContactListViewModel.kt @@ -0,0 +1,20 @@ +package org.gnunet.gnunetmessenger.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.gnunet.gnunetmessenger.model.ChatContact + +class ContactListViewModel : ViewModel() { + + private val _contacts = MutableLiveData<List<ChatContact>>() + val contacts: LiveData<List<ChatContact>> = _contacts + + fun setContacts(newContacts: List<ChatContact>) { + _contacts.value = newContacts + } + + fun clearModel(){ + _contacts.value = emptyList() + } +} +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/drawable/bubble_other.xml b/GNUnetMessenger/app/src/main/res/drawable/bubble_other.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#E5E5EA" /> + <corners android:radius="16dp" /> + <padding + android:left="8dp" + android:top="4dp" + android:right="8dp" + android:bottom="4dp" /> +</shape> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/drawable/bubble_own.xml b/GNUnetMessenger/app/src/main/res/drawable/bubble_own.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#007AFF" /> + <corners android:radius="16dp" /> + <padding + android:left="8dp" + android:top="4dp" + android:right="8dp" + android:bottom="4dp" /> +</shape> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/drawable/edittext_bg.xml b/GNUnetMessenger/app/src/main/res/drawable/edittext_bg.xml @@ -0,0 +1,5 @@ +<!-- res/drawable/edittext_bg.xml --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#f0f0f0"/> + <corners android:radius="8dp"/> +</shape> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/layout/fragment_chat.xml b/GNUnetMessenger/app/src/main/res/layout/fragment_chat.xml @@ -0,0 +1,34 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <!-- Chatverlauf --> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/chatRecyclerView" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:scrollbars="vertical" + android:clipToPadding="false" + android:padding="8dp" + android:descendantFocusability="afterDescendants" /> + + <!-- Eingabe --> + <EditText + android:id="@+id/inputMessage" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="Nachricht eingeben..." + android:inputType="text" + android:padding="10dp" + android:background="@drawable/edittext_bg" /> + + <!-- Button --> + <Button + android:id="@+id/sendButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Senden" + android:layout_marginTop="8dp" /> +</LinearLayout> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/layout/fragment_contact_list.xml b/GNUnetMessenger/app/src/main/res/layout/fragment_contact_list.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/contactRecyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/layout/fragment_join_lobby.xml b/GNUnetMessenger/app/src/main/res/layout/fragment_join_lobby.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/lobby_join_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ui.lobby.LobbyJoinFragment"> + + <androidx.camera.view.PreviewView + android:id="@+id/camera_preview" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@id/qr_text" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + + <EditText + android:id="@+id/qr_text" + android:hint="Lobby ID oder QR Code" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:inputType="text" + app:layout_constraintTop_toBottomOf="@id/camera_preview" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:layout_margin="16dp"/> + + <LinearLayout + android:id="@+id/button_row" + android:orientation="horizontal" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:gravity="end" + android:layout_margin="16dp" + app:layout_constraintTop_toBottomOf="@id/qr_text" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + + <Button + android:id="@+id/cancel_button" + android:text="Cancel" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <Button + android:id="@+id/join_button" + android:text="@string/join" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + </LinearLayout> + +</androidx.constraintlayout.widget.ConstraintLayout> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/layout/item_chat.xml b/GNUnetMessenger/app/src/main/res/layout/item_chat.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -6,11 +5,27 @@ android:elevation="4dp" android:foreground="?attr/selectableItemBackground"> - <TextView - android:id="@+id/chatName" - android:layout_width="match_parent" - android:layout_height="wrap_content" + <LinearLayout + android:orientation="vertical" android:padding="16dp" - android:textSize="18sp" - android:text="Chat Name" /> -</androidx.cardview.widget.CardView> + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/chatName" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="18sp" + android:text="Chat Name" /> + + <TextView + android:id="@+id/lastMessage" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textColor="@android:color/darker_gray" + android:maxLines="1" + android:ellipsize="end" + android:text="Letzte Nachricht..." /> + </LinearLayout> +</androidx.cardview.widget.CardView> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/layout/item_contact.xml b/GNUnetMessenger/app/src/main/res/layout/item_contact.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="12dp"> + + <TextView + android:id="@+id/contactName" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="Contact Name" + android:textSize="16sp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> +</androidx.constraintlayout.widget.ConstraintLayout> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/layout/item_message_other.xml b/GNUnetMessenger/app/src/main/res/layout/item_message_other.xml @@ -0,0 +1,32 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + android:orientation="vertical" + android:padding="8dp"> + + <TextView + android:id="@+id/senderName" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textStyle="bold" + android:textColor="#333333" + android:text="Name" /> + + <TextView + android:id="@+id/messageText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/bubble_other" + android:textColor="#000000" + android:padding="8dp" + android:text="Nachricht" /> + + <TextView + android:id="@+id/timestamp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="10sp" + android:textColor="#888888" + android:layout_marginTop="2dp"/> +</LinearLayout> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/layout/item_message_own.xml b/GNUnetMessenger/app/src/main/res/layout/item_message_own.xml @@ -0,0 +1,24 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="end" + android:orientation="vertical" + android:padding="8dp"> + + <TextView + android:id="@+id/messageText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/bubble_own" + android:textColor="#FFFFFF" + android:padding="8dp" + android:text="Nachricht" /> + + <TextView + android:id="@+id/timestamp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="10sp" + android:textColor="#888888" + android:layout_marginTop="2dp"/> +</LinearLayout> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/layout/item_message_system.xml b/GNUnetMessenger/app/src/main/res/layout/item_message_system.xml @@ -0,0 +1,23 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical" + android:padding="8dp"> + + <TextView + android:id="@+id/messageText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#888888" + android:textStyle="italic" + android:text="Systemnachricht" /> + + <TextView + android:id="@+id/timestamp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="10sp" + android:textColor="#AAAAAA" + android:layout_marginTop="2dp"/> +</LinearLayout> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/menu/chat_menu.xml b/GNUnetMessenger/app/src/main/res/menu/chat_menu.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/menu_share_identity" + android:title="@string/reveal_identity" + android:orderInCategory="1" + app:showAsAction="never" /> + + <item + android:id="@+id/menu_block_contact" + android:title="@string/block_contact" + android:orderInCategory="2" + app:showAsAction="never" /> + + <item + android:id="@+id/menu_leave_chat" + android:title="@string/leave_chat" + android:orderInCategory="3" + app:showAsAction="never" /> +</menu> +\ No newline at end of file diff --git a/GNUnetMessenger/app/src/main/res/menu/main_menu.xml b/GNUnetMessenger/app/src/main/res/menu/main_menu.xml @@ -6,6 +6,7 @@ <item android:id="@+id/menu_current_account" android:title="Account: Name" + android:orderInCategory="101" app:showAsAction="never" tools:ignore="HardcodedText" /> @@ -13,14 +14,27 @@ <item android:id="@+id/menu_create_lobby" android:title="@string/create_lobby" + android:orderInCategory="102" + app:showAsAction="never" /> + <item + android:id="@+id/menu_join_lobby" + android:title="@string/join_lobby" + android:orderInCategory="103" + app:showAsAction="never" /> + <item + android:id="@+id/menu_contact_list" + android:title="Contacts" + android:orderInCategory="104" app:showAsAction="never" /> <item android:id="@+id/menu_settings" android:title="@string/settings" + android:orderInCategory="105" app:showAsAction="never" /> <item android:id="@+id/menu_about" android:title="@string/about" + android:orderInCategory="106" app:showAsAction="never" /> </menu> diff --git a/GNUnetMessenger/app/src/main/res/navigation/nav_graph.xml b/GNUnetMessenger/app/src/main/res/navigation/nav_graph.xml @@ -47,11 +47,7 @@ app:argType="org.gnunet.gnunetmessenger.model.ChatAccount" /> <action android:id="@+id/action_accountOverviewFragment_to_chatFragment" - app:destination="@id/chatFragment"> - <argument - android:name="chatName" - app:argType="string" /> - </action> + app:destination="@id/chatFragment"/> <action android:id="@+id/action_accountOverviewFragment_to_accountDetailsFragment" app:destination="@id/accountDetailsFragment" /> @@ -61,7 +57,15 @@ <fragment android:id="@+id/chatFragment" android:name="org.gnunet.gnunetmessenger.ui.chat.ChatFragment" - android:label="Chat" /> + android:label="Chat" > + <argument + android:name="chatContext" + app:argType="org.gnunet.gnunetmessenger.model.ChatContext" + app:nullable="false" /> + <action + android:id="@+id/action_chatFragment_to_lobbyDisplayFragment" + app:destination="@id/lobbyDisplayFragment" /> + </fragment> <fragment android:id="@+id/attributeListFragment" @@ -94,6 +98,12 @@ app:destination="@id/lobbyDisplayFragment" /> </fragment> + <fragment + android:id="@+id/lobbyJoinFragment" + android:name="org.gnunet.gnunetmessenger.ui.lobby.LobbyJoinFragment" + android:label="Join Lobby" + tools:layout="@layout/fragment_join_lobby" /> + <!-- Lobby Display --> <fragment android:id="@+id/lobbyDisplayFragment" @@ -108,6 +118,11 @@ app:argType="string" /> </fragment> + <fragment + android:id="@+id/contactListFragment" + android:name="org.gnunet.gnunetmessenger.ui.contacts.ContactListFragment" + android:label="Contacts" /> + <!-- Global Navigation for Menu Actions --> <action android:id="@+id/action_global_accountDetailsFragment" @@ -116,4 +131,10 @@ <action android:id="@+id/action_global_lobbyCreateFragment" app:destination="@id/lobbyCreateFragment" /> + <action + android:id="@+id/action_global_lobbyJoinFragment" + app:destination="@id/lobbyJoinFragment" /> + <action + android:id="@+id/action_global_contactListFragment" + app:destination="@id/contactListFragment" /> </navigation> diff --git a/GNUnetMessenger/app/src/main/res/values/strings.xml b/GNUnetMessenger/app/src/main/res/values/strings.xml @@ -8,10 +8,17 @@ <string name="settings">Settings</string> <string name="about">About</string> <string name="create_lobby">Create Lobby</string> + <string name="join_lobby">Join Lobby</string> <string name="lobby_warning">Please notice that everyone with access to the lobby\'s code can enter it</string> <string name="attribute_list_description">Liste der Attribute</string> <string name="account_list_description">Account List</string> <string name="chat_list_description">Chat List</string> + <string name="join">Join Lobby</string> + <string name="chat_message_description">Nachrichtenliste</string> + <string name="share_identity">Share Identity</string> + <string name="block_contact">Block Contact</string> + <string name="leave_chat">Leave Chat</string> + <string name="reveal_identity">Reveal Identity</string> <string-array name="lobby_lifetimes"> <item>Off</item> <item>4 weeks</item> diff --git a/GNUnetMessenger/gradle/libs.versions.toml b/GNUnetMessenger/gradle/libs.versions.toml @@ -1,7 +1,6 @@ [versions] agp = "8.9.1" cardview = "1.0.0" -firebaseBom = "33.11.0" kotlin = "2.0.21" coreKtx = "1.10.1" junit = "4.13.2" @@ -12,8 +11,15 @@ material = "1.10.0" navigation = "2.7.7" zxing = "3.4.1" zxingAndroidEmbedded = "4.1.0" +camerax = "1.3.0" +mlkit-barcode = "17.2.0" [libraries] +camerax-core = { group = "androidx.camera", name = "camera-core", version.ref = "camerax" } +camerax-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" } +camerax-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" } +camerax-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" } +mlkit-barcode = { group = "com.google.mlkit", name = "barcode-scanning", version.ref = "mlkit-barcode" } zxingcore = { module = "com.google.zxing:core", version.ref = "zxing" } zxingandroidembedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" } androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.12.0" } @@ -23,13 +29,13 @@ androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" } androidx-navigation-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" } androidx-cardview = { module = "androidx.cardview:cardview", version.ref = "cardview" } -firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }