Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiplatform: make ChatModel and ChatController as object #2639

Merged
merged 6 commits into from Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -24,7 +24,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import chat.simplex.app.MainActivity.Companion.enteredBackground
import chat.simplex.app.model.*
import chat.simplex.app.model.NtfManager.Companion.getUserIdFromIntent
import chat.simplex.app.model.NtfManager.getUserIdFromIntent
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.SplashView
import chat.simplex.app.views.call.ActiveCallView
Expand All @@ -42,6 +42,7 @@ import chat.simplex.app.views.onboarding.*
import chat.simplex.app.views.usersettings.LAMode
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.distinctUntilChanged
import java.lang.ref.WeakReference

class MainActivity: FragmentActivity() {
companion object {
Expand All @@ -65,6 +66,7 @@ class MainActivity: FragmentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
SimplexApp.context.mainActivity = WeakReference(this)
// testJson()
val m = vm.chatModel
applyAppLocale(m.controller.appPrefs.appLanguage)
Expand Down Expand Up @@ -181,7 +183,6 @@ class MainActivity: FragmentActivity() {
else
generalGetString(R.string.auth_unlock),
selfDestruct = true,
this@MainActivity,
completed = { laResult ->
when (laResult) {
LAResult.Success ->
Expand Down Expand Up @@ -250,7 +251,6 @@ class MainActivity: FragmentActivity() {
authenticate(
generalGetString(R.string.auth_enable_simplex_lock),
generalGetString(R.string.auth_confirm_credential),
activity = activity,
completed = { laResult ->
when (laResult) {
LAResult.Success -> {
Expand Down Expand Up @@ -317,7 +317,6 @@ class MainActivity: FragmentActivity() {
generalGetString(R.string.auth_confirm_credential)
else
"",
activity = activity,
completed = { laResult ->
val prefPerformLA = m.controller.appPrefs.performLA
when (laResult) {
Expand Down Expand Up @@ -353,7 +352,6 @@ class MainActivity: FragmentActivity() {
generalGetString(R.string.auth_confirm_credential)
else
generalGetString(R.string.auth_disable_simplex_lock),
activity = activity,
completed = { laResult ->
val prefPerformLA = m.controller.appPrefs.performLA
val selfDestructPref = m.controller.appPrefs.selfDestruct
Expand Down
Expand Up @@ -14,6 +14,7 @@ import com.jakewharton.processphoenix.ProcessPhoenix
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import java.io.*
import java.lang.ref.WeakReference
import java.util.*
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
Expand All @@ -37,13 +38,20 @@ external fun chatParseServer(str: String): String
external fun chatPasswordHash(pwd: String, salt: String): String

class SimplexApp: Application(), LifecycleEventObserver {
var mainActivity: WeakReference<MainActivity> = WeakReference(null)
val chatModel: ChatModel
get() = chatController.chatModel
val appPreferences: AppPreferences
get() = chatController.appPrefs

val chatController: ChatController = ChatController
var isAppOnForeground: Boolean = false

val defaultLocale: Locale = Locale.getDefault()

suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) {
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
val dbAbsolutePathPrefix = getFilesDirectory(SimplexApp.context)
val dbAbsolutePathPrefix = getFilesDirectory()
val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp
val migrated: Array<Any> = chatMigrateInit(dbAbsolutePathPrefix, dbKey, confirm.value)
val res: DBMigrationResult = kotlin.runCatching {
Expand Down Expand Up @@ -84,21 +92,6 @@ class SimplexApp: Application(), LifecycleEventObserver {
}
}

val chatModel: ChatModel
get() = chatController.chatModel

private val ntfManager: NtfManager by lazy {
NtfManager(applicationContext, appPreferences)
}

private val appPreferences: AppPreferences by lazy {
AppPreferences(applicationContext)
}

val chatController: ChatController by lazy {
ChatController(0L, ntfManager, applicationContext, appPreferences)
}


override fun onCreate() {
super.onCreate()
Expand Down Expand Up @@ -162,12 +155,12 @@ class SimplexApp: Application(), LifecycleEventObserver {

fun allowToStartServiceAfterAppExit() = with(chatModel.controller) {
appPrefs.notificationsMode.get() == NotificationsMode.SERVICE.name &&
(!NotificationsMode.SERVICE.requiresIgnoringBattery || isIgnoringBatteryOptimizations(chatModel.controller.appContext))
(!NotificationsMode.SERVICE.requiresIgnoringBattery || isIgnoringBatteryOptimizations())
}

private fun allowToStartPeriodically() = with(chatModel.controller) {
appPrefs.notificationsMode.get() == NotificationsMode.PERIODIC.name &&
(!NotificationsMode.PERIODIC.requiresIgnoringBattery || isIgnoringBatteryOptimizations(chatModel.controller.appContext))
(!NotificationsMode.PERIODIC.requiresIgnoringBattery || isIgnoringBatteryOptimizations())
}

/*
Expand Down
Expand Up @@ -34,7 +34,8 @@ import kotlin.time.*
* Without this annotation an animation from ChatList to ChatView has 1 frame per the whole animation. Don't delete it
* */
@Stable
class ChatModel(val controller: ChatController) {
object ChatModel {
val controller: ChatController = ChatController
val onboardingStage = mutableStateOf<OnboardingStage?>(null)
val currentUser = mutableStateOf<User?>(null)
val users = mutableStateListOf<UserInfo>()
Expand Down Expand Up @@ -65,11 +66,11 @@ class ChatModel(val controller: ChatController) {
val appOpenUrl = mutableStateOf<Uri?>(null)

// preferences
val notificationsMode = mutableStateOf(NotificationsMode.default)
var notificationPreviewMode = mutableStateOf(NotificationPreviewMode.default)
val performLA = mutableStateOf(false)
val notificationsMode by lazy { mutableStateOf(NotificationsMode.values().firstOrNull { it.name == controller.appPrefs.notificationsMode.get() } ?: NotificationsMode.default) }
val notificationPreviewMode by lazy { mutableStateOf(NotificationPreviewMode.values().firstOrNull { it.name == controller.appPrefs.notificationPreviewMode.get() } ?: NotificationPreviewMode.default) }
val performLA by lazy { mutableStateOf(controller.appPrefs.performLA.get()) }
val showAdvertiseLAUnavailableAlert = mutableStateOf(false)
var incognito = mutableStateOf(false)
val incognito by lazy { mutableStateOf(controller.appPrefs.incognito.get()) }

// current WebRTC call
val callManager = CallManager(this)
Expand All @@ -91,7 +92,7 @@ class ChatModel(val controller: ChatController) {
val sharedContent = mutableStateOf(null as SharedContent?)

val filesToDelete = mutableSetOf<File>()
val simplexLinkMode = mutableStateOf(controller.appPrefs.simplexLinkMode.get())
val simplexLinkMode by lazy { mutableStateOf(controller.appPrefs.simplexLinkMode.get()) }

fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
currentUser.value
Expand Down
@@ -1,45 +1,47 @@
package chat.simplex.app.model

import android.Manifest
import android.app.*
import android.app.TaskStackBuilder
import android.content.*
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.hardware.display.DisplayManager
import android.media.AudioAttributes
import android.net.Uri
import android.util.Log
import android.view.Display
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.*
import chat.simplex.app.*
import chat.simplex.app.views.call.*
import chat.simplex.app.views.chatlist.acceptContactRequest
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.NotificationPreviewMode
import kotlinx.datetime.Clock

class NtfManager(val context: Context, private val appPreferences: AppPreferences) {
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"
const val ShowChatsAction: String = "chat.simplex.app.SHOW_CHATS"
object NtfManager {
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"
const val ShowChatsAction: String = "chat.simplex.app.SHOW_CHATS"

// DO NOT change notification channel settings / names
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_1"
const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL"
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
const val CallNotificationId: Int = -1
// DO NOT change notification channel settings / names
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_1"
const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL"
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
const val CallNotificationId: Int = -1
private const val UserIdKey: String = "userId"
private const val ChatIdKey: String = "chatId"
private val appPreferences: AppPreferences by lazy { ChatController.appPrefs }
private val context: Context
get() = SimplexApp.context

private const val UserIdKey: String = "userId"
private const val ChatIdKey: String = "chatId"

fun getUserIdFromIntent(intent: Intent?): Long? {
val userId = intent?.getLongExtra(UserIdKey, -1L)
return if (userId == -1L || userId == null) null else userId
}
fun getUserIdFromIntent(intent: Intent?): Long? {
val userId = intent?.getLongExtra(UserIdKey, -1L)
return if (userId == -1L || userId == null) null else userId
}

private val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val manager: NotificationManager = SimplexApp.context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private var prevNtfTime = mutableMapOf<String, Long>()
private val msgNtfTimeoutMs = 30000L

Expand All @@ -58,7 +60,7 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build()
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once)
Log.d(TAG,"callNotificationChannel sound: $soundUri")
Log.d(TAG, "callNotificationChannel sound: $soundUri")
callChannel.setSound(soundUri, attrs)
callChannel.enableVibration(true)
// the numbers below are explained here: https://developer.android.com/reference/android/os/Vibrator
Expand All @@ -70,8 +72,8 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
fun cancelNotificationsForChat(chatId: String) {
prevNtfTime.remove(chatId)
manager.cancel(chatId.hashCode())
val msgNtfs = manager.activeNotifications.filter {
ntf -> ntf.notification.channelId == MessageChannel
val msgNtfs = manager.activeNotifications.filter { ntf ->
ntf.notification.channelId == MessageChannel
}
if (msgNtfs.count() == 1) {
// Have a group notification with no children so cancel it
Expand Down Expand Up @@ -110,7 +112,6 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
val now = Clock.System.now().toEpochMilliseconds()
val recentNotification = (now - prevNtfTime.getOrDefault(chatId, 0) < msgNtfTimeoutMs)
prevNtfTime[chatId] = now

val previewMode = appPreferences.notificationPreviewMode.get()
val title = if (previewMode == NotificationPreviewMode.HIDDEN.name) generalGetString(R.string.notification_preview_somebody) else displayName
val content = if (previewMode != NotificationPreviewMode.MESSAGE.name) generalGetString(R.string.notification_preview_new_message) else msgText
Expand Down Expand Up @@ -145,7 +146,6 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
}
builder.addAction(0, actionButton, actionPendingIntent)
}

val summary = NotificationCompat.Builder(context, MessageChannel)
.setSmallIcon(R.drawable.ntf_icon)
.setColor(0x88FFFF)
Expand All @@ -157,14 +157,17 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference

with(NotificationManagerCompat.from(context)) {
// using cInfo.id only shows one notification per chat and updates it when the message arrives
notify(chatId.hashCode(), builder.build())
notify(0, summary)
if (ActivityCompat.checkSelfPermission(SimplexApp.context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
notify(chatId.hashCode(), builder.build())
notify(0, summary)
}
}
}

fun notifyCallInvitation(invitation: RcvCallInvitation) {
val keyguardManager = getKeyguardManager(context)
Log.d(TAG,
Log.d(
TAG,
"notifyCallInvitation pre-requests: " +
"keyguard locked ${keyguardManager.isKeyguardLocked}, " +
"callOnLockScreen ${appPreferences.callOnLockScreen.get()}, " +
Expand Down Expand Up @@ -223,7 +226,9 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
// This makes notification sound and vibration repeat endlessly
notification.flags = notification.flags or NotificationCompat.FLAG_INSISTENT
with(NotificationManagerCompat.from(context)) {
notify(CallNotificationId, notification)
if (ActivityCompat.checkSelfPermission(SimplexApp.context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
notify(CallNotificationId, notification)
}
}
}

Expand All @@ -237,7 +242,7 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference

fun hasNotificationsForChat(chatId: String): Boolean = manager.activeNotifications.any { it.id == chatId.hashCode() }

private fun hideSecrets(cItem: ChatItem) : String {
private fun hideSecrets(cItem: ChatItem): String {
val md = cItem.formattedText
return if (md != null) {
var res = ""
Expand Down Expand Up @@ -302,14 +307,16 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference
}
val apiId = chatId.replace("<@", "").toLongOrNull() ?: return
acceptContactRequest(apiId, cInfo, isCurrentUser, m)
m.controller.ntfManager.cancelNotificationsForChat(chatId)
cancelNotificationsForChat(chatId)
}

RejectCallAction -> {
val invitation = m.callInvitations[chatId]
if (invitation != null) {
m.callManager.endCall(invitation = invitation)
}
}

else -> {
Log.e(TAG, "Unknown action. Make sure you provide action from NotificationAction enum")
}
Expand Down