diff --git a/apps/android/.idea/codeStyles/codeStyleConfig.xml b/apps/android/.idea/codeStyles/codeStyleConfig.xml index 79ee123c2..6e6eec114 100644 --- a/apps/android/.idea/codeStyles/codeStyleConfig.xml +++ b/apps/android/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index 7a6ca488a..f5dcef32e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -22,8 +22,7 @@ import chat.simplex.app.views.chat.ChatView import chat.simplex.app.views.chatlist.ChatListView import chat.simplex.app.views.helpers.withApi import chat.simplex.app.views.newchat.* -import chat.simplex.app.views.usersettings.HelpView -import chat.simplex.app.views.usersettings.UserProfileView +import chat.simplex.app.views.usersettings.* import com.google.accompanist.insets.ExperimentalAnimatedInsets import com.google.accompanist.permissions.ExperimentalPermissionsApi import kotlinx.coroutines.DelicateCoroutinesApi @@ -54,6 +53,7 @@ class SimplexViewModel(application: Application): AndroidViewModel(application) val chatModel = getApplication().chatModel } +@ExperimentalTextApi @DelicateCoroutinesApi @ExperimentalPermissionsApi @ExperimentalMaterialApi @@ -116,6 +116,9 @@ fun Navigation(chatModel: ChatModel) { composable(route = Pages.UserProfile.route) { UserProfileView(chatModel, nav) } + composable(route = Pages.UserAddress.route) { + UserAddressView(chatModel, nav) + } composable(route = Pages.Help.route) { HelpView(chatModel, nav) } @@ -136,6 +139,7 @@ sealed class Pages(val route: String) { object Connect: Pages("connect") object ChatInfo: Pages("chat_info") object UserProfile: Pages("user_profile") + object UserAddress: Pages("user_address") object Help: Pages("help") } diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index 2602960e8..cb8fa644f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -58,8 +58,40 @@ class SimplexApp: Application() { alertView.value = null } - fun showAlertMsg(title: String, text: String? = null, - confirmText: String = "Ok", onConfirm: (() -> Unit)? = null) { + fun showAlertDialog( + title: String, + text: String? = null, + confirmText: String = "Ok", + onConfirm: (() -> Unit)? = null, + dismissText: String = "Cancel", + onDismiss: (() -> Unit)? = null + ) { + val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) } + showAlert { + AlertDialog( + onDismissRequest = this::hideAlert, + title = { Text(title) }, + text = alertText, + confirmButton = { + Button(onClick = { + onConfirm?.invoke() + hideAlert() + }) { Text(confirmText) } + }, + dismissButton = { + Button(onClick = { + onDismiss?.invoke() + hideAlert() + }) { Text(dismissText) } + } + ) + } + } + + fun showAlertMsg( + title: String, text: String? = null, + confirmText: String = "Ok", onConfirm: (() -> Unit)? = null + ) { val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) } showAlert { AlertDialog( diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 7330da333..de38fc063 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -23,6 +23,7 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale var connReqInvitation: String? = null var terminalItems = mutableStateListOf() + var userAddress = mutableStateOf(null) // set when app is opened via contact or invitation URI var appOpenUrl = mutableStateOf(null) @@ -60,14 +61,15 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale } } -// func replaceChat(_ id: String, _ chat: Chat) { -// if let i = getChatIndex(id) { -// chats[i] = chat -// } else { -// // invalid state, correcting -// chats.insert(chat, at: 0) -// } -// } + fun replaceChat(id: String, chat: Chat) { + val i = getChatIndex(id) + if (i >= 0) { + chats[i] = chat + } else { + // invalid state, correcting + chats.add(index = 0, chat) + } + } fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) { // update previews diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index a1c790134..2548f20e4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -23,6 +23,7 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert Log.d("SIMPLEX (user)", u.toString()) try { apiStartChat() + chatModel.userAddress.value = apiGetUserAddress() chatModel.chats.addAll(apiGetChats()) chatModel.chatsLoaded.value = true startReceiver() @@ -221,7 +222,12 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Connected()) // NtfManager.shared.notifyContactConnected(contact) } -// is CR.ReceivedContactRequest -> return + is CR.ReceivedContactRequest -> { + val contactRequest = r.contactRequest + val cInfo = ChatInfo.ContactRequest(contactRequest) + chatModel.addChat(Chat(chatInfo = cInfo, chatItems = listOf())) +// NtfManager.shared.notifyContactRequest(contactRequest) + } is CR.ContactUpdated -> { val cInfo = ChatInfo.Direct(r.toContact) if (chatModel.hasChat(r.toContact.id)) { @@ -257,15 +263,6 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert chatModel.addChatItem(cInfo, cItem) // NtfManager.shared.notifyMessageReceived(cInfo, cItem) } - -// switch res { -// case let .receivedContactRequest(contactRequest): - // chatModel.addChat(Chat( - // chatInfo: ChatInfo.contactRequest(contactRequest: contactRequest), - // chatItems: [] - // )) -// NtfManager.shared.notifyContactRequest(contactRequest) -// // case let .chatItemUpdated(aChatItem): // let cInfo = aChatItem.chatInfo // let cItem = aChatItem.chatItem diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt new file mode 100644 index 000000000..d13f2863a --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt @@ -0,0 +1,193 @@ +package chat.simplex.app.views.chatlist + +import android.content.res.Configuration +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.Divider +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import chat.simplex.app.Pages +import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.helpers.withApi +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.datetime.Clock + +@ExperimentalTextApi +@Composable +fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel, nav: NavController) { + ChatListNavLink( + chat = chat, + action = { + when (chat.chatInfo) { + is ChatInfo.Direct -> chatNavLink(chat, chatModel, nav) + is ChatInfo.Group -> chatNavLink(chat, chatModel, nav) + is ChatInfo.ContactRequest -> contactRequestNavLink(chat.chatInfo, chatModel, nav) + } + } + ) +} + +@DelicateCoroutinesApi +fun chatNavLink(chatPreview: Chat, chatModel: ChatModel, navController: NavController) { + withApi { + val chatInfo = chatPreview.chatInfo + val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId) + if (chat != null) { + chatModel.chatId.value = chatInfo.id + chatModel.chatItems = chat.chatItems.toMutableStateList() + navController.navigate(Pages.Chat.route) + } else { + // TODO show error? or will apiGetChat show it + } + } +} + +@DelicateCoroutinesApi +fun contactRequestNavLink(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel, navController: NavController) { + chatModel.alertManager.showAlertDialog( + title = "Accept connection request?", + text = "If you choose to reject sender will NOT be notified", + confirmText = "Accept", + onConfirm = { + withApi { + val contact = chatModel.controller.apiAcceptContactRequest(contactRequest.apiId) + if (contact != null) { + val chat = Chat(ChatInfo.Direct(contact), listOf()) + chatModel.replaceChat(contactRequest.id, chat) + } + } + }, + dismissText = "Reject", + onDismiss = { + withApi { + chatModel.controller.apiRejectContactRequest(contactRequest.apiId) + chatModel.removeChat(contactRequest.id) + } + } + ) +} + +@ExperimentalTextApi +@Composable +fun ChatListNavLink(chat: Chat, action: () -> Unit) { + ChatListNavLinkLayout( + content = { + when (chat.chatInfo) { + is ChatInfo.Direct -> ChatPreviewView(chat) + is ChatInfo.Group -> ChatPreviewView(chat) + is ChatInfo.ContactRequest -> ContactRequestView(chat) + } + }, + action = action + ) +} + +@Composable +fun ChatListNavLinkLayout(content: (@Composable () -> Unit), action: () -> Unit) { + Surface( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = action) + .height(88.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .padding(start = 8.dp) + .padding(end = 12.dp), + verticalAlignment = Alignment.Top, +// TODO? +// verticalAlignment = Alignment.CenterVertically, +// horizontalArrangement = Arrangement.SpaceEvenly + ) { + content.invoke() + } + } + Divider(Modifier.padding(horizontal = 8.dp)) +} + +@ExperimentalTextApi +@Preview +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewChatListNavLinkDirect() { + SimpleXTheme { + ChatListNavLink( + chat = Chat( + chatInfo = ChatInfo.Direct.sampleData, + chatItems = listOf( + ChatItem.getSampleData( + 1, + CIDirection.DirectSnd(), + Clock.System.now(), + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + ) + ), + chatStats = Chat.ChatStats() + ), + action = {} + ) + } +} + +@ExperimentalTextApi +@Preview +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewChatListNavLinkGroup() { + SimpleXTheme { + ChatListNavLink( + chat = Chat( + chatInfo = ChatInfo.Group.sampleData, + chatItems = listOf( + ChatItem.getSampleData( + 1, + CIDirection.DirectSnd(), + Clock.System.now(), + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + ) + ), + chatStats = Chat.ChatStats() + ), + action = {} + ) + } +} + +@ExperimentalTextApi +@Preview +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewChatListNavLinkContactRequest() { + SimpleXTheme { + ChatListNavLink( + chat = Chat( + chatInfo = ChatInfo.ContactRequest.sampleData, + chatItems = listOf(), + chatStats = Chat.ChatStats() + ), + action = {} + ) + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt index 73474a4c3..179ed1919 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt @@ -14,15 +14,12 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.navigation.NavController -import androidx.navigation.NavOptions -import chat.simplex.app.Pages -import chat.simplex.app.model.Chat import chat.simplex.app.model.ChatModel import chat.simplex.app.views.chat.ChatHelpView -import chat.simplex.app.views.helpers.withApi import chat.simplex.app.views.newchat.NewChatSheet import chat.simplex.app.views.usersettings.SettingsView import com.google.accompanist.permissions.ExperimentalPermissionsApi @@ -67,6 +64,7 @@ fun scaffoldController(): ScaffoldController { return ctrl } +@ExperimentalTextApi @DelicateCoroutinesApi @ExperimentalPermissionsApi @ExperimentalMaterialApi @@ -180,29 +178,16 @@ fun ChatListToolbar(scaffoldCtrl: ScaffoldController) { } } -@DelicateCoroutinesApi -fun goToChat(chatPreview: Chat, chatModel: ChatModel, navController: NavController) { - withApi { - val cInfo = chatPreview.chatInfo - val chat = chatModel.controller.apiGetChat(cInfo.chatType, cInfo.apiId) - if (chat != null) { - chatModel.chatId.value = cInfo.id - chatModel.chatItems = chat.chatItems.toMutableStateList() - navController.navigate(Pages.Chat.route) - } else { - // TODO show error? or will apiGetChat show it - } - } -} - +@ExperimentalTextApi @DelicateCoroutinesApi @Composable fun ChatList(chatModel: ChatModel, navController: NavController) { + Divider(Modifier.padding(horizontal = 8.dp)) LazyColumn( modifier = Modifier.fillMaxWidth() ) { items(chatModel.chats) { chat -> - ChatPreviewView(chat) { goToChat(chat, chatModel, navController) } + ChatListNavLinkView(chat, chatModel, navController) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt index 9c36141c7..fc2a85c64 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt @@ -1,108 +1,76 @@ package chat.simplex.app.views.chatlist -import androidx.compose.foundation.* +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* +import chat.simplex.app.model.Chat +import chat.simplex.app.model.getTimestampText import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.item.MarkdownText import chat.simplex.app.views.helpers.ChatInfoImage import chat.simplex.app.views.helpers.badgeLayout -import kotlinx.datetime.Clock @ExperimentalTextApi @Composable -fun ChatPreviewView(chat: Chat, goToChat: () -> Unit) { - Surface( - border = BorderStroke(0.5.dp, MaterialTheme.colors.secondary), - modifier = Modifier - .fillMaxWidth() - .clickable(onClick = goToChat) - .height(88.dp) - ) { - Row( +fun ChatPreviewView(chat: Chat) { + Row { + ChatInfoImage(chat, size = 72.dp) + Column( modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp) - .padding(start = 8.dp) - .padding(end = 12.dp), - verticalAlignment = Alignment.Top - ) { - ChatInfoImage(chat, size = 72.dp) - Column(modifier = Modifier .padding(horizontal = 8.dp) - .weight(1F)) { - Text( - chat.chatInfo.chatViewName, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.h3, - fontWeight = FontWeight.Bold - ) - if (chat.chatItems.count() > 0) { - MarkdownText( - chat.chatItems.last(), - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - } - val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.createdAt) - Column(Modifier.fillMaxHeight(), - verticalArrangement = Arrangement.Top) { - Text(ts, - color = HighOrLowlight, - style = MaterialTheme.typography.body2, - modifier = Modifier.padding(bottom = 5.dp) - ) + .weight(1F) + ) { + Text( + chat.chatInfo.chatViewName, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Bold + ) - val n = chat.chatStats.unreadCount - if (n > 0) { - Text( - if (n < 1000) "$n" else "${n / 1000}k", - color = MaterialTheme.colors.onPrimary, - fontSize = 14.sp, - modifier = Modifier - .background(MaterialTheme.colors.primary, shape = CircleShape) - .align(Alignment.End) - .badgeLayout() - .padding(horizontal = 3.dp) - .padding(vertical = 1.dp) - ) - } + if (chat.chatItems.count() > 0) { + MarkdownText( + chat.chatItems.last(), + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.createdAt) + Column( + Modifier.fillMaxHeight(), + verticalArrangement = Arrangement.Top + ) { + Text( + ts, + color = HighOrLowlight, + style = MaterialTheme.typography.body2, + modifier = Modifier.padding(bottom = 5.dp) + ) + val n = chat.chatStats.unreadCount + if (n > 0) { + Text( + if (n < 1000) "$n" else "${n / 1000}k", + color = MaterialTheme.colors.onPrimary, + fontSize = 14.sp, + modifier = Modifier + .background(MaterialTheme.colors.primary, shape = CircleShape) + .align(Alignment.End) + .badgeLayout() + .padding(horizontal = 3.dp) + .padding(vertical = 1.dp) + ) } } } } - -@ExperimentalTextApi -@Preview -@Composable -fun ChatPreviewViewExample() { - SimpleXTheme { - ChatPreviewView( - chat = Chat( - chatInfo = ChatInfo.Direct.sampleData, - chatItems = listOf(ChatItem.getSampleData( - 1, - CIDirection.DirectSnd(), - Clock.System.now(), - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." - )), - chatStats = Chat.ChatStats(unreadCount = 3) - ), - goToChat = {} - ) - } -} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt new file mode 100644 index 000000000..4cfa5c48c --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt @@ -0,0 +1,52 @@ +package chat.simplex.app.views.chatlist + +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import chat.simplex.app.model.Chat +import chat.simplex.app.model.getTimestampText +import chat.simplex.app.ui.theme.HighOrLowlight +import chat.simplex.app.views.helpers.ChatInfoImage + +@Composable +fun ContactRequestView(chat: Chat) { + Row { + ChatInfoImage(chat, size = 72.dp) + Column( + modifier = Modifier + .padding(horizontal = 8.dp) + .weight(1F) + ) { + Text( + chat.chatInfo.chatViewName, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colors.primary + ) + Text( + "wants to connect to you!", + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + val ts = getTimestampText(chat.chatInfo.createdAt) + Column( + Modifier.fillMaxHeight(), + verticalArrangement = Arrangement.Top + ) { + Text( + ts, + color = HighOrLowlight, + style = MaterialTheme.typography.body2, + modifier = Modifier.padding(bottom = 5.dp) + ) + } + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt new file mode 100644 index 000000000..be5141276 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt @@ -0,0 +1,14 @@ +package chat.simplex.app.views.helpers + +import android.content.Context +import android.content.Intent + +fun shareText(cxt: Context, text: String) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, text) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + cxt.startActivity(shareIntent) +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt index 7066024bf..f221c9756 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt @@ -1,7 +1,6 @@ package chat.simplex.app.views.newchat -import android.content.Context -import android.content.Intent +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme @@ -22,6 +21,7 @@ import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.SimpleButton import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.CloseSheetBar +import chat.simplex.app.views.helpers.shareText @Composable fun AddContactView(chatModel: ChatModel, nav: NavController) { @@ -40,54 +40,53 @@ fun AddContactView(chatModel: ChatModel, nav: NavController) { fun AddContactLayout(connReq: String, close: () -> Unit, share: () -> Unit) { Column( modifier = Modifier - .padding(horizontal = 8.dp) .fillMaxSize() - .background(MaterialTheme.colors.background), - horizontalAlignment = Alignment.CenterHorizontally + .background(MaterialTheme.colors.background) + .padding(horizontal = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) ) { CloseSheetBar(close) Text( "Add contact", style = MaterialTheme.typography.h1, - modifier = Modifier.padding(bottom = 8.dp) + color = MaterialTheme.colors.onBackground ) Text( "Show QR code to your contact\nto scan from the app", style = MaterialTheme.typography.h2, textAlign = TextAlign.Center, - modifier = Modifier.padding(bottom = 8.dp) + color = MaterialTheme.colors.onBackground ) QRCode(connReq) Text( buildAnnotatedString { - append("If you cannot meet in person, you can ") - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { + append("If you cannot meet in person, you can ") + } + withStyle(SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold)) { append("scan QR code in the video call") } - append(", or you can share the invitation link via any other channel.") + withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { + append(", or you can share the invitation link via any other channel.") + } }, textAlign = TextAlign.Center, style = MaterialTheme.typography.caption, modifier = Modifier .padding(horizontal = 16.dp) - .padding(top = 8.dp) .padding(bottom = 16.dp) ) SimpleButton("Share invitation link", icon = Icons.Outlined.Share, click = share) } } -fun shareText(cxt: Context, text: String) { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, text) - type = "text/plain" - } - val shareIntent = Intent.createChooser(sendIntent, null) - cxt.startActivity(shareIntent) -} - @Preview +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) @Composable fun PreviewAddContactView() { SimpleXTheme { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt index 2a7274a1b..5a82ea51c 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt @@ -1,5 +1,6 @@ package chat.simplex.app.views.newchat +import android.content.res.Configuration import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -30,7 +31,7 @@ fun ConnectContactView(chatModel: ChatModel, nav: NavController) { withUriAction(chatModel, uri) { action -> connectViaUri(chatModel, action, uri) } - } catch(e: RuntimeException) { + } catch (e: RuntimeException) { chatModel.alertManager.showAlertMsg( title = "Invalid QR code", text = "This QR code is not a link!" @@ -44,8 +45,10 @@ fun ConnectContactView(chatModel: ChatModel, nav: NavController) { } @DelicateCoroutinesApi -fun withUriAction(chatModel: ChatModel, uri: Uri, - run: suspend (String) -> Unit) { +fun withUriAction( + chatModel: ChatModel, uri: Uri, + run: suspend (String) -> Unit +) { val action = uri.path?.drop(1) if (action == "contact" || action == "invitation") { withApi { run(action) } @@ -74,46 +77,57 @@ suspend fun connectViaUri(chatModel: ChatModel, action: String, uri: Uri) { fun ConnectContactLayout(qrCodeScanner: @Composable () -> Unit, close: () -> Unit) { Column( modifier = Modifier - .padding(horizontal = 8.dp) .fillMaxSize() - .background(MaterialTheme.colors.background), - horizontalAlignment = Alignment.CenterHorizontally + .background(MaterialTheme.colors.background) + .padding(horizontal = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) ) { CloseSheetBar(close) Text( "Scan QR code", style = MaterialTheme.typography.h1, - modifier = Modifier.padding(bottom = 8.dp) + color = MaterialTheme.colors.onBackground ) Text( "Your chat profile will be sent\nto your contact", style = MaterialTheme.typography.h2, textAlign = TextAlign.Center, - modifier = Modifier.padding(bottom = 16.dp) + color = MaterialTheme.colors.onBackground, + modifier = Modifier.padding(bottom = 4.dp) ) - Box ( + Box( Modifier .fillMaxWidth() .aspectRatio(ratio = 1F) ) { qrCodeScanner() } Text( buildAnnotatedString { - append("If you cannot meet in person, you can ") - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { + append("If you cannot meet in person, you can ") + } + withStyle(SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold)) { append("scan QR code in the video call") } - append(", or you can create the invitation link.") + withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { + append(", or you can create the invitation link.") + } }, textAlign = TextAlign.Center, style = MaterialTheme.typography.caption, modifier = Modifier .padding(horizontal = 16.dp) - .padding(top = 16.dp) + .padding(top = 4.dp) ) } } @Preview +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) @Composable fun PreviewConnectContactLayout() { SimpleXTheme { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index aff3dea98..a949d0630 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -21,7 +21,6 @@ import chat.simplex.app.Pages import chat.simplex.app.R import chat.simplex.app.model.ChatModel import chat.simplex.app.model.Profile -import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.ui.theme.SimpleXTheme @Composable @@ -49,6 +48,7 @@ fun SettingsLayout( .fillMaxSize() // .background(MaterialTheme.colors.background) .padding(8.dp) + .padding(top = 16.dp) ) { Text( "Your Settings", @@ -87,15 +87,15 @@ fun SettingsLayout( Icon( Icons.Outlined.QrCode, contentDescription = "Address", - tint = HighOrLowlight, + tint = MaterialTheme.colors.onBackground, ) Spacer(Modifier.padding(horizontal = 4.dp)) Text( "Your SimpleX contact address", - color = HighOrLowlight + color = MaterialTheme.colors.onBackground ) }, - func = { println("navigate to address") } + func = { navigate(Pages.UserAddress.route) } ) Spacer(Modifier.height(24.dp)) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt new file mode 100644 index 000000000..d082048f8 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt @@ -0,0 +1,147 @@ +package chat.simplex.app.views.usersettings + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import chat.simplex.app.model.ChatModel +import chat.simplex.app.ui.theme.SimpleButton +import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.newchat.QRCode + +@Composable +fun UserAddressView(chatModel: ChatModel, nav: NavController) { + val cxt = LocalContext.current + UserAddressLayout( + userAddress = chatModel.userAddress.value, + back = { nav.popBackStack() }, + createAddress = { + withApi { + chatModel.userAddress.value = chatModel.controller.apiCreateUserAddress() + } + }, + share = { userAddress: String -> shareText(cxt, userAddress) }, + deleteAddress = { + chatModel.alertManager.showAlertMsg( + title = "Delete address?", + text = "All your contacts will remain connected", + confirmText = "Delete", + onConfirm = { + withApi { + chatModel.controller.apiDeleteUserAddress() + chatModel.userAddress.value = null + } + } + ) + } + ) +} + +@Composable +fun UserAddressLayout( + userAddress: String?, + back: () -> Unit, + createAddress: () -> Unit, + share: (String) -> Unit, + deleteAddress: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colors.background) + .padding(horizontal = 8.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top + ) { + CloseSheetBar(back) + Text( + "Your chat address", + Modifier.padding(bottom = 24.dp), + style = MaterialTheme.typography.h1, + color = MaterialTheme.colors.onBackground + ) + Text( + "You can share your address as a link or as a QR code - anybody will be able to connect to you, " + + "and if you later delete it - you won't lose your contacts.", + Modifier.padding(bottom = 24.dp), + color = MaterialTheme.colors.onBackground + ) + Column( + Modifier + .fillMaxWidth() + .padding(top = 12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + if (userAddress == null) { + SimpleButton("Create address", icon = Icons.Outlined.QrCode, click = createAddress) + } else { + QRCode(userAddress) + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + SimpleButton( + "Share link", + icon = Icons.Outlined.Share, + click = { share(userAddress) }) + SimpleButton( + "Delete address", + icon = Icons.Outlined.Delete, + color = Color.Red, + click = deleteAddress + ) + } + } + } + } +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewUserAddressLayoutNoAddress() { + SimpleXTheme { + UserAddressLayout( + userAddress = null, + back = {}, + createAddress = {}, + share = { _ -> }, + deleteAddress = {}, + ) + } +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewUserAddressLayoutAddressCreated() { + SimpleXTheme { + UserAddressLayout( + userAddress = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", + back = {}, + createAddress = {}, + share = { _ -> }, + deleteAddress = {}, + ) + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt index dffa108ce..6af1f44db 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt @@ -179,7 +179,6 @@ fun UserProfileLayout( showBackground = true, name = "Dark Mode" ) - @Composable fun PreviewUserProfileLayoutEditOff() { SimpleXTheme { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddress.swift b/apps/ios/Shared/Views/UserSettings/UserAddress.swift index 6ed2d0374..8b467316c 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddress.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddress.swift @@ -66,7 +66,11 @@ struct UserAddress_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.userAddress = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D" - return UserAddress() - .environmentObject(chatModel) + return Group { + UserAddress() + .environmentObject(chatModel) + UserAddress() + .environmentObject(ChatModel()) + } } }