From 5b8c786394ba2304794c1d31d2fc38b133f38ebf Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Thu, 21 Dec 2023 22:21:56 +0530 Subject: [PATCH] refactor: use coroutine flow for sms --- app/src/main/java/com/bnyro/contacts/App.kt | 22 +++- .../com/bnyro/contacts/db/dao/LocalSmsDao.kt | 3 +- .../contacts/receivers/DeleteSmsReceiver.kt | 8 +- .../bnyro/contacts/receivers/SmsReceiver.kt | 18 +-- .../com/bnyro/contacts/repo/DeviceSmsRepo.kt | 70 +++++++---- .../com/bnyro/contacts/repo/LocalSmsRepo.kt | 11 +- .../com/bnyro/contacts/repo/SmsRepository.kt | 5 +- .../contacts/ui/activities/BaseActivity.kt | 56 ++++++++- .../contacts/ui/activities/MainActivity.kt | 18 +-- .../com/bnyro/contacts/ui/models/SmsModel.kt | 119 +++++------------- .../contacts/ui/screens/SettingsScreen.kt | 2 +- .../contacts/ui/screens/SmsListScreen.kt | 13 +- .../contacts/ui/screens/SmsThreadScreen.kt | 8 +- .../java/com/bnyro/contacts/util/SmsUtil.kt | 50 ++------ 14 files changed, 196 insertions(+), 207 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/App.kt b/app/src/main/java/com/bnyro/contacts/App.kt index 39065819..2e928277 100644 --- a/app/src/main/java/com/bnyro/contacts/App.kt +++ b/app/src/main/java/com/bnyro/contacts/App.kt @@ -3,11 +3,13 @@ package com.bnyro.contacts import android.app.Application import com.bnyro.contacts.db.DatabaseHolder import com.bnyro.contacts.repo.DeviceContactsRepository +import com.bnyro.contacts.repo.DeviceSmsRepo import com.bnyro.contacts.repo.LocalContactsRepository +import com.bnyro.contacts.repo.LocalSmsRepo +import com.bnyro.contacts.repo.SmsRepository import com.bnyro.contacts.util.NotificationHelper import com.bnyro.contacts.util.Preferences import com.bnyro.contacts.util.ShortcutHelper -import com.bnyro.contacts.util.SmsUtil import com.bnyro.contacts.workers.BackupWorker class App : Application() { @@ -17,6 +19,22 @@ class App : Application() { val localContactsRepository by lazy { LocalContactsRepository(this) } + val localSmsRepo by lazy { + LocalSmsRepo() + } + val deviceSmsRepo by lazy { + DeviceSmsRepo() + } + + lateinit var smsRepo: SmsRepository + + fun initSmsRepo() { + smsRepo = if (Preferences.getBoolean(Preferences.storeSmsLocallyKey, false)) { + LocalSmsRepo() + } else { + DeviceSmsRepo() + } + } override fun onCreate() { super.onCreate() @@ -31,6 +49,6 @@ class App : Application() { NotificationHelper.createChannels(this) - SmsUtil.initSmsRepo() + initSmsRepo() } } diff --git a/app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt b/app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt index 9475e7bb..fbb5c3c1 100644 --- a/app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt +++ b/app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt @@ -4,11 +4,12 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.Query import com.bnyro.contacts.db.obj.SmsData +import kotlinx.coroutines.flow.Flow @Dao interface LocalSmsDao { @Query("SELECT * from localSms") - suspend fun getAll(): List + fun getStream(): Flow> @Query("DELETE FROM localSms WHERE id = :id") suspend fun deleteSms(id: Long) diff --git a/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt b/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt index 881a6e33..bb39aeb4 100644 --- a/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt +++ b/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt @@ -4,8 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.core.app.NotificationManagerCompat -import com.bnyro.contacts.ui.activities.MainActivity -import com.bnyro.contacts.util.SmsUtil +import com.bnyro.contacts.App import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -19,10 +18,7 @@ class DeleteSmsReceiver : BroadcastReceiver() { NotificationManagerCompat.from(context).cancel(notificationId) CoroutineScope(Dispatchers.IO).launch { - MainActivity.smsModel?.deleteSms(context, smsId, threadId) ?: run { - // if the UI is not currently opened, only delete the SMS and don't touch the UI - SmsUtil.deleteMessage(context, smsId) - } + (context.applicationContext as App).smsRepo.deleteSms(context, smsId) } } } diff --git a/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt b/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt index ee3d25d9..a33d281d 100644 --- a/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt +++ b/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt @@ -10,15 +10,14 @@ import android.provider.Telephony import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.RemoteInput +import com.bnyro.contacts.App import com.bnyro.contacts.R import com.bnyro.contacts.db.obj.SmsData import com.bnyro.contacts.enums.IntentActionType -import com.bnyro.contacts.ui.activities.MainActivity import com.bnyro.contacts.util.IntentHelper import com.bnyro.contacts.util.NotificationHelper import com.bnyro.contacts.util.NotificationHelper.MESSAGES_CHANNEL_ID import com.bnyro.contacts.util.PermissionHelper -import com.bnyro.contacts.util.SmsUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -33,16 +32,19 @@ class SmsReceiver : BroadcastReceiver() { val body = message.displayMessageBody val timestamp = message.timestampMillis val threadId = - runBlocking(Dispatchers.IO) { SmsUtil.getOrCreateThreadId(context, address) } + runBlocking(Dispatchers.IO) { + (context.applicationContext as App).smsRepo.getOrCreateThreadId( + context, + address + ) + } val bareSmsData = - SmsData(-1, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_INBOX) + SmsData(0, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_INBOX) createNotification(context, notificationId, bareSmsData) - val smsData = runBlocking(Dispatchers.IO) { - SmsUtil.persistMessage(context, bareSmsData) + runBlocking(Dispatchers.IO) { + (context.applicationContext as App).smsRepo.persistSms(context, bareSmsData) } - - MainActivity.smsModel?.addSmsToList(smsData) } } diff --git a/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt b/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt index da9746fb..9ea63876 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt @@ -1,21 +1,46 @@ package com.bnyro.contacts.repo +import android.Manifest +import android.annotation.SuppressLint +import android.content.ContentResolver import android.content.ContentValues import android.content.Context +import android.database.ContentObserver +import android.net.Uri import android.os.Build import android.provider.Telephony -import android.util.Log +import androidx.annotation.RequiresPermission import com.bnyro.contacts.db.obj.SmsData import com.bnyro.contacts.ext.intValue import com.bnyro.contacts.ext.longValue import com.bnyro.contacts.ext.stringValue +import com.bnyro.contacts.util.PermissionHelper import com.bnyro.contacts.util.SmsUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext import kotlin.random.Random class DeviceSmsRepo : SmsRepository { private val contentUri = Telephony.Sms.CONTENT_URI - override suspend fun getSmsList(context: Context): List { + @SuppressLint("MissingPermission") + override fun getSmsStream(context: Context): Flow> { + return if (PermissionHelper.hasPermission(context, Manifest.permission.READ_SMS)) { + context.contentResolver.observe(contentUri).map { + getSmsList(context) + } + } else { + emptyFlow() + } + } + + @RequiresPermission(Manifest.permission.READ_SMS) + private suspend fun getSmsList(context: Context): List = withContext(Dispatchers.IO) { val simSlotMap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { SmsUtil.getSubscriptions(context) .associateBy({ it.subscriptionId }, { it.simSlotIndex }) @@ -23,9 +48,9 @@ class DeviceSmsRepo : SmsRepository { null } context.contentResolver - .query(contentUri, null, null, null, null) + .query(contentUri, null, null, null, "date ASC") ?.use { cursor -> - if (!cursor.moveToFirst()) return emptyList() + if (!cursor.moveToFirst()) return@withContext emptyList() val smsList = mutableListOf() do { @@ -46,13 +71,13 @@ class DeviceSmsRepo : SmsRepository { smsList.add(SmsData(id, address, body, timestamp, threadId, type, simIndex)) } while (cursor.moveToNext()) - return smsList + return@withContext smsList } - return emptyList() + return@withContext emptyList() } - override suspend fun persistSms(context: Context, smsData: SmsData): SmsData { + override suspend fun persistSms(context: Context, smsData: SmsData) { val values = ContentValues() values.put(Telephony.Sms.ADDRESS, smsData.address) values.put(Telephony.Sms.BODY, smsData.body) @@ -61,20 +86,7 @@ class DeviceSmsRepo : SmsRepository { values.put(Telephony.Sms.TYPE, smsData.type) values.put(Telephony.Sms.THREAD_ID, smsData.threadId) - val messageUri = context.contentResolver.insert(contentUri, values) ?: return smsData - - Log.v("send_transaction", "inserted to uri: $messageUri") - - context.contentResolver.query( - messageUri, - arrayOf(Telephony.Sms._ID), - null, - null, - null - )?.use { - if (it.moveToFirst()) smsData.id = it.longValue(Telephony.Sms._ID)!! - } - return smsData + context.contentResolver.insert(contentUri, values) } override suspend fun deleteSms(context: Context, id: Long) { @@ -92,7 +104,7 @@ class DeviceSmsRepo : SmsRepository { )?.use { cursor -> while (cursor.moveToNext()) { val id = cursor.longValue(Telephony.Sms._ID) ?: continue - SmsUtil.deleteMessage(context, id) + deleteSms(context, id) } } } @@ -115,3 +127,17 @@ class DeviceSmsRepo : SmsRepository { return Random.nextLong() } } + +fun ContentResolver.observe(uri: Uri) = callbackFlow { + val observer = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(selfChange) + } + } + registerContentObserver(uri, true, observer) + // trigger first. + trySend(false) + awaitClose { + unregisterContentObserver(observer) + } +} diff --git a/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt b/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt index 0234890b..eca809d0 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt @@ -3,16 +3,15 @@ package com.bnyro.contacts.repo import android.content.Context import com.bnyro.contacts.db.DatabaseHolder import com.bnyro.contacts.db.obj.SmsData +import kotlinx.coroutines.flow.Flow import kotlin.random.Random class LocalSmsRepo : SmsRepository { - override suspend fun getSmsList(context: Context): List { - return DatabaseHolder.Db.localSmsDao().getAll() - } + override fun getSmsStream(context: Context): Flow> = + DatabaseHolder.Db.localSmsDao().getStream() - override suspend fun persistSms(context: Context, smsData: SmsData): SmsData { - val id = DatabaseHolder.Db.localSmsDao().createSms(smsData) - return smsData.copy(id = id) + override suspend fun persistSms(context: Context, smsData: SmsData) { + DatabaseHolder.Db.localSmsDao().createSms(smsData) } override suspend fun getOrCreateThreadId(context: Context, address: String): Long { diff --git a/app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt b/app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt index 9e4a61cb..88f250e8 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt @@ -2,10 +2,11 @@ package com.bnyro.contacts.repo import android.content.Context import com.bnyro.contacts.db.obj.SmsData +import kotlinx.coroutines.flow.Flow interface SmsRepository { - suspend fun getSmsList(context: Context): List - suspend fun persistSms(context: Context, smsData: SmsData): SmsData + fun getSmsStream(context: Context): Flow> + suspend fun persistSms(context: Context, smsData: SmsData) suspend fun getOrCreateThreadId(context: Context, address: String): Long suspend fun deleteSms(context: Context, id: Long) suspend fun deleteThread(context: Context, threadId: Long) diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt index 3eb30d07..8d5026b8 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt @@ -1,13 +1,20 @@ package com.bnyro.contacts.ui.activities import android.Manifest +import android.app.role.RoleManager +import android.content.Context +import android.content.Intent +import android.os.Build import android.os.Bundle +import android.provider.Telephony import androidx.activity.ComponentActivity import androidx.activity.viewModels import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.get import com.bnyro.contacts.ui.models.ContactsModel +import com.bnyro.contacts.ui.models.SmsModel import com.bnyro.contacts.ui.models.ThemeModel +import com.bnyro.contacts.util.NotificationHelper import com.bnyro.contacts.util.PermissionHelper abstract class BaseActivity : ComponentActivity() { @@ -15,17 +22,58 @@ abstract class BaseActivity : ComponentActivity() { val contactsModel by viewModels { ContactsModel.Factory } + val smsModel by viewModels { SmsModel.Factory } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val requiredPermissions = + smsPermissions + contactPermissions + NotificationHelper.notificationPermissions + + requestDefaultSMSApp(this) PermissionHelper.checkPermissions( this, - arrayOf( - Manifest.permission.WRITE_CONTACTS, - Manifest.permission.READ_CONTACTS - ) + requiredPermissions ) val viewModelProvider = ViewModelProvider(this) themeModel = viewModelProvider.get() } + + private fun requestDefaultSMSApp(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val roleManager = context.getSystemService(RoleManager::class.java) + if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) { + if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) { + getSmsPermissions(context) + } else { + val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS) + context.startActivity(intent) + } + } + } else { + if (Telephony.Sms.getDefaultSmsPackage(context) == context.packageName) { + getSmsPermissions(context) + } else { + val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT) + intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName) + context.startActivity(intent) + } + } + } + + private fun getSmsPermissions(context: Context) { + PermissionHelper.checkPermissions(context, smsPermissions) + } + + companion object { + private val smsPermissions = arrayOf( + Manifest.permission.SEND_SMS, + Manifest.permission.READ_SMS, + Manifest.permission.RECEIVE_SMS, + Manifest.permission.READ_PHONE_STATE + ) + private val contactPermissions = arrayOf( + Manifest.permission.WRITE_CONTACTS, + Manifest.permission.READ_CONTACTS + ) + } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt index 393f2f27..8e5ffc65 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt @@ -8,13 +8,10 @@ import android.provider.ContactsContract.Intents import android.provider.ContactsContract.QuickContact import android.util.Log import androidx.activity.compose.setContent -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.get import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.dialogs.AddToContactDialog import com.bnyro.contacts.ui.models.ContactsModel -import com.bnyro.contacts.ui.models.SmsModel import com.bnyro.contacts.ui.screens.MainAppContent import com.bnyro.contacts.ui.theme.ConnectYouTheme import com.bnyro.contacts.util.BackupHelper @@ -34,12 +31,11 @@ class MainActivity : BaseActivity() { contactsModel.initialContactData = getInsertContactData() handleVcfShareAction(contactsModel) - smsModel = ViewModelProvider(this).get() - smsModel?.initialAddressAndBody = getInitialSmsAddressAndBody() + smsModel.initialAddressAndBody = getInitialSmsAddressAndBody() setContent { ConnectYouTheme(themeModel.themeMode) { - MainAppContent(smsModel!!) + MainAppContent(smsModel) getInsertOrEditNumber()?.let { AddToContactDialog(it) } @@ -79,6 +75,7 @@ class MainActivity : BaseActivity() { ) ) } + intent?.getStringExtra("action") == "create" -> ContactData() else -> null } @@ -125,13 +122,4 @@ class MainActivity : BaseActivity() { contactsModel.importVcf(this, it) } } - - override fun onDestroy() { - super.onDestroy() - smsModel = null - } - - companion object { - var smsModel: SmsModel? = null - } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt b/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt index df963e44..3f471754 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt @@ -1,129 +1,72 @@ package com.bnyro.contacts.ui.models -import android.Manifest import android.annotation.SuppressLint -import android.app.role.RoleManager import android.content.Context -import android.content.Intent -import android.os.Build -import android.provider.Telephony import android.telephony.SubscriptionInfo import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY import androidx.lifecycle.viewModelScope -import com.bnyro.contacts.db.obj.SmsData -import com.bnyro.contacts.util.NotificationHelper -import com.bnyro.contacts.util.PermissionHelper +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.bnyro.contacts.App import com.bnyro.contacts.util.SmsUtil import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -class SmsModel : ViewModel() { - val smsList = mutableStateListOf() - val smsGroups = mutableStateMapOf>() +class SmsModel(val app: App) : ViewModel() { + var smsList = app.smsRepo.getSmsStream(context = app.applicationContext).stateIn( + viewModelScope, + started = SharingStarted.WhileSubscribed(5000L), + initialValue = listOf() + ) var initialAddressAndBody by mutableStateOf?>(null) var currentSubscription: SubscriptionInfo? = null - fun fetchSmsList(context: Context) { - val requiredPermissions = smsPermissions + NotificationHelper.notificationPermissions - if (!PermissionHelper.checkPermissions(context, requiredPermissions)) return - - requestDefaultSMSApp(context) - - viewModelScope.launch { - val tempSmsList = withContext(Dispatchers.IO) { - SmsUtil.getSmsList(context) - } - - smsList.clear() - smsGroups.clear() - - smsList.addAll(tempSmsList) - val groups = smsList.groupBy { it.threadId } - .map { (threadId, smsList) -> threadId to smsList.toMutableList() } - smsGroups.putAll(groups) - } - } - - fun addSmsToList(sms: SmsData) { - smsList.add(sms) - if (smsGroups.containsKey(sms.threadId)) { - smsGroups[sms.threadId]?.add(sms) - } else { - smsGroups[sms.threadId] = mutableListOf(sms) - } + private fun updateSmsList() { + smsList = app.smsRepo.getSmsStream(context = app.applicationContext).stateIn( + viewModelScope, + started = SharingStarted.WhileSubscribed(5000L), + initialValue = listOf() + ) } fun deleteSms(context: Context, id: Long, threadId: Long) { - smsList.removeAll { it.id == id } - smsGroups[threadId]?.removeAll { it.id == id } viewModelScope.launch(Dispatchers.IO) { - SmsUtil.deleteMessage(context, id) + app.smsRepo.deleteSms(context, id) } } fun deleteThread(context: Context, threadId: Long) { - smsList.removeAll { it.threadId == threadId } - smsGroups.remove(threadId) viewModelScope.launch(Dispatchers.IO) { - SmsUtil.deleteThread(context, threadId) + app.smsRepo.deleteThread(context, threadId) } } @SuppressLint("NewApi") fun sendSms(context: Context, address: String, body: String) { - viewModelScope.launch { - val sms = withContext(Dispatchers.IO) { - SmsUtil.sendSms(context, address, body, currentSubscription?.subscriptionId) - } ?: return@launch - addSmsToList(sms) - } - } - - private fun requestDefaultSMSApp(context: Context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val roleManager = context.getSystemService(RoleManager::class.java) - if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) { - if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) { - getSmsPermissions(context) - } else { - val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS) - context.startActivity(intent) - } - } - } else { - if (Telephony.Sms.getDefaultSmsPackage(context) == context.packageName) { - getSmsPermissions(context) - } else { - val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT) - intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName) - context.startActivity(intent) - } + viewModelScope.launch(Dispatchers.IO) { + SmsUtil.sendSms(context, address, body, currentSubscription?.subscriptionId) } } - private fun getSmsPermissions(context: Context) { - PermissionHelper.checkPermissions(context, smsPermissions) - } - - fun refreshLocalSmsPreference(context: Context) { - SmsUtil.initSmsRepo() - fetchSmsList(context) + fun refreshLocalSmsPreference() { + app.initSmsRepo() + updateSmsList() } companion object { - private val smsPermissions = arrayOf( - Manifest.permission.SEND_SMS, - Manifest.permission.READ_SMS, - Manifest.permission.RECEIVE_SMS, - Manifest.permission.READ_PHONE_STATE - ) + val Factory = viewModelFactory { + initializer { + val application = this[APPLICATION_KEY] as App + SmsModel(application) + } + } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt index c5cffc3d..eebeb715 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt @@ -100,7 +100,7 @@ fun SettingsScreen(onDismissRequest: () -> Unit) { title = stringResource(R.string.private_sms_database), summary = stringResource(R.string.private_sms_database_desc) ) { - smsModel.refreshLocalSmsPreference(context) + smsModel.refreshLocalSmsPreference() } Divider( modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt index 27fec6b7..7e8a3b9b 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt @@ -33,7 +33,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.rememberDismissState import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -67,10 +67,6 @@ fun SmsListScreen(smsModel: SmsModel, contactsModel: ContactsModel) { mutableStateOf(null) } - LaunchedEffect(Unit) { - smsModel.fetchSmsList(context) - } - Scaffold(floatingActionButton = { FloatingActionButton( onClick = { @@ -80,13 +76,14 @@ fun SmsListScreen(smsModel: SmsModel, contactsModel: ContactsModel) { Icon(Icons.Default.Edit, null) } }) { pv -> - if (smsModel.smsList.isNotEmpty()) { + val smsList by smsModel.smsList.collectAsState() + if (smsList.isNotEmpty()) { LazyColumn( modifier = Modifier .fillMaxSize() .padding(pv) ) { - val smsGroups = smsModel.smsGroups.entries.toList() + val smsGroups = smsList.groupBy { it.threadId }.toList() .sortedBy { (_, smsList) -> smsList.maxOf { it.timestamp } } .reversed() @@ -180,7 +177,7 @@ fun SmsListScreen(smsModel: SmsModel, contactsModel: ContactsModel) { Spacer(modifier = Modifier.height(3.dp)) Text( // safe call to avoid crashes when re-rendering - text = smsList.firstOrNull()?.body.orEmpty(), + text = smsList.lastOrNull()?.body.orEmpty(), maxLines = 2, fontSize = 14.sp, lineHeight = 18.sp diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index f8af039c..04e66684 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -61,9 +62,8 @@ fun SmsThreadScreen( ) { val context = LocalContext.current - val threadId = smsModel.smsList.firstOrNull { it.address == address }?.threadId - val smsList = - threadId?.let { smsModel.smsGroups[threadId]?.sortedBy { it.timestamp } } ?: listOf() + val allSmsList by smsModel.smsList.collectAsState() + val smsList = allSmsList.filter { it.address == address } val subscriptions = remember { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { SmsUtil.getSubscriptions(context) @@ -209,7 +209,7 @@ fun SmsThreadScreen( showContactScreen = false } } - + if (showAddToContactDialog) { AddToContactDialog(newNumber = address) } diff --git a/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt b/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt index 19d5625d..9c70dcf6 100644 --- a/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt +++ b/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt @@ -7,13 +7,12 @@ import android.provider.Telephony import android.telephony.SmsManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager +import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi +import com.bnyro.contacts.App import com.bnyro.contacts.R import com.bnyro.contacts.db.obj.SmsData -import com.bnyro.contacts.repo.DeviceSmsRepo -import com.bnyro.contacts.repo.LocalSmsRepo -import com.bnyro.contacts.repo.SmsRepository import java.lang.Character.UnicodeBlock import java.util.Calendar @@ -21,18 +20,6 @@ object SmsUtil { const val MAX_CHAR_LIMIT = 160 private const val MAX_CHAR_LIMIT_WITH_UNICODE = 70 - lateinit var smsRepo: SmsRepository - - fun initSmsRepo() { - smsRepo = if (Preferences.getBoolean(Preferences.storeSmsLocallyKey, false)) { - LocalSmsRepo() - } else { - DeviceSmsRepo() - } - } - - suspend fun getSmsList(context: Context) = smsRepo.getSmsList(context) - private fun getSmsManager(subscriptionId: Int?): SmsManager { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 && subscriptionId != null) { @Suppress("DEPRECATION") @@ -56,49 +43,32 @@ object SmsUtil { address: String, body: String, subscriptionId: Int? = null - ): SmsData? { + ) { + Log.d("Send SMS", body) if (!ConnectionHelper.hasSignalForSms(context)) { Toast.makeText(context, R.string.connection_error, Toast.LENGTH_LONG).show() - return null + return } getSmsManager(subscriptionId) .sendTextMessage(address, null, body, null, null) + val smsRepo = (context.applicationContext as App).smsRepo val timestamp = Calendar.getInstance().timeInMillis - val threadId = getOrCreateThreadId(context, address) + val threadId = + smsRepo.getOrCreateThreadId(context, address) val smsData = SmsData( - -1, + 0, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_SENT ) - persistMessage(context, smsData) - - return smsData + smsRepo.persistSms(context, smsData) } - suspend fun deleteMessage(context: Context, id: Long) = smsRepo.deleteSms(context, id) - - suspend fun deleteThread(context: Context, threadId: Long) = smsRepo.deleteThread( - context, - threadId - ) - - suspend fun persistMessage(context: Context, smsData: SmsData) = smsRepo.persistSms( - context, - smsData - ) - - suspend fun getOrCreateThreadId(context: Context, address: String) = - smsRepo.getOrCreateThreadId( - context, - address - ) - fun isShortEnoughForSms(text: String): Boolean { if (text.length > MAX_CHAR_LIMIT) return false