diff --git a/build.gradle b/build.gradle index e3706c381..c73ebcd77 100644 --- a/build.gradle +++ b/build.gradle @@ -1,31 +1,41 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { - ext.kotlin_version = '1.4.10' + ext { + sdkVersion = 33 + minSdkVersion = 25 + hiltVersion = "2.44" + kotlinVersion = "1.8.0" + coreKtxVersion = "1.9.0" + appCompatVersion = "1.6.0" + } repositories { google() jcenter() mavenCentral() - maven { url 'https://jitpack.io' } + maven { url "https://jitpack.io" } } dependencies { - classpath 'com.android.tools.build:gradle:7.0.4' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" - classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1' + classpath "com.android.tools.build:gradle:7.4.0" + classpath "com.google.dagger:hilt-android-gradle-plugin:2.44.2" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}" } } +plugins { + id 'com.google.dagger.hilt.android' version '2.44' apply false +} + allprojects { repositories { google() jcenter() mavenCentral() - maven { url 'https://jitpack.io' } + maven { url "https://jitpack.io" } } } -task clean(type: Delete) { +tasks.register("clean") { delete rootProject.buildDir } diff --git a/chooloolib/build.gradle b/chooloolib/build.gradle index b7b160c55..43a5af0c4 100644 --- a/chooloolib/build.gradle +++ b/chooloolib/build.gradle @@ -1,20 +1,18 @@ plugins { - id 'com.android.library' - id 'kotlin-android' - id 'kotlin-kapt' - id 'dagger.hilt.android.plugin' + id "com.android.library" + id "kotlin-android" + id "kotlin-kapt" + id 'com.google.dagger.hilt.android' + id "org.jetbrains.kotlin.android" } android { - compileSdk 31 + namespace "com.chooloo.www.chooloolib" + compileSdk rootProject.ext.sdkVersion defaultConfig { - versionCode 2 - versionName "2.0" - - minSdk 25 - targetSdk 31 - + targetSdk rootProject.ext.sdkVersion + minSdk rootProject.ext.minSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } @@ -22,71 +20,69 @@ android { buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" } } - dataBinding { - //noinspection DataBindingWithoutKapt - enabled = true - } - - buildFeatures { - viewBinding true - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_18 + targetCompatibility JavaVersion.VERSION_18 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - - hilt { - enableAggregatingTask true + jvmTarget = JavaVersion.VERSION_18.toString() } - kapt { - correctErrorTypes true - } + kotlin { jvmToolchain(18) } + dataBinding { enabled true } + kapt { correctErrorTypes true } + buildFeatures { viewBinding true } + buildFeatures { viewBinding true } + hilt { enableAggregatingTask true } } dependencies { kapt( - 'com.google.dagger:hilt-compiler:2.38.1', - 'androidx.hilt:hilt-compiler:1.0.0-alpha01' + "androidx.hilt:hilt-compiler:1.0.0-alpha01", + "com.google.dagger:hilt-compiler:${rootProject.ext.hiltVersion}" ) testImplementation( - 'junit:junit:', - 'androidx.room:room-testing:2.2.6' + "junit:junit:", + "androidx.room:room-testing:2.2.6" ) androidTestImplementation( - 'androidx.test:runner:1.3.0', - 'androidx.test.ext:junit:1.1.3', + "androidx.test:runner:1.3.0", + "androidx.test.ext:junit:1.1.3", "androidx.compose.ui:ui-test-junit4:1.0.4", - 'androidx.test.espresso:espresso-core:3.4.0' + "androidx.test.espresso:espresso-core:3.4.0" ) implementation( - 'com.squareup.picasso:picasso:2.5.2', - 'androidx.core:core-ktx:1.7.0', - 'dev.sasikanth:colorsheet:1.0.1', - 'androidx.appcompat:appcompat:1.4.1', - 'io.reactivex.rxjava2:rxjava:2.1.16', - 'androidx.preference:preference:1.1.1', - 'androidx.fragment:fragment-ktx:1.1.0', - 'androidx.activity:activity-ktx:1.1.0', - 'io.github.l4digital:fastscroll:2.0.1', - 'io.reactivex.rxjava2:rxandroid:2.0.1', - 'com.google.dagger:hilt-android:2.38.1', - 'com.google.android.material:material:1.6.0', - 'com.github.abdularis:circularimageview:1.5', - 'com.daimajia.androidanimations:library:2.4@aar', - 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0', - 'com.pushtorefresh.storio3:content-resolver:3.0.0' + // ktx + "androidx.fragment:fragment-ktx:1.5.5", + "androidx.activity:activity-ktx:1.6.1", + "androidx.preference:preference-ktx:1.2.0", + "androidx.navigation:navigation-ui-ktx:2.5.3", + "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1", + "androidx.navigation:navigation-fragment-ktx:2.5.3", + "androidx.core:core-ktx:${rootProject.ext.coreKtxVersion}", + + "androidx.compose.ui:ui:1.3.3", + "dev.sasikanth:colorsheet:1.0.1", + "com.squareup.picasso:picasso:2.5.2", + "io.reactivex.rxjava2:rxjava:2.1.16", + "androidx.preference:preference:1.1.1", + "io.github.l4digital:fastscroll:2.0.1", + "io.reactivex.rxjava2:rxandroid:2.0.1", + "com.google.android.material:material:1.6.0", + "com.github.abdularis:circularimageview:1.5", + "com.daimajia.androidanimations:library:2.4@aar", + "com.pushtorefresh.storio3:content-resolver:3.0.0", + "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4", + "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9", + "com.google.dagger:hilt-android:${rootProject.ext.hiltVersion}", + "androidx.appcompat:appcompat:${rootProject.ext.appCompatVersion}", ) } \ No newline at end of file diff --git a/chooloolib/src/main/AndroidManifest.xml b/chooloolib/src/main/AndroidManifest.xml index a88a88862..d1d125641 100644 --- a/chooloolib/src/main/AndroidManifest.xml +++ b/chooloolib/src/main/AndroidManifest.xml @@ -1,23 +1,28 @@ + xmlns:tools="http://schemas.android.com/tools"> - + - - - - + android:exported="true" + android:noHistory="true" + android:screenOrientation="unspecified" + android:showOnLockScreen="true"> + + + + + @@ -30,7 +35,7 @@ android:resource="@array/preloaded_fonts" /> diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/BaseApp.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ChoolooApp.kt similarity index 92% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/BaseApp.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/ChoolooApp.kt index f006ccec1..b02b9b4f9 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/BaseApp.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ChoolooApp.kt @@ -5,7 +5,7 @@ import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractor import javax.inject.Inject -abstract class BaseApp : Application() { +abstract class ChoolooApp : Application() { @Inject lateinit var themes: ThemesInteractor @Inject lateinit var preferences: PreferencesInteractor diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/AccountsAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/AccountsAdapter.kt index f0481f55e..92c41dc3d 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/AccountsAdapter.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/AccountsAdapter.kt @@ -3,8 +3,8 @@ package com.chooloo.www.chooloolib.adapter import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor import com.chooloo.www.chooloolib.interactor.string.StringsInteractor -import com.chooloo.www.chooloolib.model.ListData -import com.chooloo.www.chooloolib.model.RawContactAccount +import com.chooloo.www.chooloolib.data.model.ListData +import com.chooloo.www.chooloolib.data.model.RawContactAccount import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ListItemHolder import javax.inject.Inject @@ -12,7 +12,11 @@ class AccountsAdapter @Inject constructor( animations: AnimationsInteractor, private val strings: StringsInteractor ) : ListAdapter(animations) { - override fun onBindListItem(listItemHolder: ListItemHolder, item: RawContactAccount) { + override fun onBindListItem( + listItemHolder: ListItemHolder, + item: RawContactAccount, + position: Int + ) { listItemHolder.apply { captionText = null isImageVisible = false @@ -25,6 +29,6 @@ class AccountsAdapter @Inject constructor( } } - override fun convertDataToListData(data: List) = - ListData.fromRawContacts(data, accounts = true, withHeader = false) + override fun convertDataToListData(items: List) = + ListData.fromRawContacts(items, accounts = true, withHeader = false) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/CallActionsAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/CallActionsAdapter.kt new file mode 100644 index 000000000..d620f0d42 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/CallActionsAdapter.kt @@ -0,0 +1,89 @@ +package com.chooloo.www.chooloolib.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import com.chooloo.www.chooloolib.databinding.CallActionBinding +import com.chooloo.www.chooloolib.ui.callactions.CallAction + +class CallActionsAdapter : RecyclerView.Adapter() { + private var _callActions: MutableList = mutableListOf() + private var _onCallActionClickListener: (CallAction) -> Unit = {} + + + override fun getItemCount() = _callActions.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + ViewHolder(CallActionBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) = + holder.bind(_callActions[position], _onCallActionClickListener::invoke) + + + private fun getCallAction(@StringRes idRes: Int): CallAction? { + val index = _callActions.indexOfFirst { it.idRes == idRes } + if (index == -1) return null + return _callActions[index] + } + + private fun changeCallAction(@StringRes idRes: Int, change: (CallAction) -> Unit) { + val index = _callActions.indexOfFirst { it.idRes == idRes } + if (index == -1) return + change.invoke(_callActions[index]) + notifyItemChanged(index) + } + + fun isCallActionEnabled(@StringRes idRes: Int) = getCallAction(idRes)?.isEnabled ?: false + + fun isCallActionActivated(@StringRes idRes: Int) = getCallAction(idRes)?.isActivated ?: false + + fun setCallActionEnabled(@StringRes idRes: Int, isEnabled: Boolean) { + changeCallAction(idRes) { it.isEnabled = isEnabled } + } + + fun setCallActionActivated(@StringRes idRes: Int, isActivated: Boolean) { + changeCallAction(idRes) { it.isActivated = isActivated } + } + + fun setCallActionIcon(@StringRes idRes: Int, @DrawableRes iconRes: Int) { + changeCallAction(idRes) { it.tempIconRes = iconRes } + } + + fun addCallActions(callActions: List) { + for (callAction in callActions) { + if (_callActions.contains(callAction)) continue + _callActions.add(callAction) + notifyItemChanged(_callActions.indexOf(callAction)) + } + } + + fun removeCallActions(callActions: List) { + for (callAction in callActions) { + if (!_callActions.contains(callAction)) continue + val removedIndex = _callActions.indexOf(callAction) + _callActions.remove(callAction) + notifyItemRemoved(removedIndex) + } + } + + fun setOnCallActionClickListener(onCallActionClickListener: (CallAction) -> Unit = {}) { + _onCallActionClickListener = onCallActionClickListener + } + + + class ViewHolder(private val binding: CallActionBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(callAction: CallAction, onClickListener: (CallAction) -> Unit = {}) { + binding.callActionRoot.apply { + setDefaultIcon(callAction.tempIconRes ?: callAction.iconRes) + callAction.checkedIconRes?.let(::setCheckedIcon) + isEnabled = callAction.isEnabled + isActivated = callAction.isActivated + setOnClickListener { onClickListener.invoke(callAction) } + } + } + } +} diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/CallItemsAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/CallItemsAdapter.kt index 143bf071c..08430ce1a 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/CallItemsAdapter.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/CallItemsAdapter.kt @@ -3,33 +3,41 @@ package com.chooloo.www.chooloolib.adapter import android.net.Uri import android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.data.model.Call +import com.chooloo.www.chooloolib.data.model.ListData +import com.chooloo.www.chooloolib.di.module.IoScope +import com.chooloo.www.chooloolib.di.module.MainScope import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor -import com.chooloo.www.chooloolib.interactor.color.ColorsInteractor -import com.chooloo.www.chooloolib.interactor.drawable.DrawablesInteractor import com.chooloo.www.chooloolib.interactor.phoneaccounts.PhonesInteractor -import com.chooloo.www.chooloolib.model.Call -import com.chooloo.www.chooloolib.model.ListData import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ListItemHolder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject class CallItemsAdapter @Inject constructor( animationsInteractor: AnimationsInteractor, + @IoScope private val ioScope: CoroutineScope, private val phonesInteractor: PhonesInteractor, + @MainScope private val mainScope: CoroutineScope, ) : ListAdapter(animationsInteractor) { - override fun onBindListItem(listItemHolder: ListItemHolder, item: Call) { + override fun onBindListItem(listItemHolder: ListItemHolder, item: Call, position: Int) { listItemHolder.apply { - phonesInteractor.lookupAccount(item.number) { account -> - account?.photoUri?.let { - setImageUri(Uri.parse(it)) - } ?: run { - setImageResource(R.drawable.person) - } + ioScope.launch { + val account = phonesInteractor.lookupAccount(item.number) + + mainScope.launch { + account?.photoUri?.let { + setImageUri(Uri.parse(it)) + } ?: run { + setImageResource(R.drawable.person) + } - account?.displayString?.let { - titleText = it - captionText = item.number - } ?: run { - titleText = item.number + account?.displayString?.let { + titleText = it + captionText = item.number + } ?: run { + titleText = item.number + } } } @@ -40,9 +48,9 @@ class CallItemsAdapter @Inject constructor( setLeftButtonIcon(R.drawable.call_split) setRightButtonIcon(R.drawable.call_end) - setRightButtonIconTint(R.color.negative_foreground) + setRightButtonIconTint(R.color.negative) } } - override fun convertDataToListData(data: List) = ListData(data) + override fun convertDataToListData(items: List) = ListData(items) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ChoicesAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ChoicesAdapter.kt index 271cfb318..35a8d559b 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ChoicesAdapter.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ChoicesAdapter.kt @@ -3,9 +3,9 @@ package com.chooloo.www.chooloolib.adapter import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup +import com.chooloo.www.chooloolib.data.model.ListData import com.chooloo.www.chooloolib.databinding.ListItemBinding import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor -import com.chooloo.www.chooloolib.model.ListData import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ChoiceItemHolder import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ListItemHolder import javax.inject.Inject @@ -15,6 +15,7 @@ class ChoicesAdapter @Inject constructor( animations: AnimationsInteractor, ) : ListAdapter(animations) { private var _selectedIndex: Int? = null + private var _onChoiceSelectedListener: (String) -> Boolean = { true } var selectedIndex: Int? get() = _selectedIndex @@ -26,14 +27,23 @@ class ChoicesAdapter @Inject constructor( ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) ) - override fun onBindListItem(listItemHolder: ListItemHolder, item: String) { - listItemHolder.titleText = item - selectedIndex?.let { - if (items[it] == item) { - listItemHolder.setSelected() + override fun onBindListItem(listItemHolder: ListItemHolder, item: String, position: Int) { + listItemHolder.apply { + titleText = item + (this as ChoiceItemHolder).setSelected(selectedIndex == position) + setOnClickListener { + if (_onChoiceSelectedListener.invoke(item)) { + selectedIndex?.let(::notifyItemChanged) + selectedIndex = position + notifyItemChanged(position) + } } } } - override fun convertDataToListData(data: List) = ListData(data) + override fun convertDataToListData(items: List) = ListData(items) + + fun setOnChoiceSelectedListener(onChoiceSelectedListener: (String) -> Boolean) { + _onChoiceSelectedListener = onChoiceSelectedListener + } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ContactsAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ContactsAdapter.kt index f71a8ded4..1ea35864e 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ContactsAdapter.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ContactsAdapter.kt @@ -1,19 +1,24 @@ package com.chooloo.www.chooloolib.adapter import android.net.Uri +import com.chooloo.www.chooloolib.data.model.ContactAccount +import com.chooloo.www.chooloolib.data.model.ListData +import com.chooloo.www.chooloolib.di.module.IoScope +import com.chooloo.www.chooloolib.di.module.MainScope import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor import com.chooloo.www.chooloolib.interactor.phoneaccounts.PhonesInteractor -import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor -import com.chooloo.www.chooloolib.model.ContactAccount -import com.chooloo.www.chooloolib.model.ListData import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ListItemHolder import com.chooloo.www.chooloolib.util.initials +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject open class ContactsAdapter @Inject constructor( animations: AnimationsInteractor, private val phones: PhonesInteractor, + @IoScope private val ioScope: CoroutineScope, + @MainScope private val mainScope: CoroutineScope ) : ListAdapter(animations) { private var _withFavs: Boolean = true private var _withHeaders: Boolean = true @@ -31,12 +36,20 @@ open class ContactsAdapter @Inject constructor( } - override fun onBindListItem(listItemHolder: ListItemHolder, item: ContactAccount) { + override fun onBindListItem( + listItemHolder: ListItemHolder, + item: ContactAccount, + position: Int + ) { listItemHolder.apply { titleText = item.name imageInitials = item.name?.initials() - phones.getContactAccounts(item.id) { - captionText = it?.firstOrNull()?.number + + ioScope.launch { + val number = phones.getContactAccounts(item.id).firstOrNull()?.number + mainScope.launch { + captionText = number + } } setImageUri(if (item.photoUri != null) Uri.parse(item.photoUri) else null) diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ListAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ListAdapter.kt index c5202ed64..39d334a8e 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ListAdapter.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/ListAdapter.kt @@ -1,11 +1,12 @@ package com.chooloo.www.chooloolib.adapter +import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView +import com.chooloo.www.chooloolib.data.model.ListData import com.chooloo.www.chooloolib.databinding.ListItemBinding import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor -import com.chooloo.www.chooloolib.model.ListData import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ListItemHolder import com.l4digital.fastscroll.FastScroller @@ -28,6 +29,7 @@ abstract class ListAdapter( var items: List get() = _data.items + @SuppressLint("NotifyDataSetChanged") set(value) { _data = convertDataToListData(value) notifyDataSetChanged() @@ -53,9 +55,9 @@ abstract class ListAdapter( true } - animations.show(itemView, false) +// animations.show(itemView, false) - onBindListItem(this, dataItem) + onBindListItem(this, dataItem, position) } } @@ -105,5 +107,5 @@ abstract class ListAdapter( open fun convertDataToListData(items: List) = ListData(items) - abstract fun onBindListItem(listItemHolder: ListItemHolder, item: ItemType) + abstract fun onBindListItem(listItemHolder: ListItemHolder, item: ItemType, position: Int) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/MenuAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/MenuAdapter.kt index afe242bf5..2c9f60ed7 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/MenuAdapter.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/MenuAdapter.kt @@ -6,9 +6,9 @@ import android.os.Build.VERSION_CODES import android.view.LayoutInflater import android.view.MenuItem import android.view.ViewGroup +import com.chooloo.www.chooloolib.data.model.ListData import com.chooloo.www.chooloolib.databinding.ListItemBinding import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor -import com.chooloo.www.chooloolib.model.ListData import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ListItemHolder import com.chooloo.www.chooloolib.ui.widgets.listitemholder.MenuItemHolder import javax.inject.Inject @@ -21,7 +21,7 @@ class MenuAdapter @Inject constructor( ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) ) - override fun onBindListItem(listItemHolder: ListItemHolder, item: MenuItem) { + override fun onBindListItem(listItemHolder: ListItemHolder, item: MenuItem, position: Int) { listItemHolder.apply { isClickable = item.isEnabled titleText = item.title.toString() @@ -29,7 +29,7 @@ class MenuAdapter @Inject constructor( captionText = item.contentDescription?.toString() } - setImageDrawable(item.icon) + item.icon?.let(::setImageDrawable) } } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/PhonesAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/PhonesAdapter.kt index cefcab14b..7bd6a8aab 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/PhonesAdapter.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/PhonesAdapter.kt @@ -3,9 +3,9 @@ package com.chooloo.www.chooloolib.adapter import android.content.Context import android.provider.ContactsContract.CommonDataKinds.Phone import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.data.model.ListData +import com.chooloo.www.chooloolib.data.model.PhoneAccount import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor -import com.chooloo.www.chooloolib.model.ListData -import com.chooloo.www.chooloolib.model.PhoneAccount import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ListItemHolder import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @@ -14,7 +14,7 @@ class PhonesAdapter @Inject constructor( animationsInteractor: AnimationsInteractor, @ApplicationContext private val context: Context ) : ListAdapter(animationsInteractor) { - override fun onBindListItem(listItemHolder: ListItemHolder, item: PhoneAccount) { + override fun onBindListItem(listItemHolder: ListItemHolder, item: PhoneAccount, position: Int) { listItemHolder.apply { isImageVisible = false titleText = item.number @@ -26,6 +26,6 @@ class PhonesAdapter @Inject constructor( } } - override fun convertDataToListData(data: List) = - ListData.fromPhones(data, withHeader = false) + override fun convertDataToListData(items: List) = + ListData.fromPhones(items, withHeader = false) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/RecentsAdapter.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/RecentsAdapter.kt index 4103f006e..9842c1225 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/RecentsAdapter.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/adapter/RecentsAdapter.kt @@ -4,18 +4,20 @@ import android.content.Context import android.net.Uri import android.provider.ContactsContract.CommonDataKinds.Phone import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.data.model.ListData +import com.chooloo.www.chooloolib.data.model.RecentAccount +import com.chooloo.www.chooloolib.di.module.IoScope +import com.chooloo.www.chooloolib.di.module.MainScope import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor import com.chooloo.www.chooloolib.interactor.drawable.DrawablesInteractor -import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import com.chooloo.www.chooloolib.interactor.phoneaccounts.PhonesInteractor -import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor import com.chooloo.www.chooloolib.interactor.recents.RecentsInteractor -import com.chooloo.www.chooloolib.model.ListData -import com.chooloo.www.chooloolib.model.RecentAccount import com.chooloo.www.chooloolib.ui.widgets.listitemholder.ListItemHolder import com.chooloo.www.chooloolib.util.getHoursString import com.chooloo.www.chooloolib.util.initials import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject class RecentsAdapter @Inject constructor( @@ -23,8 +25,8 @@ class RecentsAdapter @Inject constructor( private val phones: PhonesInteractor, private val recents: RecentsInteractor, private val drawables: DrawablesInteractor, - private val preferences: PreferencesInteractor, - private val permissions: PermissionsInteractor, + @IoScope private val ioScope: CoroutineScope, + @MainScope private val mainScope: CoroutineScope, @ApplicationContext private val context: Context ) : ListAdapter(animations) { @@ -37,27 +39,35 @@ class RecentsAdapter @Inject constructor( } - override fun onBindListItem(listItemHolder: ListItemHolder, item: RecentAccount) { + override fun onBindListItem( + listItemHolder: ListItemHolder, + item: RecentAccount, + position: Int + ) { listItemHolder.apply { val date = context.getHoursString(item.date) captionText = if (item.groupCount > 1) "(${item.groupCount}) $date ·" else "$date ·" - phones.lookupAccount(item.number) { - titleText = it?.name ?: item.number - setImageUri(if (it?.photoUri != null) Uri.parse(it.photoUri) else null) - it?.let { - captionText = - "$captionText ${ - Phone.getTypeLabel( - context.resources, - it.type, - it.label - ) - } ·" - imageInitials = it.name?.initials() - if (it.name == null || it.name.isEmpty()) { - drawables.getDrawable(R.drawable.person)?.let(this::setImageDrawable) + ioScope.launch { + val account = phones.lookupAccount(item.number) + + mainScope.launch { + titleText = account?.name ?: item.cachedName ?: item.number + setImageUri(account?.photoUri?.let(Uri::parse)) + account?.let { + captionText = + "$captionText ${ + Phone.getTypeLabel( + context.resources, + it.type, + it.label + ) + } ·" + imageInitials = it.name?.initials() ?: it.number?.getOrNull(0).toString() + if (it.name.isNullOrEmpty()) { + drawables.getDrawable(R.drawable.person)?.let(::setImageDrawable) + } } } } @@ -66,6 +76,6 @@ class RecentsAdapter @Inject constructor( } } - override fun convertDataToListData(data: List) = - ListData.fromRecents(data, groupSimilar) + override fun convertDataToListData(items: List) = + ListData.fromRecents(items, groupSimilar) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/service/CallService.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/api/service/CallService.kt similarity index 66% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/service/CallService.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/api/service/CallService.kt index da689856a..cfeb9e8ce 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/service/CallService.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/api/service/CallService.kt @@ -1,30 +1,36 @@ -package com.chooloo.www.chooloolib.service +package com.chooloo.www.chooloolib.api.service import android.annotation.SuppressLint import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.telecom.CallAudioState import android.telecom.InCallService -import androidx.lifecycle.MutableLiveData import com.chooloo.www.chooloolib.interactor.callaudio.CallAudiosInteractor import com.chooloo.www.chooloolib.interactor.calls.CallsInteractor -import com.chooloo.www.chooloolib.model.Call +import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor +import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.IncomingCallMode +import com.chooloo.www.chooloolib.data.model.Call import com.chooloo.www.chooloolib.notification.CallNotification -import com.chooloo.www.chooloolib.repository.calls.CallsRepository +import com.chooloo.www.chooloolib.data.repository.calls.CallsRepository import com.chooloo.www.chooloolib.ui.call.CallActivity import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import javax.inject.Inject @SuppressLint("NewApi") @AndroidEntryPoint class CallService : InCallService() { + private val _calls = MutableStateFlow>(arrayListOf()) + + val calls = _calls.asStateFlow() + @Inject lateinit var callAudios: CallAudiosInteractor @Inject lateinit var callsRepository: CallsRepository @Inject lateinit var callsInteractor: CallsInteractor + @Inject lateinit var preferences: PreferencesInteractor @Inject lateinit var callNotification: CallNotification - val calls = MutableLiveData>() - override fun onCreate() { super.onCreate() @@ -34,19 +40,20 @@ class CallService : InCallService() { override fun onDestroy() { callNotification.detach() - callNotification.cancel() super.onDestroy() } override fun onCallAdded(telecomCall: android.telecom.Call) { super.onCallAdded(telecomCall) - addCall(Call(telecomCall)) - callsInteractor.entryAddCall(Call(telecomCall)) - if (!sIsActivityActive) { + val call = Call(telecomCall) + addCall(call) + callsInteractor.entryAddCall(call) + if (!sIsActivityActive && (preferences.incomingCallMode == IncomingCallMode.FULL_SCREEN || call.isDirectionOutgoing)) { startCallActivity() } } + override fun onCallRemoved(telecomCall: android.telecom.Call) { super.onCallRemoved(telecomCall) removeCall(Call(telecomCall)) @@ -66,15 +73,15 @@ class CallService : InCallService() { } private fun addCall(call: Call) { - val list = calls.value?.toMutableList() - list?.add(call) - calls.value = list + val list = calls.value.toMutableList() + list.add(call) + _calls.value = list } private fun removeCall(call: Call) { - val list = calls.value?.toMutableList() - list?.remove(call) - calls.value = list + val list = calls.value.toMutableList() + list.remove(call) + _calls.value = list } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/BaseContentResolver.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/BaseContentResolver.kt deleted file mode 100644 index 97d0df0db..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/BaseContentResolver.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.chooloo.www.chooloolib.contentresolver - -import android.annotation.SuppressLint -import android.content.Context -import android.database.Cursor -import android.net.Uri -import com.pushtorefresh.storio3.contentresolver.impl.DefaultStorIOContentResolver -import com.pushtorefresh.storio3.contentresolver.queries.Query -import io.reactivex.BackpressureStrategy -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers - -abstract class BaseContentResolver(private val context: Context) { - private var _filter: String? = null - - private val _ioContentResolver by lazy { - DefaultStorIOContentResolver.builder().contentResolver(context.contentResolver).build() - } - - private val _query: Query - get() = Query.builder() - .uri(finalUri) - .columns(*projection) - .whereArgs(*(selectionArgs ?: arrayOf())) - .where(if (selection == "") null else selection) - .sortOrder(if (sortOrder == "") null else sortOrder) - .build() - - val finalUri: Uri - get() = if (filterUri != null && _filter?.isNotEmpty() == true) { - Uri.withAppendedPath(filterUri, _filter) - } else { - uri - } - - - open var filter: String? - get() = _filter - set(value) { - _filter = if (value == "") null else value - } - - - private fun queryCursor() = - _ioContentResolver - .get() - .cursor() - .withQuery(_query) - .prepare() - .executeAsBlocking() - - @SuppressLint("CheckResult") - fun queryCursor(callback: (Cursor?) -> Unit): Disposable = - _ioContentResolver - .get() - .cursor() - .withQuery(_query) - .prepare() - .asRxSingle() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(callback::invoke) - - fun queryItems() = convertCursorToItems(queryCursor()) - - fun queryItems(callback: (List) -> Unit): Disposable = - queryCursor { callback.invoke(convertCursorToItems(it)) } - - @SuppressLint("CheckResult") - fun observeUri(observer: () -> Unit): Disposable = - _ioContentResolver - .observeChangesOfUri(finalUri, BackpressureStrategy.LATEST) - .subscribe { observer.invoke() } - .also { observer.invoke() } - - private fun observeCursor(observer: (Cursor?) -> Unit): Disposable = - _ioContentResolver - .get() - .cursor() - .withQuery(_query) - .prepare() - .asRxFlowable(BackpressureStrategy.LATEST) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(observer::invoke) - - fun observeItems(observer: (List) -> Unit) = - observeCursor { observer.invoke(convertCursorToItems(it)) } - - private fun convertCursorToItems(cursor: Cursor?): ArrayList { - val content = ArrayList() - while (cursor != null && cursor.moveToNext()) { - content.add(convertCursorToItem(cursor)) - } - cursor?.close() - return content - } - - abstract val uri: Uri - abstract val filterUri: Uri? - abstract val selection: String? - abstract val sortOrder: String? - abstract val projection: Array - abstract val selectionArgs: Array? - abstract fun convertCursorToItem(cursor: Cursor): ItemType -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/BaseContentResolver.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/BaseContentResolver.kt new file mode 100644 index 000000000..127af4671 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/BaseContentResolver.kt @@ -0,0 +1,91 @@ +package com.chooloo.www.chooloolib.data.contentresolver + +import android.content.ContentResolver +import android.database.ContentObserver +import android.database.Cursor +import android.net.Uri +import com.chooloo.www.chooloolib.di.module.IoDispatcher +import com.pushtorefresh.storio3.contentresolver.queries.Query +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +abstract class BaseContentResolver( + private val contentResolver: ContentResolver, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher +) { + private var _filter: String? = null + + private val _query: Query + get() = Query.builder() + .uri(finalUri) + .columns(*projection) + .whereArgs(*(selectionArgs ?: arrayOf())) + .where(if (selection == "") null else selection) + .sortOrder(if (sortOrder == "") null else sortOrder) + .build() + + val finalUri: Uri + get() = if (filterUri != null && _filter?.isNotEmpty() == true) { + Uri.withAppendedPath(filterUri, _filter) + } else { + uri + } + + open var filter: String? + get() = _filter + set(value) { + _filter = if (value == "") null else value + } + + suspend fun queryCursor() = withContext(ioDispatcher) { + contentResolver.query( + finalUri, + projection, + if (selection == "") null else selection, + selectionArgs ?: arrayOf(), + if (sortOrder == "") null else sortOrder + ) + } + + suspend fun queryItems() = convertCursorToItems(queryCursor()) + + fun getCursorFlow() = callbackFlow { + val callback = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + launch { + send(queryCursor()) + } + } + } + send(queryCursor()) + contentResolver.registerContentObserver(finalUri, false, callback) + awaitClose { contentResolver.unregisterContentObserver(callback) } + } + + fun getItemsFlow() = flow { + getCursorFlow().collect { + emit(convertCursorToItems(it)) + } + } + + private fun convertCursorToItems(cursor: Cursor?): ArrayList { + val content = ArrayList() + while (cursor != null && cursor.moveToNext() && !cursor.isClosed) { + content.add(convertCursorToItem(cursor)) + } + cursor?.close() + return content + } + + abstract val uri: Uri + abstract val filterUri: Uri? + abstract val selection: String? + abstract val sortOrder: String? + abstract val projection: Array + abstract val selectionArgs: Array? + abstract fun convertCursorToItem(cursor: Cursor): ItemType +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/ContactsContentResolver.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/ContactsContentResolver.kt similarity index 76% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/ContactsContentResolver.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/ContactsContentResolver.kt index 857441e81..ec490239e 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/ContactsContentResolver.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/ContactsContentResolver.kt @@ -1,15 +1,21 @@ -package com.chooloo.www.chooloolib.contentresolver +package com.chooloo.www.chooloolib.data.contentresolver import android.annotation.SuppressLint -import android.content.Context +import android.content.ContentResolver import android.database.Cursor import android.net.Uri import android.provider.ContactsContract -import com.chooloo.www.chooloolib.model.ContactAccount +import com.chooloo.www.chooloolib.di.module.IoDispatcher +import com.chooloo.www.chooloolib.data.model.ContactAccount import com.chooloo.www.chooloolib.util.SelectionBuilder +import kotlinx.coroutines.CoroutineDispatcher -class ContactsContentResolver(context: Context, contactId: Long? = null) : - BaseContentResolver(context) { +class ContactsContentResolver( + contactId: Long? = null, + contentResolver: ContentResolver, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher +) : + BaseContentResolver(contentResolver, ioDispatcher) { override val selectionArgs: Array? = null override val uri: Uri = ContactsContract.Contacts.CONTENT_URI diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/PhoneLookupContentResolver.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/PhoneLookupContentResolver.kt similarity index 73% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/PhoneLookupContentResolver.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/PhoneLookupContentResolver.kt index e2a5353d2..1e8ed4750 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/PhoneLookupContentResolver.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/PhoneLookupContentResolver.kt @@ -1,14 +1,20 @@ -package com.chooloo.www.chooloolib.contentresolver +package com.chooloo.www.chooloolib.data.contentresolver import android.annotation.SuppressLint -import android.content.Context +import android.content.ContentResolver import android.database.Cursor import android.net.Uri import android.provider.ContactsContract.PhoneLookup -import com.chooloo.www.chooloolib.model.PhoneLookupAccount +import com.chooloo.www.chooloolib.di.module.IoDispatcher +import com.chooloo.www.chooloolib.data.model.PhoneLookupAccount +import kotlinx.coroutines.CoroutineDispatcher -class PhoneLookupContentResolver(context: Context, number: String?) : - BaseContentResolver(context) { +class PhoneLookupContentResolver( + number: String?, + contentResolver: ContentResolver, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher +) : + BaseContentResolver(contentResolver, ioDispatcher) { override val selection: String? = null override val sortOrder: String? = null diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/PhonesContentResolver.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/PhonesContentResolver.kt similarity index 72% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/PhonesContentResolver.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/PhonesContentResolver.kt index e96cc70ab..3754d4498 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/PhonesContentResolver.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/PhonesContentResolver.kt @@ -1,15 +1,21 @@ -package com.chooloo.www.chooloolib.contentresolver +package com.chooloo.www.chooloolib.data.contentresolver import android.annotation.SuppressLint -import android.content.Context +import android.content.ContentResolver import android.database.Cursor import android.net.Uri import android.provider.ContactsContract.CommonDataKinds.Phone -import com.chooloo.www.chooloolib.model.PhoneAccount +import com.chooloo.www.chooloolib.di.module.IoDispatcher +import com.chooloo.www.chooloolib.data.model.PhoneAccount import com.chooloo.www.chooloolib.util.SelectionBuilder +import kotlinx.coroutines.CoroutineDispatcher -class PhonesContentResolver(context: Context, contactId: Long? = null) : - BaseContentResolver(context) { +class PhonesContentResolver( + contactId: Long? = null, + contentResolver: ContentResolver, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher +) : + BaseContentResolver(contentResolver, ioDispatcher) { override val uri: Uri = Phone.CONTENT_URI override val sortOrder: String? = null diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/RawContactsContentResolver.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/RawContactsContentResolver.kt similarity index 71% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/RawContactsContentResolver.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/RawContactsContentResolver.kt index 8cee9c8d4..08a21e5c3 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/RawContactsContentResolver.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/RawContactsContentResolver.kt @@ -1,14 +1,20 @@ -package com.chooloo.www.chooloolib.contentresolver +package com.chooloo.www.chooloolib.data.contentresolver -import android.content.Context +import android.content.ContentResolver import android.database.Cursor import android.net.Uri import android.provider.ContactsContract.Contacts import androidx.core.database.getStringOrNull -import com.chooloo.www.chooloolib.model.RawContactAccount +import com.chooloo.www.chooloolib.di.module.IoDispatcher +import com.chooloo.www.chooloolib.data.model.RawContactAccount +import kotlinx.coroutines.CoroutineDispatcher -class RawContactsContentResolver(context: Context, contactId: Long) : - BaseContentResolver(context) { +class RawContactsContentResolver( + contactId: Long, + contentResolver: ContentResolver, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher +) : + BaseContentResolver(contentResolver, ioDispatcher) { override val uri: Uri = Uri.withAppendedPath( Uri.withAppendedPath(Contacts.CONTENT_URI, contactId.toString()), Contacts.Entity.CONTENT_DIRECTORY diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/RecentsContentResolver.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/RecentsContentResolver.kt similarity index 78% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/RecentsContentResolver.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/RecentsContentResolver.kt index bbdba2aa1..85270e24e 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/contentresolver/RecentsContentResolver.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/contentresolver/RecentsContentResolver.kt @@ -1,19 +1,22 @@ -package com.chooloo.www.chooloolib.contentresolver +package com.chooloo.www.chooloolib.data.contentresolver import android.annotation.SuppressLint -import android.content.Context +import android.content.ContentResolver import android.database.Cursor import android.net.Uri import android.provider.CallLog -import com.chooloo.www.chooloolib.model.RecentAccount +import com.chooloo.www.chooloolib.di.module.IoDispatcher +import com.chooloo.www.chooloolib.data.model.RecentAccount import com.chooloo.www.chooloolib.util.SelectionBuilder +import kotlinx.coroutines.CoroutineDispatcher import java.util.* class RecentsContentResolver( - context: Context, - private val recentId: Long? = null + private val recentId: Long? = null, + contentResolver: ContentResolver, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) : - BaseContentResolver(context) { + BaseContentResolver(contentResolver, ioDispatcher) { override val uri: Uri = CallLog.Calls.CONTENT_URI override val filterUri: Uri? = null diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/Call.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/Call.kt similarity index 92% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/Call.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/Call.kt index 0fa6a2b1a..619a49161 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/Call.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/Call.kt @@ -1,4 +1,4 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model import android.net.Uri import android.os.Build @@ -6,15 +6,14 @@ import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.Q import android.os.Bundle import android.telecom.Call.* -import android.telecom.Call.Details.CAPABILITY_HOLD -import android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL +import android.telecom.Call.Details.* import android.telecom.Connection import android.telecom.DisconnectCause import android.telecom.PhoneAccountHandle import android.telecom.PhoneAccountSuggestion import androidx.annotation.RequiresApi import com.chooloo.www.chooloolib.R -import com.chooloo.www.chooloolib.model.Call.State.* +import com.chooloo.www.chooloolib.data.model.Call.State.* import com.chooloo.www.chooloolib.util.baseobservable.BaseObservable import java.util.stream.Collectors import java.util.stream.Stream @@ -32,11 +31,13 @@ class Call(telecomCall: android.telecom.Call) : BaseObservable() private var _phoneAccountSelected: Boolean = false private val _id: String = Call::class.java.simpleName + sIdCounter++ private val _call: android.telecom.Call = telecomCall + private val _pastStates = mutableListOf() init { _call.registerCallback(object : android.telecom.Call.Callback() { override fun onStateChanged(call: android.telecom.Call?, state: Int) { invokeListeners { it.onCallChanged(this@Call) } + _pastStates.add(State.fromTelecomState(state)) } override fun onDetailsChanged(call: android.telecom.Call?, details: Details?) { @@ -143,6 +144,20 @@ class Call(telecomCall: android.telecom.Call) : BaseObservable() val isIncoming: Boolean get() = state == INCOMING + val isDirectionIncoming: Boolean + get() = if (SDK_INT >= Q) { + _call.details.callDirection == DIRECTION_INCOMING + } else { + INCOMING in _pastStates + } + + val isDirectionOutgoing: Boolean + get() = if (SDK_INT >= Q) { + _call.details.callDirection == DIRECTION_OUTGOING + } else { + DIALING in _pastStates + } + val children: List get() = _call.children.map(::Call).toList() diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/CallList.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/CallList.kt similarity index 97% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/CallList.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/CallList.kt index 1ead7a643..fec873a63 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/CallList.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/CallList.kt @@ -1,4 +1,4 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model import java.util.* diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/ContactAccount.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/ContactAccount.kt similarity index 89% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/ContactAccount.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/ContactAccount.kt index ea38b8f76..00e4cdc59 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/ContactAccount.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/ContactAccount.kt @@ -1,4 +1,4 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model data class ContactAccount( val id: Long = 0, diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/ListData.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/ListData.kt similarity index 89% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/ListData.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/ListData.kt index 07d0ce77a..c4791051b 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/ListData.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/ListData.kt @@ -1,6 +1,6 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model -import com.chooloo.www.chooloolib.model.RawContactAccount.RawContactType +import com.chooloo.www.chooloolib.data.model.RawContactAccount.RawContactType import com.chooloo.www.chooloolib.util.getRelativeDateString data class ListData( @@ -65,8 +65,11 @@ data class ListData( phones: List, withHeader: Boolean = true ): ListData { - val phones = phones.toList().distinctBy { it.normalizedNumber } - return ListData(phones, mapOf(Pair(if (withHeader) "Phones" else "", phones.size))) + val distinctPhones = phones.toList().distinctBy { it.cleanNumber } + return ListData( + distinctPhones, + mapOf(Pair(if (withHeader) "Phones" else "", distinctPhones.size)) + ) } fun fromRawContacts( diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/PhoneAccount.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/PhoneAccount.kt similarity index 72% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/PhoneAccount.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/PhoneAccount.kt index 893b52d66..989ffb271 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/PhoneAccount.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/PhoneAccount.kt @@ -1,4 +1,4 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model import android.provider.ContactsContract.CommonDataKinds.Phone @@ -9,4 +9,6 @@ data class PhoneAccount( val label: String? = null, val normalizedNumber: String?, val type: Int = Phone.TYPE_OTHER -) +) { + val cleanNumber get() = number.replace("-", "") +} diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/PhoneLookupAccount.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/PhoneLookupAccount.kt similarity index 91% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/PhoneLookupAccount.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/PhoneLookupAccount.kt index b2518e530..16705fe3b 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/PhoneLookupAccount.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/PhoneLookupAccount.kt @@ -1,4 +1,4 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model import android.provider.ContactsContract.CommonDataKinds.Phone diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/RawContactAccount.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/RawContactAccount.kt similarity index 95% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/RawContactAccount.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/RawContactAccount.kt index 5bb4b3f7a..1b1a6c2b5 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/RawContactAccount.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/RawContactAccount.kt @@ -1,4 +1,4 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model import android.provider.ContactsContract.CommonDataKinds.* import androidx.annotation.StringRes diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/RecentAccount.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/RecentAccount.kt similarity index 96% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/RecentAccount.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/RecentAccount.kt index 5d5c739e7..4b176b640 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/RecentAccount.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/RecentAccount.kt @@ -1,4 +1,4 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model import android.provider.CallLog import androidx.annotation.IntDef diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/SimAccount.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/SimAccount.kt similarity index 91% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/model/SimAccount.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/SimAccount.kt index 2cc4f084d..2a6490001 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/model/SimAccount.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/model/SimAccount.kt @@ -1,4 +1,4 @@ -package com.chooloo.www.chooloolib.model +package com.chooloo.www.chooloolib.data.model import android.telecom.PhoneAccount import android.telecom.PhoneAccountHandle diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/calls/CallsRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/calls/CallsRepository.kt new file mode 100644 index 000000000..d0b6df5cc --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/calls/CallsRepository.kt @@ -0,0 +1,8 @@ +package com.chooloo.www.chooloolib.data.repository.calls + +import com.chooloo.www.chooloolib.data.model.Call +import kotlinx.coroutines.flow.Flow + +interface CallsRepository { + fun getCalls(): Flow> +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/calls/CallsRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/calls/CallsRepositoryImpl.kt new file mode 100644 index 000000000..5e836f11c --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/calls/CallsRepositoryImpl.kt @@ -0,0 +1,15 @@ +package com.chooloo.www.chooloolib.data.repository.calls + +import com.chooloo.www.chooloolib.api.service.CallService +import com.chooloo.www.chooloolib.data.model.Call +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class CallsRepositoryImpl @Inject constructor() : CallsRepository { + override fun getCalls(): Flow> = flow { + CallService.sInstance?.calls?.collect(this::emit) + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/contacts/ContactsRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/contacts/ContactsRepository.kt new file mode 100644 index 000000000..8676aa2e5 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/contacts/ContactsRepository.kt @@ -0,0 +1,9 @@ +package com.chooloo.www.chooloolib.data.repository.contacts + +import com.chooloo.www.chooloolib.data.model.ContactAccount +import kotlinx.coroutines.flow.Flow + +interface ContactsRepository { + fun getContacts(filter: String? = null): Flow> + fun getContact(contactId: Long): Flow +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/contacts/ContactsRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/contacts/ContactsRepositoryImpl.kt new file mode 100644 index 000000000..5b600afa2 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/contacts/ContactsRepositoryImpl.kt @@ -0,0 +1,24 @@ +package com.chooloo.www.chooloolib.data.repository.contacts + +import com.chooloo.www.chooloolib.data.model.ContactAccount +import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ContactsRepositoryImpl @Inject constructor( + private val contentResolverFactory: ContentResolverFactory +) : ContactsRepository { + override fun getContacts(filter: String?): Flow> = + contentResolverFactory.getContactsContentResolver().apply { + this.filter = filter + }.getItemsFlow() + + override fun getContact(contactId: Long): Flow = flow { + contentResolverFactory.getContactsContentResolver(contactId).getItemsFlow().collect { + emit(it.getOrNull(0)) + } + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/phones/PhonesRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/phones/PhonesRepository.kt new file mode 100644 index 000000000..ea4f7a7eb --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/phones/PhonesRepository.kt @@ -0,0 +1,8 @@ +package com.chooloo.www.chooloolib.data.repository.phones + +import com.chooloo.www.chooloolib.data.model.PhoneAccount +import kotlinx.coroutines.flow.Flow + +interface PhonesRepository { + fun getPhones(contactId: Long? = null, filter: String? = null): Flow> +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/phones/PhonesRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/phones/PhonesRepositoryImpl.kt new file mode 100644 index 000000000..ec279a337 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/phones/PhonesRepositoryImpl.kt @@ -0,0 +1,17 @@ +package com.chooloo.www.chooloolib.data.repository.phones + +import com.chooloo.www.chooloolib.data.model.PhoneAccount +import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PhonesRepositoryImpl @Inject constructor( + private val contentResolverFactory: ContentResolverFactory +) : PhonesRepository { + override fun getPhones(contactId: Long?, filter: String?): Flow> = + contentResolverFactory.getPhonesContentResolver(contactId).apply { + this.filter = filter + }.getItemsFlow() +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/rawcontacts/RawContactsRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/rawcontacts/RawContactsRepository.kt new file mode 100644 index 000000000..2e1b87870 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/rawcontacts/RawContactsRepository.kt @@ -0,0 +1,8 @@ +package com.chooloo.www.chooloolib.data.repository.rawcontacts + +import com.chooloo.www.chooloolib.data.model.RawContactAccount +import kotlinx.coroutines.flow.Flow + +interface RawContactsRepository { + fun getRawContacts(contactId: Long, filter: String? = null): Flow> +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/rawcontacts/RawContactsRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/rawcontacts/RawContactsRepositoryImpl.kt new file mode 100644 index 000000000..bf2901681 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/rawcontacts/RawContactsRepositoryImpl.kt @@ -0,0 +1,17 @@ +package com.chooloo.www.chooloolib.data.repository.rawcontacts + +import com.chooloo.www.chooloolib.data.model.RawContactAccount +import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RawContactsRepositoryImpl @Inject constructor( + private val contentResolverFactory: ContentResolverFactory +) : RawContactsRepository { + override fun getRawContacts(contactId: Long, filter: String?): Flow> = + contentResolverFactory.getRawContactsContentResolver(contactId).apply { + this.filter = filter + }.getItemsFlow() +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/recents/RecentsRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/recents/RecentsRepository.kt new file mode 100644 index 000000000..576decf2c --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/recents/RecentsRepository.kt @@ -0,0 +1,9 @@ +package com.chooloo.www.chooloolib.data.repository.recents + +import com.chooloo.www.chooloolib.data.model.RecentAccount +import kotlinx.coroutines.flow.Flow + +interface RecentsRepository { + fun getRecent(recentId: Long? = null): Flow + fun getRecents(filter: String? = null): Flow> +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/recents/RecentsRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/recents/RecentsRepositoryImpl.kt new file mode 100644 index 000000000..fc56c1e1d --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/data/repository/recents/RecentsRepositoryImpl.kt @@ -0,0 +1,24 @@ +package com.chooloo.www.chooloolib.data.repository.recents + +import com.chooloo.www.chooloolib.data.model.RecentAccount +import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RecentsRepositoryImpl @Inject constructor( + private val contentResolverFactory: ContentResolverFactory +) : RecentsRepository { + override fun getRecents(filter: String?): Flow> = + contentResolverFactory.getRecentsContentResolver().apply { + this.filter = filter + }.getItemsFlow() + + override fun getRecent(recentId: Long?) = flow { + contentResolverFactory.getRecentsContentResolver(recentId).getItemsFlow().collect { + emit(it.getOrNull(0)) + } + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/contentresolver/ContentResolverFactory.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/contentresolver/ContentResolverFactory.kt index 81c110c42..dc00ed975 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/contentresolver/ContentResolverFactory.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/contentresolver/ContentResolverFactory.kt @@ -1,6 +1,6 @@ package com.chooloo.www.chooloolib.di.factory.contentresolver -import com.chooloo.www.chooloolib.contentresolver.* +import com.chooloo.www.chooloolib.data.contentresolver.* interface ContentResolverFactory { fun getPhonesContentResolver(contactId: Long? = null): PhonesContentResolver diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/contentresolver/ContentResolverFactoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/contentresolver/ContentResolverFactoryImpl.kt index 65d40d069..2984f1282 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/contentresolver/ContentResolverFactoryImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/contentresolver/ContentResolverFactoryImpl.kt @@ -1,27 +1,29 @@ package com.chooloo.www.chooloolib.di.factory.contentresolver -import android.content.Context -import com.chooloo.www.chooloolib.contentresolver.* -import dagger.hilt.android.qualifiers.ApplicationContext +import android.content.ContentResolver +import com.chooloo.www.chooloolib.data.contentresolver.* +import com.chooloo.www.chooloolib.di.module.IoDispatcher +import kotlinx.coroutines.CoroutineDispatcher import javax.inject.Inject import javax.inject.Singleton @Singleton class ContentResolverFactoryImpl @Inject constructor( - @ApplicationContext private val context: Context + private val contentResolver: ContentResolver, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) : ContentResolverFactory { override fun getRecentsContentResolver(recentId: Long?) = - RecentsContentResolver(context, recentId) + RecentsContentResolver(recentId, contentResolver, ioDispatcher) override fun getPhonesContentResolver(contactId: Long?) = - PhonesContentResolver(context, contactId) + PhonesContentResolver(contactId, contentResolver, ioDispatcher) override fun getContactsContentResolver(contactId: Long?) = - ContactsContentResolver(context, contactId) + ContactsContentResolver(contactId, contentResolver, ioDispatcher) override fun getRawContactsContentResolver(contactId: Long) = - RawContactsContentResolver(context, contactId) + RawContactsContentResolver(contactId, contentResolver, ioDispatcher) override fun getPhoneLookupContentResolver(number: String?) = - PhoneLookupContentResolver(context, number) + PhoneLookupContentResolver(number, contentResolver, ioDispatcher) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/fragment/FragmentFactory.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/fragment/FragmentFactory.kt index 426f59ecf..3f5629e7d 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/fragment/FragmentFactory.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/fragment/FragmentFactory.kt @@ -7,9 +7,10 @@ import com.chooloo.www.chooloolib.ui.briefcontact.BriefContactFragment import com.chooloo.www.chooloolib.ui.briefcontact.menu.BriefContactMenuFragment import com.chooloo.www.chooloolib.ui.callitems.CallItemsFragment import com.chooloo.www.chooloolib.ui.contacts.ContactsFragment -import com.chooloo.www.chooloolib.ui.contacts.ContactsSuggestionsFragment +import com.chooloo.www.chooloolib.ui.contacts.suggestions.ContactsSuggestionsFragment import com.chooloo.www.chooloolib.ui.dialer.DialerFragment import com.chooloo.www.chooloolib.ui.dialpad.DialpadFragment +import com.chooloo.www.chooloolib.ui.permission.PermissionFragment import com.chooloo.www.chooloolib.ui.phones.PhonesFragment import com.chooloo.www.chooloolib.ui.prompt.PromptFragment import com.chooloo.www.chooloolib.ui.recent.RecentFragment @@ -24,6 +25,7 @@ interface FragmentFactory { fun getContactsFragment(): ContactsFragment fun getCallItemsFragment(): CallItemsFragment fun getRecentMenuFragment(): RecentMenuFragment + fun getPermissionFragment(): PermissionFragment fun getRecentFragment(recentId: Long): RecentFragment fun getBriefContactMenuFragment(): BriefContactMenuFragment fun getDialerFragment(text: String? = null): DialerFragment @@ -31,7 +33,7 @@ interface FragmentFactory { fun getContactsSuggestionsFragment(): ContactsSuggestionsFragment fun getBriefContactFragment(contactId: Long): BriefContactFragment fun getAccountsFragment(contactId: Long? = null): AccountsFragment - fun getPromptFragment(title: String, subtitle: String): PromptFragment + fun getPromptFragment(title: String, subtitle: String, isActivated: Boolean): PromptFragment fun getRecentsHistoryFragment(filter: String? = null): RecentsHistoryFragment fun getRecentsFragment(filter: String? = null, isGrouped: Boolean? = null): RecentsFragment fun getChoicesFragment( diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/fragment/FragmentFactoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/fragment/FragmentFactoryImpl.kt index 9c8b1dd3a..25b687d30 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/fragment/FragmentFactoryImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/fragment/FragmentFactoryImpl.kt @@ -7,9 +7,10 @@ import com.chooloo.www.chooloolib.ui.briefcontact.BriefContactFragment import com.chooloo.www.chooloolib.ui.briefcontact.menu.BriefContactMenuFragment import com.chooloo.www.chooloolib.ui.callitems.CallItemsFragment import com.chooloo.www.chooloolib.ui.contacts.ContactsFragment -import com.chooloo.www.chooloolib.ui.contacts.ContactsSuggestionsFragment +import com.chooloo.www.chooloolib.ui.contacts.suggestions.ContactsSuggestionsFragment import com.chooloo.www.chooloolib.ui.dialer.DialerFragment import com.chooloo.www.chooloolib.ui.dialpad.DialpadFragment +import com.chooloo.www.chooloolib.ui.permission.PermissionFragment import com.chooloo.www.chooloolib.ui.phones.PhonesFragment import com.chooloo.www.chooloolib.ui.prompt.PromptFragment import com.chooloo.www.chooloolib.ui.recent.RecentFragment @@ -26,12 +27,12 @@ class FragmentFactoryImpl @Inject constructor() : FragmentFactory { override fun getSettingsFragment() = SettingsFragment() override fun getContactsFragment() = ContactsFragment() override fun getCallItemsFragment() = CallItemsFragment() + override fun getPermissionFragment() = PermissionFragment() override fun getRecentMenuFragment() = RecentMenuFragment() override fun getContactsSuggestionsFragment() = ContactsSuggestionsFragment() override fun getDialerFragment(text: String?) = DialerFragment.newInstance(text) override fun getRecentFragment(recentId: Long) = RecentFragment.newInstance(recentId) override fun getBriefContactMenuFragment() = BriefContactMenuFragment() - override fun getAccountsFragment(contactId: Long?) = AccountsFragment.newInstance(contactId) @@ -41,8 +42,12 @@ class FragmentFactoryImpl @Inject constructor() : FragmentFactory { override fun getPhonesFragment(contactId: Long?) = PhonesFragment.newInstance(contactId) - override fun getPromptFragment(title: String, subtitle: String) = - PromptFragment.newInstance(title, subtitle) + override fun getPromptFragment( + title: String, + subtitle: String, + isActivated: Boolean + ): PromptFragment = + PromptFragment.newInstance(title, subtitle, isActivated) override fun getRecentsHistoryFragment(filter: String?) = RecentsHistoryFragment.newInstance(filter) diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/livedata/LiveDataFactory.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/livedata/LiveDataFactory.kt deleted file mode 100644 index 98aaed0cf..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/livedata/LiveDataFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.chooloo.www.chooloolib.di.factory.livedata - -import com.chooloo.www.chooloolib.livedata.ContactsLiveData -import com.chooloo.www.chooloolib.livedata.PhonesLiveData -import com.chooloo.www.chooloolib.livedata.RawContactsLiveData -import com.chooloo.www.chooloolib.livedata.RecentsLiveData - -interface LiveDataFactory { - fun getPhonesLiveData(contactId: Long? = null): PhonesLiveData - fun getRecentsLiveData(recentId: Long? = null): RecentsLiveData - fun getRawContactsLiveData(contactId: Long): RawContactsLiveData - fun getContactsLiveData(contactId: Long? = null): ContactsLiveData -} diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/livedata/LiveDataFactoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/livedata/LiveDataFactoryImpl.kt deleted file mode 100644 index 3b89742d0..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/factory/livedata/LiveDataFactoryImpl.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.chooloo.www.chooloolib.di.factory.livedata - -import android.content.Context -import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory -import com.chooloo.www.chooloolib.livedata.ContactsLiveData -import com.chooloo.www.chooloolib.livedata.PhonesLiveData -import com.chooloo.www.chooloolib.livedata.RawContactsLiveData -import com.chooloo.www.chooloolib.livedata.RecentsLiveData -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LiveDataFactoryImpl @Inject constructor( - @ApplicationContext private val context: Context, - private val contentResolverFactory: ContentResolverFactory -) : LiveDataFactory { - override fun getRecentsLiveData(recentId: Long?) = - RecentsLiveData(context, recentId, contentResolverFactory) - - override fun getRawContactsLiveData(contactId: Long) = - RawContactsLiveData(context, contactId, contentResolverFactory) - - override fun getPhonesLiveData(contactId: Long?) = - PhonesLiveData(context, contactId, contentResolverFactory) - - override fun getContactsLiveData(contactId: Long?) = - ContactsLiveData(context, contactId, contentResolverFactory) -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/ApplicationModule.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/ApplicationModule.kt index eb2267557..57fe3ddc0 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/ApplicationModule.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/ApplicationModule.kt @@ -1,7 +1,9 @@ package com.chooloo.www.chooloolib.di.module import android.app.KeyguardManager +import android.app.UiModeManager import android.content.ClipboardManager +import android.content.ContentResolver import android.content.Context import android.media.AudioManager import android.os.PowerManager @@ -11,12 +13,20 @@ import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.view.inputmethod.InputMethodManager import androidx.core.app.NotificationManagerCompat +import com.chooloo.www.chooloolib.data.repository.calls.CallsRepository +import com.chooloo.www.chooloolib.data.repository.calls.CallsRepositoryImpl +import com.chooloo.www.chooloolib.data.repository.contacts.ContactsRepository +import com.chooloo.www.chooloolib.data.repository.contacts.ContactsRepositoryImpl +import com.chooloo.www.chooloolib.data.repository.phones.PhonesRepository +import com.chooloo.www.chooloolib.data.repository.phones.PhonesRepositoryImpl +import com.chooloo.www.chooloolib.data.repository.rawcontacts.RawContactsRepository +import com.chooloo.www.chooloolib.data.repository.rawcontacts.RawContactsRepositoryImpl +import com.chooloo.www.chooloolib.data.repository.recents.RecentsRepository +import com.chooloo.www.chooloolib.data.repository.recents.RecentsRepositoryImpl import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactoryImpl import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactoryImpl -import com.chooloo.www.chooloolib.di.factory.livedata.LiveDataFactory -import com.chooloo.www.chooloolib.di.factory.livedata.LiveDataFactoryImpl import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractorImpl import com.chooloo.www.chooloolib.interactor.audio.AudiosInteractor @@ -55,16 +65,6 @@ import com.chooloo.www.chooloolib.interactor.telecom.TelecomInteractor import com.chooloo.www.chooloolib.interactor.telecom.TelecomInteractorImpl import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractor import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractorImpl -import com.chooloo.www.chooloolib.repository.calls.CallsRepository -import com.chooloo.www.chooloolib.repository.calls.CallsRepositoryImpl -import com.chooloo.www.chooloolib.repository.contacts.ContactsRepository -import com.chooloo.www.chooloolib.repository.contacts.ContactsRepositoryImpl -import com.chooloo.www.chooloolib.repository.phones.PhonesRepository -import com.chooloo.www.chooloolib.repository.phones.PhonesRepositoryImpl -import com.chooloo.www.chooloolib.repository.rawcontacts.RawContactsRepository -import com.chooloo.www.chooloolib.repository.rawcontacts.RawContactsRepositoryImpl -import com.chooloo.www.chooloolib.repository.recents.RecentsRepository -import com.chooloo.www.chooloolib.repository.recents.RecentsRepositoryImpl import com.chooloo.www.chooloolib.util.PreferencesManager import dagger.Binds import dagger.Module @@ -84,8 +84,15 @@ class ApplicationModule { fun provideVibrator(@ApplicationContext context: Context): Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + @Provides + fun provideContentResolver(@ApplicationContext context: Context): ContentResolver = + context.contentResolver //region manager + @Provides + fun provideUiManager(@ApplicationContext context: Context): UiModeManager = + context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager + @Provides fun providePowerManager(@ApplicationContext context: Context): PowerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager @@ -152,9 +159,6 @@ class ApplicationModule { //region factory - @Binds - fun bindLiveDataFactory(liveDataFactoryImpl: LiveDataFactoryImpl): LiveDataFactory - @Binds fun bindFragmentFactory(fragmentFactoryImpl: FragmentFactoryImpl): FragmentFactory diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/DispatcherModule.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/DispatcherModule.kt new file mode 100644 index 000000000..d0968b515 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/DispatcherModule.kt @@ -0,0 +1,37 @@ +package com.chooloo.www.chooloolib.di.module + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import javax.inject.Qualifier + +@InstallIn(SingletonComponent::class) +@Module +class DispatcherModule { + @DefaultDispatcher + @Provides + fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default + + @IoDispatcher + @Provides + fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO + + @MainDispatcher + @Provides + fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main +} + +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class DefaultDispatcher + +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class IoDispatcher + +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class MainDispatcher \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/ScopeModule.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/ScopeModule.kt new file mode 100644 index 000000000..60d2a6b18 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/di/module/ScopeModule.kt @@ -0,0 +1,40 @@ +package com.chooloo.www.chooloolib.di.module + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import javax.inject.Qualifier + +@InstallIn(SingletonComponent::class) +@Module +class ScopeModule { + @DefaultDispatcher + @Provides + fun provideDefaultScope(@DefaultDispatcher defaultDispatcher: CoroutineDispatcher): CoroutineScope = + CoroutineScope(defaultDispatcher) + + @IoScope + @Provides + fun provideIoScope(@IoDispatcher ioDispatcher: CoroutineDispatcher): CoroutineScope = + CoroutineScope(ioDispatcher) + + @MainScope + @Provides + fun provideMainScope(@MainDispatcher mainDispatcher: CoroutineDispatcher): CoroutineScope = + CoroutineScope(mainDispatcher) +} + +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class DefaultScope + +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class IoScope + +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class MainScope \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/animation/AnimationsInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/animation/AnimationsInteractorImpl.kt index 421f985ea..a32890896 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/animation/AnimationsInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/animation/AnimationsInteractorImpl.kt @@ -1,5 +1,6 @@ package com.chooloo.www.chooloolib.interactor.animation +import android.animation.ValueAnimator.REVERSE import android.os.Handler import android.os.Looper import android.view.View @@ -30,8 +31,10 @@ class AnimationsInteractorImpl @Inject constructor( override fun focus(view: View) { if (_isEnabled) { - YoYo.with(Techniques.Tada) - .duration(200) + YoYo.with(Techniques.FadeIn) + .duration(800) + .repeatMode(REVERSE) + .repeat(Animation.INFINITE) .playOn(view) } } @@ -42,7 +45,7 @@ class AnimationsInteractorImpl @Inject constructor( } view.visibility = VISIBLE if (_isEnabled) { - YoYo.with(Techniques.FadeInDown) + YoYo.with(Techniques.SlideInDown) .duration(100) .playOn(view) } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/audio/AudiosInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/audio/AudiosInteractor.kt index 30a9de269..106603e39 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/audio/AudiosInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/audio/AudiosInteractor.kt @@ -1,6 +1,7 @@ package com.chooloo.www.chooloolib.interactor.audio import com.chooloo.www.chooloolib.interactor.base.BaseInteractor +import kotlinx.coroutines.flow.StateFlow interface AudiosInteractor : BaseInteractor { interface Listener @@ -17,7 +18,7 @@ interface AudiosInteractor : BaseInteractor { fun vibrate(millis: Long = 10) - + enum class AudioMode(val mode: Int) { NORMAL(android.media.AudioManager.MODE_NORMAL), IN_CALL(android.media.AudioManager.MODE_IN_CALL), @@ -26,7 +27,6 @@ interface AudiosInteractor : BaseInteractor { IN_COMMUNICATION(android.media.AudioManager.MODE_IN_COMMUNICATION) } - companion object { const val SHORT_VIBRATE_LENGTH: Long = 20 } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/blocked/BlockedInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/blocked/BlockedInteractor.kt index c4d9f1691..330faf995 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/blocked/BlockedInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/blocked/BlockedInteractor.kt @@ -5,7 +5,7 @@ import com.chooloo.www.chooloolib.interactor.base.BaseInteractor interface BlockedInteractor : BaseInteractor { interface Listener - fun blockNumber(number: String) - fun unblockNumber(number: String) - fun isNumberBlocked(number: String): Boolean + suspend fun blockNumber(number: String) + suspend fun unblockNumber(number: String) + suspend fun isNumberBlocked(number: String): Boolean } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/blocked/BlockedInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/blocked/BlockedInteractorImpl.kt index 7bf22f0dc..dd100d64a 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/blocked/BlockedInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/blocked/BlockedInteractorImpl.kt @@ -4,36 +4,45 @@ import android.content.ContentValues import android.content.Context import android.provider.BlockedNumberContract import android.provider.BlockedNumberContract.BlockedNumbers +import com.chooloo.www.chooloolib.di.module.IoDispatcher import com.chooloo.www.chooloolib.interactor.base.BaseInteractorImpl import com.chooloo.www.chooloolib.util.annotation.RequiresDefaultDialer import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @Singleton class BlockedInteractorImpl @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) : BaseInteractorImpl(), BlockedInteractor { @RequiresDefaultDialer - override fun isNumberBlocked(number: String) = + override suspend fun isNumberBlocked(number: String) = withContext(ioDispatcher) { BlockedNumberContract.isBlocked(context, number) + } @RequiresDefaultDialer - override fun blockNumber(number: String) { - if (isNumberBlocked(number)) return - val contentValues = ContentValues() - contentValues.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number) - context.contentResolver.insert(BlockedNumbers.CONTENT_URI, contentValues) + override suspend fun blockNumber(number: String) { + withContext(ioDispatcher) { + if (isNumberBlocked(number)) return@withContext + val contentValues = ContentValues() + contentValues.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number) + context.contentResolver.insert(BlockedNumbers.CONTENT_URI, contentValues) + } } @RequiresDefaultDialer - override fun unblockNumber(number: String) { - if (!isNumberBlocked(number)) return - val contentValues = ContentValues() - contentValues.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number) - context.contentResolver.insert(BlockedNumbers.CONTENT_URI, contentValues)?.also { - context.contentResolver.delete(it, null, null) + override suspend fun unblockNumber(number: String) { + withContext(ioDispatcher) { + if (!isNumberBlocked(number)) return@withContext + val contentValues = ContentValues() + contentValues.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number) + context.contentResolver.insert(BlockedNumbers.CONTENT_URI, contentValues)?.also { + context.contentResolver.delete(it, null, null) + } } } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/callaudio/CallAudiosInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/callaudio/CallAudiosInteractor.kt index fe8ec8095..5243ad52a 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/callaudio/CallAudiosInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/callaudio/CallAudiosInteractor.kt @@ -1,7 +1,11 @@ package com.chooloo.www.chooloolib.interactor.callaudio import android.telecom.CallAudioState -import android.telecom.CallAudioState.* +import android.telecom.CallAudioState.ROUTE_BLUETOOTH +import android.telecom.CallAudioState.ROUTE_EARPIECE +import android.telecom.CallAudioState.ROUTE_SPEAKER +import android.telecom.CallAudioState.ROUTE_WIRED_HEADSET +import android.telecom.CallAudioState.ROUTE_WIRED_OR_EARPIECE import androidx.annotation.StringRes import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.interactor.base.BaseInteractor diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/callaudio/CallAudiosInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/callaudio/CallAudiosInteractorImpl.kt index da6da81da..cf28e03db 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/callaudio/CallAudiosInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/callaudio/CallAudiosInteractorImpl.kt @@ -3,7 +3,7 @@ package com.chooloo.www.chooloolib.interactor.callaudio import android.telecom.CallAudioState import com.chooloo.www.chooloolib.interactor.callaudio.CallAudiosInteractor.AudioRoute import com.chooloo.www.chooloolib.interactor.callaudio.CallAudiosInteractor.AudioRoute.* -import com.chooloo.www.chooloolib.service.CallService +import com.chooloo.www.chooloolib.api.service.CallService import com.chooloo.www.chooloolib.util.baseobservable.BaseObservable import javax.inject.Inject import javax.inject.Singleton diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/calls/CallsInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/calls/CallsInteractor.kt index 52e68c53d..76d12d07d 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/calls/CallsInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/calls/CallsInteractor.kt @@ -1,6 +1,6 @@ package com.chooloo.www.chooloolib.interactor.calls -import com.chooloo.www.chooloolib.model.Call +import com.chooloo.www.chooloolib.data.model.Call import com.chooloo.www.chooloolib.interactor.base.BaseInteractor interface CallsInteractor : BaseInteractor, Call.Listener { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/calls/CallsInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/calls/CallsInteractorImpl.kt index aea340d81..8be1da6a3 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/calls/CallsInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/calls/CallsInteractorImpl.kt @@ -1,8 +1,8 @@ package com.chooloo.www.chooloolib.interactor.calls -import com.chooloo.www.chooloolib.model.Call -import com.chooloo.www.chooloolib.model.Call.State.* -import com.chooloo.www.chooloolib.model.CallList +import com.chooloo.www.chooloolib.data.model.Call +import com.chooloo.www.chooloolib.data.model.Call.State.* +import com.chooloo.www.chooloolib.data.model.CallList import com.chooloo.www.chooloolib.util.baseobservable.BaseObservable import javax.inject.Inject import javax.inject.Singleton @@ -12,7 +12,6 @@ class CallsInteractorImpl @Inject constructor() : BaseObservable(), CallsInteractor { - private val _callList: CallList = CallList() override val mainCall: Call? diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/contacts/ContactsInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/contacts/ContactsInteractor.kt index 90d73caf3..7464828ef 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/contacts/ContactsInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/contacts/ContactsInteractor.kt @@ -1,16 +1,17 @@ package com.chooloo.www.chooloolib.interactor.contacts +import com.chooloo.www.chooloolib.data.model.ContactAccount import com.chooloo.www.chooloolib.interactor.base.BaseInteractor -import com.chooloo.www.chooloolib.model.ContactAccount +import kotlinx.coroutines.flow.Flow interface ContactsInteractor : BaseInteractor { interface Listener fun deleteContact(contactId: Long) - fun queryContact(contactId: Long, callback: (ContactAccount?) -> Unit) - fun observeContact(contactId: Long, callback: (ContactAccount?) -> Unit) + fun getContact(contactId: Long): Flow + fun getContacts(): Flow> fun toggleContactFavorite(contactId: Long, isFavorite: Boolean) - fun blockContact(contactId: Long, onSuccess: (() -> Unit)? = null) - fun unblockContact(contactId: Long, onSuccess: (() -> Unit)? = null) - fun getIsContactBlocked(contactId: Long, callback: (Boolean) -> Unit) + suspend fun blockContact(contactId: Long) + suspend fun unblockContact(contactId: Long) + suspend fun getIsContactBlocked(contactId: Long): Boolean } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/contacts/ContactsInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/contacts/ContactsInteractorImpl.kt index a762b030f..55e230c90 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/contacts/ContactsInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/contacts/ContactsInteractorImpl.kt @@ -6,14 +6,12 @@ import android.content.Context import android.net.Uri import android.provider.ContactsContract.Contacts import androidx.annotation.RequiresPermission -import com.chooloo.www.chooloolib.contentresolver.ContactsContentResolver +import com.chooloo.www.chooloolib.data.repository.contacts.ContactsRepository import com.chooloo.www.chooloolib.interactor.base.BaseInteractorImpl import com.chooloo.www.chooloolib.interactor.blocked.BlockedInteractor import com.chooloo.www.chooloolib.interactor.phoneaccounts.PhonesInteractor -import com.chooloo.www.chooloolib.model.ContactAccount import com.chooloo.www.chooloolib.util.annotation.RequiresDefaultDialer import dagger.hilt.android.qualifiers.ApplicationContext -import io.reactivex.disposables.CompositeDisposable import javax.inject.Inject import javax.inject.Singleton @@ -21,26 +19,13 @@ import javax.inject.Singleton class ContactsInteractorImpl @Inject constructor( private val phones: PhonesInteractor, private val blocked: BlockedInteractor, - private val disposables: CompositeDisposable, + private val contactsRepository: ContactsRepository, @ApplicationContext private val context: Context, ) : BaseInteractorImpl(), ContactsInteractor { - override fun queryContact(contactId: Long, callback: (ContactAccount?) -> Unit) { - disposables.add( - ContactsContentResolver(context, contactId).queryItems { contacts -> - contacts.let { callback.invoke(contacts.getOrNull(0)) } - } - ) - } - - override fun observeContact(contactId: Long, callback: (ContactAccount?) -> Unit) { - disposables.add( - ContactsContentResolver(context, contactId).observeItems { contacts -> - contacts.let { callback.invoke(contacts.getOrNull(0)) } - } - ) - } + override fun getContact(contactId: Long) = contactsRepository.getContact(contactId) + override fun getContacts() = contactsRepository.getContacts() @RequiresPermission(WRITE_CONTACTS) override fun deleteContact(contactId: Long) { @@ -53,18 +38,14 @@ class ContactsInteractorImpl @Inject constructor( } @RequiresDefaultDialer - override fun blockContact(contactId: Long, onSuccess: (() -> Unit)?) { - phones.getContactAccounts(contactId) { accounts -> - accounts?.forEach { blocked.blockNumber(it.number) } - onSuccess?.invoke() - } + override suspend fun blockContact(contactId: Long) { + val contactAccounts = phones.getContactAccounts(contactId) + contactAccounts.forEach { blocked.blockNumber(it.number) } } - override fun unblockContact(contactId: Long, onSuccess: (() -> Unit)?) { - phones.getContactAccounts(contactId) { accounts -> - accounts?.forEach { blocked.unblockNumber(it.number) } - onSuccess?.invoke() - } + override suspend fun unblockContact(contactId: Long) { + val contactAccounts = phones.getContactAccounts(contactId) + contactAccounts.forEach { blocked.unblockNumber(it.number) } } @RequiresPermission(WRITE_CONTACTS) @@ -75,9 +56,8 @@ class ContactsInteractorImpl @Inject constructor( context.contentResolver.update(Contacts.CONTENT_URI, contentValues, filter, null) } - override fun getIsContactBlocked(contactId: Long, callback: (Boolean) -> Unit) { - phones.getContactAccounts(contactId) { accounts -> - callback.invoke(accounts?.all { blocked.isNumberBlocked(it.number) } ?: false) - } + override suspend fun getIsContactBlocked(contactId: Long): Boolean { + val contactAccounts = phones.getContactAccounts(contactId) + return contactAccounts.all { blocked.isNumberBlocked(it.number) } } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/dialog/DialogsInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/dialog/DialogsInteractor.kt index 21fbe534b..e31116ac1 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/dialog/DialogsInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/dialog/DialogsInteractor.kt @@ -7,16 +7,21 @@ import androidx.annotation.ArrayRes import androidx.annotation.ColorInt import androidx.annotation.RequiresPermission import androidx.annotation.StringRes +import com.chooloo.www.chooloolib.data.model.SimAccount import com.chooloo.www.chooloolib.interactor.base.BaseInteractor import com.chooloo.www.chooloolib.interactor.callaudio.CallAudiosInteractor +import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.IncomingCallMode import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.Page import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractor.ThemeMode -import com.chooloo.www.chooloolib.model.SimAccount interface DialogsInteractor : BaseInteractor { interface Listener - fun askForBoolean(@StringRes titleRes: Int, callback: (result: Boolean) -> Unit) + fun askForBoolean( + @StringRes titleRes: Int, + isActivated: Boolean, + callback: (result: Boolean) -> Boolean + ) fun askForValidation(@StringRes titleRes: Int, callback: (result: Boolean) -> Unit) @@ -25,7 +30,7 @@ interface DialogsInteractor : BaseInteractor { @StringRes titleRes: Int, @StringRes subtitleRes: Int? = null, selectedChoiceIndex: Int? = null, - choiceCallback: (String) -> Unit + choiceCallback: (String) -> Boolean ) fun askForChoice( @@ -34,7 +39,7 @@ interface DialogsInteractor : BaseInteractor { @StringRes titleRes: Int, @StringRes subtitleRes: Int? = null, selectedChoice: T? = null, - choiceCallback: (T) -> Unit + choiceCallback: (T) -> Boolean ) fun askForColor( @@ -45,17 +50,18 @@ interface DialogsInteractor : BaseInteractor { ) @RequiresPermission(READ_PHONE_STATE) - fun askForSim(callback: (SimAccount?) -> Unit) - fun askForDefaultPage(callback: (Page) -> Unit) - fun askForThemeMode(callback: (ThemeMode) -> Unit) - fun askForRoute(callback: (CallAudiosInteractor.AudioRoute) -> Unit) + fun askForSim(callback: (SimAccount?) -> Boolean) + fun askForDefaultPage(callback: (Page) -> Boolean) + fun askForThemeMode(callback: (ThemeMode) -> Boolean) + fun askForIncomingCallMode(callback: (IncomingCallMode) -> Boolean) + fun askForRoute(callback: (CallAudiosInteractor.AudioRoute) -> Boolean) fun askForPhoneAccountHandle( phonesAccountHandles: List, - callback: (PhoneAccountHandle) -> Unit + callback: (PhoneAccountHandle) -> Boolean ) fun askForPhoneAccountSuggestion( phoneAccountSuggestions: List, - callback: (PhoneAccountSuggestion) -> Unit + callback: (PhoneAccountSuggestion) -> Boolean ) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/dialog/DialogsInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/dialog/DialogsInteractorImpl.kt index 080c86636..7833e773e 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/dialog/DialogsInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/dialog/DialogsInteractorImpl.kt @@ -8,21 +8,24 @@ import android.telecom.TelecomManager import androidx.annotation.RequiresApi import androidx.annotation.StringRes import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.data.model.SimAccount import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory import com.chooloo.www.chooloolib.interactor.callaudio.CallAudiosInteractor import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor +import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.IncomingCallMode import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.Page import com.chooloo.www.chooloolib.interactor.prompt.PromptsInteractor import com.chooloo.www.chooloolib.interactor.sim.SimsInteractor import com.chooloo.www.chooloolib.interactor.string.StringsInteractor import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractor.ThemeMode -import com.chooloo.www.chooloolib.model.SimAccount import com.chooloo.www.chooloolib.ui.base.BaseActivity import com.chooloo.www.chooloolib.util.baseobservable.BaseObservable import com.chooloo.www.chooloolib.util.fullLabel import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.scopes.ActivityScoped -import dev.sasikanth.colorsheet.ColorSheet +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import javax.inject.Inject @ActivityScoped @@ -37,13 +40,15 @@ class DialogsInteractorImpl @Inject constructor( private val preferences: PreferencesInteractor, ) : BaseObservable(), DialogsInteractor { - private val activity = context as BaseActivity<*> - - - override fun askForBoolean(titleRes: Int, callback: (result: Boolean) -> Unit) { + override fun askForBoolean( + @StringRes titleRes: Int, + isActivated: Boolean, + callback: (result: Boolean) -> Boolean + ) { prompts.showFragment(fragmentsFactory.getPromptFragment( strings.getString(R.string.prompt_yes_or_no), - strings.getString(titleRes) + strings.getString(titleRes), + isActivated ).apply { setOnItemClickListener(callback::invoke) }) @@ -52,9 +57,13 @@ class DialogsInteractorImpl @Inject constructor( override fun askForValidation(@StringRes titleRes: Int, callback: (result: Boolean) -> Unit) { prompts.showFragment(fragmentsFactory.getPromptFragment( strings.getString(R.string.prompt_are_you_sure), - strings.getString(titleRes) + strings.getString(titleRes), + true ).apply { - setOnItemClickListener(callback::invoke) + setOnItemClickListener { + callback.invoke(it) + true + } }) } @@ -64,16 +73,12 @@ class DialogsInteractorImpl @Inject constructor( @StringRes titleRes: Int, @StringRes subtitleRes: Int?, selectedChoiceIndex: Int?, - choiceCallback: (String) -> Unit + choiceCallback: (String) -> Boolean ) { prompts.showFragment( fragmentsFactory.getChoicesFragment(titleRes, subtitleRes, choices, selectedChoiceIndex) - .apply { - setOnChoiceClickListener { - choiceCallback.invoke(it) - this@apply.finish() - } - }) + .apply { setOnChoiceClickListener(choiceCallback) } + ) } override fun askForChoice( @@ -82,7 +87,7 @@ class DialogsInteractorImpl @Inject constructor( @StringRes titleRes: Int, @StringRes subtitleRes: Int?, selectedChoice: T?, - choiceCallback: (T) -> Unit + choiceCallback: (T) -> Boolean ) { val objectsToStringMap = choices.associateBy({ choiceToString.invoke(it) }, { it }) askForChoice( @@ -101,18 +106,18 @@ class DialogsInteractorImpl @Inject constructor( noColorOption: Boolean, selectedColor: Int? ) { - ColorSheet().colorPicker( - colors = activity.resources.getIntArray(R.array.accent_colors), - listener = callback::invoke, - noColorOption = true, - selectedColor = selectedColor - ).show(activity.supportFragmentManager) +// ColorSheet().colorPicker( +// colors = activity.resources.getIntArray(R.array.accent_colors), +// listener = callback::invoke, +// noColorOption = true, +// selectedColor = selectedColor +// ).show(activity.supportFragmentManager) } - override fun askForSim(callback: (SimAccount?) -> Unit) { - sims.getSimAccounts { simAccounts -> + override fun askForSim(callback: (SimAccount?) -> Boolean) { + CoroutineScope(Dispatchers.IO).launch { askForChoice( - choices = simAccounts, + choices = sims.getSimAccounts(), choiceCallback = callback::invoke, choiceToString = SimAccount::label, titleRes = R.string.hint_sim_account, @@ -121,7 +126,7 @@ class DialogsInteractorImpl @Inject constructor( } } - override fun askForDefaultPage(callback: (Page) -> Unit) { + override fun askForDefaultPage(callback: (Page) -> Boolean) { askForChoice( choices = Page.values().toList(), titleRes = R.string.hint_default_page, @@ -132,7 +137,7 @@ class DialogsInteractorImpl @Inject constructor( ) } - override fun askForThemeMode(callback: (ThemeMode) -> Unit) { + override fun askForThemeMode(callback: (ThemeMode) -> Boolean) { askForChoice( choices = ThemeMode.values().toList(), titleRes = R.string.hint_theme_mode, @@ -143,7 +148,18 @@ class DialogsInteractorImpl @Inject constructor( ) } - override fun askForRoute(callback: (CallAudiosInteractor.AudioRoute) -> Unit) { + override fun askForIncomingCallMode(callback: (IncomingCallMode) -> Boolean) { + askForChoice( + choices = IncomingCallMode.values().toList(), + titleRes = R.string.hint_incoming_call_mode, + choiceCallback = callback::invoke, + subtitleRes = R.string.explain_choose_incoming_call_mode, + selectedChoice = preferences.incomingCallMode, + choiceToString = { strings.getString(it.titleRes) } + ) + } + + override fun askForRoute(callback: (CallAudiosInteractor.AudioRoute) -> Boolean) { askForChoice( choiceCallback = callback::invoke, titleRes = R.string.action_choose_audio_route, @@ -156,7 +172,7 @@ class DialogsInteractorImpl @Inject constructor( override fun askForPhoneAccountHandle( phonesAccountHandles: List, - callback: (PhoneAccountHandle) -> Unit + callback: (PhoneAccountHandle) -> Boolean ) { askForChoice( choiceCallback = callback::invoke, @@ -170,7 +186,7 @@ class DialogsInteractorImpl @Inject constructor( @RequiresApi(Q) override fun askForPhoneAccountSuggestion( phoneAccountSuggestions: List, - callback: (PhoneAccountSuggestion) -> Unit + callback: (PhoneAccountSuggestion) -> Boolean ) { askForChoice( choiceCallback = callback::invoke, diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/permission/PermissionsInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/permission/PermissionsInteractor.kt index a26b7f729..c22daab2e 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/permission/PermissionsInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/permission/PermissionsInteractor.kt @@ -22,12 +22,21 @@ interface PermissionsInteractor : BaseInteractor callback: (List) -> Unit ) + suspend fun checkPermission(permission: String): Boolean + suspend fun checkPermissions(vararg permissions: String): Boolean + fun runWithPermissions( permissions: Array, grantedCallback: () -> Unit, deniedCallback: (() -> Unit)? = null ) + fun runWithPermissions( + vararg permissions: String, + callback: (Boolean) -> Unit + ) + + fun runWithDefaultDialer( @StringRes errorMessageRes: Int? = null, callback: () -> Unit, diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/permission/PermissionsInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/permission/PermissionsInteractorImpl.kt index 2f6c8ab23..d95c8dd3c 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/permission/PermissionsInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/permission/PermissionsInteractorImpl.kt @@ -8,6 +8,7 @@ import android.content.pm.PackageManager import android.telecom.TelecomManager import android.widget.Toast import androidx.core.app.ActivityCompat +import com.chooloo.www.chooloolib.di.module.IoDispatcher import com.chooloo.www.chooloolib.ui.permissions.DefaultDialerRequestActivity import com.chooloo.www.chooloolib.ui.permissions.PermissionRequestActivity import com.chooloo.www.chooloolib.ui.permissions.PermissionRequestActivity.Companion.PERMISSIONS_ARGUMENT_KEY @@ -16,14 +17,19 @@ import com.chooloo.www.chooloolib.ui.permissions.PermissionRequestActivity.Compa import com.chooloo.www.chooloolib.ui.permissions.PermissionRequestActivity.Companion.REQUEST_CODE_ARGUMENT_KEY import com.chooloo.www.chooloolib.util.baseobservable.BaseObservable import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import javax.inject.Singleton +import kotlin.coroutines.resume @Singleton class PermissionsInteractorImpl @Inject constructor( private val telecomManager: TelecomManager, - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) : BaseObservable(), PermissionsInteractor { @@ -84,6 +90,23 @@ class PermissionsInteractorImpl @Inject constructor( _onPermissionsResultListeners[_requestCode] = callback } + override suspend fun checkPermissions(vararg permissions: String): Boolean = + withContext(ioDispatcher) { + suspendCancellableCoroutine { continuation -> + checkPermissions(*permissions) { results -> + continuation.resume(results.all { it.state == GRANTED }) + } + } + } + + override suspend fun checkPermission(permission: String): Boolean = withContext(ioDispatcher) { + suspendCancellableCoroutine { continuation -> + checkPermissions(permission) { results -> + continuation.resume(results.all { it.state == GRANTED }) + } + } + } + override fun runWithPermissions( permissions: Array, grantedCallback: () -> Unit, @@ -102,6 +125,14 @@ class PermissionsInteractorImpl @Inject constructor( } } + override fun runWithPermissions(vararg permissions: String, callback: (Boolean) -> Unit) { + runWithPermissions( + permissions as Array, + { callback.invoke(true) }, + { callback.invoke(false) } + ) + } + override fun runWithDefaultDialer(errorMessageRes: Int?, callback: () -> Unit) { checkDefaultDialer { if (it) { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/phoneaccounts/PhonesInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/phoneaccounts/PhonesInteractor.kt index 233a2b8a0..afbc394b7 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/phoneaccounts/PhonesInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/phoneaccounts/PhonesInteractor.kt @@ -1,12 +1,12 @@ package com.chooloo.www.chooloolib.interactor.phoneaccounts -import com.chooloo.www.chooloolib.model.PhoneAccount -import com.chooloo.www.chooloolib.model.PhoneLookupAccount +import com.chooloo.www.chooloolib.data.model.PhoneAccount +import com.chooloo.www.chooloolib.data.model.PhoneLookupAccount import com.chooloo.www.chooloolib.interactor.base.BaseInteractor interface PhonesInteractor : BaseInteractor { interface Listener - fun lookupAccount(number: String?, callback: (PhoneLookupAccount?) -> Unit) - fun getContactAccounts(contactId: Long, callback: (Array?) -> Unit) + suspend fun getContactAccounts(contactId: Long): List + suspend fun lookupAccount(number: String?): PhoneLookupAccount? } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/phoneaccounts/PhonesInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/phoneaccounts/PhonesInteractorImpl.kt index b4bbb00d2..3fde98603 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/phoneaccounts/PhonesInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/phoneaccounts/PhonesInteractorImpl.kt @@ -1,38 +1,27 @@ package com.chooloo.www.chooloolib.interactor.phoneaccounts -import android.content.Context -import com.chooloo.www.chooloolib.contentresolver.PhoneLookupContentResolver -import com.chooloo.www.chooloolib.contentresolver.PhonesContentResolver -import com.chooloo.www.chooloolib.model.PhoneAccount -import com.chooloo.www.chooloolib.model.PhoneLookupAccount +import com.chooloo.www.chooloolib.data.model.PhoneAccount +import com.chooloo.www.chooloolib.data.model.PhoneLookupAccount +import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory import com.chooloo.www.chooloolib.interactor.base.BaseInteractorImpl -import dagger.hilt.android.qualifiers.ApplicationContext import io.reactivex.exceptions.OnErrorNotImplementedException import javax.inject.Inject import javax.inject.Singleton @Singleton class PhonesInteractorImpl @Inject constructor( - @ApplicationContext private val context: Context + private val contentResolverFactory: ContentResolverFactory, ) : BaseInteractorImpl(), PhonesInteractor { - override fun lookupAccount(number: String?, callback: (PhoneLookupAccount?) -> Unit) { + override suspend fun lookupAccount(number: String?): PhoneLookupAccount? = if (number == null || number.isEmpty()) { - callback.invoke(PhoneLookupAccount.PRIVATE) - return - } - try { - PhoneLookupContentResolver(context, number).queryItems { phones -> - callback.invoke(phones.getOrNull(0)) - } + PhoneLookupAccount.PRIVATE + } else try { + contentResolverFactory.getPhoneLookupContentResolver(number).queryItems().getOrNull(0) } catch (e: OnErrorNotImplementedException) { - callback.invoke(null) + null } - } - override fun getContactAccounts(contactId: Long, callback: (Array?) -> Unit) { - PhonesContentResolver(context, contactId).queryItems { - callback.invoke(it.toTypedArray()) - } - } + override suspend fun getContactAccounts(contactId: Long): List = + contentResolverFactory.getPhonesContentResolver(contactId).queryItems().toList() } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/preferences/PreferencesInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/preferences/PreferencesInteractor.kt index 7ae01c2ce..44b52ab54 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/preferences/PreferencesInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/preferences/PreferencesInteractor.kt @@ -14,32 +14,29 @@ interface PreferencesInteractor : BaseInteractor var defaultPage: Page var themeMode: ThemeMode - var accentTheme: AccentTheme + var incomingCallMode: IncomingCallMode fun setDefaultValues() companion object { - enum class AccentTheme(val key: String, val theme: Int) { - RED("red", R.style.Accent_Red), - BLUE("blue", R.style.Accent_Blue), - GREEN("green", R.style.Accent_Green), - PURPLE("purple", R.style.Accent_Purple), - DEFAULT(BLUE.key, BLUE.theme); + enum class Page(val key: String, val index: Int, val titleRes: Int) { + CONTACTS("contacts", 0, R.string.contacts), + RECENTS("recents", 1, R.string.recents); companion object { fun fromKey(key: String?) = - values().associateBy(AccentTheme::key).getOrDefault(key ?: "", DEFAULT) + values().associateBy(Page::key).getOrDefault(key ?: "", CONTACTS) } } - enum class Page(val key: String, val index: Int, val titleRes: Int) { - CONTACTS("contacts", 0, R.string.contacts), - RECENTS("recents", 1, R.string.recents), - DEFAULT(CONTACTS.key, CONTACTS.index, R.string.default_page); + enum class IncomingCallMode(val key: String, val index: Int, val titleRes: Int) { + POP_UP("popup_notification", 0, R.string.pop_up_notification), + FULL_SCREEN("full_screen", 1, R.string.full_screen); companion object { fun fromKey(key: String?) = - values().associateBy(Page::key).getOrDefault(key ?: "", DEFAULT) + IncomingCallMode.values().associateBy(IncomingCallMode::key) + .getOrDefault(key ?: "", FULL_SCREEN) } } } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/preferences/PreferencesInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/preferences/PreferencesInteractorImpl.kt index 338c5a97d..1a0cf366a 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/preferences/PreferencesInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/preferences/PreferencesInteractorImpl.kt @@ -4,7 +4,7 @@ import android.content.Context import androidx.preference.PreferenceManager import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.interactor.base.BaseInteractorImpl -import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.AccentTheme +import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.IncomingCallMode import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.Page import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractor.ThemeMode import com.chooloo.www.chooloolib.util.PreferencesManager @@ -14,63 +14,63 @@ import javax.inject.Singleton @Singleton class PreferencesInteractorImpl @Inject constructor( - @ApplicationContext private val context: Context, - private val preferencesManager: PreferencesManager + private val prefs: PreferencesManager, + @ApplicationContext private val context: Context ) : BaseInteractorImpl(), PreferencesInteractor { override var isAnimations: Boolean - get() = preferencesManager.getBoolean( + get() = prefs.getBoolean( R.string.pref_key_animations, R.bool.pref_default_value_animations ) set(value) { - preferencesManager.putBoolean(R.string.pref_key_animations, value) + prefs.putBoolean(R.string.pref_key_animations, value) } override var isGroupRecents: Boolean - get() = preferencesManager.getBoolean( + get() = prefs.getBoolean( R.string.pref_key_group_recents, R.bool.pref_default_value_group_recents ) set(value) { - preferencesManager.putBoolean(R.string.pref_key_group_recents, value) + prefs.putBoolean(R.string.pref_key_group_recents, value) } override var isDialpadTones: Boolean - get() = preferencesManager.getBoolean( + get() = prefs.getBoolean( R.string.pref_key_dialpad_tones, R.bool.pref_default_value_dialpad_tones ) set(value) { - preferencesManager.putBoolean(R.string.pref_key_dialpad_tones, value) + prefs.putBoolean(R.string.pref_key_dialpad_tones, value) } override var isDialpadVibrate: Boolean - get() = preferencesManager.getBoolean( + get() = prefs.getBoolean( R.string.pref_key_dialpad_vibrate, R.bool.pref_default_value_dialpad_vibrate ) set(value) { - preferencesManager.putBoolean(R.string.pref_key_dialpad_vibrate, value) + prefs.putBoolean(R.string.pref_key_dialpad_vibrate, value) } override var defaultPage: Page - get() = Page.fromKey(preferencesManager.getString(R.string.pref_key_default_page)) + get() = Page.fromKey(prefs.getString(R.string.pref_key_default_page)) set(value) { - preferencesManager.putString(R.string.pref_key_default_page, value.key) + prefs.putString(R.string.pref_key_default_page, value.key) } override var themeMode: ThemeMode - get() = ThemeMode.fromKey(preferencesManager.getString(R.string.pref_key_theme_mode)) + get() = ThemeMode.fromKey(prefs.getString(R.string.pref_key_theme_mode)) set(value) { - preferencesManager.putString(R.string.pref_key_theme_mode, value.key) + prefs.putString(R.string.pref_key_theme_mode, value.key) } - override var accentTheme: AccentTheme - get() = AccentTheme.fromKey(preferencesManager.getString(R.string.pref_key_color)) + override var incomingCallMode: IncomingCallMode + get() = IncomingCallMode.fromKey(prefs.getString(R.string.pref_key_incoming_call_mode)) set(value) { - preferencesManager.putString(R.string.pref_key_color, value.key) + prefs.putString(R.string.pref_key_incoming_call_mode, value.key) } override fun setDefaultValues() { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/rawcontacts/RawContactsInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/rawcontacts/RawContactsInteractor.kt index 05dd16c44..aa0bbdb9d 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/rawcontacts/RawContactsInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/rawcontacts/RawContactsInteractor.kt @@ -1,10 +1,11 @@ package com.chooloo.www.chooloolib.interactor.rawcontacts +import com.chooloo.www.chooloolib.data.model.RawContactAccount import com.chooloo.www.chooloolib.interactor.base.BaseInteractor -import com.chooloo.www.chooloolib.model.RawContactAccount +import kotlinx.coroutines.flow.Flow interface RawContactsInteractor : BaseInteractor { interface Listener - fun queryRawContacts(contactId: Long, callback: (List) -> Unit) + fun getRawContacts(contactId: Long): Flow> } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/rawcontacts/RawContactsInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/rawcontacts/RawContactsInteractorImpl.kt index 7397c9895..6e259dc92 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/rawcontacts/RawContactsInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/rawcontacts/RawContactsInteractorImpl.kt @@ -1,21 +1,16 @@ package com.chooloo.www.chooloolib.interactor.rawcontacts -import android.content.Context -import com.chooloo.www.chooloolib.contentresolver.RawContactsContentResolver +import com.chooloo.www.chooloolib.data.repository.rawcontacts.RawContactsRepository import com.chooloo.www.chooloolib.interactor.base.BaseInteractorImpl -import com.chooloo.www.chooloolib.model.RawContactAccount -import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton @Singleton class RawContactsInteractorImpl @Inject constructor( - @ApplicationContext private val context: Context + private val rawContactsRepository: RawContactsRepository ) : BaseInteractorImpl(), RawContactsInteractor { - override fun queryRawContacts(contactId: Long, callback: (List) -> Unit) { - RawContactsContentResolver(context, contactId).queryItems(callback) - } + override fun getRawContacts(contactId: Long) = rawContactsRepository.getRawContacts(contactId) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/recents/RecentsInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/recents/RecentsInteractor.kt index 53844e37c..e9cc79885 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/recents/RecentsInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/recents/RecentsInteractor.kt @@ -1,15 +1,15 @@ package com.chooloo.www.chooloolib.interactor.recents +import com.chooloo.www.chooloolib.data.model.RecentAccount import com.chooloo.www.chooloolib.interactor.base.BaseInteractor -import com.chooloo.www.chooloolib.model.RecentAccount +import kotlinx.coroutines.flow.Flow interface RecentsInteractor : BaseInteractor { interface Listener fun deleteRecent(recentId: Long) - fun queryRecent(recentId: Long): RecentAccount? - fun queryRecent(recentId: Long, callback: (RecentAccount?) -> Unit) - fun observeRecent(recentId: Long, callback: (RecentAccount?) -> Unit) + fun getRecents(): Flow> + fun getRecent(recentId: Long): Flow fun getCallTypeImage(@RecentAccount.CallType callType: Int): Int fun getLastOutgoingCall(): String } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/recents/RecentsInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/recents/RecentsInteractorImpl.kt index 4ae359439..8b7278026 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/recents/RecentsInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/recents/RecentsInteractorImpl.kt @@ -5,16 +5,17 @@ import android.content.Context import android.provider.CallLog import androidx.annotation.RequiresPermission import com.chooloo.www.chooloolib.R -import com.chooloo.www.chooloolib.contentresolver.RecentsContentResolver +import com.chooloo.www.chooloolib.data.model.RecentAccount +import com.chooloo.www.chooloolib.data.repository.recents.RecentsRepository import com.chooloo.www.chooloolib.interactor.base.BaseInteractorImpl -import com.chooloo.www.chooloolib.model.RecentAccount import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton @Singleton class RecentsInteractorImpl @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + private val recentsRepository: RecentsRepository ) : BaseInteractorImpl(), RecentsInteractor { @RequiresPermission(WRITE_CALL_LOG) @@ -26,20 +27,9 @@ class RecentsInteractorImpl @Inject constructor( ) } - override fun queryRecent(recentId: Long) = - RecentsContentResolver(context, recentId).queryItems().getOrNull(0) + override fun getRecent(recentId: Long) = recentsRepository.getRecent(recentId) - override fun queryRecent(recentId: Long, callback: (RecentAccount?) -> Unit) { - RecentsContentResolver(context, recentId).queryItems { - callback.invoke(it.getOrNull(0)) - } - } - - override fun observeRecent(recentId: Long, callback: (RecentAccount?) -> Unit) { - RecentsContentResolver(context, recentId).observeItems { - callback.invoke(it.getOrNull(0)) - } - } + override fun getRecents() = recentsRepository.getRecents() override fun getCallTypeImage(@RecentAccount.CallType callType: Int) = when (callType) { RecentAccount.TYPE_INCOMING -> R.drawable.call_received diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/screen/ScreensInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/screen/ScreensInteractorImpl.kt index eb3d3b410..4a050a718 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/screen/ScreensInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/screen/ScreensInteractorImpl.kt @@ -35,8 +35,8 @@ class ScreensInteractorImpl @Inject constructor( override fun showWhenLocked() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { activity.apply { - setShowWhenLocked(true) setTurnScreenOn(true) + setShowWhenLocked(true) keyguardManager.requestDismissKeyguard(this, null) } } else { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/sim/SimsInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/sim/SimsInteractor.kt index d74e4b0d0..80d2a26bb 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/sim/SimsInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/sim/SimsInteractor.kt @@ -2,14 +2,14 @@ package com.chooloo.www.chooloolib.interactor.sim import android.telecom.PhoneAccount import android.telephony.SubscriptionInfo -import com.chooloo.www.chooloolib.model.SimAccount +import com.chooloo.www.chooloolib.data.model.SimAccount import com.chooloo.www.chooloolib.interactor.base.BaseInteractor interface SimsInteractor : BaseInteractor { interface Listener - fun getIsMultiSim(callback: (Boolean) -> Unit) - fun getSimAccounts(callback: (List) -> Unit) - fun getPhoneAccounts(callback: (List) -> Unit) - fun getSubscriptionInfos(callback: (List) -> Unit) + suspend fun getIsMultiSim(): Boolean + suspend fun getSimAccounts(): List + suspend fun getPhoneAccounts(): List + suspend fun getSubscriptionInfos(): List } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/sim/SimsInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/sim/SimsInteractorImpl.kt index 2c4a29ad2..9c386e9b1 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/sim/SimsInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/sim/SimsInteractorImpl.kt @@ -6,7 +6,7 @@ import android.telecom.TelecomManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import androidx.annotation.RequiresPermission -import com.chooloo.www.chooloolib.model.SimAccount +import com.chooloo.www.chooloolib.data.model.SimAccount import com.chooloo.www.chooloolib.util.baseobservable.BaseObservable import javax.inject.Inject import javax.inject.Singleton @@ -18,30 +18,18 @@ class SimsInteractorImpl @Inject constructor( ) : BaseObservable(), SimsInteractor { @RequiresPermission(READ_PHONE_STATE) - override fun getIsMultiSim(callback: (isMultiSim: Boolean) -> Unit) { - callback.invoke(telecomManager.callCapablePhoneAccounts.size > 1) - } + override suspend fun getIsMultiSim(): Boolean = + telecomManager.callCapablePhoneAccounts.size > 1 @RequiresPermission(READ_PHONE_STATE) - override fun getSimAccounts(callback: (List) -> Unit) { - getPhoneAccounts { - callback.invoke(it.mapIndexed { index, phoneAccount -> - SimAccount(index, phoneAccount) - }) - } - } + override suspend fun getSimAccounts(): List = + getPhoneAccounts().mapIndexed(::SimAccount) @RequiresPermission(READ_PHONE_STATE) - override fun getPhoneAccounts(callback: (List) -> Unit) { - val phoneAccounts = ArrayList() - telecomManager.callCapablePhoneAccounts.forEach { pah -> - phoneAccounts.add(telecomManager.getPhoneAccount(pah)) - } - callback.invoke(phoneAccounts) - } + override suspend fun getPhoneAccounts(): List = + telecomManager.callCapablePhoneAccounts.map(telecomManager::getPhoneAccount) @RequiresPermission(READ_PHONE_STATE) - override fun getSubscriptionInfos(callback: (List) -> Unit) { - callback.invoke(subscriptionManager.activeSubscriptionInfoList) - } + override suspend fun getSubscriptionInfos(): List = + subscriptionManager.activeSubscriptionInfoList } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/telecom/TelecomInteractor.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/telecom/TelecomInteractor.kt index 60724801c..31a8b7148 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/telecom/TelecomInteractor.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/telecom/TelecomInteractor.kt @@ -1,7 +1,7 @@ package com.chooloo.www.chooloolib.interactor.telecom import com.chooloo.www.chooloolib.interactor.base.BaseInteractor -import com.chooloo.www.chooloolib.model.SimAccount +import com.chooloo.www.chooloolib.data.model.SimAccount interface TelecomInteractor : BaseInteractor { interface Listener {} diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/telecom/TelecomInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/telecom/TelecomInteractorImpl.kt index 8f2d90673..e7bafaecc 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/telecom/TelecomInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/telecom/TelecomInteractorImpl.kt @@ -11,7 +11,7 @@ import android.telecom.PhoneAccount import android.telecom.TelecomManager import android.telephony.TelephonyManager import androidx.annotation.RequiresPermission -import com.chooloo.www.chooloolib.model.SimAccount +import com.chooloo.www.chooloolib.data.model.SimAccount import com.chooloo.www.chooloolib.util.CallUtils import com.chooloo.www.chooloolib.util.PhoneNumberUtils import com.chooloo.www.chooloolib.util.baseobservable.BaseObservable diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/theme/ThemesInteractorImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/theme/ThemesInteractorImpl.kt index 400aae501..0d8083cca 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/theme/ThemesInteractorImpl.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/interactor/theme/ThemesInteractorImpl.kt @@ -1,7 +1,9 @@ package com.chooloo.www.chooloolib.interactor.theme import android.app.Application +import android.app.UiModeManager import android.content.Context +import android.os.Build import androidx.appcompat.app.AppCompatDelegate import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractor.ThemeMode import com.chooloo.www.chooloolib.util.baseobservable.BaseObservable @@ -12,14 +14,19 @@ import javax.inject.Singleton @Singleton class ThemesInteractorImpl @Inject constructor( - @ApplicationContext private val context: Context + private val uiManager: UiModeManager, + @ApplicationContext private val context: Context, ) : BaseObservable(), ThemesInteractor { override fun applyThemeMode(themeMode: ThemeMode) { if (themeMode == ThemeMode.DYNAMIC) { DynamicColors.applyToActivitiesIfAvailable(context as Application) } else { - AppCompatDelegate.setDefaultNightMode(themeMode.mode) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + uiManager.setApplicationNightMode(themeMode.mode) + } else { + AppCompatDelegate.setDefaultNightMode(themeMode.mode) + } } } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/ContactsLiveData.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/ContactsLiveData.kt deleted file mode 100644 index b94c1c4e2..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/ContactsLiveData.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.chooloo.www.chooloolib.livedata - -import android.content.Context -import com.chooloo.www.chooloolib.contentresolver.ContactsContentResolver -import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory -import com.chooloo.www.chooloolib.model.ContactAccount -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Singleton - -@Singleton -class ContactsLiveData( - @ApplicationContext context: Context, - private val contactId: Long? = null, - private val contentResolverFactory: ContentResolverFactory -) : ContentProviderLiveData(context) { - override val contentResolver by lazy { contentResolverFactory.getContactsContentResolver(contactId) } -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/ContentProviderLiveData.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/ContentProviderLiveData.kt deleted file mode 100644 index 69fdb0925..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/ContentProviderLiveData.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.chooloo.www.chooloolib.livedata - -import android.content.Context -import android.database.ContentObserver -import androidx.lifecycle.LiveData -import com.chooloo.www.chooloolib.contentresolver.BaseContentResolver -import dagger.hilt.android.qualifiers.ApplicationContext - -abstract class ContentProviderLiveData, T : Any>( - @ApplicationContext private val context: Context -) : - LiveData>() { - - private var observer = object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - super.onChange(selfChange) - postValue(contentResolver.queryItems()) - } - } - - var filter: String? - get() = contentResolver.filter - @Synchronized - set(value) { - contentResolver.filter = value - onActive() - } - - - override fun onActive() { - detachObserver() - attachObserver() - value = contentResolver.queryItems() - } - - override fun onInactive() { - detachObserver() - } - - private fun attachObserver() { - context.contentResolver.registerContentObserver(contentResolver.finalUri, true, observer) - } - - private fun detachObserver() { - context.contentResolver.unregisterContentObserver(observer) - } - - - abstract val contentResolver: ContentResolver -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/PhonesLiveData.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/PhonesLiveData.kt deleted file mode 100644 index 381f1acb3..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/PhonesLiveData.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.chooloo.www.chooloolib.livedata - -import android.content.Context -import com.chooloo.www.chooloolib.contentresolver.PhonesContentResolver -import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory -import com.chooloo.www.chooloolib.model.PhoneAccount - -class PhonesLiveData( - context: Context, - private val contactId: Long? = null, - private val contentResolverFactory: ContentResolverFactory, -) : ContentProviderLiveData(context) { - override val contentResolver by lazy { contentResolverFactory.getPhonesContentResolver(contactId) } -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/RawContactsLiveData.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/RawContactsLiveData.kt deleted file mode 100644 index 0c16ebf84..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/RawContactsLiveData.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.chooloo.www.chooloolib.livedata - -import android.content.Context -import com.chooloo.www.chooloolib.contentresolver.RawContactsContentResolver -import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory -import com.chooloo.www.chooloolib.model.RawContactAccount - -class RawContactsLiveData( - context: Context, - private val contactId: Long, - private val contentResolverFactory: ContentResolverFactory, -) : ContentProviderLiveData(context) { - override val contentResolver by lazy { - contentResolverFactory.getRawContactsContentResolver(contactId) - } -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/RecentsLiveData.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/RecentsLiveData.kt deleted file mode 100644 index 6df53b4de..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/livedata/RecentsLiveData.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.chooloo.www.chooloolib.livedata - -import android.content.Context -import com.chooloo.www.chooloolib.contentresolver.RecentsContentResolver -import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory -import com.chooloo.www.chooloolib.model.RecentAccount - -class RecentsLiveData( - context: Context, - private val recentId: Long? = null, - private val contentResolverFactory: ContentResolverFactory -) : ContentProviderLiveData(context) { - override val contentResolver by lazy { contentResolverFactory.getRecentsContentResolver(recentId) } -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/notification/CallNotification.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/notification/CallNotification.kt index 4d28bbe78..cf2b85b07 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/notification/CallNotification.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/notification/CallNotification.kt @@ -1,7 +1,9 @@ package com.chooloo.www.chooloolib.notification import android.app.Notification +import android.app.Notification.CATEGORY_CALL import android.app.Notification.EXTRA_NOTIFICATION_ID +import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.PendingIntent import android.app.PendingIntent.FLAG_CANCEL_CURRENT @@ -16,14 +18,17 @@ import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.data.model.Call +import com.chooloo.www.chooloolib.data.model.Call.State.DISCONNECTED +import com.chooloo.www.chooloolib.data.model.Call.State.DISCONNECTING +import com.chooloo.www.chooloolib.di.module.IoScope import com.chooloo.www.chooloolib.interactor.callaudio.CallAudiosInteractor import com.chooloo.www.chooloolib.interactor.calls.CallsInteractor import com.chooloo.www.chooloolib.interactor.color.ColorsInteractor import com.chooloo.www.chooloolib.interactor.phoneaccounts.PhonesInteractor +import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor +import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.IncomingCallMode import com.chooloo.www.chooloolib.interactor.string.StringsInteractor -import com.chooloo.www.chooloolib.model.Call -import com.chooloo.www.chooloolib.model.Call.State.DISCONNECTED -import com.chooloo.www.chooloolib.model.Call.State.DISCONNECTING import com.chooloo.www.chooloolib.receiver.CallBroadcastReceiver import com.chooloo.www.chooloolib.receiver.CallBroadcastReceiver.Companion.ACTION_HANGUP import com.chooloo.www.chooloolib.receiver.CallBroadcastReceiver.Companion.ACTION_MUTE @@ -32,6 +37,8 @@ import com.chooloo.www.chooloolib.receiver.CallBroadcastReceiver.Companion.ACTIO import com.chooloo.www.chooloolib.receiver.CallBroadcastReceiver.Companion.ACTION_UNSPEAKER import com.chooloo.www.chooloolib.ui.call.CallActivity import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton @@ -42,48 +49,36 @@ class CallNotification @Inject constructor( private val colors: ColorsInteractor, private val phones: PhonesInteractor, private val strings: StringsInteractor, + @IoScope private val ioScope: CoroutineScope, private val callAudios: CallAudiosInteractor, + private val preferences: PreferencesInteractor, @ApplicationContext private val context: Context, private val notificationManager: NotificationManagerCompat ) : CallsInteractor.Listener, CallAudiosInteractor.Listener { private var _call: Call? = null + private val _channel + get() = if (_call?.isIncoming == true && preferences.incomingCallMode == IncomingCallMode.POP_UP) { + _channelImportanceHigh + } else { + _channelImportanceLow + } - override fun onNoCalls() { - detach() - cancel() - } - - override fun onCallChanged(call: Call) { - } - - override fun onMainCallChanged(call: Call) { - show(call) - _call = call - } - - - override fun onMuteChanged(isMuted: Boolean) { - _call?.let(::show) - } - - override fun onAudioRouteChanged(audiosRoute: CallAudiosInteractor.AudioRoute) { - _call?.let(::show) - } - - - fun attach() { - createNotificationChannel() - calls.registerListener(this) - callAudios.registerListener(this) - Thread.currentThread().setUncaughtExceptionHandler { _, _ -> cancel() } + private val _channelImportanceLow by lazy { + NotificationChannelCompat.Builder(CHANNEL_ID_PRIORITY_LOW, IMPORTANCE_DEFAULT) + .setName(strings.getString(R.string.call_notification_channel_name)) + .setDescription(strings.getString(R.string.call_notification_channel_description)) + .build() + .also(notificationManager::createNotificationChannel) } - fun detach() { - calls.unregisterListener(this) - callAudios.unregisterListener(this) - cancel() + private val _channelImportanceHigh by lazy { + NotificationChannelCompat.Builder(CHANNEL_ID_PRIORITY_HIGH, IMPORTANCE_HIGH) + .setName(strings.getString(R.string.call_notification_channel_name)) + .setDescription(strings.getString(R.string.call_notification_channel_description)) + .build() + .also(notificationManager::createNotificationChannel) } private val _contentPendingIntent by lazy { @@ -145,12 +140,25 @@ class CallNotification @Inject constructor( ) } - private val _channel by lazy { - NotificationChannelCompat.Builder(CHANNEL_ID, IMPORTANCE_HIGH) - .setName(strings.getString(R.string.call_notification_channel_name)) - .setDescription(strings.getString(R.string.call_notification_channel_description)) - .setLightsEnabled(true) - .build() + + override fun onNoCalls() { + detach() + } + + override fun onCallChanged(call: Call) { + } + + override fun onMainCallChanged(call: Call) { + _call = call + refresh() + } + + override fun onMuteChanged(isMuted: Boolean) { + refresh() + } + + override fun onAudioRouteChanged(audioRoute: CallAudiosInteractor.AudioRoute) { + refresh() } @@ -168,56 +176,70 @@ class CallNotification @Inject constructor( FLAG_CANCEL_CURRENT or FLAG_IMMUTABLE ) + private suspend fun buildNotification(call: Call, callback: (Notification) -> Unit) { + val phoneAccount = phones.lookupAccount(call.number) + val builder = NotificationCompat.Builder(context, _channel.id) + .setWhen(0) + .setOngoing(true) + .setColorized(true) + .setPriority(PRIORITY) + .setOnlyAlertOnce(true) + .setCategory(CATEGORY_CALL) + .setContentTitle(phoneAccount?.displayString ?: call.number) + .setSmallIcon(R.drawable.icon_full_144) + .setContentIntent(_contentPendingIntent) + .setColor(colors.getAttrColor(R.attr.colorSecondary)) + .setContentText(strings.getString(call.state.stringRes)) + + if (call.state !in arrayOf(DISCONNECTED, DISCONNECTING)) { + builder.addAction(_hangupAction) + } - private fun buildNotification(call: Call, callback: (Notification) -> Unit) { - phones.lookupAccount(call.number) { - val builder = NotificationCompat.Builder(context, CHANNEL_ID) - .setWhen(0) - .setOngoing(true) - .setColorized(true) - .setPriority(PRIORITY) - .setOnlyAlertOnce(true) - .setContentTitle(it?.displayString ?: call.number) - .setSmallIcon(R.drawable.icon_full_144) - .setContentIntent(_contentPendingIntent) - .setColor(colors.getAttrColor(R.attr.colorSecondary)) - .setContentText(strings.getString(call.state.stringRes)) - if (call.isIncoming) { - builder.addAction(_answerAction) - } - if (call.state !in arrayOf(DISCONNECTED, DISCONNECTING)) { - builder.addAction(_hangupAction) - } + if (call.isIncoming) { + builder.addAction(_answerAction) + } else if (call.state !in arrayOf(DISCONNECTED, DISCONNECTING)) { callAudios.isMuted?.let { isMuted -> if (call.isCapable(CAPABILITY_MUTE)) { builder.addAction(if (isMuted) _unmuteAction else _muteAction) } } + callAudios.isSpeakerOn?.let { isSpeakerOn -> builder.addAction(if (isSpeakerOn) _unspeakerAction else _speakerAction) } - callback.invoke(builder.build()) } + + callback.invoke(builder.build()) } - fun show(call: Call) { - buildNotification(call) { - notificationManager.notify(ID, it) - } + fun attach() { + calls.registerListener(this) + callAudios.registerListener(this) + Thread.currentThread().setUncaughtExceptionHandler { _, _ -> detach() } } - fun cancel() { + fun detach() { + calls.unregisterListener(this) + callAudios.unregisterListener(this) notificationManager.cancel(ID) } - fun createNotificationChannel() { - notificationManager.createNotificationChannel(_channel) + fun show(call: Call) { + ioScope.launch { + buildNotification(call) { notificationManager.notify(ID, it) } + } + } + + fun refresh() { + _call?.let(::show) } + companion object { const val ID = 420 - const val CHANNEL_ID = "call_notification_channel" - const val PRIORITY = NotificationCompat.PRIORITY_LOW + const val PRIORITY = NotificationCompat.PRIORITY_HIGH + const val CHANNEL_ID_PRIORITY_LOW = "cnc_priority_low" + const val CHANNEL_ID_PRIORITY_HIGH = "cnc_priority_high" } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/calls/CallsRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/calls/CallsRepository.kt deleted file mode 100644 index 1b713e881..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/calls/CallsRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.chooloo.www.chooloolib.repository.calls - -import androidx.lifecycle.LiveData -import com.chooloo.www.chooloolib.model.Call - -interface CallsRepository { - fun getCalls(): LiveData> -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/calls/CallsRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/calls/CallsRepositoryImpl.kt deleted file mode 100644 index d6ffbfa31..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/calls/CallsRepositoryImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.chooloo.www.chooloolib.repository.calls - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.chooloo.www.chooloolib.model.Call -import com.chooloo.www.chooloolib.service.CallService -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class CallsRepositoryImpl @Inject constructor() : CallsRepository { - val calls = MutableLiveData>() - - override fun getCalls(): LiveData> = CallService.sInstance?.calls ?: calls -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/contacts/ContactsRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/contacts/ContactsRepository.kt deleted file mode 100644 index a97a9f49f..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/contacts/ContactsRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.chooloo.www.chooloolib.repository.contacts - -import androidx.lifecycle.LiveData -import com.chooloo.www.chooloolib.model.ContactAccount - -interface ContactsRepository { - fun getContacts(): LiveData> - fun getContact(contactId: Long, callback: (ContactAccount?) -> Unit) -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/contacts/ContactsRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/contacts/ContactsRepositoryImpl.kt deleted file mode 100644 index 60a948325..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/contacts/ContactsRepositoryImpl.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.chooloo.www.chooloolib.repository.contacts - -import androidx.lifecycle.LiveData -import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory -import com.chooloo.www.chooloolib.di.factory.livedata.LiveDataFactory -import com.chooloo.www.chooloolib.model.ContactAccount -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ContactsRepositoryImpl @Inject constructor( - private val liveDataFactory: LiveDataFactory, - private val contentResolverFactory: ContentResolverFactory -) : ContactsRepository { - override fun getContacts(): LiveData> = - liveDataFactory.getContactsLiveData() - - override fun getContact(contactId: Long, callback: (ContactAccount?) -> Unit) { - contentResolverFactory.getContactsContentResolver(contactId).queryItems { - callback.invoke(it.getOrNull(0)) - } - } -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/phones/PhonesRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/phones/PhonesRepository.kt deleted file mode 100644 index 712eae141..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/phones/PhonesRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.chooloo.www.chooloolib.repository.phones - -import androidx.lifecycle.LiveData -import com.chooloo.www.chooloolib.model.PhoneAccount - -interface PhonesRepository { - fun getPhones(contactId: Long? = null): LiveData> -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/phones/PhonesRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/phones/PhonesRepositoryImpl.kt deleted file mode 100644 index 6cc5445f5..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/phones/PhonesRepositoryImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.chooloo.www.chooloolib.repository.phones - -import com.chooloo.www.chooloolib.di.factory.livedata.LiveDataFactory -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class PhonesRepositoryImpl @Inject constructor( - private val liveDataFactory: LiveDataFactory -) : PhonesRepository { - override fun getPhones(contactId: Long?) = liveDataFactory.getPhonesLiveData(contactId) -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/rawcontacts/RawContactsRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/rawcontacts/RawContactsRepository.kt deleted file mode 100644 index b53529646..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/rawcontacts/RawContactsRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.chooloo.www.chooloolib.repository.rawcontacts - -import androidx.lifecycle.LiveData -import com.chooloo.www.chooloolib.model.RawContactAccount - -interface RawContactsRepository { - fun getRawContacts(contactId: Long): LiveData> -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/rawcontacts/RawContactsRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/rawcontacts/RawContactsRepositoryImpl.kt deleted file mode 100644 index e755e851c..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/rawcontacts/RawContactsRepositoryImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.chooloo.www.chooloolib.repository.rawcontacts - -import com.chooloo.www.chooloolib.di.factory.livedata.LiveDataFactory -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RawContactsRepositoryImpl @Inject constructor( - private val liveDataFactory: LiveDataFactory -) : RawContactsRepository { - override fun getRawContacts(contactId: Long) = liveDataFactory.getRawContactsLiveData(contactId) -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/recents/RecentsRepository.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/recents/RecentsRepository.kt deleted file mode 100644 index df5208347..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/recents/RecentsRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.chooloo.www.chooloolib.repository.recents - -import androidx.lifecycle.LiveData -import com.chooloo.www.chooloolib.model.RecentAccount - -interface RecentsRepository { - fun getRecents(): LiveData> - fun getRecent(recentId: Long? = null, callback: (RecentAccount?) -> Unit) -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/recents/RecentsRepositoryImpl.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/recents/RecentsRepositoryImpl.kt deleted file mode 100644 index 7bbfd91f3..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/repository/recents/RecentsRepositoryImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.chooloo.www.chooloolib.repository.recents - -import com.chooloo.www.chooloolib.di.factory.contentresolver.ContentResolverFactory -import com.chooloo.www.chooloolib.di.factory.livedata.LiveDataFactory -import com.chooloo.www.chooloolib.model.RecentAccount -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RecentsRepositoryImpl @Inject constructor( - private val liveDataFactory: LiveDataFactory, - private val contentResolverFactory: ContentResolverFactory -) : RecentsRepository { - override fun getRecents() = liveDataFactory.getRecentsLiveData() - - override fun getRecent(recentId: Long?, callback: (RecentAccount?) -> Unit) { - contentResolverFactory.getRecentsContentResolver(recentId).queryItems { - callback.invoke(it.getOrNull(0)) - } - } -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/accounts/AccountsFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/accounts/AccountsFragment.kt index dc8e36dd6..5a0b6b117 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/accounts/AccountsFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/accounts/AccountsFragment.kt @@ -5,7 +5,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.chooloo.www.chooloolib.adapter.AccountsAdapter import com.chooloo.www.chooloolib.interactor.telecom.TelecomInteractor -import com.chooloo.www.chooloolib.model.RawContactAccount +import com.chooloo.www.chooloolib.data.model.RawContactAccount import com.chooloo.www.chooloolib.ui.briefcontact.BriefContactFragment.Companion.ARG_CONTACT_ID import com.chooloo.www.chooloolib.ui.list.ListFragment import dagger.hilt.android.AndroidEntryPoint diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/accounts/AccountsViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/accounts/AccountsViewState.kt index 4e541387d..e14f89725 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/accounts/AccountsViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/accounts/AccountsViewState.kt @@ -1,34 +1,32 @@ package com.chooloo.www.chooloolib.ui.accounts -import androidx.lifecycle.LiveData +import android.Manifest.permission.READ_CONTACTS import androidx.lifecycle.MutableLiveData import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.data.model.RawContactAccount +import com.chooloo.www.chooloolib.data.repository.rawcontacts.RawContactsRepository import com.chooloo.www.chooloolib.interactor.navigation.NavigationsInteractor import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor -import com.chooloo.www.chooloolib.model.RawContactAccount -import com.chooloo.www.chooloolib.repository.rawcontacts.RawContactsRepository import com.chooloo.www.chooloolib.ui.list.ListViewState import com.chooloo.www.chooloolib.util.DataLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow import javax.inject.Inject @HiltViewModel class AccountsViewState @Inject constructor( + permissions: PermissionsInteractor, private val navigations: NavigationsInteractor, - private val permissions: PermissionsInteractor, private val rawContactsRepository: RawContactsRepository ) : - ListViewState() { + ListViewState(permissions) { override val noResultsIconRes = R.drawable.call override val noResultsTextRes = R.string.error_no_results_phones - override val noPermissionsTextRes = R.string.error_no_permissions_phones + override val requiredPermissions = listOf(READ_CONTACTS) - val contactId = MutableLiveData(0L) val callEvent = DataLiveEvent() - - private val accountsLiveData - get() = contactId.value?.let { rawContactsRepository.getRawContacts(it) } + val contactId = MutableLiveData(0L) override fun onItemRightClick(item: RawContactAccount) { @@ -38,12 +36,8 @@ class AccountsViewState @Inject constructor( } } - override fun getItemsObservable(callback: (LiveData>) -> Unit) { - permissions.runWithReadContactsPermissions { - onPermissionsChanged(it) - if (it) accountsLiveData?.let(callback::invoke) - } - } + override fun getItemsFlow(filter: String?): Flow>? = + contactId.value?.let { rawContactsRepository.getRawContacts(it, filter) } fun onContactId(contactId: Long) { this.contactId.value = contactId diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseChoicesFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseChoicesFragment.kt index 50fab603e..634bdc8f4 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseChoicesFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseChoicesFragment.kt @@ -14,7 +14,7 @@ open class BaseChoicesFragment @Inject constructor() : BaseFragment Unit = {} + private var _onChoiceSelectedListener: (String) -> Boolean = { true } protected open val binding by lazy { MenuBinding.inflate(layoutInflater) } @Inject lateinit var adapter: ChoicesAdapter @@ -22,11 +22,11 @@ open class BaseChoicesFragment @Inject constructor() : BaseFragment } - + binding.apply { menuRecyclerView.adapter = adapter @@ -45,8 +45,8 @@ open class BaseChoicesFragment @Inject constructor() : BaseFragment Unit) { - _onChoiceClickListener = onChoiceClickListener + fun setOnChoiceClickListener(onChoiceSelectedListener: (String) -> Boolean) { + _onChoiceSelectedListener = onChoiceSelectedListener } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseFragment.kt index 622d13191..e163243ef 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseFragment.kt @@ -6,6 +6,8 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes import androidx.fragment.app.Fragment +import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory +import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import javax.inject.Inject abstract class BaseFragment : Fragment(), BaseView { @@ -14,6 +16,8 @@ abstract class BaseFragment : Fragment(), BaseView { val args get() = arguments ?: Bundle() @Inject lateinit var baseActivity: BaseActivity<*> + @Inject lateinit var fragmentFactory: FragmentFactory + @Inject lateinit var permissions: PermissionsInteractor override fun onCreateView( @@ -24,10 +28,9 @@ abstract class BaseFragment : Fragment(), BaseView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - onSetup() - viewState.apply { - attach() + onSetupHook() + viewState.apply { errorEvent.observe(this@BaseFragment) { it.ifNew?.let(this@BaseFragment::showError) } @@ -45,11 +48,11 @@ abstract class BaseFragment : Fragment(), BaseView { } override fun showError(@StringRes stringResId: Int) { - baseActivity.viewState.errorEvent.call(stringResId) + baseActivity.viewState.onError(stringResId) } override fun showMessage(@StringRes stringResId: Int) { - baseActivity.viewState.messageEvent.call(stringResId) + baseActivity.viewState.onMessage(stringResId) } override fun finish() { @@ -57,6 +60,11 @@ abstract class BaseFragment : Fragment(), BaseView { _onFinishListener.invoke() } + protected open fun onSetupHook() { + onSetup() + viewState.attach() + } + fun setOnFinishListener(onFinishListener: () -> Unit) { _onFinishListener = onFinishListener } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseView.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseView.kt index 13b6f451b..759c1866f 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseView.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseView.kt @@ -1,7 +1,6 @@ package com.chooloo.www.chooloolib.ui.base import androidx.annotation.StringRes -import androidx.lifecycle.Lifecycle interface BaseView { val viewState: VM? diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseViewState.kt index ce30d68da..3ff7f40a4 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BaseViewState.kt @@ -1,14 +1,34 @@ package com.chooloo.www.chooloolib.ui.base +import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import com.chooloo.www.chooloolib.util.DataLiveEvent import com.chooloo.www.chooloolib.util.LiveEvent +import com.chooloo.www.chooloolib.util.MutableDataLiveEvent +import com.chooloo.www.chooloolib.util.MutableLiveEvent open class BaseViewState : ViewModel() { - val finishEvent = LiveEvent() - val errorEvent = DataLiveEvent() - val messageEvent = DataLiveEvent() + protected val _finishEvent = MutableLiveEvent() + protected val _errorEvent = MutableDataLiveEvent() + protected val _messageEvent = MutableDataLiveEvent() + + val finishEvent = _finishEvent as LiveEvent + val errorEvent = _errorEvent as DataLiveEvent + val messageEvent = _messageEvent as DataLiveEvent + open fun attach() {} open fun detach() {} + + fun onFinish() { + _finishEvent.call() + } + + fun onError(@StringRes errMessageRes: Int) { + _errorEvent.call(errMessageRes) + } + + fun onMessage(@StringRes messageRes: Int) { + _messageEvent.call(messageRes) + } } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BottomFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BottomFragment.kt index 2b6a9e658..ca9c3e787 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BottomFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/BottomFragment.kt @@ -6,12 +6,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes +import androidx.fragment.app.commit import androidx.fragment.app.viewModels import com.chooloo.www.chooloolib.databinding.BottomDialogBinding import com.google.android.material.bottomsheet.BottomSheetDialogFragment import javax.inject.Inject -open class BottomFragment>( +open class BottomFragment>( private val fragment: FragmentType ) : BottomSheetDialogFragment(), BaseView { override val viewState: BaseViewState by viewModels() @@ -57,15 +58,16 @@ open class BottomFragment>( } } - childFragmentManager.beginTransaction() - .replace(binding.bottomDialogFragmentPlaceholder.id, fragment).commit() + childFragmentManager.commit { + replace(binding.bottomDialogFragmentPlaceholder.id, fragment) + } } override fun showError(@StringRes stringResId: Int) { - baseActivity.viewState.errorEvent.call(stringResId) + baseActivity.viewState.onError(stringResId) } override fun showMessage(@StringRes stringResId: Int) { - baseActivity.viewState.messageEvent.call(stringResId) + baseActivity.viewState.onMessage(stringResId) } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/menu/BaseMenuFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/menu/BaseMenuFragment.kt index 5228e1cef..158c23ba7 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/menu/BaseMenuFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/menu/BaseMenuFragment.kt @@ -18,6 +18,7 @@ import javax.inject.Inject open class BaseMenuFragment @Inject constructor() : BaseFragment() { override val contentView by lazy { binding.root } override val viewState: BaseMenuViewState by viewModels() + private val binding by lazy { MenuBinding.inflate(layoutInflater) } protected var menu: MenuBuilder? = null diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/menu/BaseMenuViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/menu/BaseMenuViewState.kt index ac1bffe4f..e93f98856 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/menu/BaseMenuViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/base/menu/BaseMenuViewState.kt @@ -1,14 +1,20 @@ package com.chooloo.www.chooloolib.ui.base.menu import android.view.MenuItem +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.chooloo.www.chooloolib.ui.base.BaseViewState abstract class BaseMenuViewState : BaseViewState() { - val title = MutableLiveData() - val subtitle = MutableLiveData() + protected val _title = MutableLiveData() + protected val _subtitle = MutableLiveData() + + val title = _title as LiveData + val subtitle = _subtitle as LiveData + abstract val menuResList: List + open fun onMenuItemClick(menuItem: MenuItem) { onMenuItemClick(menuItem.itemId) } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/BriefContactFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/BriefContactFragment.kt index c015de15c..75e073012 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/BriefContactFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/BriefContactFragment.kt @@ -1,19 +1,21 @@ package com.chooloo.www.chooloolib.ui.briefcontact import android.os.Bundle +import android.view.View.LAYOUT_DIRECTION_LTR +import android.view.View.LAYOUT_DIRECTION_RTL import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels +import androidx.fragment.app.commitNow import androidx.fragment.app.viewModels import com.chooloo.www.chooloolib.databinding.BriefContactBinding -import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory import com.chooloo.www.chooloolib.interactor.dialog.DialogsInteractor -import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import com.chooloo.www.chooloolib.interactor.prompt.PromptsInteractor import com.chooloo.www.chooloolib.interactor.telecom.TelecomInteractor import com.chooloo.www.chooloolib.ui.accounts.AccountsViewState import com.chooloo.www.chooloolib.ui.base.BaseFragment import com.chooloo.www.chooloolib.ui.briefcontact.menu.BriefContactMenuViewState import com.chooloo.www.chooloolib.ui.phones.PhonesViewState +import com.chooloo.www.chooloolib.util.isRTL import com.squareup.picasso.Picasso import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -32,8 +34,6 @@ open class BriefContactFragment @Inject constructor() : BaseFragment() - val contactImage = MutableLiveData() - val isFavorite = MutableLiveData() - val contactName = MutableLiveData() - val contactNumber = MutableLiveData() - - val showMoreEvent = LiveEvent() - val callEvent = DataLiveEvent() - private var firstNumber: String? = null private var contact: ContactAccount? = null + private val _contactId = MutableLiveData() + private val _contactImage = MutableLiveData() + private val _isFavorite = MutableLiveData() + private val _contactName = MutableLiveData() + private val _contactNumber = MutableLiveData() + private val _showMoreEvent = MutableLiveEvent() + private val _callEvent = MutableDataLiveEvent() + + val contactId = _contactId as LiveData + val contactImage = _contactImage as LiveData + val isFavorite = _isFavorite as LiveData + val contactName = _contactName as LiveData + val contactNumber = _contactNumber as LiveData + val showMoreEvent = _showMoreEvent as LiveEvent + val callEvent = _callEvent as DataLiveEvent + private fun withFirstNumber(callback: (String?) -> Unit) { if (firstNumber != null) { callback.invoke(firstNumber) } else { contact?.let { - phones.getContactAccounts(it.id) { phones -> - firstNumber = phones?.getOrNull(0)?.number + viewModelScope.launch { + firstNumber = phones.getContactAccounts(it.id).getOrNull(0)?.number callback.invoke(firstNumber) } } @@ -48,22 +59,24 @@ class BriefContactViewState @Inject constructor( } fun onContactId(contactId: Long) { - this.contactId.value = contactId - contacts.observeContact(contactId) { - contact = it - contactName.value = it?.name - isFavorite.value = it?.starred == true - withFirstNumber { contactNumber.value = it } - it?.photoUri?.let { uri -> - contactImage.value = Uri.parse(uri) + _contactId.value = contactId + viewModelScope.launch { + contacts.getContact(contactId).collect { + contact = it + _contactName.value = it?.name + _isFavorite.value = it?.starred == true + withFirstNumber { _contactNumber.value = it } + it?.photoUri?.let { uri -> + _contactImage.value = Uri.parse(uri) + } } } } fun onCallClick() { withFirstNumber { - it?.let(callEvent::call) ?: run { - errorEvent.call(R.string.error_no_number_to_call) + it?.let(_callEvent::call) ?: run { + onError(R.string.error_no_number_to_call) } } } @@ -76,14 +89,7 @@ class BriefContactViewState @Inject constructor( contactId.value?.let(navigations::editContact) } - fun onDelete() { - permissions.runWithWriteContactsPermissions { - contactId.value?.let(contacts::deleteContact) - finishEvent.call() - } - } - fun onMoreClick() { - showMoreEvent.call() + _showMoreEvent.call() } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/menu/BriefContactMenuFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/menu/BriefContactMenuFragment.kt index 7ab7521ad..035eaf967 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/menu/BriefContactMenuFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/menu/BriefContactMenuFragment.kt @@ -1,9 +1,7 @@ package com.chooloo.www.chooloolib.ui.briefcontact.menu -import android.view.MenuItem import androidx.fragment.app.activityViewModels import com.chooloo.www.chooloolib.R -import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory import com.chooloo.www.chooloolib.interactor.dialog.DialogsInteractor import com.chooloo.www.chooloolib.interactor.prompt.PromptsInteractor import com.chooloo.www.chooloolib.ui.base.menu.BaseMenuFragment @@ -14,31 +12,34 @@ import javax.inject.Inject class BriefContactMenuFragment @Inject constructor() : BaseMenuFragment() { override val viewState: BriefContactMenuViewState by activityViewModels() - @Inject lateinit var dialogs: DialogsInteractor - @Inject lateinit var prompts: PromptsInteractor - @Inject lateinit var fragmentFactory: FragmentFactory + @Inject + lateinit var dialogs: DialogsInteractor + + @Inject + lateinit var prompts: PromptsInteractor override fun onSetup() { super.onSetup() viewState.apply { - showHistoryEvent.observe(this@BriefContactMenuFragment) { ev -> - ev.ifNew?.let { prompts.showFragment(fragmentFactory.getRecentsFragment(viewState.contactName.value)) } - } - isFavorite.observe(this@BriefContactMenuFragment) { changeItemVisibility(R.id.menu_brief_contact_set_favorite, !it) changeItemVisibility(R.id.menu_brief_contact_unset_favorite, it) } - } - } - override fun onMenuItemClick(menuItem: MenuItem) { - if (menuItem.itemId == R.id.menu_brief_contact_delete) { - dialogs.askForValidation(R.string.explain_delete_contact) { bl -> - if (bl) viewState.onDelete() + showHistoryEvent.observe(this@BriefContactMenuFragment) { + it.ifNew?.let { + prompts.showFragment(fragmentFactory.getRecentsHistoryFragment(viewState.contactName.value)) + } + } + + confirmDeleteEvent.observe(this@BriefContactMenuFragment) { + it.ifNew?.let { + dialogs.askForValidation(R.string.explain_delete_contact) { bl -> + if (bl) viewState.onDelete() + } + } } } - super.onMenuItemClick(menuItem) } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/menu/BriefContactMenuViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/menu/BriefContactMenuViewState.kt index 0304a71de..04f93940f 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/menu/BriefContactMenuViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/briefcontact/menu/BriefContactMenuViewState.kt @@ -1,12 +1,14 @@ package com.chooloo.www.chooloolib.ui.briefcontact.menu +import android.Manifest +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.interactor.contacts.ContactsInteractor import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import com.chooloo.www.chooloolib.ui.base.menu.BaseMenuViewState -import com.chooloo.www.chooloolib.util.DataLiveEvent import com.chooloo.www.chooloolib.util.LiveEvent +import com.chooloo.www.chooloolib.util.MutableLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -17,32 +19,55 @@ class BriefContactMenuViewState @Inject constructor( ) : BaseMenuViewState() { override val menuResList = listOf(R.menu.menu_brief_contact_extra) - val contactId = MutableLiveData() - val contactName = MutableLiveData() - val isFavorite = MutableLiveData() - val showHistoryEvent = LiveEvent() + private val _contactId = MutableLiveData() + private val _contactName = MutableLiveData() + private val _isFavorite = MutableLiveData() + private val _showHistoryEvent = MutableLiveEvent() + private val _confirmDeleteEvent = MutableLiveEvent() + + val contactId = _contactId as LiveData + val isFavorite = _isFavorite as LiveData + val contactName = _contactName as LiveData + val showHistoryEvent = _showHistoryEvent as LiveEvent + val confirmDeleteEvent = _confirmDeleteEvent as LiveEvent + override fun onMenuItemClick(itemId: Int) { when (itemId) { R.id.menu_brief_contact_show_history -> onShowHistory() + R.id.menu_brief_contact_delete -> _confirmDeleteEvent.call() R.id.menu_brief_contact_set_favorite -> onSetFavorite(true) R.id.menu_brief_contact_unset_favorite -> onSetFavorite(false) } } fun onDelete() { - permissions.runWithWriteContactsPermissions { + permissions.runWithPermissions(arrayOf(Manifest.permission.WRITE_CONTACTS), { contactId.value?.let(contacts::deleteContact) - finishEvent.call() - } + onFinish() + }, { + onError(R.string.error_no_permissions_edit_contacts) + }) } fun onShowHistory() { - showHistoryEvent.call() + _showHistoryEvent.call() } fun onSetFavorite(isFavorite: Boolean) { contactId.value?.let { contacts.toggleContactFavorite(it, isFavorite) } - finishEvent.call() + onFinish() + } + + fun onIsFavorite(isFavorite: Boolean) { + _isFavorite.value = isFavorite + } + + fun onContactId(contactId: Long) { + _contactId.value = contactId + } + + fun onContactName(contactName: String) { + _contactName.value = contactName } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/call/CallActivity.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/call/CallActivity.kt index 2a955c26a..e6d63a5aa 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/call/CallActivity.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/call/CallActivity.kt @@ -28,11 +28,11 @@ class CallActivity : BaseActivity() { private val dialpadViewState: DialpadViewState by viewModels() private val binding by lazy { CallBinding.inflate(layoutInflater) } - @Inject lateinit var screens:ScreensInteractor + @Inject lateinit var screens: ScreensInteractor @Inject lateinit var dialogs: DialogsInteractor @Inject lateinit var prompts: PromptsInteractor - @Inject lateinit var animations: AnimationsInteractor @Inject lateinit var fragmentFactory: FragmentFactory + @Inject lateinit var animations: AnimationsInteractor override fun onSetup() { @@ -91,13 +91,18 @@ class CallActivity : BaseActivity() { showActiveLayout() binding.callActions.showMultiCallUI() } + UIState.ACTIVE -> { showActiveLayout() binding.callActions.showSingleCallUI() } + UIState.INCOMING -> { transitionLayoutTo(R.id.constraint_set_incoming_call) } + + else -> { + } } } @@ -142,7 +147,12 @@ class CallActivity : BaseActivity() { } askForRouteEvent.observe(this@CallActivity) { - it.ifNew?.let { dialogs.askForRoute(viewState::onAudioRoutePicked) } + it.ifNew?.let { + dialogs.askForRoute { + viewState.onAudioRoutePicked(it) + true + } + } } showDialerEvent.observe(this@CallActivity) { @@ -161,6 +171,7 @@ class CallActivity : BaseActivity() { it.ifNew?.let { dialogs.askForPhoneAccountHandle(it) { viewState.onPhoneAccountHandleSelected(it) + true } } } @@ -170,7 +181,9 @@ class CallActivity : BaseActivity() { dialogs.askForPhoneAccountSuggestion(it) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { viewState.onPhoneAccountHandleSelected(it.phoneAccountHandle) + return@askForPhoneAccountSuggestion true } + false } } } @@ -196,7 +209,6 @@ class CallActivity : BaseActivity() { dialpadViewState.char.observe(this@CallActivity, viewState::onCharKey) } - private fun showActiveLayout() { transitionLayoutTo(R.id.constraint_set_active_call) if (binding.callActions.visibility != View.VISIBLE) { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/call/CallViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/call/CallViewState.kt index 98e264a11..de167af50 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/call/CallViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/call/CallViewState.kt @@ -2,12 +2,26 @@ package com.chooloo.www.chooloolib.ui.call import android.net.Uri import android.os.Build -import android.telecom.Call.Details.* +import android.telecom.Call.Details.CAPABILITY_HOLD +import android.telecom.Call.Details.CAPABILITY_MUTE +import android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE import android.telecom.PhoneAccountHandle import android.telecom.PhoneAccountSuggestion -import android.telecom.TelecomManager +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.api.service.CallService +import com.chooloo.www.chooloolib.data.model.Call +import com.chooloo.www.chooloolib.data.model.Call.State.ACTIVE +import com.chooloo.www.chooloolib.data.model.Call.State.DISCONNECTED +import com.chooloo.www.chooloolib.data.model.Call.State.DISCONNECTING +import com.chooloo.www.chooloolib.data.model.Call.State.HOLDING +import com.chooloo.www.chooloolib.data.model.Call.State.INCOMING +import com.chooloo.www.chooloolib.data.model.Call.State.SELECT_PHONE_ACCOUNT +import com.chooloo.www.chooloolib.data.model.CantHoldCallException +import com.chooloo.www.chooloolib.data.model.CantMergeCallException +import com.chooloo.www.chooloolib.data.model.CantSwapCallException import com.chooloo.www.chooloolib.interactor.audio.AudiosInteractor import com.chooloo.www.chooloolib.interactor.audio.AudiosInteractor.AudioMode.NORMAL import com.chooloo.www.chooloolib.interactor.callaudio.CallAudiosInteractor @@ -17,27 +31,23 @@ import com.chooloo.www.chooloolib.interactor.color.ColorsInteractor import com.chooloo.www.chooloolib.interactor.phoneaccounts.PhonesInteractor import com.chooloo.www.chooloolib.interactor.proximity.ProximitiesInteractor import com.chooloo.www.chooloolib.interactor.string.StringsInteractor -import com.chooloo.www.chooloolib.model.Call -import com.chooloo.www.chooloolib.model.Call.State.* -import com.chooloo.www.chooloolib.model.CantHoldCallException -import com.chooloo.www.chooloolib.model.CantMergeCallException -import com.chooloo.www.chooloolib.model.CantSwapCallException -import com.chooloo.www.chooloolib.service.CallService import com.chooloo.www.chooloolib.ui.base.BaseViewState -import com.chooloo.www.chooloolib.ui.widgets.CallActions +import com.chooloo.www.chooloolib.ui.callactions.CallActions import com.chooloo.www.chooloolib.util.DataLiveEvent import com.chooloo.www.chooloolib.util.LiveEvent +import com.chooloo.www.chooloolib.util.MutableDataLiveEvent +import com.chooloo.www.chooloolib.util.MutableLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.launch import java.util.concurrent.TimeUnit import javax.inject.Inject @HiltViewModel class CallViewState @Inject constructor( - private val telecomManger: TelecomManager, private val calls: CallsInteractor, private val audios: AudiosInteractor, private val colors: ColorsInteractor, @@ -52,32 +62,61 @@ class CallViewState @Inject constructor( CallAudiosInteractor.Listener, CallActions.CallActionsListener { - val name = MutableLiveData() - val imageRes = MutableLiveData() - val uiState = MutableLiveData() - val imageURI = MutableLiveData(null) - val bannerText = MutableLiveData() - val stateText = MutableLiveData() - val elapsedTime = MutableLiveData() - val stateTextColor = MutableLiveData() - - val isHoldEnabled = MutableLiveData() - val isMuteEnabled = MutableLiveData() - val isSwapEnabled = MutableLiveData() - val isMergeEnabled = MutableLiveData() - val isMuteActivated = MutableLiveData() - val isHoldActivated = MutableLiveData() - val isManageEnabled = MutableLiveData(false) - val isSpeakerEnabled = MutableLiveData(true) - val isSpeakerActivated = MutableLiveData() - val isBluetoothActivated = MutableLiveData() - - val showDialerEvent = LiveEvent() - val showDialpadEvent = LiveEvent() - val askForRouteEvent = LiveEvent() - val showCallManagerEvent = LiveEvent() - val selectPhoneHandleEvent = DataLiveEvent>() - val selectPhoneSuggestionEvent = DataLiveEvent>() + private val _name = MutableLiveData() + private val _imageRes = MutableLiveData() + private val _uiState = MutableLiveData(UIState.ACTIVE) + private val _imageURI = MutableLiveData(null) + private val _bannerText = MutableLiveData() + private val _stateText = MutableLiveData() + private val _elapsedTime = MutableLiveData() + private val _stateTextColor = MutableLiveData() + + private val _isHoldEnabled = MutableLiveData() + private val _isMuteEnabled = MutableLiveData() + private val _isSwapEnabled = MutableLiveData() + private val _isMergeEnabled = MutableLiveData() + private val _isMuteActivated = MutableLiveData() + private val _isHoldActivated = MutableLiveData() + private val _isManageEnabled = MutableLiveData(false) + private val _isSpeakerEnabled = MutableLiveData(true) + private val _isSpeakerActivated = MutableLiveData() + private val _isBluetoothActivated = MutableLiveData() + + private val _showDialerEvent = MutableLiveEvent() + private val _showDialpadEvent = MutableLiveEvent() + private val _askForRouteEvent = MutableLiveEvent() + private val _showCallManagerEvent = MutableLiveEvent() + private val _selectPhoneHandleEvent = MutableDataLiveEvent>() + private val _selectPhoneSuggestionEvent = MutableDataLiveEvent>() + + + val name = _name as LiveData + val imageRes = _imageRes as LiveData + val uiState = _uiState as LiveData + val imageURI = _imageURI as LiveData + val bannerText = _bannerText as LiveData + val stateText = _stateText as LiveData + val elapsedTime = _elapsedTime as LiveData + val stateTextColor = _stateTextColor as LiveData + + val isHoldEnabled = _isHoldEnabled as LiveData + val isMuteEnabled = _isMuteEnabled as LiveData + val isSwapEnabled = _isSwapEnabled as LiveData + val isMergeEnabled = _isMergeEnabled as LiveData + val isMuteActivated = _isMuteActivated as LiveData + val isHoldActivated = _isHoldActivated as LiveData + val isManageEnabled = _isManageEnabled as LiveData + val isSpeakerEnabled = _isSpeakerEnabled as LiveData + val isSpeakerActivated = _isSpeakerActivated as LiveData + val isBluetoothActivated = _isBluetoothActivated as LiveData + + val showDialerEvent = _showDialerEvent as LiveEvent + val showDialpadEvent = _showDialpadEvent as LiveEvent + val askForRouteEvent = _askForRouteEvent as LiveEvent + val showCallManagerEvent = _showCallManagerEvent as LiveEvent + val selectPhoneHandleEvent = _selectPhoneHandleEvent as DataLiveEvent> + val selectPhoneSuggestionEvent = + _selectPhoneSuggestionEvent as DataLiveEvent> private var _currentCallId: String? = null @@ -100,7 +139,12 @@ class CallViewState @Inject constructor( callAudios.audioRoute?.let(this@CallViewState::onAudioRouteChanged) } - isManageEnabled.value = false + _isManageEnabled.value = false + +// // text code +// _uiState.value = UIState.INCOMING +// _elapsedTime.value = 1L +// _stateText.value = strings.getString(R.string.call_status_connecting) } override fun detach() { @@ -109,27 +153,27 @@ class CallViewState @Inject constructor( } fun onAnswerClick() { - _currentCallId?.let { calls.answerCall(it) } + _currentCallId?.let(calls::answerCall) } fun onRejectClick() { - _currentCallId?.let { calls.rejectCall(it) } + _currentCallId?.let(calls::rejectCall) } override fun onSwapClick() { try { - _currentCallId?.let { calls.swapCall(it) } + _currentCallId?.let(calls::swapCall) } catch (e: CantSwapCallException) { - errorEvent.call(R.string.error_cant_swap_calls) + onError(R.string.error_cant_swap_calls) e.printStackTrace() } } override fun onHoldClick() { try { - _currentCallId?.let { calls.toggleHold(it) } + _currentCallId?.let(calls::toggleHold) } catch (e: CantHoldCallException) { - errorEvent.call(R.string.error_cant_hold_call) + onError(R.string.error_cant_hold_call) e.printStackTrace() } } @@ -140,25 +184,25 @@ class CallViewState @Inject constructor( override fun onMergeClick() { try { - _currentCallId?.let { calls.mergeCall(it) } + _currentCallId?.let(calls::mergeCall) } catch (e: CantMergeCallException) { - errorEvent.call(R.string.error_cant_merge_call) + onError(R.string.error_cant_merge_call) e.printStackTrace() } } override fun onKeypadClick() { - showDialpadEvent.call() + _showDialpadEvent.call() } override fun onAddCallClick() { - showDialerEvent.call() + _showDialerEvent.call() } override fun onSpeakerClick() { callAudios.apply { if (supportedAudioRoutes.contains(AudioRoute.BLUETOOTH)) { - askForRouteEvent.call() + _askForRouteEvent.call() } else { isSpeakerOn = !isSpeakerActivated.value!! } @@ -172,21 +216,21 @@ class CallViewState @Inject constructor( override fun onNoCalls() { audios.audioMode = NORMAL - finishEvent.call() + onFinish() } override fun onCallChanged(call: Call) { if (calls.getFirstState(HOLDING)?.id == _currentCallId) { - bannerText.value = null + _bannerText.value = null } else if (call.isHolding && _currentCallId != call.id && !call.isInConference) { - phones.lookupAccount(call.number) { - bannerText.value = String.format( + viewModelScope.launch { + _bannerText.value = String.format( strings.getString(R.string.explain_is_on_hold), - it?.displayString ?: call.number + phones.lookupAccount(call.number)?.displayString ?: call.number ) } } else if (calls.getStateCount(HOLDING) == 0) { - bannerText.value = null + _bannerText.value = null } } @@ -194,69 +238,76 @@ class CallViewState @Inject constructor( _currentCallId = call.id if (call.isEnterprise) { - imageRes.value = R.drawable.corporate_fare + _imageRes.value = R.drawable.corporate_fare } if (call.isIncoming) { - uiState.value = UIState.INCOMING + _uiState.value = UIState.INCOMING } if (call.isConference) { - name.value = strings.getString(R.string.conference) + _name.value = strings.getString(R.string.conference) } else { - phones.lookupAccount(call.number) { account -> - account?.photoUri?.let { imageURI.value = Uri.parse(it) } - name.value = account?.displayString ?: call.number + viewModelScope.launch { + val account = phones.lookupAccount(call.number) + account?.photoUri?.let { _imageURI.value = Uri.parse(it) } + _name.value = account?.displayString ?: call.number } } - isHoldActivated.value = call.isHolding - isManageEnabled.value = call.isConference - isHoldEnabled.value = call.isCapable(CAPABILITY_HOLD) - isMuteEnabled.value = call.isCapable(CAPABILITY_MUTE) - isSwapEnabled.value = call.isCapable(CAPABILITY_SWAP_CONFERENCE) - stateText.value = strings.getString(call.state.stringRes) + _isHoldActivated.value = call.isHolding + _isManageEnabled.value = call.isConference + _isHoldEnabled.value = call.isCapable(CAPABILITY_HOLD) + _isMuteEnabled.value = call.isCapable(CAPABILITY_MUTE) + _isSwapEnabled.value = call.isCapable(CAPABILITY_SWAP_CONFERENCE) + _stateText.value = strings.getString(call.state.stringRes) when { - call.isIncoming -> uiState.value = UIState.INCOMING - calls.isMultiCall -> uiState.value = UIState.MULTI - else -> uiState.value = UIState.ACTIVE + call.isIncoming -> _uiState.value = UIState.INCOMING + calls.isMultiCall -> _uiState.value = UIState.MULTI + else -> _uiState.value = UIState.ACTIVE } when (call.state) { - INCOMING, ACTIVE -> stateTextColor.value = - colors.getColor(R.color.green_on_primary_container) - HOLDING, DISCONNECTING, DISCONNECTED -> stateTextColor.value = - colors.getAttrColor(R.attr.colorError) + ACTIVE, + INCOMING -> _stateTextColor.value = + colors.getColor(R.color.on_positive) + + HOLDING, + DISCONNECTING, + DISCONNECTED -> _stateTextColor.value = + colors.getColor(R.color.on_negative) + + else -> {} } if (call.state == SELECT_PHONE_ACCOUNT && !call.phoneAccountSelected) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - selectPhoneSuggestionEvent.call(call.suggestedPhoneAccounts) + _selectPhoneSuggestionEvent.call(call.suggestedPhoneAccounts) } else { - selectPhoneHandleEvent.call(call.availablePhoneAccounts) + _selectPhoneHandleEvent.call(call.availablePhoneAccounts) } } } override fun onMuteChanged(isMuted: Boolean) { - isMuteActivated.value = isMuted + _isMuteActivated.value = isMuted } override fun onAudioRouteChanged(audioRoute: AudioRoute) { - isSpeakerActivated.value = audioRoute == AudioRoute.SPEAKER - isBluetoothActivated.value = audioRoute == AudioRoute.BLUETOOTH + _isSpeakerActivated.value = audioRoute == AudioRoute.SPEAKER + _isBluetoothActivated.value = audioRoute == AudioRoute.BLUETOOTH } private fun displayCallTime() { calls.mainCall?.let { - elapsedTime.value = if (it.isStarted) it.durationTimeMilis else null + _elapsedTime.value = if (it.isStarted) it.durationTimeMilis else null } } fun onManageClick() { - showCallManagerEvent.call() + _showCallManagerEvent.call() } fun onAudioRoutePicked(audioRoute: AudioRoute) { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callactions/CallAction.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callactions/CallAction.kt new file mode 100644 index 000000000..ee359e680 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callactions/CallAction.kt @@ -0,0 +1,13 @@ +package com.chooloo.www.chooloolib.ui.callactions + +import androidx.annotation.DrawableRes + +abstract class CallAction { + var tempIconRes: Int? = null + var isEnabled: Boolean = true + var isActivated: Boolean = false + open val checkedIconRes: Int? = null + + abstract val idRes: Int + @get:DrawableRes abstract val iconRes: Int +} diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callactions/CallActions.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callactions/CallActions.kt new file mode 100644 index 000000000..dca29cc39 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callactions/CallActions.kt @@ -0,0 +1,222 @@ +package com.chooloo.www.chooloolib.ui.callactions + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.GridLayoutManager +import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.adapter.CallActionsAdapter +import com.chooloo.www.chooloolib.databinding.CallActionsBinding +import com.chooloo.www.chooloolib.util.GridSpacingItemDecoration + + +class CallActions : ConstraintLayout { + private val _binding: CallActionsBinding + private var _isBluetoothActivated: Boolean = false + private var _callActionsListener: CallActionsListener? = null + private val _adapter by lazy { CallActionsAdapter() } + + constructor(context: Context) : this(context, null) + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + _callActionsListener = getEmptyListener() + + _binding = CallActionsBinding.inflate(LayoutInflater.from(context), this, true) + _binding.callActionsRecyclerView.apply { + layoutManager = GridLayoutManager(context, 3) + adapter = _adapter + val gridPadding = resources.getDimension(R.dimen.default_spacing_medium).toInt() + addItemDecoration(GridSpacingItemDecoration(3, gridPadding, false)) + } + + _adapter.setOnCallActionClickListener { + when (it.idRes) { + R.string.call_action_id_hold -> _callActionsListener?.onHoldClick() + R.string.call_action_id_mute -> _callActionsListener?.onMuteClick() + R.string.call_action_id_swap -> _callActionsListener?.onSwapClick() + R.string.call_action_id_add -> _callActionsListener?.onAddCallClick() + R.string.call_action_id_merge -> _callActionsListener?.onMergeClick() + R.string.call_action_id_keypad -> _callActionsListener?.onKeypadClick() + R.string.call_action_id_speaker -> _callActionsListener?.onSpeakerClick() + } + } + } + + + var isHoldActivated: Boolean + get() = _adapter.isCallActionActivated(R.string.call_action_id_hold) + set(value) { + _adapter.setCallActionActivated(R.string.call_action_id_hold, value) + } + + var isMuteActivated: Boolean + get() = _adapter.isCallActionActivated(R.string.call_action_id_mute) + set(value) { + _adapter.setCallActionActivated(R.string.call_action_id_mute, value) + } + + var isSpeakerActivated: Boolean + get() = _adapter.isCallActionActivated(R.string.call_action_id_speaker) + set(value) { + _adapter.setCallActionActivated(R.string.call_action_id_speaker, value) + } + + var isBluetoothActivated: Boolean + get() = _isBluetoothActivated + set(value) { + _isBluetoothActivated = value + _adapter.setCallActionIcon( + R.string.call_action_id_speaker, + if (value) R.drawable.bluetooth_searching else R.drawable.volume_down + ) + if (!value) { + isSpeakerActivated = isSpeakerActivated + } + } + + + var isHoldEnabled: Boolean + get() = _adapter.isCallActionEnabled(R.string.call_action_id_hold) + set(value) { + _adapter.setCallActionEnabled(R.string.call_action_id_hold, value) + } + + var isMuteEnabled: Boolean + get() = _adapter.isCallActionEnabled(R.string.call_action_id_mute) + set(value) { + _adapter.setCallActionEnabled(R.string.call_action_id_mute, value) + } + + var isSwapEnabled: Boolean + get() = _adapter.isCallActionEnabled(R.string.call_action_id_swap) + set(value) { + _adapter.setCallActionEnabled(R.string.call_action_id_swap, value) + } + + var isMergeEnabled: Boolean + get() = _adapter.isCallActionEnabled(R.string.call_action_id_merge) + set(value) { + _adapter.setCallActionEnabled(R.string.call_action_id_merge, value) + } + + var isSpeakerEnabled: Boolean + get() = _adapter.isCallActionEnabled(R.string.call_action_id_speaker) + set(value) { + _adapter.setCallActionEnabled(R.string.call_action_id_speaker, value) + } + + + fun showSingleCallUI() { + _adapter.addCallActions( + listOf( + callActionAdd, + callActionMute, + callActionHold, + callActionKeypad, + callActionSpeaker, + ) + ) + _adapter.removeCallActions( + listOf( + callActionSwap, + callActionMerge + ) + ) + } + + fun showMultiCallUI() { + _adapter.addCallActions( + listOf( + callActionAdd, + callActionMute, + callActionHold, + callActionSwap, + callActionKeypad, + callActionMerge, + callActionSpeaker, + ) + ) + } + + fun setCallActionsListener(callActionsListener: CallActionsListener?) { + _callActionsListener = callActionsListener + } + + + private fun getEmptyListener() = object : CallActionsListener { + override fun onHoldClick() {} + override fun onMuteClick() {} + override fun onSwapClick() {} + override fun onMergeClick() {} + override fun onKeypadClick() {} + override fun onSpeakerClick() {} + override fun onAddCallClick() {} + } + + + interface CallActionsListener { + fun onHoldClick() + fun onMuteClick() + fun onSwapClick() + fun onMergeClick() + fun onKeypadClick() + fun onSpeakerClick() + fun onAddCallClick() + } + + private val callActionKeypad by lazy { + object : CallAction() { + override val iconRes = R.drawable.dialpad + override val idRes = R.string.call_action_id_keypad + } + } + + private val callActionAdd by lazy { + object : CallAction() { + override val iconRes = R.drawable.add + override val idRes = R.string.call_action_id_add + } + } + + private val callActionMute by lazy { + object : CallAction() { + override val iconRes = R.drawable.mic + override val checkedIconRes = R.drawable.mic_off + override val idRes = R.string.call_action_id_mute + } + } + private val callActionSpeaker by lazy { + object : CallAction() { + override val iconRes = R.drawable.volume_down + override val checkedIconRes = R.drawable.volume_up + override val idRes = R.string.call_action_id_speaker + } + } + + private val callActionHold by lazy { + object : CallAction() { + override val iconRes = R.drawable.pause + override val idRes = R.string.call_action_id_hold + override val checkedIconRes = R.drawable.play_arrow + } + } + + private val callActionMerge by lazy { + object : CallAction() { + override val iconRes = R.drawable.call_merge + override val idRes = R.string.call_action_id_merge + } + } + + private val callActionSwap by lazy { + object : CallAction() { + override val iconRes = R.drawable.swap_calls + override val idRes = R.string.call_action_id_swap + } + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callitems/CallItemsFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callitems/CallItemsFragment.kt index 456034457..08bc873f2 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callitems/CallItemsFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callitems/CallItemsFragment.kt @@ -3,7 +3,7 @@ package com.chooloo.www.chooloolib.ui.callitems import androidx.core.view.isVisible import androidx.fragment.app.viewModels import com.chooloo.www.chooloolib.adapter.CallItemsAdapter -import com.chooloo.www.chooloolib.model.Call +import com.chooloo.www.chooloolib.data.model.Call import com.chooloo.www.chooloolib.ui.list.ListFragment import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callitems/CallItemsViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callitems/CallItemsViewState.kt index db7358bde..0eff919c8 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callitems/CallItemsViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/callitems/CallItemsViewState.kt @@ -1,31 +1,32 @@ package com.chooloo.www.chooloolib.ui.callitems -import androidx.lifecycle.LiveData -import com.chooloo.www.chooloolib.model.Call -import com.chooloo.www.chooloolib.repository.calls.CallsRepository +import com.chooloo.www.chooloolib.data.model.Call +import com.chooloo.www.chooloolib.data.repository.calls.CallsRepository +import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import com.chooloo.www.chooloolib.ui.list.ListViewState import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow import javax.inject.Inject @HiltViewModel class CallItemsViewState @Inject constructor( + permissions: PermissionsInteractor, private val callsRepository: CallsRepository ) : - ListViewState() { + ListViewState(permissions) { override fun onItemLeftClick(item: Call) { super.onItemLeftClick(item) item.leaveConference() - finishEvent.call() + onFinish() } override fun onItemRightClick(item: Call) { super.onItemRightClick(item) item.reject() - finishEvent.call() + onFinish() } - override fun getItemsObservable(callback: (LiveData>) -> Unit) { - callback.invoke(callsRepository.getCalls()) - } + override fun getItemsFlow(filter: String?): Flow>? = + callsRepository.getCalls() } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsFragment.kt index fbe4b0078..08e31b6b6 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsFragment.kt @@ -2,9 +2,8 @@ package com.chooloo.www.chooloolib.ui.contacts import androidx.fragment.app.activityViewModels import com.chooloo.www.chooloolib.adapter.ContactsAdapter -import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory import com.chooloo.www.chooloolib.interactor.prompt.PromptsInteractor -import com.chooloo.www.chooloolib.model.ContactAccount +import com.chooloo.www.chooloolib.data.model.ContactAccount import com.chooloo.www.chooloolib.ui.list.ListFragment import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -15,7 +14,6 @@ open class ContactsFragment @Inject constructor() : override val viewState: ContactsViewState by activityViewModels() @Inject lateinit var prompts: PromptsInteractor - @Inject lateinit var fragmentFactory: FragmentFactory @Inject override lateinit var adapter: ContactsAdapter diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsViewState.kt index 07b985986..6cbab105a 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsViewState.kt @@ -1,50 +1,40 @@ package com.chooloo.www.chooloolib.ui.contacts import android.Manifest.permission.READ_CONTACTS -import androidx.lifecycle.LiveData import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.data.model.ContactAccount +import com.chooloo.www.chooloolib.data.repository.contacts.ContactsRepository import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor -import com.chooloo.www.chooloolib.livedata.ContactsLiveData -import com.chooloo.www.chooloolib.model.ContactAccount -import com.chooloo.www.chooloolib.repository.contacts.ContactsRepository import com.chooloo.www.chooloolib.ui.list.ListViewState import com.chooloo.www.chooloolib.util.DataLiveEvent +import com.chooloo.www.chooloolib.util.MutableDataLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow import javax.inject.Inject @HiltViewModel open class ContactsViewState @Inject constructor( - contactsRepository: ContactsRepository, - private val permissions: PermissionsInteractor, + permissions: PermissionsInteractor, + private val contactsRepository: ContactsRepository, ) : - ListViewState() { + ListViewState(permissions) { override val noResultsIconRes = R.drawable.group override val noResultsTextRes = R.string.error_no_results_contacts - override val noPermissionsTextRes = R.string.error_no_permissions_contacts + override val permissionsImageRes = R.drawable.group + override val permissionsTextRes = R.string.error_no_permissions_contacts + override val requiredPermissions = listOf(READ_CONTACTS) - private val contactsLiveData = contactsRepository.getContacts() as ContactsLiveData + private val _showContactEvent = MutableDataLiveEvent() - val showContactEvent = DataLiveEvent() + val showContactEvent = _showContactEvent as DataLiveEvent - override fun onFilterChanged(filter: String?) { - super.onFilterChanged(filter) - if (permissions.hasSelfPermission(READ_CONTACTS)) { - onPermissionsChanged(true) - contactsLiveData.filter = filter - } - } - override fun onItemClick(item: ContactAccount) { super.onItemClick(item) - showContactEvent.call(item.id) + _showContactEvent.call(item.id) } - override fun getItemsObservable(callback: (LiveData>) -> Unit) { - permissions.runWithReadContactsPermissions { - onPermissionsChanged(it) - if (it) callback.invoke(contactsLiveData) - } - } + override fun getItemsFlow(filter: String?): Flow>? = + contactsRepository.getContacts(filter) } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsSuggestionsFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/suggestions/ContactsSuggestionsFragment.kt similarity index 86% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsSuggestionsFragment.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/suggestions/ContactsSuggestionsFragment.kt index 094233a6a..2365fda84 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsSuggestionsFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/suggestions/ContactsSuggestionsFragment.kt @@ -1,7 +1,8 @@ -package com.chooloo.www.chooloolib.ui.contacts +package com.chooloo.www.chooloolib.ui.contacts.suggestions import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels +import com.chooloo.www.chooloolib.ui.contacts.ContactsFragment import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsSuggestionsViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/suggestions/ContactsSuggestionsViewState.kt similarity index 55% rename from chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsSuggestionsViewState.kt rename to chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/suggestions/ContactsSuggestionsViewState.kt index 7b71a8466..c9fa8288a 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/ContactsSuggestionsViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/contacts/suggestions/ContactsSuggestionsViewState.kt @@ -1,7 +1,8 @@ -package com.chooloo.www.chooloolib.ui.contacts +package com.chooloo.www.chooloolib.ui.contacts.suggestions import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor -import com.chooloo.www.chooloolib.repository.contacts.ContactsRepository +import com.chooloo.www.chooloolib.data.repository.contacts.ContactsRepository +import com.chooloo.www.chooloolib.ui.contacts.ContactsViewState import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -9,4 +10,4 @@ import javax.inject.Inject class ContactsSuggestionsViewState @Inject constructor( permissions: PermissionsInteractor, contactsRepository: ContactsRepository -) : ContactsViewState(contactsRepository, permissions) \ No newline at end of file +) : ContactsViewState(permissions,contactsRepository) \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialer/DialerFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialer/DialerFragment.kt index 6a9d5e9c9..35f3d0960 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialer/DialerFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialer/DialerFragment.kt @@ -1,27 +1,24 @@ package com.chooloo.www.chooloolib.ui.dialer -import android.content.Context import android.os.Bundle import android.telephony.PhoneNumberFormattingTextWatcher import android.view.View import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels -import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory +import androidx.fragment.app.commitNow import com.chooloo.www.chooloolib.interactor.telecom.TelecomInteractor -import com.chooloo.www.chooloolib.ui.contacts.ContactsSuggestionsViewState +import com.chooloo.www.chooloolib.ui.contacts.suggestions.ContactsSuggestionsViewState import com.chooloo.www.chooloolib.ui.dialpad.DialpadFragment import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint class DialerFragment @Inject constructor() : DialpadFragment() { - override val viewState: DialerViewState by viewModels() + override val viewState: DialerViewState by activityViewModels() private val suggestionsViewState: ContactsSuggestionsViewState by activityViewModels() private val _suggestionsFragment by lazy { fragmentFactory.getContactsSuggestionsFragment() } - @Inject lateinit var fragmentFactory: FragmentFactory @Inject lateinit var telecomInteractor: TelecomInteractor @@ -109,19 +106,13 @@ class DialerFragment @Inject constructor() : DialpadFragment() { } } - suggestionsViewState.itemsChangedEvent.observe(this@DialerFragment) { - it.peekContent()?.let(viewState::onSuggestionsChanged) - } + suggestionsViewState.items.observe(this@DialerFragment, viewState::onSuggestionsChanged) args.getString(ARG_NUMBER)?.forEach(viewState::onCharClick) - } - override fun onAttach(context: Context) { - super.onAttach(context) - childFragmentManager - .beginTransaction() - .add(binding.dialpadSuggestionsContainer.id, _suggestionsFragment) - .commitNow() + childFragmentManager.commitNow { + add(binding.dialpadSuggestionsContainer.id, _suggestionsFragment) + } } companion object { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialer/DialerViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialer/DialerViewState.kt index a23c900ad..d5b5b1e52 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialer/DialerViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialer/DialerViewState.kt @@ -1,15 +1,19 @@ package com.chooloo.www.chooloolib.ui.dialer +import android.Manifest import android.content.ClipboardManager import androidx.lifecycle.MutableLiveData import com.chooloo.www.chooloolib.interactor.audio.AudiosInteractor import com.chooloo.www.chooloolib.interactor.navigation.NavigationsInteractor import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor import com.chooloo.www.chooloolib.interactor.recents.RecentsInteractor -import com.chooloo.www.chooloolib.model.ContactAccount +import com.chooloo.www.chooloolib.data.model.ContactAccount +import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import com.chooloo.www.chooloolib.ui.dialpad.DialpadViewState import com.chooloo.www.chooloolib.util.DataLiveEvent import com.chooloo.www.chooloolib.util.LiveEvent +import com.chooloo.www.chooloolib.util.MutableDataLiveEvent +import com.chooloo.www.chooloolib.util.MutableLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -17,17 +21,26 @@ import javax.inject.Inject class DialerViewState @Inject constructor( audios: AudiosInteractor, preferences: PreferencesInteractor, + clipboardManager: ClipboardManager, + permissions: PermissionsInteractor, private val recents: RecentsInteractor, - private val clipboardManager: ClipboardManager, - private val navigations: NavigationsInteractor + private val navigations: NavigationsInteractor, ) : - DialpadViewState(audios, clipboardManager, preferences) { + DialpadViewState(permissions, audios, preferences, clipboardManager) { - val callVoicemailEvent = LiveEvent() - val callNumberEvent = DataLiveEvent() - val isSuggestionsVisible = MutableLiveData(false) - val isDeleteButtonVisible = MutableLiveData() - val isAddContactButtonVisible = MutableLiveData() + override val requiredPermissions = listOf(Manifest.permission.CALL_PHONE) + + private val _isSuggestionsVisible = MutableLiveData(false) + private val _isDeleteButtonVisible = MutableLiveData() + private val _isAddContactButtonVisible = MutableLiveData() + private val _callVoicemailEvent = MutableLiveEvent() + private val _callNumberEvent = MutableDataLiveEvent() + + val isSuggestionsVisible = _isSuggestionsVisible as MutableLiveData + val isDeleteButtonVisible = _isDeleteButtonVisible as MutableLiveData + val isAddContactButtonVisible = _isAddContactButtonVisible as MutableLiveData + val callVoicemailEvent = _callVoicemailEvent as LiveEvent + val callNumberEvent = _callNumberEvent as DataLiveEvent override fun onLongKeyClick(char: Char) = when (char) { @@ -36,7 +49,7 @@ class DialerViewState @Inject constructor( true } '1' -> { - callVoicemailEvent.call() + _callVoicemailEvent.call() true } else -> super.onLongKeyClick(char) @@ -50,29 +63,29 @@ class DialerViewState @Inject constructor( } fun onDeleteClick() { - text.value?.dropLast(1)?.let(this::onTextChanged) + _text.value?.dropLast(1)?.let(this::onTextChanged) } fun onLongDeleteClick(): Boolean { onTextChanged("") - text.value = "" + _text.value = "" return true } fun onCallClick() { if (text.value?.isEmpty() == true) { - text.value = recents.getLastOutgoingCall() + _text.value = recents.getLastOutgoingCall() } else { - text.value?.let(callNumberEvent::call) - finishEvent.call() + _text.value?.let(_callNumberEvent::call) + onFinish() } } fun onAddContactClick() { - text.value?.let(navigations::addContact) + _text.value?.let(navigations::addContact) } fun onSuggestionsChanged(contacts: List) { - isSuggestionsVisible.value = contacts.isNotEmpty() && text.value?.isNotEmpty() == true + isSuggestionsVisible.value = contacts.isNotEmpty() && _text.value?.isNotEmpty() == true } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialpad/DialpadFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialpad/DialpadFragment.kt index 3f4269c44..9fb7ad5bf 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialpad/DialpadFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialpad/DialpadFragment.kt @@ -6,14 +6,15 @@ import androidx.fragment.app.activityViewModels import com.chooloo.www.chooloolib.databinding.DialpadBinding import com.chooloo.www.chooloolib.interactor.animation.AnimationsInteractor import com.chooloo.www.chooloolib.ui.base.BaseFragment +import com.chooloo.www.chooloolib.ui.permissioned.PermissionedFragment import com.chooloo.www.chooloolib.ui.widgets.DialpadKey import com.chooloo.www.chooloolib.ui.widgets.TextContextMenuItemListener import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint -open class DialpadFragment @Inject constructor() : BaseFragment(), TextContextMenuItemListener { - override val contentView by lazy { binding.root } +open class DialpadFragment @Inject constructor() : PermissionedFragment(), TextContextMenuItemListener { + override val mainContentView by lazy { binding.root } override val viewState: DialpadViewState by activityViewModels() @Inject lateinit var animationsInteractor: AnimationsInteractor diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialpad/DialpadViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialpad/DialpadViewState.kt index 72c424d62..99d0de372 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialpad/DialpadViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/dialpad/DialpadViewState.kt @@ -1,27 +1,36 @@ package com.chooloo.www.chooloolib.ui.dialpad import android.content.ClipboardManager +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.chooloo.www.chooloolib.interactor.audio.AudiosInteractor +import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor -import com.chooloo.www.chooloolib.ui.base.BaseViewState +import com.chooloo.www.chooloolib.ui.permissioned.PermissionedViewState import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @HiltViewModel open class DialpadViewState @Inject constructor( + permissions: PermissionsInteractor, private val audios: AudiosInteractor, + private val preferences: PreferencesInteractor, private val clipboardManager: ClipboardManager, - private val preferences: PreferencesInteractor ) : - BaseViewState() { + PermissionedViewState(permissions) { - val text = MutableLiveData("") - val char = MutableLiveData() + private val _char = MutableLiveData() + protected val _text = MutableLiveData("") + val char = _char as LiveData + val text = _text as LiveData + + open fun onTextChanged(text: String) { + _text.value = text + } open fun onCharClick(char: Char) { - this.char.value = char + _char.value = char if (preferences.isDialpadTones) audios.playToneByChar(char) if (preferences.isDialpadVibrate) audios.vibrate(AudiosInteractor.SHORT_VIBRATE_LENGTH) onTextChanged((text.value ?: "") + char) @@ -34,10 +43,5 @@ open class DialpadViewState @Inject constructor( val text = item?.text.toString().replace(Regex("[^+#*0-9]"), "") onTextChanged(text) } - - - protected open fun onTextChanged(text: String) { - this.text.value = text - } } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/list/ListFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/list/ListFragment.kt index 4cd0e490a..46f87f18d 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/list/ListFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/list/ListFragment.kt @@ -3,18 +3,24 @@ package com.chooloo.www.chooloolib.ui.list import androidx.core.view.isVisible import com.chooloo.www.chooloolib.adapter.ListAdapter import com.chooloo.www.chooloolib.databinding.ItemsBinding -import com.chooloo.www.chooloolib.ui.base.BaseFragment +import com.chooloo.www.chooloolib.ui.permissioned.PermissionedFragment + +abstract class ListFragment> : PermissionedFragment() { + override val mainContentView by lazy { binding.root } -abstract class ListFragment> : BaseFragment() { - override val contentView by lazy { binding.root } protected val binding by lazy { ItemsBinding.inflate(layoutInflater) } + override fun onSetup() { viewState.apply { isEmpty.observe(this@ListFragment, this@ListFragment::showEmpty) emptyMessage.observe(this@ListFragment, binding.empty.emptyText::setText) emptyIcon.observe(this@ListFragment, binding.empty.emptyIcon::setImageResource) + items.observe(this@ListFragment) { + adapter.items = it + } + filter.observe(this@ListFragment) { adapter.titleFilter = it } @@ -26,14 +32,6 @@ abstract class ListFragment> : BaseFragme isScrollerVisible.observe(this@ListFragment) { binding.itemsScrollView.fastScroller.isVisible = it } - - itemsChangedEvent.observe(this@ListFragment) { ev -> - ev.ifNew?.let { adapter.items = it } - } - - if (args.getBoolean(ARG_OBSERVE, true)) { - getItemsObservable { it.observe(this@ListFragment, viewState::onItemsChanged) } - } } adapter.apply { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/list/ListViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/list/ListViewState.kt index ae87b0607..ed76d23df 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/list/ListViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/list/ListViewState.kt @@ -2,56 +2,82 @@ package com.chooloo.www.chooloolib.ui.list import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.chooloo.www.chooloolib.ui.base.BaseViewState +import androidx.lifecycle.viewModelScope +import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor +import com.chooloo.www.chooloolib.ui.permissioned.PermissionedViewState import com.chooloo.www.chooloolib.util.DataLiveEvent +import com.chooloo.www.chooloolib.util.LiveEvent +import com.chooloo.www.chooloolib.util.MutableDataLiveEvent +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch -abstract class ListViewState : BaseViewState() { - val filter = MutableLiveData() - val emptyIcon = MutableLiveData() - val emptyMessage = MutableLiveData() - val isEmpty = MutableLiveData(true) - val isLoading = MutableLiveData(true) - val isScrollerVisible = MutableLiveData() +abstract class ListViewState( + permissions: PermissionsInteractor +) : PermissionedViewState(permissions) { + private var _itemsCollectJob: Job? = null + private var _itemsFlow: Flow>? = null - val itemClickedEvent = DataLiveEvent() - val itemLongClickedEvent = DataLiveEvent() - val itemsChangedEvent = DataLiveEvent>() + private val _items = MutableLiveData>() + private val _filter = MutableLiveData() + private val _emptyIcon = MutableLiveData() + private val _emptyMessage = MutableLiveData() + private val _isEmpty = MutableLiveData(true) + private val _isLoading = MutableLiveData(true) + private val _isScrollerVisible = MutableLiveData() + private val _itemClickedEvent = MutableDataLiveEvent() + private val _itemLongClickedEvent = MutableDataLiveEvent() + + val items = _items as LiveData> + val filter = _filter as LiveData + val emptyIcon = _emptyIcon as LiveData + val emptyMessage = _emptyMessage as LiveData + val isEmpty = _isEmpty as LiveData + val isLoading = _isLoading as LiveData + val isScrollerVisible = _isScrollerVisible as LiveData + val itemClickedEvent = _itemClickedEvent as DataLiveEvent + val itemLongClickedEvent = _itemLongClickedEvent as DataLiveEvent protected open val noResultsIconRes: Int? = null protected open val noResultsTextRes: Int? = null - protected open val noPermissionsTextRes: Int? = null - + override fun attach() { - emptyIcon.value = noResultsIconRes - emptyMessage.value = noResultsTextRes + _emptyIcon.value = noResultsIconRes + _emptyMessage.value = noResultsTextRes + updateItemsFlow() + } + + protected fun updateItemsFlow() { + _itemsCollectJob?.cancel() + _itemsCollectJob = viewModelScope.launch { + _itemsFlow = getItemsFlow(filter.value) + _itemsFlow?.collect(this@ListViewState::onItemsChanged) + } } open fun onItemsChanged(items: List) { - isLoading.value = false - isEmpty.value = items.isEmpty() - itemsChangedEvent.call(items) + _isLoading.value = false + _isEmpty.value = items.isEmpty() + _items.value = items } open fun onFilterChanged(filter: String?) { - this.filter.value = filter + _filter.value = filter + updateItemsFlow() } open fun onItemClick(item: ItemType) { - itemClickedEvent.call(item) + _itemClickedEvent.call(item) } open fun onItemLongClick(item: ItemType) { - itemLongClickedEvent.call(item) - } - - fun onPermissionsChanged(hasPermissions: Boolean) { - emptyMessage.value = if (hasPermissions) noResultsTextRes else noPermissionsTextRes - isEmpty.value = !hasPermissions + _itemLongClickedEvent.call(item) } open fun onItemLeftClick(item: ItemType) {} open fun onItemRightClick(item: ItemType) {} - abstract fun getItemsObservable(callback: (LiveData>) -> Unit) + abstract fun getItemsFlow(filter: String?): Flow>? } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permission/PermissionFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permission/PermissionFragment.kt new file mode 100644 index 000000000..c16bb25e6 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permission/PermissionFragment.kt @@ -0,0 +1,31 @@ +package com.chooloo.www.chooloolib.ui.permission + +import androidx.fragment.app.viewModels +import com.chooloo.www.chooloolib.databinding.PermissionBinding +import com.chooloo.www.chooloolib.ui.base.BaseFragment +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +class PermissionFragment @Inject constructor() : BaseFragment() { + override val contentView by lazy { _binding.root } + override val viewState: PermissionViewState by viewModels( + ownerProducer = { requireParentFragment() } + ) + + private val _binding by lazy { PermissionBinding.inflate(layoutInflater) } + + + override fun onSetup() { + viewState.apply { + textRes.observe(this@PermissionFragment, _binding.noPermissionText::setText) + imageRes.observe(this@PermissionFragment) { + _binding.noPermissionImage.setImageResource(it) + } + } + + _binding.apply { + noPermissionButton.setOnClickListener { viewState.onGrantClick() } + } + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permission/PermissionViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permission/PermissionViewState.kt new file mode 100644 index 000000000..6ded4fad3 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permission/PermissionViewState.kt @@ -0,0 +1,52 @@ +package com.chooloo.www.chooloolib.ui.permission + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor +import com.chooloo.www.chooloolib.ui.base.BaseViewState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class PermissionViewState @Inject constructor( + private val permissions: PermissionsInteractor +) : BaseViewState() { + private val _textRes = MutableLiveData() + private val _imageRes = MutableLiveData() + private var _isGranted = MutableLiveData() + private var _permissions: List = emptyList() + + val textRes: LiveData = _textRes + val imageRes: LiveData = _imageRes + val isGranted: LiveData = _isGranted + + + override fun attach() { + super.attach() + checkPermission() + } + + private fun checkPermission() { + viewModelScope.launch { + _isGranted.value = permissions.checkPermissions(*_permissions.toTypedArray()) + } + } + + fun onGrantClick() { + checkPermission() + } + + fun onTextRes(textRes: Int) { + _textRes.value = textRes + } + + fun onImageRes(imageRes: Int) { + _imageRes.value = imageRes + } + + fun onPermissions(permissions: List) { + _permissions = permissions + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissioned/PermissionedFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissioned/PermissionedFragment.kt new file mode 100644 index 000000000..b6158d238 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissioned/PermissionedFragment.kt @@ -0,0 +1,72 @@ +package com.chooloo.www.chooloolib.ui.permissioned + +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.fragment.app.commit +import androidx.fragment.app.viewModels +import com.chooloo.www.chooloolib.databinding.PermissionedBinding +import com.chooloo.www.chooloolib.ui.base.BaseFragment +import com.chooloo.www.chooloolib.ui.permission.PermissionViewState + +abstract class PermissionedFragment : BaseFragment() { + + override val contentView by lazy { _binding.root } + + private val _permissionViewState: PermissionViewState by viewModels() + private val _binding by lazy { PermissionedBinding.inflate(layoutInflater) } + private val _permissionFragment by lazy { fragmentFactory.getPermissionFragment() } + + + override fun onSetupHook() { + _permissionViewState.apply { + onPermissions(viewState.requiredPermissions) + viewState.permissionsTextRes?.let(::onTextRes) + viewState.permissionsImageRes?.let(::onImageRes) + isGranted.observe(this@PermissionedFragment, viewState::onIsPermissionGranted) + } + + viewState.shouldShowMainFragment.observe(this@PermissionedFragment) { + if (it) { + showMainFragment() + } else { + showPermissionsFragment() + } + } + + viewState.attachHook() + } + + private fun showMainFragment() { + childFragmentManager.findFragmentByTag(PERMISSIONS_FRAGMENT_TAG)?.let { fragment -> + childFragmentManager.commit { remove(fragment) } + } + + _binding.permissionedPermissionContainer.isVisible = false + mainContentView.parent?.let { (it as ViewGroup).removeView(mainContentView) } + _binding.root.addView(mainContentView) + super.onSetupHook() + } + + private fun showPermissionsFragment() { + _binding.root.removeView(mainContentView) + _binding.permissionedPermissionContainer.isVisible = true + + childFragmentManager.findFragmentByTag(PERMISSIONS_FRAGMENT_TAG)?.let { } ?: run { + childFragmentManager.commit { + add( + _binding.permissionedPermissionContainer.id, + fragmentFactory.getPermissionFragment(), + PERMISSIONS_FRAGMENT_TAG + ) + } + } + } + + abstract val mainContentView: View + + + companion object { + const val PERMISSIONS_FRAGMENT_TAG = "permissions_fragment" + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissioned/PermissionedViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissioned/PermissionedViewState.kt new file mode 100644 index 000000000..5a805a0d1 --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissioned/PermissionedViewState.kt @@ -0,0 +1,31 @@ +package com.chooloo.www.chooloolib.ui.permissioned + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor +import com.chooloo.www.chooloolib.ui.base.BaseViewState + +abstract class PermissionedViewState( + private val permissions: PermissionsInteractor +) : BaseViewState() { + private val _isPermissionGranted = MutableLiveData() + private val _shouldShowMainFragment = MutableLiveData() + + open val permissionsTextRes: Int? = null + open val permissionsImageRes: Int? = null + open val requiredPermissions: List = emptyList() + + val isPermissionGranted: LiveData = _isPermissionGranted + val shouldShowMainFragment: LiveData = _shouldShowMainFragment + + + fun attachHook() { + _shouldShowMainFragment.value = + permissions.hasSelfPermissions(requiredPermissions.toTypedArray()) + } + + fun onIsPermissionGranted(isGranted: Boolean) { + _isPermissionGranted.value = isGranted + _shouldShowMainFragment.value = isGranted + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissions/PermissionRequestActivity.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissions/PermissionRequestActivity.kt index a55d8434b..91c99f30f 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissions/PermissionRequestActivity.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/permissions/PermissionRequestActivity.kt @@ -1,7 +1,10 @@ package com.chooloo.www.chooloolib.ui.permissions +import android.content.Intent import android.content.pm.PackageManager +import android.net.Uri import android.os.Bundle +import android.provider.Settings import android.view.View import androidx.activity.viewModels import androidx.core.app.ActivityCompat @@ -11,12 +14,14 @@ import com.chooloo.www.chooloolib.ui.base.BaseViewState import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject + @AndroidEntryPoint class PermissionRequestActivity : BaseActivity() { override val contentView: View? = null override val viewState: BaseViewState by viewModels() - @Inject lateinit var permissions: PermissionsInteractor + @Inject + lateinit var permissions: PermissionsInteractor override fun onCreate(savedInstanceState: Bundle?) { @@ -63,6 +68,12 @@ class PermissionRequestActivity : BaseActivity() { finishWithResult(permissionResults) } + private fun goToSettings() { + startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", packageName, null) + }) + } + private fun finishWithResult(permissionResult: List = listOf()) { val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1 permissions.entryPermissionResult(permissionResult, requestCode) diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/phones/PhonesFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/phones/PhonesFragment.kt index c70c6e915..4560112e2 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/phones/PhonesFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/phones/PhonesFragment.kt @@ -5,7 +5,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.chooloo.www.chooloolib.adapter.PhonesAdapter import com.chooloo.www.chooloolib.interactor.telecom.TelecomInteractor -import com.chooloo.www.chooloolib.model.PhoneAccount +import com.chooloo.www.chooloolib.data.model.PhoneAccount import com.chooloo.www.chooloolib.ui.briefcontact.BriefContactFragment.Companion.ARG_CONTACT_ID import com.chooloo.www.chooloolib.ui.list.ListFragment import dagger.hilt.android.AndroidEntryPoint @@ -18,6 +18,7 @@ class PhonesFragment @Inject constructor() : ListFragment() { + ListViewState(permissions) { override val noResultsIconRes = R.drawable.call override val noResultsTextRes = R.string.error_no_results_phones - override val noPermissionsTextRes = R.string.error_no_permissions_phones + override val requiredPermissions = listOf(READ_CONTACTS) - val callEvent = DataLiveEvent() - val contactId = MutableLiveData(0L) + private val _contactId = MutableLiveData(0L) + private val _callEvent = MutableDataLiveEvent() - private val phonesLiveData get() = phonesRepository.getPhones(if (contactId.value == 0L) null else contactId.value) as PhonesLiveData + val contactId = _contactId as LiveData + val callEvent = _callEvent as DataLiveEvent - override fun onFilterChanged(filter: String?) { - super.onFilterChanged(filter) - phonesLiveData.filter = filter - } + override fun getItemsFlow(filter: String?): Flow> = + phonesRepository.getPhones(if (contactId.value == 0L) null else contactId.value, filter) override fun onItemRightClick(item: PhoneAccount) { super.onItemRightClick(item) permissions.runWithPermissions(arrayOf(READ_PHONE_STATE, CALL_PHONE), { - callEvent.call(item.number) + _callEvent.call(item.number) }, { - errorEvent.call(R.string.error_no_permissions_make_call) + onError(R.string.error_no_permissions_make_call) }) } @@ -52,17 +52,11 @@ class PhonesViewState @Inject constructor( clipboardManager.setPrimaryClip( ClipData.newPlainText("Copied number", item.number) ) - messageEvent.call(R.string.number_copied_to_clipboard) - } - - override fun getItemsObservable(callback: (LiveData>) -> Unit) { - permissions.runWithReadContactsPermissions { - onPermissionsChanged(it) - if (it) callback.invoke(phonesLiveData) - } + onMessage(R.string.number_copied_to_clipboard) } fun onContactId(contactId: Long) { - this.contactId.value = contactId + _contactId.value = contactId + updateItemsFlow() } } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/prompt/PromptFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/prompt/PromptFragment.kt index af791dfbb..998c3d997 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/prompt/PromptFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/prompt/PromptFragment.kt @@ -9,7 +9,7 @@ import javax.inject.Inject @AndroidEntryPoint class PromptFragment @Inject constructor() : BaseFragment() { - private var _onItemClickListener: (Boolean) -> Unit = {} + private var _onItemClickListener: (Boolean) -> Boolean = { true } override val contentView by lazy { binding.root } override val viewState: PromptViewState by viewModels() @@ -19,21 +19,22 @@ class PromptFragment @Inject constructor() : BaseFragment() { override fun onSetup() { binding.apply { - viewState.title.value = args.getString(ARG_TITLE) - viewState.subtitle.value = args.getString(ARG_SUBTITLE) promptButtonNo.setOnClickListener { - viewState.onNoClick() - _onItemClickListener.invoke(false) + viewState.onActivated(!_onItemClickListener.invoke(false)) } promptButtonYes.setOnClickListener { - viewState.onYesClick() - _onItemClickListener.invoke(true) + viewState.onActivated(_onItemClickListener.invoke(true)) } } viewState.apply { + onTitle(args.getString(ARG_TITLE)) + onSubtitle(args.getString(ARG_SUBTITLE)) + onActivated(args.getBoolean(ARG_IS_ACTIVATED, true)) + + title.observe(this@PromptFragment) { binding.promptTitle.text = it } @@ -41,10 +42,15 @@ class PromptFragment @Inject constructor() : BaseFragment() { subtitle.observe(this@PromptFragment) { binding.promptSubtitle.text = it } + + isActivated.observe(this@PromptFragment) { + binding.promptButtonNo.isActivated = !it + binding.promptButtonYes.isActivated = it + } } } - fun setOnItemClickListener(onItemClickListener: (Boolean) -> Unit) { + fun setOnItemClickListener(onItemClickListener: (Boolean) -> Boolean) { _onItemClickListener = onItemClickListener } @@ -52,12 +58,15 @@ class PromptFragment @Inject constructor() : BaseFragment() { companion object { const val ARG_TITLE = "title" const val ARG_SUBTITLE = "subtitle" + const val ARG_IS_ACTIVATED = "is_activated" - fun newInstance(title: String, subtitle: String) = PromptFragment().apply { - arguments = Bundle().apply { - putString(ARG_TITLE, title) - putString(ARG_SUBTITLE, subtitle) + fun newInstance(title: String, subtitle: String, isActivated: Boolean) = + PromptFragment().apply { + arguments = Bundle().apply { + putString(ARG_TITLE, title) + putString(ARG_SUBTITLE, subtitle) + putBoolean(ARG_IS_ACTIVATED, isActivated) + } } - } } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/prompt/PromptViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/prompt/PromptViewState.kt index db41803ce..d5ba76d4c 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/prompt/PromptViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/prompt/PromptViewState.kt @@ -1,5 +1,6 @@ package com.chooloo.www.chooloolib.ui.prompt +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.chooloo.www.chooloolib.ui.base.BaseViewState import dagger.hilt.android.lifecycle.HiltViewModel @@ -7,15 +8,24 @@ import javax.inject.Inject @HiltViewModel class PromptViewState @Inject constructor() : BaseViewState() { - val title = MutableLiveData() - val subtitle = MutableLiveData() + private val _title = MutableLiveData() + private val _subtitle = MutableLiveData() + private val _isActivated = MutableLiveData(false) - - fun onNoClick() { - finishEvent.call() + val title: LiveData get() = _title + val subtitle: LiveData get() = _subtitle + val isActivated: LiveData get() = _isActivated + + + fun onTitle(title: String?) { + _title.value = title + } + + fun onSubtitle(subtitle: String?) { + _subtitle.value = subtitle } - fun onYesClick() { - finishEvent.call() + fun onActivated(isActivated: Boolean) { + _isActivated.value = isActivated } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/RecentFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/RecentFragment.kt index c3a400762..e2f4529cc 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/RecentFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/RecentFragment.kt @@ -4,10 +4,7 @@ import android.os.Bundle import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.databinding.RecentBinding -import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory -import com.chooloo.www.chooloolib.interactor.dialog.DialogsInteractor import com.chooloo.www.chooloolib.interactor.prompt.PromptsInteractor import com.chooloo.www.chooloolib.interactor.telecom.TelecomInteractor import com.chooloo.www.chooloolib.ui.base.BaseFragment @@ -27,51 +24,31 @@ class RecentFragment @Inject constructor() : BaseFragment() { private val binding by lazy { RecentBinding.inflate(layoutInflater) } @Inject lateinit var prompts: PromptsInteractor - @Inject lateinit var dialogs: DialogsInteractor - @Inject lateinit var fragmentFactory: FragmentFactory @Inject lateinit var telecomInteractor: TelecomInteractor override fun onSetup() { binding.recentContactImage.isVisible = false - binding.apply { - recentButtonMore.setOnClickListener { - viewState.onMoreClick() - } - - recentMainActions.recentButtonSms.setOnClickListener { - viewState.onSms() - } - - recentMainActions.recentButtonCall.setOnClickListener { - viewState.onCall() - } - - recentMainActions.recentButtonContact.setOnClickListener { - viewState.onOpenContact() - } - - recentMainActions.recentButtonAddContact.setOnClickListener { - viewState.onAddContact() - } - + binding.recentMainActions.apply { + recentButtonSms.setOnClickListener { viewState.onSms() } + recentButtonCall.setOnClickListener { viewState.onCall() } + recentButtonMore.setOnClickListener { viewState.onMoreClick() } + recentButtonContact.setOnClickListener { viewState.onOpenContact() } + recentButtonAddContact.setOnClickListener { viewState.onAddContact() } } viewState.apply { onRecentId(args.getLong(ARG_RECENT_ID)) - menuViewState.recentId.value = args.getLong(ARG_RECENT_ID) + isBlocked.observe(this@RecentFragment, menuViewState::onIsBlocked) + recentNumber.observe(this@RecentFragment, menuViewState::onRecentNumber) + typeImage.observe(this@RecentFragment, binding.recentTypeImage::setImageResource) imageUri.observe(this@RecentFragment) { binding.recentContactImage.isVisible = true Picasso.with(baseActivity).load(it).into(binding.recentContactImage) } - typeImage.observe(this@RecentFragment) { - binding.recentTypeImage.setImageResource(it) - } - typeImage.observe(this@RecentFragment, binding.recentTypeImage::setImageResource) - name.observe(this@RecentFragment) { binding.recentTextName.text = it } @@ -80,14 +57,6 @@ class RecentFragment @Inject constructor() : BaseFragment() { binding.recentCaption.text = it } - isBlocked.observe(this@RecentFragment) { - menuViewState.isBlocked.value = it - } - - recentNumber.observe(this@RecentFragment) { - menuViewState.recentNumber.value = it - } - isContactVisible.observe(this@RecentFragment) { binding.recentMainActions.recentButtonContact.isVisible = it } @@ -115,19 +84,19 @@ class RecentFragment @Inject constructor() : BaseFragment() { showRecentEvent.observe(this@RecentFragment) { ev -> ev.ifNew?.let { prompts.showFragment(fragmentFactory.getRecentFragment(it)) } } - - confirmRecentDeleteEvent.observe(this@RecentFragment) { - it.ifNew?.let { - dialogs.askForValidation(R.string.explain_delete_recent) { result -> - if (result) viewState.onConfirmDelete() - } - } - } } historyViewState.itemClickedEvent.observe(this) { it.ifNew?.let { recent -> viewState.onRecentClick(recent.id) } } + + menuViewState.apply { + finishEvent.observe(this@RecentFragment) { + viewState.onFinish() + } + + onRecentId(args.getLong(ARG_RECENT_ID)) + } } companion object { diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/RecentViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/RecentViewState.kt index c41ab5bfd..48baf0392 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/RecentViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/RecentViewState.kt @@ -1,49 +1,53 @@ package com.chooloo.www.chooloolib.ui.recent import android.net.Uri +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.chooloo.www.chooloolib.interactor.blocked.BlockedInteractor -import com.chooloo.www.chooloolib.interactor.drawable.DrawablesInteractor +import androidx.lifecycle.viewModelScope +import com.chooloo.www.chooloolib.data.model.RecentAccount import com.chooloo.www.chooloolib.interactor.navigation.NavigationsInteractor -import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import com.chooloo.www.chooloolib.interactor.phoneaccounts.PhonesInteractor -import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor import com.chooloo.www.chooloolib.interactor.recents.RecentsInteractor -import com.chooloo.www.chooloolib.model.RecentAccount import com.chooloo.www.chooloolib.ui.base.BaseViewState -import com.chooloo.www.chooloolib.util.DataLiveEvent -import com.chooloo.www.chooloolib.util.LiveEvent -import com.chooloo.www.chooloolib.util.getElapsedTimeString +import com.chooloo.www.chooloolib.util.* import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class RecentViewState @Inject constructor( private val phones: PhonesInteractor, private val recents: RecentsInteractor, - private val blocked: BlockedInteractor, - private val drawables: DrawablesInteractor, - private val preferences: PreferencesInteractor, private val navigations: NavigationsInteractor, - private val permissions: PermissionsInteractor ) : BaseViewState() { - val name = MutableLiveData() - val imageUri = MutableLiveData() - val recentId = MutableLiveData(0L) - val typeImage = MutableLiveData() - val caption = MutableLiveData() - val isBlocked = MutableLiveData() - val recentNumber = MutableLiveData() - - val isContactVisible = MutableLiveData(false) - val isAddContactVisible = MutableLiveData(false) - - val showMoreEvent = LiveEvent() - val callEvent = DataLiveEvent() - val confirmRecentDeleteEvent = LiveEvent() - val showRecentEvent = DataLiveEvent() - val showContactEvent = DataLiveEvent() + private val _name = MutableLiveData() + private val _imageUri = MutableLiveData() + private val _recentId = MutableLiveData(0L) + private val _typeImage = MutableLiveData() + private val _caption = MutableLiveData() + private val _isBlocked = MutableLiveData() + private val _recentNumber = MutableLiveData() + private val _isContactVisible = MutableLiveData(false) + private val _isAddContactVisible = MutableLiveData(false) + private val _showMoreEvent = MutableLiveEvent() + private val _callEvent = MutableDataLiveEvent() + private val _showRecentEvent = MutableDataLiveEvent() + private val _showContactEvent = MutableDataLiveEvent() + + val name = _name as LiveData + val caption = _caption as LiveData + val imageUri = _imageUri as LiveData + val recentId = _recentId as LiveData + val typeImage = _typeImage as LiveData + val isBlocked = _isBlocked as LiveData + val recentNumber = _recentNumber as LiveData + val isContactVisible = _isContactVisible as LiveData + val isAddContactVisible = _isAddContactVisible as LiveData + val showMoreEvent = _showMoreEvent as LiveEvent + val callEvent = _callEvent as DataLiveEvent + val showRecentEvent = _showRecentEvent as DataLiveEvent + val showContactEvent = _showContactEvent as DataLiveEvent private var _recent: RecentAccount? = null @@ -54,23 +58,26 @@ class RecentViewState @Inject constructor( } fun onRecentId(recentId: Long) { - this.recentId.value = recentId - recents.observeRecent(recentId) { - _recent = it - recentNumber.value = it?.number - name.value = - if (_recent!!.cachedName?.isNotEmpty() == true) it?.cachedName else it?.number - it?.type?.let { typeImage.value = recents.getCallTypeImage(it) } - - caption.value = "${it?.relativeTime} • ${ - if (it?.duration ?: -1 > 0L) "${ - getElapsedTimeString(it?.duration!!) - } •" else "" - }" - phones.lookupAccount(it?.number) { - it?.photoUri?.let { imageUri.value = Uri.parse(it) } - isContactVisible.value = it?.name != null - isAddContactVisible.value = it?.name == null + _recentId.value = recentId + viewModelScope.launch { + recents.getRecent(recentId).collect { + _recent = it + it?.number?.let { _recentNumber.value = it } + _name.value = + if (_recent?.cachedName?.isNotEmpty() == true) it?.cachedName else it?.number + it?.type?.let { _typeImage.value = recents.getCallTypeImage(it) } + + _caption.value = "${it?.relativeTime ?: ""} • ${ + if ((it?.duration ?: -1) > 0L) "${ + getElapsedTimeString(it?.duration!!) + } •" else "" + }" + + phones.lookupAccount(it?.number).also { phoneLookupAccount -> + phoneLookupAccount?.photoUri?.let { pu -> _imageUri.value = Uri.parse(pu) } + _isContactVisible.value = phoneLookupAccount?.name != null + _isAddContactVisible.value = phoneLookupAccount?.name == null + } } } } @@ -80,31 +87,25 @@ class RecentViewState @Inject constructor( } fun onCall() { - recentNumber.value?.let { callEvent.call(it) } + recentNumber.value?.let(_callEvent::call) } fun onAddContact() { - recentNumber.value?.let { navigations.addContact(it) } + recentNumber.value?.let(navigations::addContact) } fun onOpenContact() { - phones.lookupAccount(recentNumber.value) { account -> + viewModelScope.launch { + val account = phones.lookupAccount(recentNumber.value) account?.contactId?.let(navigations::viewContact) } } fun onRecentClick(recentId: Long) { - showRecentEvent.call(recentId) - } - - fun onConfirmDelete() { - recentId.value?.let { - recents.deleteRecent(it) - finishEvent.call() - } + _showRecentEvent.call(recentId) } fun onMoreClick() { - showMoreEvent.call() + _showMoreEvent.call() } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/menu/RecentMenuFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/menu/RecentMenuFragment.kt index 195c7ee54..541459f38 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/menu/RecentMenuFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/menu/RecentMenuFragment.kt @@ -3,7 +3,6 @@ package com.chooloo.www.chooloolib.ui.recent.menu import android.view.MenuItem import androidx.fragment.app.activityViewModels import com.chooloo.www.chooloolib.R -import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory import com.chooloo.www.chooloolib.interactor.dialog.DialogsInteractor import com.chooloo.www.chooloolib.interactor.prompt.PromptsInteractor import com.chooloo.www.chooloolib.ui.base.menu.BaseMenuFragment @@ -14,32 +13,34 @@ import javax.inject.Inject class RecentMenuFragment @Inject constructor() : BaseMenuFragment() { override val viewState: RecentMenuViewState by activityViewModels() - @Inject lateinit var prompts: PromptsInteractor - @Inject lateinit var dialogs: DialogsInteractor - @Inject lateinit var fragmentFactory: FragmentFactory + @Inject + lateinit var prompts: PromptsInteractor + + @Inject + lateinit var dialogs: DialogsInteractor + override fun onSetup() { super.onSetup() viewState.apply { - showHistoryEvent.observe(this@RecentMenuFragment) { ev -> - ev.ifNew?.let { - prompts.showFragment(fragmentFactory.getRecentsHistoryFragment(viewState.recentNumber.value)) - } - } - isBlocked.observe(this@RecentMenuFragment) { changeItemVisibility(R.id.menu_recent_block, !it) changeItemVisibility(R.id.menu_recent_unblock, it) } - } - } - override fun onMenuItemClick(menuItem: MenuItem) { - if (menuItem.itemId == R.id.menu_brief_contact_delete) { - dialogs.askForValidation(R.string.explain_delete_contact) { bl -> - if (bl) viewState.onDelete() + showHistoryEvent.observe(this@RecentMenuFragment) { + it.ifNew?.let { + prompts.showFragment(fragmentFactory.getRecentsHistoryFragment(viewState.recentNumber.value)) + } + } + + confirmDeleteEvent.observe(this@RecentMenuFragment) { + it.ifNew?.let { + dialogs.askForValidation(R.string.explain_delete_contact) { bl -> + if (bl) viewState.onDelete() + } + } } } - super.onMenuItemClick(menuItem) } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/menu/RecentMenuViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/menu/RecentMenuViewState.kt index 190df08cb..3767e6936 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/menu/RecentMenuViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recent/menu/RecentMenuViewState.kt @@ -1,7 +1,9 @@ package com.chooloo.www.chooloolib.ui.recent.menu import android.Manifest +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.interactor.blocked.BlockedInteractor import com.chooloo.www.chooloolib.interactor.navigation.NavigationsInteractor @@ -9,7 +11,9 @@ import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor import com.chooloo.www.chooloolib.interactor.recents.RecentsInteractor import com.chooloo.www.chooloolib.ui.base.menu.BaseMenuViewState import com.chooloo.www.chooloolib.util.LiveEvent +import com.chooloo.www.chooloolib.util.MutableLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -21,10 +25,17 @@ class RecentMenuViewState @Inject constructor( ) : BaseMenuViewState() { override val menuResList = listOf(R.menu.menu_recent_extra) - val recentId = MutableLiveData() - val isBlocked = MutableLiveData() - val recentNumber = MutableLiveData() - val showHistoryEvent = LiveEvent() + private val _recentId = MutableLiveData() + private val _isBlocked = MutableLiveData() + private val _recentNumber = MutableLiveData() + private val _showHistoryEvent = MutableLiveEvent() + private val _confirmDeleteEvent = MutableLiveEvent() + + val recentId = _recentId as LiveData + val isBlocked = _isBlocked as LiveData + val recentNumber = _recentNumber as LiveData + val showHistoryEvent = _showHistoryEvent as LiveEvent + val confirmDeleteEvent = _confirmDeleteEvent as LiveEvent override fun onMenuItemClick(itemId: Int) { super.onMenuItemClick(itemId) @@ -33,11 +44,12 @@ class RecentMenuViewState @Inject constructor( R.id.menu_recent_whatsapp -> onOpenWhatsapp() R.id.menu_recent_block -> onBlock(true) R.id.menu_recent_unblock -> onBlock(false) + R.id.menu_recent_delete -> _confirmDeleteEvent.call() } } fun onShowHistory() { - showHistoryEvent.call() + _showHistoryEvent.call() } fun onOpenWhatsapp() { @@ -47,23 +59,37 @@ class RecentMenuViewState @Inject constructor( fun onBlock(isBlock: Boolean) { permissions.runWithDefaultDialer(R.string.error_not_default_dialer_blocked) { recentNumber.value?.let { - if (isBlock) { - blocked.blockNumber(it) - } else { - blocked.unblockNumber(it) + viewModelScope.launch { + if (isBlock) { + blocked.blockNumber(it) + } else { + blocked.unblockNumber(it) + } } - isBlocked.value = isBlock - finishEvent.call() + _isBlocked.value = isBlock + onFinish() } } } fun onDelete() { permissions.runWithPermissions(arrayOf(Manifest.permission.WRITE_CALL_LOG), { - recentId.value?.let { recents.deleteRecent(it) } - finishEvent.call() + recentId.value?.let(recents::deleteRecent) + onFinish() }, { - errorEvent.call(R.string.error_no_permissions_edit_call_log) + onError(R.string.error_no_permissions_edit_call_log) }) } + + fun onRecentId(recentId: Long) { + _recentId.value = recentId + } + + fun onIsBlocked(isBlocked: Boolean) { + _isBlocked.value = isBlocked + } + + fun onRecentNumber(recentNumber: String) { + _recentNumber.value = recentNumber + } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recents/RecentsFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recents/RecentsFragment.kt index 92765e134..2527e5acf 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recents/RecentsFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recents/RecentsFragment.kt @@ -3,10 +3,9 @@ package com.chooloo.www.chooloolib.ui.recents import android.os.Bundle import androidx.fragment.app.activityViewModels import com.chooloo.www.chooloolib.adapter.RecentsAdapter -import com.chooloo.www.chooloolib.di.factory.fragment.FragmentFactory +import com.chooloo.www.chooloolib.data.model.RecentAccount import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor import com.chooloo.www.chooloolib.interactor.prompt.PromptsInteractor -import com.chooloo.www.chooloolib.model.RecentAccount import com.chooloo.www.chooloolib.ui.list.ListFragment import com.chooloo.www.chooloolib.ui.recentshistory.RecentsHistoryViewState import dagger.hilt.android.AndroidEntryPoint @@ -17,14 +16,15 @@ open class RecentsFragment @Inject constructor() : ListFragment diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recents/RecentsViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recents/RecentsViewState.kt index 72145e037..e6acda6d8 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recents/RecentsViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recents/RecentsViewState.kt @@ -4,62 +4,47 @@ import android.Manifest.permission.READ_CALL_LOG import android.Manifest.permission.READ_CONTACTS import android.content.ClipData import android.content.ClipboardManager -import androidx.lifecycle.LiveData import com.chooloo.www.chooloolib.R +import com.chooloo.www.chooloolib.data.model.RecentAccount +import com.chooloo.www.chooloolib.data.repository.recents.RecentsRepository import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor -import com.chooloo.www.chooloolib.livedata.RecentsLiveData -import com.chooloo.www.chooloolib.model.RecentAccount -import com.chooloo.www.chooloolib.repository.recents.RecentsRepository import com.chooloo.www.chooloolib.ui.list.ListViewState import com.chooloo.www.chooloolib.util.DataLiveEvent +import com.chooloo.www.chooloolib.util.MutableDataLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow import javax.inject.Inject @HiltViewModel open class RecentsViewState @Inject constructor( - recentsRepository: RecentsRepository, - private val permissions: PermissionsInteractor, - private val clipboardManager: ClipboardManager + permissions: PermissionsInteractor, + private val clipboardManager: ClipboardManager, + private val recentsRepository: RecentsRepository, ) : - ListViewState() { + ListViewState(permissions) { override val noResultsIconRes = R.drawable.history override val noResultsTextRes = R.string.error_no_results_recents - override var noPermissionsTextRes = R.string.error_no_permissions_recents + override val permissionsImageRes = R.drawable.history + override val permissionsTextRes = R.string.error_no_permissions_recents + override val requiredPermissions = listOf(READ_CALL_LOG, READ_CONTACTS) - private val recentsLiveData = recentsRepository.getRecents() as RecentsLiveData + private val _showRecentEvent = MutableDataLiveEvent() - val showRecentEvent = DataLiveEvent() + val showRecentEvent = _showRecentEvent as DataLiveEvent - override fun onFilterChanged(filter: String?) { - super.onFilterChanged(filter) - if (permissions.hasSelfPermissions(arrayOf(READ_CONTACTS, READ_CALL_LOG))) { - onPermissionsChanged(true) - recentsLiveData.filter = filter - } - } - override fun onItemClick(item: RecentAccount) { super.onItemClick(item) - permissions.runWithReadCallLogPermissions { showRecentEvent.call(item) } + _showRecentEvent.call(item) } override fun onItemLongClick(item: RecentAccount) { super.onItemLongClick(item) clipboardManager.setPrimaryClip(ClipData.newPlainText("Copied number", item.number)) - messageEvent.call(R.string.number_copied_to_clipboard) + onMessage(R.string.number_copied_to_clipboard) } - override fun getItemsObservable(callback: (LiveData>) -> Unit) { - permissions.runWithReadCallLogPermissions { clp -> - if (!clp) noPermissionsTextRes = R.string.error_no_permissions_recents - onPermissionsChanged(clp) - permissions.runWithReadContactsPermissions { rcp -> - if (!rcp) noPermissionsTextRes = R.string.error_no_permissions_contacts - onPermissionsChanged(clp && rcp) - if (clp && rcp) callback.invoke(recentsLiveData) - } - } - } -} \ No newline at end of file + override fun getItemsFlow(filter: String?): Flow>? = + recentsRepository.getRecents(filter) +} diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recentshistory/RecentsHistoryViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recentshistory/RecentsHistoryViewState.kt index bfa389689..0f427db3d 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recentshistory/RecentsHistoryViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/recentshistory/RecentsHistoryViewState.kt @@ -2,7 +2,7 @@ package com.chooloo.www.chooloolib.ui.recentshistory import android.content.ClipboardManager import com.chooloo.www.chooloolib.interactor.permission.PermissionsInteractor -import com.chooloo.www.chooloolib.repository.recents.RecentsRepository +import com.chooloo.www.chooloolib.data.repository.recents.RecentsRepository import com.chooloo.www.chooloolib.ui.recents.RecentsViewState import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -12,5 +12,5 @@ class RecentsHistoryViewState @Inject constructor( permissions: PermissionsInteractor, clipboardManager: ClipboardManager, recentsRepository: RecentsRepository -) : RecentsViewState(recentsRepository, permissions, clipboardManager) { +) : RecentsViewState(permissions, clipboardManager, recentsRepository) { } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/settings/SettingsFragment.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/settings/SettingsFragment.kt index 30cd1f121..12a28b9cf 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/settings/SettingsFragment.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/settings/SettingsFragment.kt @@ -1,6 +1,5 @@ package com.chooloo.www.chooloolib.ui.settings -import android.view.MenuItem import androidx.fragment.app.viewModels import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.interactor.dialog.DialogsInteractor @@ -23,12 +22,23 @@ open class SettingsFragment @Inject constructor() : BaseMenuFragment() { } askForThemeModeEvent.observe(this@SettingsFragment) { - it.ifNew?.let { dialogs.askForThemeMode(viewState::onThemeModeResponse) } + it.ifNew?.let { + dialogs.askForThemeMode { + viewState.onThemeModeResponse(it) + true + } + } } askForAnimationsEvent.observe(this@SettingsFragment) { - it.ifNew?.let { - dialogs.askForBoolean(R.string.hint_animations, viewState::onAnimationsResponse) + it.ifNew?.let { isActivated -> + dialogs.askForBoolean( + R.string.hint_animations, + isActivated + ) { + viewState.onAnimationsResponse(it) + true + } } } } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/settings/SettingsViewState.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/settings/SettingsViewState.kt index 2b1becf20..b00031e01 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/settings/SettingsViewState.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/settings/SettingsViewState.kt @@ -4,13 +4,14 @@ import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.interactor.color.ColorsInteractor import com.chooloo.www.chooloolib.interactor.navigation.NavigationsInteractor import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor -import com.chooloo.www.chooloolib.interactor.preferences.PreferencesInteractor.Companion.AccentTheme.* import com.chooloo.www.chooloolib.interactor.string.StringsInteractor import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractor import com.chooloo.www.chooloolib.interactor.theme.ThemesInteractor.ThemeMode import com.chooloo.www.chooloolib.ui.base.menu.BaseMenuViewState import com.chooloo.www.chooloolib.util.DataLiveEvent import com.chooloo.www.chooloolib.util.LiveEvent +import com.chooloo.www.chooloolib.util.MutableDataLiveEvent +import com.chooloo.www.chooloolib.util.MutableLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -25,12 +26,16 @@ open class SettingsViewState @Inject constructor( BaseMenuViewState() { override val menuResList = listOf(R.menu.menu_chooloo) - val askForThemeModeEvent = LiveEvent() - val askForAnimationsEvent = LiveEvent() - val askForColorEvent = DataLiveEvent() + private val _askForThemeModeEvent = MutableLiveEvent() + private val _askForAnimationsEvent = MutableDataLiveEvent() + private val _askForColorEvent = MutableDataLiveEvent() + + val askForThemeModeEvent = _askForThemeModeEvent as LiveEvent + val askForColorEvent = _askForColorEvent as DataLiveEvent + val askForAnimationsEvent = _askForAnimationsEvent as DataLiveEvent init { - title.value = strings.getString(R.string.settings) + _title.value = strings.getString(R.string.settings) } override fun onMenuItemClick(itemId: Int) { @@ -38,21 +43,20 @@ open class SettingsViewState @Inject constructor( R.id.menu_chooloo_rate -> navigations.rateApp() R.id.menu_chooloo_email -> navigations.sendEmail() R.id.menu_chooloo_report_bugs -> navigations.reportBug() - R.id.menu_chooloo_theme_mode -> askForThemeModeEvent.call() - R.id.menu_chooloo_animations -> askForAnimationsEvent.call() - R.id.menu_chooloo_accent_color -> askForColorEvent.call(R.array.accent_colors) + R.id.menu_chooloo_theme_mode -> _askForThemeModeEvent.call() + R.id.menu_chooloo_animations -> _askForAnimationsEvent.call(preferences.isAnimations) else -> super.onMenuItemClick(itemId) } } fun onColorResponse(color: Int) { - preferences.accentTheme = when (color) { - colors.getColor(R.color.red_primary) -> RED - colors.getColor(R.color.blue_primary) -> BLUE - colors.getColor(R.color.green_primary) -> GREEN - colors.getColor(R.color.purple_primary) -> PURPLE - else -> DEFAULT - } +// preferences.accentTheme = when (color) { +// colors.getColor(R.color.red_primary) -> RED +// colors.getColor(R.color.primary) -> BLUE +// colors.getColor(R.color.green_primary) -> GREEN +// colors.getColor(R.color.purple_primary) -> PURPLE +// else -> DEFAULT +// } navigations.goToLauncherActivity() } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/CallActions.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/CallActions.kt deleted file mode 100644 index 94b30ed2d..000000000 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/CallActions.kt +++ /dev/null @@ -1,142 +0,0 @@ -package com.chooloo.www.chooloolib.ui.widgets - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import androidx.constraintlayout.motion.widget.MotionLayout -import com.chooloo.www.chooloolib.R -import com.chooloo.www.chooloolib.databinding.CallActionsBinding - -class CallActions : MotionLayout { - private val _binding: CallActionsBinding - private var _isBluetoothActivated: Boolean = false - private var _callActionsListener: CallActionsListener? = null - - constructor(context: Context) : this(context, null) - constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( - context, - attrs, - defStyleAttr - ) { - _callActionsListener = getEmptyListener() - - _binding = CallActionsBinding.inflate(LayoutInflater.from(context), this, true) - _binding.callActionSwap.setOnClickListener { _callActionsListener?.onSwapClick() } - _binding.callActionHold.setOnClickListener { _callActionsListener?.onHoldClick() } - _binding.callActionMute.setOnClickListener { _callActionsListener?.onMuteClick() } - _binding.callActionMerge.setOnClickListener { _callActionsListener?.onMergeClick() } - _binding.callActionKeypad.setOnClickListener { _callActionsListener?.onKeypadClick() } - _binding.callActionSpeaker.setOnClickListener { _callActionsListener?.onSpeakerClick() } - _binding.callActionAddCall.setOnClickListener { _callActionsListener?.onAddCallClick() } - } - - - private fun transitionLayoutTo(constraintRes: Int) { - if (_binding.root.currentState != constraintRes) { - _binding.root.setTransition(_binding.root.currentState, constraintRes) - _binding.root.transitionToEnd() - } - } - - - var isHoldActivated: Boolean - get() = _binding.callActionHold.isChecked - set(value) { - _binding.callActionHold.isChecked = value - } - - var isMuteActivated: Boolean - get() = _binding.callActionMute.isChecked - set(value) { - _binding.callActionMute.isChecked = value - } - - var isSpeakerActivated: Boolean - get() = _binding.callActionSpeaker.isChecked - set(value) { - _binding.callActionSpeaker.isChecked = value - _binding.callActionSpeaker.setIconResource(if (value) R.drawable.volume_up else R.drawable.volume_down) - } - - var isBluetoothActivated: Boolean - get() = _isBluetoothActivated - set(value) { - _isBluetoothActivated = value - if (value) { - _binding.callActionSpeaker.setIconResource(R.drawable.bluetooth_searching) - } else { - isSpeakerActivated = isSpeakerActivated - } - } - - - var isHoldEnabled: Boolean - get() = _binding.callActionHold.isEnabled - set(value) { - _binding.callActionHold.isEnabled = value - } - - var isMuteEnabled: Boolean - get() = _binding.callActionMute.isEnabled - set(value) { - _binding.callActionMute.isEnabled = value - } - - var isSwapEnabled: Boolean - get() = _binding.callActionSwap.isEnabled - set(value) { - _binding.callActionSwap.isEnabled = value - } - - var isMergeEnabled: Boolean - get() = _binding.callActionMerge.isEnabled - set(value) { - _binding.callActionMerge.isEnabled = value - } - - var isSpeakerEnabled: Boolean - get() = _binding.callActionSpeaker.isEnabled - set(value) { - _binding.callActionSpeaker.isEnabled = value - } - - - fun showSingleCallUI() { - transitionLayoutTo(R.id.constraint_set_single_call) - _binding.callActionSwap.visibility = GONE - _binding.callActionMerge.visibility = GONE - } - - fun showMultiCallUI() { - transitionLayoutTo(R.id.constraint_set_multi_call) - _binding.callActionSwap.visibility = VISIBLE - _binding.callActionMerge.visibility = VISIBLE - } - - fun setCallActionsListener(callActionsListener: CallActionsListener?) { - _callActionsListener = callActionsListener - } - - - private fun getEmptyListener() = object : CallActionsListener { - override fun onHoldClick() {} - override fun onMuteClick() {} - override fun onSwapClick() {} - override fun onMergeClick() {} - override fun onKeypadClick() {} - override fun onSpeakerClick() {} - override fun onAddCallClick() {} - } - - - interface CallActionsListener { - fun onHoldClick() - fun onMuteClick() - fun onSwapClick() - fun onMergeClick() - fun onKeypadClick() - fun onSpeakerClick() - fun onAddCallClick() - } -} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/DialpadKey.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/DialpadKey.kt index d2e974f33..cd4d18d50 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/DialpadKey.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/DialpadKey.kt @@ -34,7 +34,7 @@ class DialpadKey : LinearLayout { _digitTextView = TextView(context, attrs, defStyleRes).apply { layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT) - setTextAppearance(R.style.TextAppearance_Material3_HeadlineMedium) + setTextAppearance(R.style.TextAppearance_Material3_HeadlineLarge) typeface = ResourcesCompat.getFont(context, R.font.google_sans_bold) }.also { addView(it) diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/IconButton.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/IconButton.kt index 11ac165ed..4c6966f93 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/IconButton.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/IconButton.kt @@ -3,18 +3,25 @@ package com.chooloo.www.chooloolib.ui.widgets import android.annotation.SuppressLint import android.content.Context import android.content.res.ColorStateList -import android.graphics.drawable.Drawable import android.util.AttributeSet -import androidx.core.content.ContextCompat.getDrawable +import android.widget.ImageButton +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.util.getAttrColor -import com.google.android.material.button.MaterialButton @SuppressLint("CustomViewStyleable", "WrongConstant") -class IconButton : MaterialButton { - private var _iconDefault: Drawable? = null - private var _iconChecked: Drawable? = null +class IconButton : ImageButton { + @DrawableRes private var _iconDefault: Int? = null + @DrawableRes private var _iconChecked: Int? = null + + @ColorInt private val _defaultForegroundTint: Int + @ColorInt private val _defaultBackgroundTint: Int + + private val _colorBackground by lazy { context.getAttrColor(R.attr.colorSecondaryContainer) } + private val _colorForeground by lazy { context.getAttrColor(R.attr.colorOnSecondaryContainer) } + private val _colorForegroundDisabled by lazy { context.getAttrColor(R.attr.colorBackgroundVariant) } constructor(context: Context) : this(context, null) @@ -22,24 +29,54 @@ class IconButton : MaterialButton { constructor( context: Context, attrs: AttributeSet? = null, - defStyleRes: Int = 0 + defStyleRes: Int = R.style.Chooloo_Button_Secondary ) : super(context, attrs, defStyleRes) { - context.obtainStyledAttributes(attrs, R.styleable.Chooloo_IconButton, defStyleRes, 0).also { - val iconRes = it.getResourceId(R.styleable.Chooloo_IconButton_checkedIcon, NO_ID) - _iconChecked = if (iconRes == NO_ID) null else getDrawable(context, iconRes) + var isActivated = false + context.obtainStyledAttributes(attrs, R.styleable.Chooloo_IconButton, 0, defStyleRes).also { + val iconRes = it.getResourceId(R.styleable.Chooloo_IconButton_icon, NO_ID) + val checkedIconRes = it.getResourceId(R.styleable.Chooloo_IconButton_checkedIcon, NO_ID) + isActivated = it.getBoolean(R.styleable.Chooloo_IconButton_activated, false) + + _iconDefault = if (iconRes == NO_ID) null else iconRes + _iconChecked = if (checkedIconRes == NO_ID) null else checkedIconRes }.recycle() - _iconDefault = icon + _defaultForegroundTint = imageTintList?.defaultColor ?: _colorForeground + _defaultBackgroundTint = backgroundTintList?.defaultColor ?: _colorBackground + + setIcon(_iconDefault) + refreshColors() + setActivated(isActivated) + } + + override fun setActivated(activated: Boolean) { + super.setActivated(activated) + (if (isActivated) _iconChecked else _iconDefault)?.let(::setIcon) + refreshColors() + } + + private fun refreshColors() { + imageTintList = + ColorStateList.valueOf( + if (isEnabled) { + if (isActivated) _defaultBackgroundTint else _defaultForegroundTint + } else { + _colorForegroundDisabled + } + ) + backgroundTintList = + ColorStateList.valueOf(if (isActivated) _defaultForegroundTint else _defaultBackgroundTint) + } + + fun setIcon(@DrawableRes iconRes: Int?) { + iconRes?.let(::setImageResource) + } - if (isCheckable && !isChecked && isEnabled) { - iconTint = ColorStateList.valueOf(context.getAttrColor(R.attr.colorPrimary)) - } + fun setDefaultIcon(@DrawableRes iconRes: Int?) { + _iconDefault = iconRes } - override fun setChecked(checked: Boolean) { - super.setChecked(checked) - _iconChecked?.let { - (if (isChecked) _iconChecked else _iconDefault)?.let { icon = it } - } + fun setCheckedIcon(@DrawableRes iconRes: Int?) { + _iconChecked = iconRes } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/SearchBar.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/SearchBar.kt index b4570a00a..e6640243a 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/SearchBar.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/SearchBar.kt @@ -24,8 +24,8 @@ class SearchBar : TextInputLayout { private var _textInputEditText: TextInputEditText private var _onTextChangedListener: ((text: String) -> Unit?)? = null - private val colorBackground by lazy { context.getAttrColor(R.attr.colorSurfaceVariant) } - private val colorForeground by lazy { context.getAttrColor(R.attr.colorOnSurfaceVariant) } + private val colorBackground by lazy { context.getAttrColor(R.attr.colorSecondaryContainer) } + private val colorForeground by lazy { context.getAttrColor(R.attr.colorOnSecondaryContainer) } private val spacing by lazy { resources.getDimensionPixelSize(R.dimen.default_spacing) } private val spacingSmall by lazy { resources.getDimensionPixelSize(R.dimen.default_spacing_small) } @@ -60,8 +60,8 @@ class SearchBar : TextInputLayout { return@InputFilter null }) - setTextAppearance(R.style.TextAppearance_Material3_TitleSmall) setPadding(spacing, 0, spacingSmall, 0) + setTextAppearance(R.style.Chooloo_Text_Title_Small) setHintTextColor(ColorStateList.valueOf(colorForeground)) addTextChangedListener( diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/listitemholder/ChoiceItemHolder.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/listitemholder/ChoiceItemHolder.kt index 68ac6e94b..8b89628cd 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/listitemholder/ChoiceItemHolder.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/listitemholder/ChoiceItemHolder.kt @@ -2,19 +2,32 @@ package com.chooloo.www.chooloolib.ui.widgets.listitemholder import com.chooloo.www.chooloolib.R import com.chooloo.www.chooloolib.databinding.ListItemBinding -import com.google.android.material.internal.ViewUtils +import com.chooloo.www.chooloolib.util.getAttrColor class ChoiceItemHolder(binding: ListItemBinding) : ListItemHolder(binding) { val dimenSpacingSmall by lazy { context.resources.getDimensionPixelSize(R.dimen.default_spacing_small) } - val dimenSpacingMedium by lazy { context.resources.getDimensionPixelSize(R.dimen.default_spacing_medium) } + private val _defaultBackgroundColor: Int + private val _defaultTextColor: Int init { isImageVisible = false - isRightButtonVisible = true - setRightButtonIcon(R.drawable.chevron_right) - binding.root.setPadding(dimenSpacingSmall, dimenSpacingSmall, dimenSpacingSmall, dimenSpacingSmall) - binding.listItemMainLayout.strokeWidth = ViewUtils.dpToPx(context, 1).toInt() - binding.listItemMainConstraintLayout.setPadding(dimenSpacingSmall, dimenSpacingSmall, 0, dimenSpacingSmall) + _defaultTextColor = context.getAttrColor(R.attr.colorOnSurfaceVariant) + _defaultBackgroundColor = context.getAttrColor(R.attr.colorSurfaceVariant) + + binding.root.setPadding(dimenSpacing, dimenSpacingSmall, dimenSpacing, dimenSpacingSmall) + binding.listItemTitle.setTextColor(_defaultTextColor) + binding.listItemMainLayout.setCardBackgroundColor(_defaultBackgroundColor) + binding.listItemMainConstraintLayout.setPadding( + dimenSpacing, + dimenSpacing, + 0, + dimenSpacing + ) + } + + fun setSelected(isSelected: Boolean) { + binding.listItemMainLayout.setCardBackgroundColor(if (isSelected) context.getAttrColor(R.attr.colorSecondaryContainer) else _defaultBackgroundColor) + binding.listItemTitle.setTextColor(if (isSelected) context.getAttrColor(R.attr.colorOnSecondaryContainer) else _defaultTextColor) } } \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/listitemholder/ListItemHolder.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/listitemholder/ListItemHolder.kt index 2b80e2560..7854e6088 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/listitemholder/ListItemHolder.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/ui/widgets/listitemholder/ListItemHolder.kt @@ -1,6 +1,7 @@ package com.chooloo.www.chooloolib.ui.widgets.listitemholder import android.content.res.ColorStateList +import android.graphics.Color import android.graphics.drawable.Drawable import android.net.Uri import android.text.Spannable @@ -8,7 +9,9 @@ import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import android.widget.TextView +import androidx.annotation.ColorInt import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import com.chooloo.www.chooloolib.R @@ -16,7 +19,6 @@ import com.chooloo.www.chooloolib.databinding.ListItemBinding import com.chooloo.www.chooloolib.util.getAttrColor import com.github.abdularis.civ.AvatarImageView.Companion.SHOW_IMAGE import com.github.abdularis.civ.AvatarImageView.Companion.SHOW_INITIAL -import com.squareup.picasso.Picasso open class ListItemHolder(protected val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) { @@ -137,12 +139,12 @@ open class ListItemHolder(protected val binding: ListItemBinding) : fun setImageUri(imageUri: Uri?) { showImageOrInitials = imageUri != null - Picasso.with(context).load(imageUri).into(binding.listItemImage) + binding.listItemImage.setImageURI(imageUri) } fun setImageResource(imageRes: Int) { showImageOrInitials = true - Picasso.with(context).load(imageRes).into(binding.listItemImage) + binding.listItemImage.setImageResource(imageRes) } fun setImageDrawable(drawable: Drawable) { @@ -156,11 +158,12 @@ open class ListItemHolder(protected val binding: ListItemBinding) : } fun setLeftButtonIcon(iconRes: Int) { - binding.listItemLeftButton.setIconResource(iconRes) + binding.listItemLeftButton.setImageResource(iconRes) } fun setLeftButtonIconTint(tintRes: Int) { - binding.listItemLeftButton.setIconTintResource(tintRes) + binding.listItemLeftButton.imageTintList = + ColorStateList.valueOf(ContextCompat.getColor(context, tintRes)) } fun setLeftButtonBackgroundTint(tintRes: Int) { @@ -169,13 +172,19 @@ open class ListItemHolder(protected val binding: ListItemBinding) : } fun setRightButtonIcon(iconRes: Int) { - binding.listItemRightButton.setIconResource(iconRes) + binding.listItemRightButton.setImageResource(iconRes) } fun setRightButtonIconTint(tintRes: Int) { - binding.listItemRightButton.setIconTintResource(tintRes) + binding.listItemRightButton.imageTintList = + ColorStateList.valueOf(ContextCompat.getColor(context, tintRes)) } + fun setRightButtonIconTintColor(@ColorInt color: Int) { + binding.listItemRightButton.imageTintList = ColorStateList.valueOf(color) + } + + fun setRightButtonBackgroundTint(tintRes: Int) { binding.listItemRightButton.backgroundTintList = ColorStateList.valueOf(context.getColor(tintRes)) @@ -189,9 +198,4 @@ open class ListItemHolder(protected val binding: ListItemBinding) : height = size } } - - fun setSelected() { - binding.listItemMainLayout.strokeColor = context.getAttrColor(R.attr.colorPrimary) - binding.listItemTitle.setTextColor(context.getAttrColor(R.attr.colorPrimary)) - } } diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/GridSpacingItemDecoration.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/GridSpacingItemDecoration.kt new file mode 100644 index 000000000..4a23e2a1a --- /dev/null +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/GridSpacingItemDecoration.kt @@ -0,0 +1,37 @@ +package com.chooloo.www.chooloolib.util + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class GridSpacingItemDecoration( + private val spanCount: Int, + private val spacing: Int, + private val includeEdge: Boolean +) : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + val position = parent.getChildAdapterPosition(view); // item position + val column = position % spanCount; // item column + + outRect.left = + if (includeEdge) spacing - column * spacing / spanCount else column * spacing / spanCount + outRect.right = + if (includeEdge) (column + 1) * spacing / spanCount else spacing - (column + 1) * spacing / spanCount + + if (includeEdge) { + if (position < spanCount) { // top edge + outRect.top = spacing; + } + outRect.bottom = spacing; // item bottom + } else { + if (position >= spanCount) { + outRect.top = spacing; // item top + } + } + } +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/LiveEvent.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/LiveEvent.kt index f2e6c2070..79442d8bf 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/LiveEvent.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/LiveEvent.kt @@ -1,19 +1,29 @@ package com.chooloo.www.chooloolib.util -import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.LiveData -class DataLiveEvent : MutableLiveData> { +open class DataLiveEvent : LiveData> { + constructor() : super() + constructor(value: T) : this(Event(value)) + constructor(value: Event) : super(value) +} + +class MutableDataLiveEvent : DataLiveEvent { constructor() : super() constructor(value: T) : this(Event(value)) constructor(value: Event) : super(value) fun call(value: T) { - this@DataLiveEvent.value = Event(value) + this.value = Event(value) } } -class LiveEvent : MutableLiveData>() { +open class LiveEvent : LiveData>() { +} + +class MutableLiveEvent : LiveEvent() { fun call() { value = Event(0) } -} \ No newline at end of file +} + diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/StringUtils.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/StringUtils.kt index 354ba523d..6e77d842c 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/StringUtils.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/StringUtils.kt @@ -2,4 +2,12 @@ package com.chooloo.www.chooloolib.util fun String.initials() = - split(' ').mapNotNull { it.firstOrNull()?.toString() }.reduce { acc, s -> acc + s } \ No newline at end of file + split(' ').mapNotNull { it.firstOrNull()?.toString() }.reduce { acc, s -> acc + s } + +fun String.isRTL() = if (length < 1) { + true +} else { + val directionality = Character.getDirectionality(get(0)) + directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT || + directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC +} \ No newline at end of file diff --git a/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/ViewUtils.kt b/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/ViewUtils.kt index 3760ce757..bdee81fa2 100644 --- a/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/ViewUtils.kt +++ b/chooloolib/src/main/java/com/chooloo/www/chooloolib/util/ViewUtils.kt @@ -7,15 +7,6 @@ import android.util.TypedValue import androidx.annotation.ColorInt import androidx.core.content.ContextCompat -fun Context.navBarHeight(): Int { - val resource = resources.getIdentifier("navigation_bar_height", "dimen", "android") - return if (resource > 0) resources.getDimensionPixelSize(resource) else 0 -} - -fun Context.hasNavBar(): Boolean { - val resource = resources.getIdentifier("config_showNavigationBar", "bool", "android") - return resource > 0 && resources.getBoolean(resource) -} fun Context.getSizeInDp(px: Int) = (px * resources.displayMetrics.density + 0.5f).toInt() diff --git a/chooloolib/src/main/res/drawable/backspace.xml b/chooloolib/src/main/res/drawable/backspace.xml new file mode 100644 index 000000000..0b7e9cef9 --- /dev/null +++ b/chooloolib/src/main/res/drawable/backspace.xml @@ -0,0 +1,11 @@ + + + diff --git a/chooloolib/src/main/res/drawable/bubble_background.xml b/chooloolib/src/main/res/drawable/bubble_background.xml index 342b1cab9..756d12e00 100644 --- a/chooloolib/src/main/res/drawable/bubble_background.xml +++ b/chooloolib/src/main/res/drawable/bubble_background.xml @@ -1,7 +1,6 @@ - + android:color="?colorControlHighlight"> diff --git a/chooloolib/src/main/res/drawable/bubble_background_no_ripple.xml b/chooloolib/src/main/res/drawable/bubble_background_no_ripple.xml deleted file mode 100644 index 490c5d596..000000000 --- a/chooloolib/src/main/res/drawable/bubble_background_no_ripple.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/chooloolib/src/main/res/drawable/call.xml b/chooloolib/src/main/res/drawable/call.xml index 013a39163..a0a0cd670 100644 --- a/chooloolib/src/main/res/drawable/call.xml +++ b/chooloolib/src/main/res/drawable/call.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/chooloolib/src/main/res/drawable/circle_background.xml b/chooloolib/src/main/res/drawable/circle_background.xml new file mode 100644 index 000000000..5cb31f881 --- /dev/null +++ b/chooloolib/src/main/res/drawable/circle_background.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/chooloolib/src/main/res/drawable/icon_button_color_selector.xml b/chooloolib/src/main/res/drawable/icon_button_color_selector.xml deleted file mode 100644 index 2472d18bc..000000000 --- a/chooloolib/src/main/res/drawable/icon_button_color_selector.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/chooloolib/src/main/res/drawable/more_vert.xml b/chooloolib/src/main/res/drawable/more_vert.xml index a07f3abf9..861113087 100644 --- a/chooloolib/src/main/res/drawable/more_vert.xml +++ b/chooloolib/src/main/res/drawable/more_vert.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/chooloolib/src/main/res/drawable/phone_msg.xml b/chooloolib/src/main/res/drawable/phone_msg.xml new file mode 100644 index 000000000..8880aa5cb --- /dev/null +++ b/chooloolib/src/main/res/drawable/phone_msg.xml @@ -0,0 +1,10 @@ + + + diff --git a/chooloolib/src/main/res/drawable/selector_button_background.xml b/chooloolib/src/main/res/drawable/selector_button_background.xml new file mode 100644 index 000000000..8bb276212 --- /dev/null +++ b/chooloolib/src/main/res/drawable/selector_button_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/chooloolib/src/main/res/drawable/swipe_indicator_background.xml b/chooloolib/src/main/res/drawable/swipe_indicator_background.xml deleted file mode 100644 index 81c10f958..000000000 --- a/chooloolib/src/main/res/drawable/swipe_indicator_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/chooloolib/src/main/res/layout/bottom_dialog.xml b/chooloolib/src/main/res/layout/bottom_dialog.xml index 9a0940fff..4fbd2b1b6 100644 --- a/chooloolib/src/main/res/layout/bottom_dialog.xml +++ b/chooloolib/src/main/res/layout/bottom_dialog.xml @@ -6,16 +6,6 @@ android:layout_height="wrap_content" android:fitsSystemWindows="true"> - - - - - - - - - - + android:layout_height="wrap_content" + android:paddingBottom="@dimen/default_spacing"> - - - - + app:layout_constraintTop_toTopOf="parent"> - + - + + + + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/brief_contact_name_layout" /> diff --git a/chooloolib/src/main/res/layout/brief_contact_main_actions.xml b/chooloolib/src/main/res/layout/brief_contact_main_actions.xml index 27e749f64..0adcb6e4e 100644 --- a/chooloolib/src/main/res/layout/brief_contact_main_actions.xml +++ b/chooloolib/src/main/res/layout/brief_contact_main_actions.xml @@ -1,43 +1,60 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:columnCount="4" + android:rowCount="1" + android:useDefaultMargins="true"> - + android:gravity="center" + app:icon='@drawable/call' /> - + app:layout_constraintStart_toEndOf="@id/brief_contact_button_call" + app:layout_constraintTop_toTopOf="parent" /> - - \ No newline at end of file + android:gravity="center" + android:padding="15dp" + app:icon="@drawable/chat" /> + + + diff --git a/chooloolib/src/main/res/layout/call.xml b/chooloolib/src/main/res/layout/call.xml index 57a39c614..0a1631e70 100644 --- a/chooloolib/src/main/res/layout/call.xml +++ b/chooloolib/src/main/res/layout/call.xml @@ -2,9 +2,9 @@ @@ -31,21 +31,20 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/default_spacing" - android:textAppearance="@style/TextAppearance.Material3.LabelMedium" - android:visibility="invisible" + android:textAppearance="@style/Chooloo.Text.Caption" app:layout_constraintEnd_toEndOf="parent" - tools:text="94:34" - tools:visibility="visible" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:text="94:34" + tools:visibility="visible" />