From 3f3a503def118ae1aec0e972a5b4825facacef52 Mon Sep 17 00:00:00 2001 From: IanRDavies Date: Sun, 27 Feb 2022 12:14:26 +0000 Subject: [PATCH] android: notifications (#369) * minimal implementation of notifications and broken framework for background check for messages * linting and need different id to have multiple messages * working notification on new messages * add autocancel to notifications * add rudimentary linking to chat from notification * group notifications from the same chat * clarify comment * revert to working version * refactor * minors * two channels, silent and shouty * rudimentary state control for notifications * check if running in foreground * more elegant solution to don't notify if in chat * tidy up DisposableEffect use * change message notification priority to high * nuke opt-ins * navigation via notification occasionally works with race condition (WIP) * notification navigation is working; remove chat list/view from navigation; refactor ChatListNavLinkView * group all simplex notifications, only show the latest message per chat, notification icons * increase time to 30 sec Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- apps/android/app/build.gradle | 9 ++ .../java/chat/simplex/app/MainActivity.kt | 110 ++++++++---------- .../main/java/chat/simplex/app/SimplexApp.kt | 23 +++- .../java/chat/simplex/app/model/BGManager.kt | 39 +++++++ .../java/chat/simplex/app/model/ChatModel.kt | 5 +- .../java/chat/simplex/app/model/NtfManager.kt | 78 +++++++++++++ .../java/chat/simplex/app/model/SimpleXAPI.kt | 38 ++++-- .../java/chat/simplex/app/views/SplashView.kt | 9 +- .../chat/simplex/app/views/TerminalView.kt | 2 - .../chat/simplex/app/views/WelcomeView.kt | 14 +-- .../simplex/app/views/chat/ChatInfoView.kt | 8 +- .../chat/simplex/app/views/chat/ChatView.kt | 91 +++++++-------- .../simplex/app/views/chat/item/CIMetaView.kt | 1 - .../app/views/chat/item/ChatItemView.kt | 3 - .../app/views/chat/item/TextItemView.kt | 12 +- .../app/views/chatlist/ChatListNavLinkView.kt | 80 +++++-------- .../app/views/chatlist/ChatListView.kt | 40 ++----- .../app/views/chatlist/ChatPreviewView.kt | 3 - .../app/views/helpers/ChatInfoImage.kt | 3 - .../chat/simplex/app/views/helpers/Util.kt | 1 - .../app/views/newchat/ConnectContactView.kt | 3 - .../simplex/app/views/newchat/NewChatSheet.kt | 13 +-- .../views/usersettings/MarkdownHelpView.kt | 3 - .../src/main/res/drawable-hdpi/ntf_icon.png | Bin 0 -> 861 bytes .../src/main/res/drawable-mdpi/ntf_icon.png | Bin 0 -> 569 bytes .../src/main/res/drawable-xhdpi/ntf_icon.png | Bin 0 -> 1089 bytes .../src/main/res/drawable-xxhdpi/ntf_icon.png | Bin 0 -> 1761 bytes .../main/res/drawable-xxxhdpi/ntf_icon.png | Bin 0 -> 2652 bytes apps/android/build.gradle | 6 +- 29 files changed, 319 insertions(+), 275 deletions(-) create mode 100644 apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt create mode 100644 apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt create mode 100644 apps/android/app/src/main/res/drawable-hdpi/ntf_icon.png create mode 100644 apps/android/app/src/main/res/drawable-mdpi/ntf_icon.png create mode 100644 apps/android/app/src/main/res/drawable-xhdpi/ntf_icon.png create mode 100644 apps/android/app/src/main/res/drawable-xxhdpi/ntf_icon.png create mode 100644 apps/android/app/src/main/res/drawable-xxxhdpi/ntf_icon.png diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index 30811d9b05..1a91671c05 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -40,6 +40,11 @@ android { } kotlinOptions { jvmTarget = '1.8' + freeCompilerArgs += "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi" + freeCompilerArgs += "-opt-in=androidx.compose.ui.text.ExperimentalTextApi" + freeCompilerArgs += "-opt-in=androidx.compose.material.ExperimentalMaterialApi" + freeCompilerArgs += "-opt-in=com.google.accompanist.insets.ExperimentalAnimatedInsets" + freeCompilerArgs += "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi" } externalNativeBuild { cmake { @@ -73,6 +78,10 @@ dependencies { implementation "androidx.navigation:navigation-compose:2.4.1" implementation "com.google.accompanist:accompanist-insets:0.23.0" + def work_version = "2.7.1" + implementation "androidx.work:work-runtime-ktx:$work_version" + implementation "androidx.work:work-multiprocess:$work_version" + def camerax_version = "1.1.0-beta01" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" 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 a935883631..30ee947668 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 @@ -2,15 +2,14 @@ package chat.simplex.app import android.app.Application import android.content.Intent +import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.layout.Box -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.runtime.* import androidx.lifecycle.AndroidViewModel import androidx.navigation.* import androidx.navigation.compose.* @@ -20,26 +19,20 @@ import chat.simplex.app.views.* import chat.simplex.app.views.chat.ChatInfoView import chat.simplex.app.views.chat.ChatView import chat.simplex.app.views.chatlist.ChatListView +import chat.simplex.app.views.chatlist.openChat import chat.simplex.app.views.helpers.withApi import chat.simplex.app.views.newchat.* import chat.simplex.app.views.usersettings.* -import com.google.accompanist.insets.ExperimentalAnimatedInsets -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.serialization.decodeFromString -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalAnimatedInsets -@ExperimentalPermissionsApi -@ExperimentalMaterialApi class MainActivity: ComponentActivity() { private val vm by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // testJson() - connectIfOpenedViaUri(intent, vm.chatModel) + processIntent(intent, vm.chatModel) +// vm.app.initiateBackgroundWork() setContent { SimpleXTheme { Navigation(vm.chatModel) @@ -48,50 +41,37 @@ class MainActivity: ComponentActivity() { } } -@DelicateCoroutinesApi class SimplexViewModel(application: Application): AndroidViewModel(application) { - val chatModel = getApplication().chatModel + val app = getApplication() + val chatModel = app.chatModel } -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalPermissionsApi -@ExperimentalMaterialApi @Composable fun MainPage(chatModel: ChatModel, nav: NavController) { when (chatModel.userCreated.value) { null -> SplashView() - false -> WelcomeView(chatModel) { nav.navigate(Pages.ChatList.route) } - true -> ChatListView(chatModel, nav) + false -> WelcomeView(chatModel) // { nav.navigate(Pages.ChatList.route) } + true -> if (chatModel.chatId.value == null) { + ChatListView(chatModel, nav) + } else { + ChatView(chatModel, nav) + } } } -@ExperimentalTextApi -@ExperimentalAnimatedInsets -@DelicateCoroutinesApi -@ExperimentalPermissionsApi -@ExperimentalMaterialApi @Composable fun Navigation(chatModel: ChatModel) { + println("*** in Navigation") val nav = rememberNavController() - + val scope = rememberCoroutineScope() Box { NavHost(navController = nav, startDestination = Pages.Home.route) { composable(route = Pages.Home.route) { + println("*** composable MainPage") MainPage(chatModel, nav) } composable(route = Pages.Welcome.route) { - WelcomeView(chatModel) { - nav.navigate(Pages.Home.route) { - popUpTo(Pages.Home.route) { inclusive = true } - } - } - } - composable(route = Pages.ChatList.route) { - ChatListView(chatModel, nav) - } - composable(route = Pages.Chat.route) { - ChatView(chatModel, nav) + WelcomeView(chatModel) } composable(route = Pages.AddContact.route) { AddContactView(chatModel, nav) @@ -136,8 +116,6 @@ sealed class Pages(val route: String) { object Terminal: Pages("terminal") object Welcome: Pages("welcome") object TerminalItemDetails: Pages("details") - object ChatList: Pages("chats") - object Chat: Pages("chat") object AddContact: Pages("add_contact") object Connect: Pages("connect") object ChatInfo: Pages("chat_info") @@ -147,28 +125,42 @@ sealed class Pages(val route: String) { object Markdown: Pages("markdown") } -@DelicateCoroutinesApi -fun connectIfOpenedViaUri(intent: Intent?, chatModel: ChatModel) { - val uri = intent?.data - if (intent?.action == "android.intent.action.VIEW" && uri != null) { - Log.d("SIMPLEX", "connectIfOpenedViaUri: opened via link") - if (chatModel.currentUser.value == null) { - chatModel.appOpenUrl.value = uri - } else { - withUriAction(chatModel, uri) { action -> - chatModel.alertManager.showAlertMsg( - title = "Connect via $action link?", - text = "Your profile will be sent to the contact that you received this link from.", - confirmText = "Connect", - onConfirm = { - withApi { - Log.d("SIMPLEX", "connectIfOpenedViaUri: connecting") - connectViaUri(chatModel, action, uri) - } - } - ) +fun processIntent(intent: Intent?, chatModel: ChatModel) { + when (intent?.action) { + NtfManager.OpenChatAction -> { + val chatId = intent.getStringExtra("chatId") + Log.d("SIMPLEX", "processIntent: OpenChatAction $chatId") + if (chatId != null) { + val cInfo = chatModel.getChat(chatId)?.chatInfo + if (cInfo != null) withApi { openChat(chatModel, cInfo) } } } + "android.intent.action.VIEW" -> { + val uri = intent.data + if (uri != null) connectIfOpenedViaUri(uri, chatModel) + } + } +} + +fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { + Log.d("SIMPLEX", "connectIfOpenedViaUri: opened via link") + if (chatModel.currentUser.value == null) { + // TODO open from chat list view + chatModel.appOpenUrl.value = uri + } else { + withUriAction(chatModel, uri) { action -> + chatModel.alertManager.showAlertMsg( + title = "Connect via $action link?", + text = "Your profile will be sent to the contact that you received this link from.", + confirmText = "Connect", + onConfirm = { + withApi { + Log.d("SIMPLEX", "connectIfOpenedViaUri: connecting") + connectViaUri(chatModel, action, uri) + } + } + ) + } } } 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 cb8fa644fe..aa04839b3f 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 @@ -6,14 +6,14 @@ import android.util.Log import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf -import chat.simplex.app.model.ChatController -import chat.simplex.app.model.ChatModel +import androidx.work.* +import chat.simplex.app.model.* import chat.simplex.app.views.helpers.withApi -import kotlinx.coroutines.DelicateCoroutinesApi import java.io.BufferedReader import java.io.InputStreamReader import java.util.* import java.util.concurrent.Semaphore +import java.util.concurrent.TimeUnit import kotlin.concurrent.thread // ghc's rts @@ -27,15 +27,28 @@ external fun chatInit(path: String): ChatCtrl external fun chatSendCmd(ctrl: ChatCtrl, msg: String) : String external fun chatRecvMsg(ctrl: ChatCtrl) : String -@DelicateCoroutinesApi class SimplexApp: Application() { private lateinit var controller: ChatController lateinit var chatModel: ChatModel + private lateinit var ntfManager: NtfManager + + fun initiateBackgroundWork() { + val backgroundConstraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + val request = OneTimeWorkRequestBuilder() + .setInitialDelay(5, TimeUnit.MINUTES) + .setConstraints(backgroundConstraints) + .build() + WorkManager.getInstance(applicationContext) + .enqueue(request) + } override fun onCreate() { super.onCreate() + ntfManager = NtfManager(applicationContext) val ctrl = chatInit(applicationContext.filesDir.toString()) - controller = ChatController(ctrl, AlertManager()) + controller = ChatController(ctrl, AlertManager(), ntfManager, applicationContext) chatModel = controller.chatModel withApi { val user = controller.apiGetActiveUser() diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt b/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt new file mode 100644 index 0000000000..a511bf06fe --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt @@ -0,0 +1,39 @@ +package chat.simplex.app.model + +import android.content.Context +import android.util.Log +import androidx.work.* +import chat.simplex.app.chatRecvMsg +import java.util.concurrent.TimeUnit + +class BackgroundAPIWorker(appContext: Context, workerParams: WorkerParameters, ctrl: ChatCtrl): + Worker(appContext, workerParams) { + val controller = ctrl + override fun doWork(): Result { + Log.d("BackgroundAPIWorker", "running") + getNewItems() + + // Enqueue another request for later to make this periodic + val request = buildRequest() + WorkManager.getInstance(applicationContext) + .enqueue(request) + + return Result.success() + } + + private fun getNewItems() { + val json = chatRecvMsg(controller) + val r = APIResponse.decodeStr(json).resp + Log.d("SIMPLEX", "chatRecvMsg: ${r.responseType}") + } + + private fun buildRequest(): OneTimeWorkRequest { + val backgroundConstraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + return OneTimeWorkRequestBuilder() + .setInitialDelay(5, TimeUnit.MINUTES) + .setConstraints(backgroundConstraints) + .build() + } +} 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 82b1dbf827..726b2fb5d4 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 @@ -8,18 +8,15 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.* import androidx.compose.ui.text.style.TextDecoration import chat.simplex.app.SimplexApp -import chat.simplex.app.ui.theme.* -import kotlinx.coroutines.DelicateCoroutinesApi +import chat.simplex.app.ui.theme.SecretColor import kotlinx.datetime.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@DelicateCoroutinesApi class ChatModel(val controller: ChatController, val alertManager: SimplexApp.AlertManager) { var currentUser = mutableStateOf(null) var userCreated = mutableStateOf(null) var chats = mutableStateListOf() - var chatsLoaded = mutableStateOf(null) var chatId = mutableStateOf(null) var chatItems = mutableStateListOf() diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt b/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt new file mode 100644 index 0000000000..114cf22bf6 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt @@ -0,0 +1,78 @@ +package chat.simplex.app.model + +import android.app.* +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import chat.simplex.app.MainActivity +import chat.simplex.app.R +import kotlinx.datetime.Clock + +class NtfManager(val context: Context) { + companion object { + const val MessageChannel: String = "chat.simplex.app.MESSAGE_NOTIFICATION" + const val MessageGroup: String = "chat.simplex.app.MESSAGE_NOTIFICATION" + const val OpenChatAction: String = "chat.simplex.app.OPEN_CHAT" + } + + private val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private var prevNtfTime = mutableMapOf() + private val msgNtfTimeoutMs = 30000L + + init { + manager.createNotificationChannel(NotificationChannel( + MessageChannel, + "SimpleX Chat messages", + NotificationManager.IMPORTANCE_HIGH + )) + } + + fun notifyMessageReceived(cInfo: ChatInfo, cItem: ChatItem) { + Log.d("SIMPLEX", "notifyMessageReceived ${cInfo.id}") + val now = Clock.System.now().toEpochMilliseconds() + val recentNotification = (now - prevNtfTime.getOrDefault(cInfo.id, 0) < msgNtfTimeoutMs) + prevNtfTime[cInfo.id] = now + + val notification = NotificationCompat.Builder(context, MessageChannel) + .setContentTitle(cInfo.displayName) + .setContentText(cItem.content.text) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setGroup(MessageGroup) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) + .setSmallIcon(R.drawable.ntf_icon) + .setColor(0x88FFFF) + .setAutoCancel(true) + .setContentIntent(getMsgPendingIntent(cInfo)) + .setSilent(recentNotification) + .build() + + val summary = NotificationCompat.Builder(context, MessageChannel) + .setSmallIcon(R.drawable.ntf_icon) + .setColor(0x88FFFF) + .setGroup(MessageGroup) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) + .setGroupSummary(true) + .build() + + with(NotificationManagerCompat.from(context)) { + // using cInfo.id only shows one notification per chat and updates it when the message arrives + notify(cInfo.id.hashCode(), notification) + notify(0, summary) + } + } + + private fun getMsgPendingIntent(cInfo: ChatInfo) : PendingIntent{ + Log.d("SIMPLEX", "getMsgPendingIntent ${cInfo.id}") + val uniqueInt = (System.currentTimeMillis() and 0xfffffff).toInt() + val intent = Intent(context, MainActivity::class.java) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra("chatId", cInfo.id) + .setAction(OpenChatAction) + return TaskStackBuilder.create(context).run { + addNextIntentWithParentStack(intent) + getPendingIntent(uniqueInt, PendingIntent.FLAG_IMMUTABLE) + } + } +} 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 613003c036..875b90b1e2 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 @@ -1,10 +1,14 @@ package chat.simplex.app.model +import android.app.ActivityManager +import android.app.ActivityManager.RunningAppProcessInfo +import android.content.Context import android.util.Log import androidx.compose.runtime.mutableStateOf import chat.simplex.app.* import chat.simplex.app.views.helpers.withApi -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.* @@ -14,19 +18,17 @@ import kotlin.concurrent.thread typealias ChatCtrl = Long -@DelicateCoroutinesApi -open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.AlertManager) { +open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.AlertManager, val ntfManager: NtfManager, val appContext: Context) { var chatModel = ChatModel(this, alertManager) suspend fun startChat(u: User) { - chatModel.currentUser = mutableStateOf(u) - chatModel.userCreated.value = true Log.d("SIMPLEX (user)", u.toString()) try { apiStartChat() chatModel.userAddress.value = apiGetUserAddress() chatModel.chats.addAll(apiGetChats()) - chatModel.chatsLoaded.value = true + chatModel.currentUser = mutableStateOf(u) + chatModel.userCreated.value = true startReceiver() Log.d("SIMPLEX", "started chat") } catch(e: Error) { @@ -41,6 +43,18 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert } } + open fun isAppOnForeground(context: Context): Boolean { + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val appProcesses = activityManager.runningAppProcesses ?: return false + val packageName = context.packageName + for (appProcess in appProcesses) { + if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName == packageName) { + return true + } + } + return false + } + suspend fun sendCmd(cmd: CC): CR { return withContext(Dispatchers.IO) { val c = cmd.cmdString @@ -146,9 +160,9 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert suspend fun apiDeleteChat(type: ChatType, id: Long): Boolean { val r = sendCmd(CC.ApiDeleteChat(type, id)) - when { - r is CR.ContactDeleted -> return true // TODO groups - r is CR.ChatCmdError -> { + when (r) { + is CR.ContactDeleted -> return true // TODO groups + is CR.ChatCmdError -> { val e = r.chatError if (e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.ContactGroups) { alertManager.showAlertMsg( @@ -260,7 +274,9 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert val cInfo = r.chatItem.chatInfo val cItem = r.chatItem.chatItem chatModel.addChatItem(cInfo, cItem) -// NtfManager.shared.notifyMessageReceived(cInfo, cItem) + if (!isAppOnForeground(appContext) || chatModel.chatId.value != cInfo.id) { + ntfManager.notifyMessageReceived(cInfo, cItem) + } } // case let .chatItemUpdated(aChatItem): // let cInfo = aChatItem.chatInfo @@ -432,7 +448,7 @@ sealed class CR { is ChatStarted -> "chatStarted" is ChatRunning -> "chatRunning" is ApiChats -> "apiChats" - is ApiChat -> "apiChats" + is ApiChat -> "apiChat" is Invitation -> "invitation" is SentConfirmation -> "sentConfirmation" is SentInvitation -> "sentInvitation" diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt index 503a50e211..4fa4b311d3 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt @@ -1,16 +1,11 @@ package chat.simplex.app.views -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize 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.res.painterResource -import androidx.compose.ui.unit.dp -import chat.simplex.app.R @Composable fun SplashView() { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt index 4d83b38b1d..d12a4a62d9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -23,10 +23,8 @@ import chat.simplex.app.views.helpers.CloseSheetBar import chat.simplex.app.views.helpers.withApi import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsWithImePadding -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.launch -@DelicateCoroutinesApi @Composable fun TerminalView(chatModel: ChatModel, nav: NavController) { TerminalLayout(chatModel.terminalItems, nav::popBackStack, nav::navigate) { cmd -> diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt index fcfafb82d0..bacd1dad29 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt @@ -19,11 +19,9 @@ import chat.simplex.app.model.Profile import chat.simplex.app.views.helpers.withApi import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsWithImePadding -import kotlinx.coroutines.DelicateCoroutinesApi -@DelicateCoroutinesApi @Composable -fun WelcomeView(chatModel: ChatModel, routeHome: () -> Unit) { +fun WelcomeView(chatModel: ChatModel) { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { Box( modifier = Modifier @@ -60,7 +58,7 @@ fun WelcomeView(chatModel: ChatModel, routeHome: () -> Unit) { color = MaterialTheme.colors.onBackground ) Spacer(Modifier.height(24.dp)) - CreateProfilePanel(chatModel, routeHome) + CreateProfilePanel(chatModel) } } } @@ -71,9 +69,8 @@ fun isValidDisplayName(name: String) : Boolean { return (name.firstOrNull { it.isWhitespace() }) == null } -@DelicateCoroutinesApi @Composable -fun CreateProfilePanel(chatModel: ChatModel, routeHome: () -> Unit) { +fun CreateProfilePanel(chatModel: ChatModel) { var displayName by remember { mutableStateOf("") } var fullName by remember { mutableStateOf("") } @@ -154,10 +151,9 @@ fun CreateProfilePanel(chatModel: ChatModel, routeHome: () -> Unit) { Profile(displayName, fullName) ) chatModel.controller.startChat(user) - routeHome() } }, enabled = displayName.isNotEmpty() - ) { Text("Create")} + ) { Text("Create") } } -} \ No newline at end of file +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index 4f1dc1b823..061fab5e3b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -15,19 +15,16 @@ import androidx.compose.ui.text.style.TextAlign 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.* import chat.simplex.app.views.helpers.* -import kotlinx.coroutines.DelicateCoroutinesApi -@DelicateCoroutinesApi @Composable fun ChatInfoView(chatModel: ChatModel, nav: NavController) { val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value } if (chat != null) { ChatInfoLayout(chat, - close = { nav.popBackStack() }, + close = nav::popBackStack, deleteContact = { chatModel.alertManager.showAlertMsg( title = "Delete contact?", @@ -39,7 +36,8 @@ fun ChatInfoView(chatModel: ChatModel, nav: NavController) { val r = chatModel.controller.apiDeleteChat(cInfo.chatType, cInfo.apiId) if (r) { chatModel.removeChat(cInfo.id) - nav.navigate(Pages.ChatList.route) + chatModel.chatId.value = null + nav.popBackStack() } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 5b6e3044f5..7270720ebf 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -1,6 +1,8 @@ package chat.simplex.app.views.chat import android.content.res.Configuration +import android.util.Log +import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -12,7 +14,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler -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 @@ -24,56 +25,54 @@ import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.item.ChatItemView import chat.simplex.app.views.helpers.ChatInfoImage import chat.simplex.app.views.helpers.withApi -import com.google.accompanist.insets.* -import kotlinx.coroutines.* +import com.google.accompanist.insets.ProvideWindowInsets +import com.google.accompanist.insets.navigationBarsWithImePadding +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.datetime.Clock -@ExperimentalTextApi -@ExperimentalAnimatedInsets -@DelicateCoroutinesApi @Composable fun ChatView(chatModel: ChatModel, nav: NavController) { - if (chatModel.chatId.value != null && chatModel.chats.count() > 0) { - val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value } - if (chat != null) { - // TODO a more advanced version would mark as read only if in view - LaunchedEffect(chat.chatItems) { - delay(1000L) - if (chat.chatItems.count() > 0) { - chatModel.markChatItemsRead(chat.chatInfo) - withApi { - chatModel.controller.apiChatRead( - chat.chatInfo.chatType, - chat.chatInfo.apiId, - CC.ItemRange(chat.chatStats.minUnreadItemId, chat.chatItems.last().id) - ) - } + val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value } + if (chat == null) { + chatModel.chatId.value = null + } else { + BackHandler { chatModel.chatId.value = null } + // TODO a more advanced version would mark as read only if in view + LaunchedEffect(chat.chatItems) { + Log.d("SIMPLEX", "ChatView ${chatModel.chatId.value}: LaunchedEffect") + delay(1000L) + if (chat.chatItems.count() > 0) { + chatModel.markChatItemsRead(chat.chatInfo) + withApi { + chatModel.controller.apiChatRead( + chat.chatInfo.chatType, + chat.chatInfo.apiId, + CC.ItemRange(chat.chatStats.minUnreadItemId, chat.chatItems.last().id) + ) } } - ChatLayout(chat, chatModel.chatItems, - back = { nav.popBackStack() }, - info = { nav.navigate(Pages.ChatInfo.route) }, - sendMessage = { msg -> - withApi { - // show "in progress" - val cInfo = chat.chatInfo - val newItem = chatModel.controller.apiSendMessage( - type = cInfo.chatType, - id = cInfo.apiId, - mc = MsgContent.MCText(msg) - ) - // hide "in progress" - if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem) - } - } - ) } + ChatLayout(chat, chatModel.chatItems, + back = { chatModel.chatId.value = null }, + info = { nav.navigate(Pages.ChatInfo.route) }, + sendMessage = { msg -> + withApi { + // show "in progress" + val cInfo = chat.chatInfo + val newItem = chatModel.controller.apiSendMessage( + type = cInfo.chatType, + id = cInfo.apiId, + mc = MsgContent.MCText(msg) + ) + // hide "in progress" + if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem) + } + } + ) } } -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalAnimatedInsets @Composable fun ChatLayout( chat: Chat, chatItems: List, @@ -101,7 +100,10 @@ fun ChatLayout( @Composable fun ChatInfoToolbar(chat: Chat, back: () -> Unit, info: () -> Unit) { - Box(Modifier.height(60.dp).padding(horizontal = 8.dp), + Box( + Modifier + .height(60.dp) + .padding(horizontal = 8.dp), contentAlignment = Alignment.CenterStart ) { IconButton(onClick = back) { @@ -136,9 +138,6 @@ fun ChatInfoToolbar(chat: Chat, back: () -> Unit, info: () -> Unit) { } } -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalAnimatedInsets @Composable fun ChatItemsList(chatItems: List) { val listState = rememberLazyListState() @@ -157,8 +156,6 @@ fun ChatItemsList(chatItems: List) { } } -@ExperimentalTextApi -@ExperimentalAnimatedInsets @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt index fcbd726a6d..aafcb7d041 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt @@ -1,6 +1,5 @@ package chat.simplex.app.views.chat.item -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt index 8a252f714b..ddcbbdc1a5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.UriHandler -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.model.CIDirection @@ -13,7 +12,6 @@ import chat.simplex.app.model.ChatItem import chat.simplex.app.ui.theme.SimpleXTheme import kotlinx.datetime.Clock -@ExperimentalTextApi @Composable fun ChatItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) { val sent = chatItem.chatDir.sent @@ -33,7 +31,6 @@ fun ChatItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) { } } -@ExperimentalTextApi @Preview @Composable fun PreviewChatItemView() { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt index 5be27d0e57..949648138e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt @@ -1,6 +1,7 @@ package chat.simplex.app.views.chat.item -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.SelectionContainer @@ -15,8 +16,8 @@ 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 chat.simplex.app.model.* -import chat.simplex.app.ui.theme.LightGray +import chat.simplex.app.model.CIDirection +import chat.simplex.app.model.ChatItem import chat.simplex.app.ui.theme.SimpleXTheme import kotlinx.datetime.Clock @@ -24,7 +25,6 @@ import kotlinx.datetime.Clock val SentColorLight = Color(0x1E45B8FF) val ReceivedColorLight = Color(0x1EB1B0B5) -@ExperimentalTextApi @Composable fun TextItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) { val sent = chatItem.chatDir.sent @@ -55,7 +55,6 @@ fun appendGroupMember(b: AnnotatedString.Builder, chatItem: ChatItem, groupMembe } } -@ExperimentalTextApi @Composable fun MarkdownText ( chatItem: ChatItem, @@ -110,7 +109,6 @@ fun MarkdownText ( } } -@ExperimentalTextApi @Preview @Composable fun PreviewTextItemViewSnd() { @@ -123,7 +121,6 @@ fun PreviewTextItemViewSnd() { } } -@ExperimentalTextApi @Preview @Composable fun PreviewTextItemViewRcv() { @@ -136,7 +133,6 @@ fun PreviewTextItemViewRcv() { } } -@ExperimentalTextApi @Preview @Composable fun PreviewTextItemViewLong() { 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 index d13f2863ad..9efbb1183a 100644 --- 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 @@ -9,7 +9,6 @@ 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 @@ -17,41 +16,31 @@ 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( + ChatListNavLinkLayout( 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) + click = { + if (chat.chatInfo is ChatInfo.ContactRequest) { + contactRequestAlertDialog(chat.chatInfo, chatModel, nav) + } else { + withApi { openChat(chatModel, chat.chatInfo) } } } ) } -@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 - } +suspend fun openChat(chatModel: ChatModel, cInfo: ChatInfo) { + val chat = chatModel.controller.apiGetChat(cInfo.chatType, cInfo.apiId) + if (chat != null) { + chatModel.chatItems = chat.chatItems.toMutableStateList() + chatModel.chatId.value = cInfo.id } } -@DelicateCoroutinesApi -fun contactRequestNavLink(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel, navController: NavController) { +fun contactRequestAlertDialog(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", @@ -75,27 +64,12 @@ fun contactRequestNavLink(contactRequest: ChatInfo.ContactRequest, chatModel: Ch ) } -@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) { +fun ChatListNavLinkLayout(chat: Chat, click: () -> Unit) { Surface( modifier = Modifier .fillMaxWidth() - .clickable(onClick = action) + .clickable(onClick = click) .height(88.dp) ) { Row( @@ -104,18 +78,18 @@ fun ChatListNavLinkLayout(content: (@Composable () -> Unit), action: () -> Unit) .padding(vertical = 8.dp) .padding(start = 8.dp) .padding(end = 12.dp), - verticalAlignment = Alignment.Top, -// TODO? -// verticalAlignment = Alignment.CenterVertically, -// horizontalArrangement = Arrangement.SpaceEvenly + verticalAlignment = Alignment.Top ) { - content.invoke() + if (chat.chatInfo is ChatInfo.ContactRequest) { + ContactRequestView(chat) + } else { + ChatPreviewView(chat) + } } } Divider(Modifier.padding(horizontal = 8.dp)) } -@ExperimentalTextApi @Preview @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -125,7 +99,7 @@ fun ChatListNavLinkLayout(content: (@Composable () -> Unit), action: () -> Unit) @Composable fun PreviewChatListNavLinkDirect() { SimpleXTheme { - ChatListNavLink( + ChatListNavLinkLayout( chat = Chat( chatInfo = ChatInfo.Direct.sampleData, chatItems = listOf( @@ -138,12 +112,11 @@ fun PreviewChatListNavLinkDirect() { ), chatStats = Chat.ChatStats() ), - action = {} + click = {} ) } } -@ExperimentalTextApi @Preview @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -153,7 +126,7 @@ fun PreviewChatListNavLinkDirect() { @Composable fun PreviewChatListNavLinkGroup() { SimpleXTheme { - ChatListNavLink( + ChatListNavLinkLayout( chat = Chat( chatInfo = ChatInfo.Group.sampleData, chatItems = listOf( @@ -166,12 +139,11 @@ fun PreviewChatListNavLinkGroup() { ), chatStats = Chat.ChatStats() ), - action = {} + click = {} ) } } -@ExperimentalTextApi @Preview @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -181,13 +153,13 @@ fun PreviewChatListNavLinkGroup() { @Composable fun PreviewChatListNavLinkContactRequest() { SimpleXTheme { - ChatListNavLink( + ChatListNavLinkLayout( chat = Chat( chatInfo = ChatInfo.ContactRequest.sampleData, chatItems = listOf(), chatStats = Chat.ChatStats() ), - action = {} + click = {} ) } } 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 179ed1919e..6f06d3e366 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,7 +14,6 @@ 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 @@ -22,10 +21,9 @@ import chat.simplex.app.model.ChatModel import chat.simplex.app.views.chat.ChatHelpView import chat.simplex.app.views.newchat.NewChatSheet import chat.simplex.app.views.usersettings.SettingsView -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch -@ExperimentalMaterialApi class ScaffoldController(val scope: CoroutineScope) { lateinit var state: BottomSheetScaffoldState val expanded = mutableStateOf(false) @@ -49,7 +47,6 @@ class ScaffoldController(val scope: CoroutineScope) { } } -@ExperimentalMaterialApi @Composable fun scaffoldController(): ScaffoldController { val ctrl = ScaffoldController(scope = rememberCoroutineScope()) @@ -64,10 +61,6 @@ fun scaffoldController(): ScaffoldController { return ctrl } -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalPermissionsApi -@ExperimentalMaterialApi @Composable fun ChatListView(chatModel: ChatModel, nav: NavController) { val scaffoldCtrl = scaffoldController() @@ -86,14 +79,11 @@ fun ChatListView(chatModel: ChatModel, nav: NavController) { .background(MaterialTheme.colors.background) ) { ChatListToolbar(scaffoldCtrl) - when (chatModel.chatsLoaded.value) { - true -> if (chatModel.chats.isNotEmpty()) { - ChatList(chatModel, nav) - } else { - val user = chatModel.currentUser.value - Help(scaffoldCtrl, displayName = user?.profile?.displayName) - } - else -> ChatList(chatModel, nav) + if (chatModel.chats.isNotEmpty()) { + ChatList(chatModel, nav) + } else { + val user = chatModel.currentUser.value + Help(scaffoldCtrl, displayName = user?.profile?.displayName) } } if (scaffoldCtrl.expanded.value) { @@ -108,7 +98,6 @@ fun ChatListView(chatModel: ChatModel, nav: NavController) { } } -@ExperimentalMaterialApi @Composable fun Help(scaffoldCtrl: ScaffoldController, displayName: String?) { Column( @@ -142,7 +131,6 @@ fun Help(scaffoldCtrl: ScaffoldController, displayName: String?) { } } -@ExperimentalMaterialApi @Composable fun ChatListToolbar(scaffoldCtrl: ScaffoldController) { Row( @@ -178,8 +166,6 @@ fun ChatListToolbar(scaffoldCtrl: ScaffoldController) { } } -@ExperimentalTextApi -@DelicateCoroutinesApi @Composable fun ChatList(chatModel: ChatModel, navController: NavController) { Divider(Modifier.padding(horizontal = 8.dp)) @@ -191,15 +177,3 @@ fun ChatList(chatModel: ChatModel, navController: NavController) { } } } -//@Preview -//@Composable -//fun PreviewChatListView() { -// SimpleXTheme { -// ChatListView( -// chats = listOf( -// Chat() -// ), -// -// ) -// } -//} 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 caad7bdb1d..95fc05f7c2 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 @@ -9,7 +9,6 @@ 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 @@ -23,7 +22,6 @@ import chat.simplex.app.views.chat.item.MarkdownText import chat.simplex.app.views.helpers.ChatInfoImage import chat.simplex.app.views.helpers.badgeLayout -@ExperimentalTextApi @Composable fun ChatPreviewView(chat: Chat) { Row { @@ -78,7 +76,6 @@ fun ChatPreviewView(chat: Chat) { } } -@ExperimentalTextApi @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt index 27eac8842b..cdc1f86039 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt @@ -1,7 +1,6 @@ package chat.simplex.app.views.helpers import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons @@ -9,8 +8,6 @@ import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.SupervisedUserCircle import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt index a8f5ec9dd4..8f148fbd3b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt @@ -2,6 +2,5 @@ package chat.simplex.app.views.helpers import kotlinx.coroutines.* -@DelicateCoroutinesApi fun withApi(action: suspend CoroutineScope.() -> Unit): Job = GlobalScope.launch { withContext(Dispatchers.Main, action) } 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 5a82ea51c9..e9715229f0 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 @@ -18,9 +18,7 @@ import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.CloseSheetBar import chat.simplex.app.views.helpers.withApi -import kotlinx.coroutines.DelicateCoroutinesApi -@DelicateCoroutinesApi @Composable fun ConnectContactView(chatModel: ChatModel, nav: NavController) { ConnectContactLayout( @@ -44,7 +42,6 @@ fun ConnectContactView(chatModel: ChatModel, nav: NavController) { ) } -@DelicateCoroutinesApi fun withUriAction( chatModel: ChatModel, uri: Uri, run: suspend (String) -> Unit diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt index 64db15a0cf..4b338f4f3d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt @@ -21,13 +21,8 @@ import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chatlist.ScaffoldController import chat.simplex.app.views.helpers.withApi -import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberPermissionState -import kotlinx.coroutines.DelicateCoroutinesApi -@DelicateCoroutinesApi -@ExperimentalPermissionsApi -@ExperimentalMaterialApi @Composable fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController, nav: NavController) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) @@ -47,15 +42,12 @@ fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController, nav: Nav newChatCtrl.collapse() nav.navigate(Pages.Connect.route) cameraPermissionState.launchPermissionRequest() - }, - close = { - newChatCtrl.collapse() } ) } @Composable -fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit, close: () -> Unit) { +fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit) { Row(Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 48.dp), @@ -116,8 +108,7 @@ fun PreviewNewChatSheet() { SimpleXTheme { NewChatSheetLayout( addContact = {}, - scanCode = {}, - close = {}, + scanCode = {} ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt index ec33dca2ad..0e9ec8fb87 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt @@ -7,10 +7,7 @@ import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.* -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController diff --git a/apps/android/app/src/main/res/drawable-hdpi/ntf_icon.png b/apps/android/app/src/main/res/drawable-hdpi/ntf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e17716d5938412ecdcdc63ff5ef87cd58eaaf10c GIT binary patch literal 861 zcmV-j1ETziP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uy~$4Nv%R9Fesn7eLNF%X6W1$luW zBB+sg1=`cnJN1?v6P@FEA(259rRA?b13NA3?t%w-6Lc zgR$LDnA=MSjwYrc9~>d;4|4ltcbw_XzLqYxoy_BK2YT-aS>I)fahVyL2+XC)03ruC z7N~cQl=b)EA7^8;uVA(#Un`Rl;4&0}G6?tU%S`z-2~@PF4CW2YYL*E{O`8upE#D`o zY;O@Xfx&Je)W%+?FHz2k-;mB?-#O#Au)CkYU$&WfI>yjbw=x7-Hhm- zK`M}HU!boY_xF!#&w~XNNq{atv9cW&PXxCh2Y~Hbm?<_ovwP#{2Q>gkjgX=R>IztzM>L({A zyqrs4-Ixgi6Imtkn^|617U^B;_K(&)KctT+JyaH;qg^?it*VUn;1gK43uDU|}9A3O! zeYP>uFN>zf6?VExK11K2ys0W5+GnlD##c%F{P08!$7++!luF1_k z(ViK+XOdlX^=SE;4oe^OGzNz%2|Chl;S&O7nJLwo@uPiO@R}aSDxSK}LNK0(-k{d^ n@0cvup82spf!ZIlITyM=3tYT+9pOe&00000NkvXXu0mjfbCYdK literal 0 HcmV?d00001 diff --git a/apps/android/app/src/main/res/drawable-mdpi/ntf_icon.png b/apps/android/app/src/main/res/drawable-mdpi/ntf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4f9c5cf450ae43e2e4a13c58eeb4b253889a1938 GIT binary patch literal 569 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY_X?{V~7Xu z(W$oH%#H#M!u5@<60Vjys#6mLH%$CvzuV!|9|q~%Edov(Hg4Xqaoc9GjRN+Ms@tpI zy_G$=&{C%2&bhiXXWpH8S8DZN_4Cg?-0cQUy(WCWwk7h^zF79yyHt$#q2RrTLR{)w z${$MEJz!+>=3OQnG3B6bP17XnhuKf0!ew^1o-L_Wp4)0&z$#Uwo_>GV#M|Y3N6$$~ z`l=^7sJ~O0CQxboc#X-z;{m%vf3bhyah#X&X8FMtm**c8{G%dwG|O<|ymU>iFLHMd zO7CfU#Fv*~Th=63mi_4WQHfJ5TF<@aAJcbe?yG)g%<)U}?H$hhCh_VUn|GbENSHV= z+h=AIpKjpd@RF?CJEwMO%iKtCin%RS%jl{sb)Snn<9%b>T=DZ;FMTTFEB20OyW5#6 z+3kKs+t+2``Gz!s?$nr552JQ%FqJ+3R#x!XZC@ikwFD#Ih{C^_>(}^1@6UK^C)%ym zp3HT9eww0O>eI-`l;veS$}hjh-!1O_$fI?A{=QvZt7}`c7xT@PkaAkR-$yvVw0A+t oU*0CWa-+8Uf4>~|`}gZF)6)GM8GAO&nhlCJPgg&ebxsLQ0PJktQUCw| literal 0 HcmV?d00001 diff --git a/apps/android/app/src/main/res/drawable-xhdpi/ntf_icon.png b/apps/android/app/src/main/res/drawable-xhdpi/ntf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..44f7315eeca217d1144f9d635fb72f082cc121cc GIT binary patch literal 1089 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 DXT~}I literal 0 HcmV?d00001 diff --git a/apps/android/app/src/main/res/drawable-xxxhdpi/ntf_icon.png b/apps/android/app/src/main/res/drawable-xxxhdpi/ntf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..71e75ec56997e471d8bc3bd37675c15301fc8ef4 GIT binary patch literal 2652 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