From 7f984f715e4d1713effa69d497f3a5c83dbaaa93 Mon Sep 17 00:00:00 2001 From: Ben Iofel Date: Mon, 12 Jan 2026 01:42:29 -0500 Subject: [PATCH 1/4] Finish migration to ViewBinding --- domain/build.gradle | 1 - presentation/build.gradle | 5 - .../com/moez/QKSMS/common/base/QkActivity.kt | 7 +- .../moez/QKSMS/common/base/QkController.kt | 29 +- .../QKSMS/common/base/QkThemedActivity.kt | 1 - .../moez/QKSMS/common/base/QkViewHolder.kt | 5 +- .../QKSMS/common/widget/PreferenceView.kt | 2 +- .../common/widget/RadioPreferenceView.kt | 4 + .../QKSMS/feature/backup/BackupController.kt | 89 ++--- .../feature/blocking/BlockingController.kt | 30 +- .../MessageContentFiltersController.kt | 47 +-- .../manager/BlockingManagerController.kt | 51 +-- .../manager/BlockingManagerPreferenceView.kt | 2 + .../messages/BlockedMessagesAdapter.kt | 40 +- .../messages/BlockedMessagesController.kt | 21 +- .../numbers/BlockedNumbersController.kt | 25 +- .../QKSMS/feature/compose/ComposeActivity.kt | 353 ++++++++---------- .../compose/ComposeAttachmentAdapter.kt | 40 +- .../QKSMS/feature/compose/MessagesAdapter.kt | 282 +++++++------- .../compose/editing/ComposeItemAdapter.kt | 124 +++--- .../compose/editing/DetailedChipView.kt | 24 +- .../editing/PhoneNumberPickerAdapter.kt | 19 +- .../QKSMS/feature/compose/part/AudioBinder.kt | 90 ++--- .../QKSMS/feature/compose/part/FileBinder.kt | 25 +- .../QKSMS/feature/compose/part/ImageBinder.kt | 11 +- .../feature/compose/part/PartsAdapter.kt | 10 +- .../QKSMS/feature/compose/part/VCardBinder.kt | 27 +- .../feature/contacts/ContactsActivity.kt | 25 +- .../ConversationInfoActivity.kt | 9 +- .../ConversationInfoAdapter.kt | 92 +++-- .../ConversationInfoController.kt | 19 +- .../conversations/ConversationsAdapter.kt | 50 +-- .../QKSMS/feature/gallery/GalleryActivity.kt | 19 +- .../feature/gallery/GalleryPagerAdapter.kt | 26 +- .../moez/QKSMS/feature/main/MainActivity.kt | 200 +++++----- .../messageutils/MessageUtilsController.kt | 36 +- .../NotificationPrefsActivity.kt | 67 ++-- .../moez/QKSMS/feature/plus/PlusActivity.kt | 2 +- .../QKSMS/feature/qkreply/QkReplyActivity.kt | 48 +-- .../feature/scheduled/ScheduledActivity.kt | 44 +-- .../feature/settings/SettingsActivity.kt | 9 +- .../feature/settings/SettingsController.kt | 96 ++--- .../feature/settings/about/AboutController.kt | 16 +- .../settings/swipe/SwipeActionsController.kt | 38 +- .../feature/themepicker/HSVPickerView.kt | 41 +- .../QKSMS/feature/themepicker/ThemeAdapter.kt | 52 +-- .../themepicker/ThemePickerController.kt | 44 +-- 47 files changed, 1175 insertions(+), 1122 deletions(-) diff --git a/domain/build.gradle b/domain/build.gradle index 9c0d0c16..17daa30a 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -19,7 +19,6 @@ apply plugin: 'com.android.library' apply plugin: 'realm-android' // Realm needs to be before Kotlin or the build will fail apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { diff --git a/presentation/build.gradle b/presentation/build.gradle index adfb4fbe..6cb8b000 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -19,7 +19,6 @@ apply plugin: 'com.android.application' apply plugin: 'realm-android' // Realm needs to be before Kotlin or the build will fail apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { @@ -107,10 +106,6 @@ android { } } -androidExtensions { - experimental = true -} - configurations { debug release diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkActivity.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkActivity.kt index 3a49726b..1328d33c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkActivity.kt @@ -24,11 +24,13 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.WindowManager +import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.util.Preferences import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.toolbar.* import javax.inject.Inject abstract class QkActivity : AppCompatActivity() { @@ -36,6 +38,9 @@ abstract class QkActivity : AppCompatActivity() { protected val menu: Subject = BehaviorSubject.create() + protected val toolbar: Toolbar? get() = findViewById(R.id.toolbar) + protected val toolbarTitle: TextView? get() = findViewById(R.id.toolbarTitle) + @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkController.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkController.kt index 85e0bfff..19131b43 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkController.kt @@ -21,15 +21,14 @@ package dev.octoshrimpy.quik.common.base import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.LayoutRes +import android.widget.TextView import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding import com.bluelinelabs.conductor.archlifecycle.LifecycleController -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.* -import kotlinx.android.synthetic.main.toolbar.view.* +import dev.octoshrimpy.quik.R -abstract class QkController, State, Presenter : QkPresenter> : LifecycleController(), LayoutContainer { +abstract class QkController, State, Presenter : QkPresenter> : LifecycleController() { abstract var presenter: Presenter @@ -39,16 +38,16 @@ abstract class QkController, State, Present protected val themedActivity: QkThemedActivity? get() = activity as? QkThemedActivity - override var containerView: View? = null + private var _binding: VB? = null + protected val binding: VB + get() = requireNotNull(_binding) - @LayoutRes - var layoutRes: Int = 0 + protected abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): VB override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { - return inflater.inflate(layoutRes, container, false).also { - containerView = it - onViewCreated() - } + val vb = inflateBinding(inflater, container) + _binding = vb + return vb.root.also { onViewCreated() } } open fun onViewCreated() { @@ -60,7 +59,7 @@ abstract class QkController, State, Present fun setTitle(title: CharSequence?) { activity?.title = title - view?.toolbarTitle?.text = title + view?.findViewById(R.id.toolbarTitle)?.text = title } fun showBackButton(show: Boolean) { @@ -68,8 +67,8 @@ abstract class QkController, State, Present } override fun onDestroyView(view: View) { - containerView = null - clearFindViewByIdCache() + super.onDestroyView(view) + _binding = null } override fun onDestroy() { diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkThemedActivity.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkThemedActivity.kt index edd3b493..cd83ebfb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkThemedActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkThemedActivity.kt @@ -43,7 +43,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxkotlin.Observables import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.toolbar.* import java.util.concurrent.TimeUnit import javax.inject.Inject diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewHolder.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewHolder.kt index 889fdd36..8db52e77 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewHolder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewHolder.kt @@ -21,10 +21,7 @@ package dev.octoshrimpy.quik.common.base import android.view.View import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -import kotlinx.android.extensions.LayoutContainer -open class QkViewHolder(view: View) : RecyclerView.ViewHolder(view), LayoutContainer { - override val containerView: View = view -} +open class QkViewHolder(view: View) : RecyclerView.ViewHolder(view) open class QkBindingViewHolder(val binding: T) : QkViewHolder(binding.root) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt index 8486e510..abaf1788 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt @@ -38,7 +38,7 @@ class PreferenceView @JvmOverloads constructor( private var layout: PreferenceViewBinding - val titleTextView: TextView get() = layout.titleView + val titleView: TextView get() = layout.titleView var title: String? = null set(value) { diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt index 7a51e062..6ae133f6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt @@ -69,6 +69,10 @@ class RadioPreferenceView @JvmOverloads constructor( } } + val radioButton get() = layout.radioButton + val titleView get() = layout.titleView + val summaryView get() = layout.summaryView + init { if (!isInEditMode) { appComponent.inject(this) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupController.kt index 69026d1f..322cc639 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupController.kt @@ -22,7 +22,9 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.Typeface import android.net.Uri +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog @@ -41,14 +43,13 @@ import dev.octoshrimpy.quik.common.util.extensions.setTint import dev.octoshrimpy.quik.common.widget.PreferenceView import dev.octoshrimpy.quik.injection.appComponent import dev.octoshrimpy.quik.repository.BackupRepository +import dev.octoshrimpy.quik.databinding.BackupControllerBinding import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.backup_controller.* -import kotlinx.android.synthetic.main.preference_view.view.* import javax.inject.Inject -class BackupController : QkController(), BackupView { +class BackupController : QkController(), BackupView { @Inject override lateinit var presenter: BackupPresenter @@ -109,9 +110,11 @@ class BackupController : QkController( init { appComponent.inject(this) - layoutRes = R.layout.backup_controller } + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): BackupControllerBinding = + BackupControllerBinding.inflate(inflater, container, false) + override fun onContextAvailable(context: Context) { // Init activity result contracts openDirectory = themedActivity!! @@ -136,57 +139,57 @@ class BackupController : QkController( super.onViewCreated() themedActivity?.colors?.theme()?.let { theme -> - progressBar.indeterminateTintList = ColorStateList.valueOf(theme.theme) - progressBar.progressTintList = ColorStateList.valueOf(theme.theme) - fab.setBackgroundTint(theme.theme) - fabIcon.setTint(theme.textPrimary) - fabLabel.setTextColor(theme.textPrimary) + binding.progressBar.indeterminateTintList = ColorStateList.valueOf(theme.theme) + binding.progressBar.progressTintList = ColorStateList.valueOf(theme.theme) + binding.fab.setBackgroundTint(theme.theme) + binding.fabIcon.setTint(theme.textPrimary) + binding.fabLabel.setTextColor(theme.textPrimary) } // Make the list titles bold - linearLayout.children - .mapNotNull { it as? PreferenceView } - .map { it.titleView } - .forEach { it.setTypeface(it.typeface, Typeface.BOLD) } + binding.linearLayout.children + .mapNotNull { it as? PreferenceView } + .map { it.titleView } + .forEach { it.setTypeface(it.typeface, Typeface.BOLD) } } override fun render(state: BackupState) { when { state.backupProgress.running -> { - progressIcon.setImageResource(R.drawable.ic_file_upload_black_24dp) - progressTitle.setText(R.string.backup_backing_up) - progressSummary.text = state.backupProgress.getLabel(activity!!) - progressSummary.isVisible = progressSummary.text.isNotEmpty() - progressCancel.isVisible = false + binding.progressIcon.setImageResource(R.drawable.ic_file_upload_black_24dp) + binding.progressTitle.setText(R.string.backup_backing_up) + binding.progressSummary.text = state.backupProgress.getLabel(activity!!) + binding.progressSummary.isVisible = binding.progressSummary.text.isNotEmpty() + binding.progressCancel.isVisible = false val running = (state.backupProgress as? BackupRepository.Progress.Running) - progressBar.isVisible = + binding.progressBar.isVisible = state.backupProgress.indeterminate || (running?.max ?: 0) > 0 - progressBar.isIndeterminate = state.backupProgress.indeterminate - progressBar.max = running?.max ?: 0 - progressBar.progress = running?.count ?: 0 - progress.isVisible = true - fab.isVisible = false + binding.progressBar.isIndeterminate = state.backupProgress.indeterminate + binding.progressBar.max = running?.max ?: 0 + binding.progressBar.progress = running?.count ?: 0 + binding.progress.isVisible = true + binding.fab.isVisible = false } state.restoreProgress.running -> { - progressIcon.setImageResource(R.drawable.ic_file_download_black_24dp) - progressTitle.setText(R.string.backup_restoring) - progressSummary.text = state.restoreProgress.getLabel(activity!!) - progressSummary.isVisible = progressSummary.text.isNotEmpty() - progressCancel.isVisible = true + binding.progressIcon.setImageResource(R.drawable.ic_file_download_black_24dp) + binding.progressTitle.setText(R.string.backup_restoring) + binding.progressSummary.text = state.restoreProgress.getLabel(activity!!) + binding.progressSummary.isVisible = binding.progressSummary.text.isNotEmpty() + binding.progressCancel.isVisible = true val running = (state.restoreProgress as? BackupRepository.Progress.Running) - progressBar.isVisible = + binding.progressBar.isVisible = state.restoreProgress.indeterminate || (running?.max ?: 0) > 0 - progressBar.isIndeterminate = state.restoreProgress.indeterminate - progressBar.max = running?.max ?: 0 - progressBar.progress = running?.count ?: 0 - progress.isVisible = true - fab.isVisible = false + binding.progressBar.isIndeterminate = state.restoreProgress.indeterminate + binding.progressBar.max = running?.max ?: 0 + binding.progressBar.progress = running?.count ?: 0 + binding.progress.isVisible = true + binding.fab.isVisible = false } else -> { - progress.isVisible = false - fab.isVisible = true + binding.progress.isVisible = false + binding.fab.isVisible = true } } @@ -199,20 +202,20 @@ class BackupController : QkController( stopRestoreDialog.setShowing(state.showStopRestoreDialog) - fabIcon.setImageResource(when (state.upgraded) { + binding.fabIcon.setImageResource(when (state.upgraded) { true -> R.drawable.ic_file_upload_black_24dp false -> R.drawable.ic_star_black_24dp }) - fabLabel.setText(when (state.upgraded) { + binding.fabLabel.setText(when (state.upgraded) { true -> R.string.backup_now false -> R.string.title_qksms_plus }) } - override fun setBackupLocationClicks(): Observable<*> = location.clicks() + override fun setBackupLocationClicks(): Observable<*> = binding.location.clicks() - override fun restoreClicks(): Observable<*> = restore.clicks() + override fun restoreClicks(): Observable<*> = binding.restore.clicks() override fun locationRationaleConfirmClicks(): Observable<*> = selectFolderConfirmSubject @@ -224,13 +227,13 @@ class BackupController : QkController( override fun confirmRestoreBackupCancelClicks(): Observable<*> = confirmRestoreCancelSubject - override fun stopRestoreClicks(): Observable<*> = progressCancel.clicks() + override fun stopRestoreClicks(): Observable<*> = binding.progressCancel.clicks() override fun stopRestoreConfirmed(): Observable<*> = stopRestoreConfirmSubject override fun stopRestoreCancel(): Observable<*> = stopRestoreCancelSubject - override fun backupClicks(): Observable<*> = fab.clicks() + override fun backupClicks(): Observable<*> = binding.fab.clicks() override fun documentTreeSelected(): Observable = documentTreeSelectedSubject diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt index 40bf0207..c543f775 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt @@ -18,7 +18,9 @@ */ package dev.octoshrimpy.quik.feature.blocking +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import com.bluelinelabs.conductor.RouterTransaction import com.jakewharton.rxbinding2.view.clicks import dev.octoshrimpy.quik.R @@ -31,17 +33,20 @@ import dev.octoshrimpy.quik.feature.blocking.messages.BlockedMessagesController import dev.octoshrimpy.quik.feature.blocking.numbers.BlockedNumbersController import dev.octoshrimpy.quik.feature.blocking.filters.MessageContentFiltersController import dev.octoshrimpy.quik.injection.appComponent -import kotlinx.android.synthetic.main.blocking_controller.* -import kotlinx.android.synthetic.main.settings_switch_widget.view.* +import dev.octoshrimpy.quik.databinding.BlockingControllerBinding +import dev.octoshrimpy.quik.common.widget.QkSwitch import javax.inject.Inject -class BlockingController : QkController(), BlockingView { +class BlockingController : QkController(), BlockingView { - override val blockingManagerIntent by lazy { blockingManager.clicks() } - override val blockedNumbersIntent by lazy { blockedNumbers.clicks() } - override val messageContentFiltersIntent by lazy { messageContentFilters.clicks() } - override val blockedMessagesIntent by lazy { blockedMessages.clicks() } - override val dropClickedIntent by lazy { drop.clicks() } + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): BlockingControllerBinding = + BlockingControllerBinding.inflate(inflater, container, false) + + override val blockingManagerIntent get() = binding.blockingManager.clicks() + override val blockedNumbersIntent get() = binding.blockedNumbers.clicks() + override val messageContentFiltersIntent get() = binding.messageContentFilters.clicks() + override val blockedMessagesIntent get() = binding.blockedMessages.clicks() + override val dropClickedIntent get() = binding.drop.clicks() @Inject lateinit var colors: Colors @Inject override lateinit var presenter: BlockingPresenter @@ -49,12 +54,11 @@ class BlockingController : QkController(R.id.checkbox)?.isChecked = state.dropEnabled + binding.blockedMessages.isEnabled = !state.dropEnabled } override fun openBlockedNumbers() { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt index 61eb2079..34afb5e9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt @@ -32,15 +32,15 @@ import dev.octoshrimpy.quik.common.util.extensions.setTint import dev.octoshrimpy.quik.common.widget.PreferenceView import dev.octoshrimpy.quik.injection.appComponent import dev.octoshrimpy.quik.model.MessageContentFilterData +import dev.octoshrimpy.quik.databinding.MessageContentFiltersControllerBinding +import dev.octoshrimpy.quik.databinding.MessageContentFiltersAddDialogBinding +import dev.octoshrimpy.quik.common.widget.QkSwitch import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.message_content_filters_add_dialog.view.* -import kotlinx.android.synthetic.main.message_content_filters_controller.* -import kotlinx.android.synthetic.main.settings_switch_widget.view.* import javax.inject.Inject -class MessageContentFiltersController : QkController(), MessageContentFiltersView { @Inject override lateinit var presenter: MessageContentFiltersPresenter @@ -52,9 +52,11 @@ class MessageContentFiltersController : QkController = adapter.removeMessageContentFilter - override fun addFilter(): Observable<*> = add.clicks() + override fun addFilter(): Observable<*> = binding.add.clicks() override fun saveFilter(): Observable = saveFilterSubject override fun showAddDialog() { - val layout = LayoutInflater.from(activity).inflate(R.layout.message_content_filters_add_dialog, null) + val layout = MessageContentFiltersAddDialogBinding.inflate(LayoutInflater.from(activity)) - (0 until layout.add_dialog.childCount) - .map { index -> layout.add_dialog.getChildAt(index) } + (0 until layout.addDialog.childCount) + .map { index -> layout.addDialog.getChildAt(index) } .mapNotNull { view -> view as? PreferenceView } .map { preference -> preference.clicks().map { preference } } .let { Observable.merge(it) } .autoDisposable(scope()) - .subscribe { - it.checkbox.isChecked = !it.checkbox.isChecked - layout.caseSensitivity.isEnabled = !layout.regexp.checkbox.isChecked + .subscribe { pref -> + val checkbox = pref.findViewById(R.id.checkbox) + checkbox?.isChecked = !(checkbox?.isChecked ?: false) + val regexChecked = layout.regexp.findViewById(R.id.checkbox)?.isChecked ?: false + layout.caseSensitivity.isEnabled = !regexChecked } val dialog = AlertDialog.Builder(activity!!) - .setView(layout) + .setView(layout.root) .setPositiveButton(R.string.message_content_filters_dialog_create) { _, _ -> var text = layout.input.text.toString(); if (!text.isBlank()) { - if (!layout.regexp.checkbox.isChecked) text = text.trim() + val regexChecked = layout.regexp.findViewById(R.id.checkbox)?.isChecked ?: false + if (!regexChecked) text = text.trim() saveFilterSubject.onNext( MessageContentFilterData( text, - layout.caseSensitivity.checkbox.isChecked && !layout.regexp.checkbox.isChecked, - layout.regexp.checkbox.isChecked, - layout.contacts.checkbox.isChecked + (layout.caseSensitivity.findViewById(R.id.checkbox)?.isChecked == true) && !regexChecked, + regexChecked, + layout.contacts.findViewById(R.id.checkbox)?.isChecked == true ) ) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt index e1e34a59..321418e9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt @@ -3,25 +3,29 @@ package dev.octoshrimpy.quik.feature.blocking.manager import android.app.Activity import android.app.AlertDialog import android.content.res.ColorStateList +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.core.view.isInvisible import com.jakewharton.rxbinding2.view.clicks import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkController import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor +import dev.octoshrimpy.quik.databinding.BlockingManagerControllerBinding import dev.octoshrimpy.quik.injection.appComponent import dev.octoshrimpy.quik.util.Preferences import io.reactivex.Observable import io.reactivex.Single import io.reactivex.subjects.PublishSubject -import kotlinx.android.synthetic.main.blocking_manager_controller.* -import kotlinx.android.synthetic.main.blocking_manager_list_option.view.* import javax.inject.Inject -class BlockingManagerController : QkController(), +class BlockingManagerController : QkController(), BlockingManagerView { + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): BlockingManagerControllerBinding = + BlockingManagerControllerBinding.inflate(inflater, container, false) + @Inject lateinit var colors: Colors @Inject override lateinit var presenter: BlockingManagerPresenter @@ -30,7 +34,6 @@ class BlockingManagerController : QkController = activityResumedSubject - override fun qksmsClicked(): Observable<*> = qksms.clicks() - override fun callBlockerClicked(): Observable<*> = callBlocker.clicks() - override fun callControlClicked(): Observable<*> = callControl.clicks() - override fun siaClicked(): Observable<*> = shouldIAnswer.clicks() + override fun qksmsClicked(): Observable<*> = binding.qksms.clicks() + override fun callBlockerClicked(): Observable<*> = binding.callBlocker.clicks() + override fun callControlClicked(): Observable<*> = binding.callControl.clicks() + override fun siaClicked(): Observable<*> = binding.shouldIAnswer.clicks() override fun showCopyDialog(manager: String): Single = Single.create { emitter -> AlertDialog.Builder(activity) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPreferenceView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPreferenceView.kt index a668d4bf..02192892 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPreferenceView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPreferenceView.kt @@ -39,6 +39,8 @@ class BlockingManagerPreferenceView @JvmOverloads constructor( private val binding: BlockingManagerPreferenceViewBinding = BlockingManagerPreferenceViewBinding.inflate(LayoutInflater.from(context), this) + val actionView: ImageView get() = binding.widgetFrame.findViewById(R.id.action) + var icon: Drawable? = null set(value) { field = value diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt index 5a2a88de..a51617e3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt @@ -28,11 +28,10 @@ import dev.octoshrimpy.quik.common.base.QkRealmAdapter import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.DateFormatter import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor +import dev.octoshrimpy.quik.databinding.BlockedListItemBinding import dev.octoshrimpy.quik.model.Conversation import dev.octoshrimpy.quik.util.Preferences import io.reactivex.subjects.PublishSubject -import kotlinx.android.synthetic.main.blocked_list_item.* -import kotlinx.android.synthetic.main.blocked_list_item.view.* import javax.inject.Inject class BlockedMessagesAdapter @Inject constructor( @@ -43,26 +42,26 @@ class BlockedMessagesAdapter @Inject constructor( val clicks: PublishSubject = PublishSubject.create() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.blocked_list_item, parent, false) + val binding = BlockedListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) if (viewType == 0) { - view.title.setTypeface(view.title.typeface, Typeface.BOLD) - view.date.setTypeface(view.date.typeface, Typeface.BOLD) - view.date.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorPrimary)) + binding.title.setTypeface(binding.title.typeface, Typeface.BOLD) + binding.date.setTypeface(binding.date.typeface, Typeface.BOLD) + binding.date.setTextColor(binding.root.context.resolveThemeColor(android.R.attr.textColorPrimary)) } - return QkViewHolder(view).apply { - view.setOnClickListener { + return QkViewHolder(binding.root).apply { + binding.root.setOnClickListener { val conversation = getItem(adapterPosition) ?: return@setOnClickListener when (toggleSelection(conversation.id, false)) { - true -> view.isActivated = isSelected(conversation.id) + true -> binding.root.isActivated = isSelected(conversation.id) false -> clicks.onNext(conversation.id) } } - view.setOnLongClickListener { + binding.root.setOnLongClickListener { val conversation = getItem(adapterPosition) ?: return@setOnLongClickListener true toggleSelection(conversation.id) - view.isActivated = isSelected(conversation.id) + binding.root.isActivated = isSelected(conversation.id) true } } @@ -70,23 +69,24 @@ class BlockedMessagesAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return + val binding = BlockedListItemBinding.bind(holder.itemView) - holder.containerView.isActivated = isSelected(conversation.id) + holder.itemView.isActivated = isSelected(conversation.id) - holder.avatars.recipients = conversation.recipients - holder.title.collapseEnabled = conversation.recipients.size > 1 - holder.title.text = conversation.getTitle() - holder.date.text = dateFormatter.getConversationTimestamp(conversation.date) + binding.avatars.recipients = conversation.recipients + binding.title.collapseEnabled = conversation.recipients.size > 1 + binding.title.text = conversation.getTitle() + binding.date.text = dateFormatter.getConversationTimestamp(conversation.date) - holder.blocker.text = when (conversation.blockingClient) { + binding.blocker.text = when (conversation.blockingClient) { Preferences.BLOCKING_MANAGER_CC -> context.getString(R.string.blocking_manager_call_control_title) Preferences.BLOCKING_MANAGER_SIA -> context.getString(R.string.blocking_manager_sia_title) else -> null } - holder.reason.text = conversation.blockReason - holder.blocker.isVisible = holder.blocker.text.isNotEmpty() - holder.reason.isVisible = holder.blocker.text.isNotEmpty() + binding.reason.text = conversation.blockReason + binding.blocker.isVisible = binding.blocker.text.isNotEmpty() + binding.reason.isVisible = binding.blocker.text.isNotEmpty() } override fun getItemViewType(position: Int): Int { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesController.kt index 44dfefee..9e30bacd 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesController.kt @@ -19,25 +19,30 @@ package dev.octoshrimpy.quik.feature.blocking.messages import android.content.Context +import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import android.view.ViewGroup import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkController import dev.octoshrimpy.quik.common.util.Colors +import dev.octoshrimpy.quik.databinding.BlockedMessagesControllerBinding import dev.octoshrimpy.quik.feature.blocking.BlockingDialog import dev.octoshrimpy.quik.injection.appComponent import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.blocked_messages_controller.* -import kotlinx.android.synthetic.main.container_activity.* import javax.inject.Inject -class BlockedMessagesController : QkController(), +class BlockedMessagesController : QkController(), BlockedMessagesView { + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): BlockedMessagesControllerBinding = + BlockedMessagesControllerBinding.inflate(inflater, container, false) + override val menuReadyIntent: Subject = PublishSubject.create() override val optionsItemIntent: Subject = PublishSubject.create() override val conversationClicks by lazy { blockedMessagesAdapter.clicks } @@ -54,13 +59,12 @@ class BlockedMessagesController : QkController 0 - themedActivity?.toolbar?.menu?.findItem(R.id.delete)?.isVisible = state.selected > 0 + val toolbarMenu = themedActivity?.findViewById(R.id.toolbar)?.menu + toolbarMenu?.findItem(R.id.block)?.isVisible = state.selected > 0 + toolbarMenu?.findItem(R.id.delete)?.isVisible = state.selected > 0 setTitle(when (state.selected) { 0 -> context.getString(R.string.blocked_messages_title) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersController.kt index ebadee3d..26e59847 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersController.kt @@ -20,6 +20,7 @@ package dev.octoshrimpy.quik.feature.blocking.numbers import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import com.jakewharton.rxbinding2.view.clicks import dev.octoshrimpy.quik.R @@ -32,11 +33,11 @@ import dev.octoshrimpy.quik.util.PhoneNumberUtils import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.blocked_numbers_add_dialog.view.* -import kotlinx.android.synthetic.main.blocked_numbers_controller.* +import dev.octoshrimpy.quik.databinding.BlockedNumbersControllerBinding +import dev.octoshrimpy.quik.databinding.BlockedNumbersAddDialogBinding import javax.inject.Inject -class BlockedNumbersController : QkController(), +class BlockedNumbersController : QkController(), BlockedNumbersView { @Inject override lateinit var presenter: BlockedNumbersPresenter @@ -49,9 +50,11 @@ class BlockedNumbersController : QkController = adapter.unblockAddress - override fun addAddress(): Observable<*> = add.clicks() + override fun addAddress(): Observable<*> = binding.add.clicks() override fun saveAddress(): Observable = saveAddressSubject override fun showAddDialog() { - val layout = LayoutInflater.from(activity).inflate(R.layout.blocked_numbers_add_dialog, null) + val layout = BlockedNumbersAddDialogBinding.inflate(LayoutInflater.from(activity)) val textWatcher = BlockedNumberTextWatcher(layout.input, phoneNumberUtils) val dialog = AlertDialog.Builder(activity!!) - .setView(layout) + .setView(layout.root) .setPositiveButton(R.string.blocked_numbers_dialog_block) { _, _ -> saveAddressSubject.onNext(layout.input.text.toString()) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt index aa569b43..05ce0621 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt @@ -75,7 +75,6 @@ import dev.octoshrimpy.quik.common.util.extensions.setTint import dev.octoshrimpy.quik.common.util.extensions.setVisible import dev.octoshrimpy.quik.common.util.extensions.showKeyboard import dev.octoshrimpy.quik.common.widget.MicInputCloudView -import dev.octoshrimpy.quik.common.widget.QkEditText import dev.octoshrimpy.quik.extensions.mapNotNull import dev.octoshrimpy.quik.feature.compose.editing.ChipsAdapter import dev.octoshrimpy.quik.feature.contacts.ContactsActivity @@ -85,60 +84,9 @@ import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import dev.octoshrimpy.quik.databinding.ComposeActivityBinding import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.compose_activity.attach -import kotlinx.android.synthetic.main.compose_activity.attachAFileIcon -import kotlinx.android.synthetic.main.compose_activity.attachAFileLabel -import kotlinx.android.synthetic.main.compose_activity.attachAnAudioMessageIcon -import kotlinx.android.synthetic.main.compose_activity.attachAnAudioMessageLabel -import kotlinx.android.synthetic.main.compose_activity.attaching -import kotlinx.android.synthetic.main.compose_activity.audioMsgAbort -import kotlinx.android.synthetic.main.compose_activity.audioMsgAttach -import kotlinx.android.synthetic.main.compose_activity.audioMsgBackground -import kotlinx.android.synthetic.main.compose_activity.audioMsgBluetooth -import kotlinx.android.synthetic.main.compose_activity.audioMsgDuration -import kotlinx.android.synthetic.main.compose_activity.audioMsgPlayerBackground -import kotlinx.android.synthetic.main.compose_activity.audioMsgPlayerPlayPause -import kotlinx.android.synthetic.main.compose_activity.audioMsgPlayerSeekBar -import kotlinx.android.synthetic.main.compose_activity.audioMsgRecord -import kotlinx.android.synthetic.main.compose_activity.camera -import kotlinx.android.synthetic.main.compose_activity.cameraLabel -import kotlinx.android.synthetic.main.compose_activity.chips -import kotlinx.android.synthetic.main.compose_activity.composeBar -import kotlinx.android.synthetic.main.compose_activity.contact -import kotlinx.android.synthetic.main.compose_activity.contactLabel -import kotlinx.android.synthetic.main.compose_activity.contentView -import kotlinx.android.synthetic.main.compose_activity.counter -import kotlinx.android.synthetic.main.compose_activity.gallery -import kotlinx.android.synthetic.main.compose_activity.galleryLabel -import kotlinx.android.synthetic.main.compose_activity.loading -import kotlinx.android.synthetic.main.compose_activity.message -import kotlinx.android.synthetic.main.compose_activity.messageAttachments -import kotlinx.android.synthetic.main.compose_activity.messageList -import kotlinx.android.synthetic.main.compose_activity.messagesEmpty -import kotlinx.android.synthetic.main.compose_activity.noValidRecipients -import kotlinx.android.synthetic.main.compose_activity.recordAudioMsg -import kotlinx.android.synthetic.main.compose_activity.schedule -import kotlinx.android.synthetic.main.compose_activity.scheduleLabel -import kotlinx.android.synthetic.main.compose_activity.scheduledCancel -import kotlinx.android.synthetic.main.compose_activity.scheduledGroup -import kotlinx.android.synthetic.main.compose_activity.scheduledTime -import kotlinx.android.synthetic.main.compose_activity.scheduledSend -import kotlinx.android.synthetic.main.compose_activity.send -import kotlinx.android.synthetic.main.compose_activity.sendAsGroup -import kotlinx.android.synthetic.main.compose_activity.sendAsGroupBackground -import kotlinx.android.synthetic.main.compose_activity.sendAsGroupSummary -import kotlinx.android.synthetic.main.compose_activity.sendAsGroupSwitch -import kotlinx.android.synthetic.main.compose_activity.shadeBackground -import kotlinx.android.synthetic.main.compose_activity.sim -import kotlinx.android.synthetic.main.compose_activity.simIndex -import kotlinx.android.synthetic.main.compose_activity.speechToTextFrame -import kotlinx.android.synthetic.main.compose_activity.speechToTextIcon -import kotlinx.android.synthetic.main.compose_activity.speechToTextIconBorder -import kotlinx.android.synthetic.main.compose_activity.toolbarSubtitle -import kotlinx.android.synthetic.main.compose_activity.toolbarTitle -import kotlinx.android.synthetic.main.main_activity.toolbar import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date @@ -156,6 +104,8 @@ class ComposeActivity : QkThemedActivity(), ComposeView { @Inject lateinit var navigator: Navigator @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + private lateinit var binding: ComposeActivityBinding + override val activityVisibleIntent: Subject = PublishSubject.create() override val chipsSelectedIntent: Subject> = PublishSubject.create() override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } @@ -163,7 +113,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val optionsItemIntent: Subject = PublishSubject.create() override val contextItemIntent: Subject = PublishSubject.create() override val scheduleAction: Subject = PublishSubject.create() - override val sendAsGroupIntent by lazy { sendAsGroupBackground.clicks() } + override val sendAsGroupIntent by lazy { binding.sendAsGroupBackground.clicks() } override val messagePartClickIntent: Subject by lazy { messageAdapter.partClicks } override val messagePartContextMenuRegistrar: Subject by lazy { messageAdapter.partContextMenuRegistrar } override val messagesSelectedIntent by lazy { messageAdapter.selectionChanges } @@ -171,35 +121,35 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val sendDelayedNowIntent: Subject by lazy { messageAdapter.sendNowClicks } override val resendIntent: Subject by lazy { messageAdapter.resendClicks } override val attachmentDeletedIntent: Subject by lazy { composeAttachmentAdapter.attachmentDeleted } - override val textChangedIntent by lazy { message.textChanges() } - override val attachIntent: Observable by lazy { Observable.merge(attach.clicks(), shadeBackground.clicks()) } - override val cameraIntent: Observable by lazy { Observable.merge(camera.clicks(), cameraLabel.clicks()) } - override val attachImageFileIntent: Observable by lazy { Observable.merge(gallery.clicks(), galleryLabel.clicks()) } - override val attachAnyFileIntent: Observable by lazy { Observable.merge(attachAFileIcon.clicks(), attachAFileLabel.clicks()) } - override val scheduleIntent: Observable by lazy { Observable.merge(schedule.clicks(), scheduleLabel.clicks()) } - override val attachContactIntent: Observable by lazy { Observable.merge(contact.clicks(), contactLabel.clicks()) } + override val textChangedIntent by lazy { binding.message.textChanges() } + override val attachIntent: Observable by lazy { Observable.merge(binding.attach.clicks(), binding.shadeBackground.clicks()) } + override val cameraIntent: Observable by lazy { Observable.merge(binding.camera.clicks(), binding.cameraLabel.clicks()) } + override val attachImageFileIntent: Observable by lazy { Observable.merge(binding.gallery.clicks(), binding.galleryLabel.clicks()) } + override val attachAnyFileIntent: Observable by lazy { Observable.merge(binding.attachAFileIcon.clicks(), binding.attachAFileLabel.clicks()) } + override val scheduleIntent: Observable by lazy { Observable.merge(binding.schedule.clicks(), binding.scheduleLabel.clicks()) } + override val attachContactIntent: Observable by lazy { Observable.merge(binding.contact.clicks(), binding.contactLabel.clicks()) } override val attachAnyFileSelectedIntent: Subject = PublishSubject.create() override val contactSelectedIntent: Subject = PublishSubject.create() - override val inputContentIntent by lazy { message.inputContentSelected } + override val inputContentIntent by lazy { binding.message.inputContentSelected } override val scheduleSelectedIntent: Subject = PublishSubject.create() - override val changeSimIntent by lazy { sim.clicks() } - override val scheduleCancelIntent by lazy { scheduledCancel.clicks() } - override val sendIntent by lazy { Observable.merge(send.clicks(), scheduledSend.clicks()) } + override val changeSimIntent by lazy { binding.sim.clicks() } + override val scheduleCancelIntent by lazy { binding.scheduledCancel.clicks() } + override val sendIntent by lazy { Observable.merge(binding.send.clicks(), binding.scheduledSend.clicks()) } override val viewQksmsPlusIntent: Subject = PublishSubject.create() override val backPressedIntent: Subject = PublishSubject.create() override val confirmDeleteIntent: Subject> = PublishSubject.create() override val clearCurrentMessageIntent: Subject = PublishSubject.create() override val messageLinkAskIntent: Subject by lazy { messageAdapter.messageLinkClicks } - override val speechRecogniserIntent by lazy { speechToTextIcon.clicks() } - override val shadeIntent by lazy { shadeBackground.clicks() } + override val speechRecogniserIntent by lazy { binding.speechToTextIcon.clicks() } + override val shadeIntent by lazy { binding.shadeBackground.clicks() } override val recordAudioStartStopRecording: Subject = PublishSubject.create() override val recordAnAudioMessage: Observable by lazy { - Observable.merge(recordAudioMsg.clicks(), - attachAnAudioMessageIcon.clicks(), - attachAnAudioMessageLabel.clicks()) + Observable.merge(binding.recordAudioMsg.clicks(), + binding.attachAnAudioMessageIcon.clicks(), + binding.attachAnAudioMessageLabel.clicks()) } - override val recordAudioAbort by lazy { audioMsgAbort.clicks() } - override val recordAudioAttach by lazy { audioMsgAttach.clicks() } + override val recordAudioAbort by lazy { binding.audioMsgAbort.clicks() } + override val recordAudioAttach by lazy { binding.audioMsgAttach.clicks() } override val recordAudioPlayerPlayPause: Subject = PublishSubject.create() override val recordAudioPlayerConfigUI: Subject = PublishSubject.create() override val recordAudioPlayerVisible: Subject = PublishSubject.create() @@ -227,56 +177,57 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.compose_activity) + binding = ComposeActivityBinding.inflate(layoutInflater) + setContentView(binding.root) showBackButton(true) viewModel.bindView(this) - contentView.layoutTransition = LayoutTransition().apply { + binding.contentView.layoutTransition = LayoutTransition().apply { disableTransitionType(LayoutTransition.CHANGING) } - chipsAdapter.view = chips + chipsAdapter.view = binding.chips - chips.itemAnimator = null - chips.layoutManager = FlexboxLayoutManager(this) + binding.chips.itemAnimator = null + binding.chips.layoutManager = FlexboxLayoutManager(this) - messageAdapter.autoScrollToStart(messageList) - messageAdapter.emptyView = messagesEmpty + messageAdapter.autoScrollToStart(binding.messageList) + messageAdapter.emptyView = binding.messagesEmpty - messageList.setHasFixedSize(true) - messageList.adapter = messageAdapter + binding.messageList.setHasFixedSize(true) + binding.messageList.adapter = messageAdapter - messageAttachments.adapter = composeAttachmentAdapter + binding.messageAttachments.adapter = composeAttachmentAdapter - message.supportsInputContent = true + binding.message.supportsInputContent = true theme .doOnNext { - loading.setTint(it.theme) + binding.loading.setTint(it.theme) // entire attach menu - attach.setBackgroundTint(it.theme); attach.setTint(it.textPrimary) - contact.setBackgroundTint(it.theme); contact.setTint(it.textPrimary) - contactLabel.setBackgroundTint(it.theme); contactLabel.setTint(it.textPrimary) - schedule.setBackgroundTint(it.theme); schedule.setTint(it.textPrimary) - scheduleLabel.setBackgroundTint(it.theme); scheduleLabel.setTint(it.textPrimary) - attachAFileIcon.setBackgroundTint(it.theme); attachAFileIcon.setTint(it.textPrimary) - attachAFileLabel.setBackgroundTint(it.theme); attachAFileLabel.setTint(it.textPrimary) - attachAnAudioMessageIcon.setBackgroundTint(it.theme); attachAnAudioMessageIcon.setTint(it.textPrimary) - attachAnAudioMessageLabel.setBackgroundTint(it.theme); attachAnAudioMessageLabel.setTint(it.textPrimary) - gallery.setBackgroundTint(it.theme); gallery.setTint(it.textPrimary) - galleryLabel.setBackgroundTint(it.theme); galleryLabel.setTint(it.textPrimary) - camera.setBackgroundTint(it.theme); camera.setTint(it.textPrimary) - cameraLabel.setBackgroundTint(it.theme); cameraLabel.setTint(it.textPrimary) + binding.attach.setBackgroundTint(it.theme); binding.attach.setTint(it.textPrimary) + binding.contact.setBackgroundTint(it.theme); binding.contact.setTint(it.textPrimary) + binding.contactLabel.setBackgroundTint(it.theme); binding.contactLabel.setTint(it.textPrimary) + binding.schedule.setBackgroundTint(it.theme); binding.schedule.setTint(it.textPrimary) + binding.scheduleLabel.setBackgroundTint(it.theme); binding.scheduleLabel.setTint(it.textPrimary) + binding.attachAFileIcon.setBackgroundTint(it.theme); binding.attachAFileIcon.setTint(it.textPrimary) + binding.attachAFileLabel.setBackgroundTint(it.theme); binding.attachAFileLabel.setTint(it.textPrimary) + binding.attachAnAudioMessageIcon.setBackgroundTint(it.theme); binding.attachAnAudioMessageIcon.setTint(it.textPrimary) + binding.attachAnAudioMessageLabel.setBackgroundTint(it.theme); binding.attachAnAudioMessageLabel.setTint(it.textPrimary) + binding.gallery.setBackgroundTint(it.theme); binding.gallery.setTint(it.textPrimary) + binding.galleryLabel.setBackgroundTint(it.theme); binding.galleryLabel.setTint(it.textPrimary) + binding.camera.setBackgroundTint(it.theme); binding.camera.setTint(it.textPrimary) + binding.cameraLabel.setBackgroundTint(it.theme); binding.cameraLabel.setTint(it.textPrimary) // speech to text floating button - speechToTextIconBorder.setBackgroundTint(it.theme) - speechToTextIcon.setBackgroundTint(it.textPrimary) - speechToTextIcon.setTint(it.theme) + binding.speechToTextIconBorder.setBackgroundTint(it.theme) + binding.speechToTextIcon.setBackgroundTint(it.textPrimary) + binding.speechToTextIcon.setTint(it.theme) // audio message recording - audioMsgRecord.setColor(it.theme) - audioMsgPlayerPlayPause.setTint(it.theme) - audioMsgPlayerSeekBar.apply { + binding.audioMsgRecord.setColor(it.theme) + binding.audioMsgPlayerPlayPause.setTint(it.theme) + binding.audioMsgPlayerSeekBar.apply { thumbTintList = ColorStateList.valueOf(it.theme) progressBackgroundTintList = ColorStateList.valueOf(it.theme) progressTintList = ColorStateList.valueOf(it.theme) @@ -295,23 +246,23 @@ class ComposeActivity : QkThemedActivity(), ComposeView { // drag drop handlers for speech-to-text icon if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - speechToTextIcon.setOnLongClickListener { - it.startDragAndDrop(null, View.DragShadowBuilder(speechToTextFrame), null, 0) - speechToTextFrame.isVisible = false + binding.speechToTextIcon.setOnLongClickListener { + it.startDragAndDrop(null, View.DragShadowBuilder(binding.speechToTextFrame), null, 0) + binding.speechToTextFrame.isVisible = false - contentView.setOnDragListener { _, event -> + binding.contentView.setOnDragListener { _, event -> when (event.action) { ACTION_DROP -> { - speechToTextFrame.x = (event.x - (speechToTextFrame.width / 2)) - speechToTextFrame.y = (event.y - (speechToTextFrame.height / 2)) + binding.speechToTextFrame.x = (event.x - (binding.speechToTextFrame.width / 2)) + binding.speechToTextFrame.y = (event.y - (binding.speechToTextFrame.height / 2)) // get offset from root view as a percentage of root view for saving - prefs.showSttOffsetX.set((speechToTextFrame.x - contentView.x) / contentView.width) - prefs.showSttOffsetY.set((speechToTextFrame.y - contentView.y) / contentView.height) + prefs.showSttOffsetX.set((binding.speechToTextFrame.x - binding.contentView.x) / binding.contentView.width) + prefs.showSttOffsetY.set((binding.speechToTextFrame.y - binding.contentView.y) / binding.contentView.height) } ACTION_DRAG_ENDED, ACTION_DRAG_EXITED -> { - speechToTextFrame.isVisible = true + binding.speechToTextFrame.isVisible = true } } true @@ -321,8 +272,8 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } // start/stop audio message recording - audioMsgRecord.setOnClickListener { - recordAudioRecord.onNext(audioMsgRecord.getState()) + binding.audioMsgRecord.setOnClickListener { + recordAudioRecord.onNext(binding.audioMsgRecord.getState()) } recordAudioChronometer @@ -331,17 +282,17 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .autoDisposable(scope()) .subscribe { if (it) { - audioMsgDuration.base = SystemClock.elapsedRealtime() - audioMsgDuration.start() + binding.audioMsgDuration.base = SystemClock.elapsedRealtime() + binding.audioMsgDuration.start() } else { - audioMsgDuration.stop() + binding.audioMsgDuration.stop() } } // audio record playback play/pause button - audioMsgPlayerPlayPause.setOnClickListener { + binding.audioMsgPlayerPlayPause.setOnClickListener { recordAudioPlayerPlayPause.onNext( - audioMsgPlayerPlayPause.tag as QkMediaPlayer.PlayingState + binding.audioMsgPlayerPlayPause.tag as QkMediaPlayer.PlayingState ) } @@ -350,10 +301,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .distinctUntilChanged() .autoDisposable(scope()) .subscribe { - audioMsgRecord.isVisible = it - audioMsgDuration.isVisible = + binding.audioMsgRecord.isVisible = it + binding.audioMsgDuration.isVisible = it // chronometer follows record button visibility - audioMsgBluetooth.isVisible = !it + binding.audioMsgBluetooth.isVisible = !it } recordAudioPlayerVisible @@ -361,7 +312,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .distinctUntilChanged() .autoDisposable(scope()) .subscribe { - audioMsgPlayerBackground.isVisible = it + binding.audioMsgPlayerBackground.isVisible = it recordAudioPlayerConfigUI.onNext(QkMediaPlayer.PlayingState.Stopped) } @@ -372,35 +323,35 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .subscribe { when (it) { QkMediaPlayer.PlayingState.Playing -> { - audioMsgPlayerPlayPause.tag = QkMediaPlayer.PlayingState.Playing + binding.audioMsgPlayerPlayPause.tag = QkMediaPlayer.PlayingState.Playing QkMediaPlayer.start() - audioMsgPlayerPlayPause.setImageResource(R.drawable.exo_icon_pause) + binding.audioMsgPlayerPlayPause.setImageResource(R.drawable.exo_icon_pause) seekBarUpdater = getSeekBarUpdater().subscribe { - audioMsgPlayerSeekBar.progress = QkMediaPlayer.currentPosition - audioMsgPlayerSeekBar.max = QkMediaPlayer.duration + binding.audioMsgPlayerSeekBar.progress = QkMediaPlayer.currentPosition + binding.audioMsgPlayerSeekBar.max = QkMediaPlayer.duration } - audioMsgPlayerSeekBar.isEnabled = true + binding.audioMsgPlayerSeekBar.isEnabled = true } QkMediaPlayer.PlayingState.Paused -> { - audioMsgPlayerPlayPause.tag = QkMediaPlayer.PlayingState.Paused + binding.audioMsgPlayerPlayPause.tag = QkMediaPlayer.PlayingState.Paused QkMediaPlayer.pause() - audioMsgPlayerPlayPause.setImageResource(R.drawable.exo_icon_play) + binding.audioMsgPlayerPlayPause.setImageResource(R.drawable.exo_icon_play) seekBarUpdater?.dispose() } else -> { - audioMsgPlayerPlayPause.tag = QkMediaPlayer.PlayingState.Stopped + binding.audioMsgPlayerPlayPause.tag = QkMediaPlayer.PlayingState.Stopped QkMediaPlayer.reset() - audioMsgPlayerPlayPause.setImageResource(R.drawable.exo_icon_play) + binding.audioMsgPlayerPlayPause.setImageResource(R.drawable.exo_icon_play) seekBarUpdater?.dispose() - audioMsgPlayerSeekBar.progress = 0 - audioMsgPlayerSeekBar.isEnabled = false + binding.audioMsgPlayerSeekBar.progress = 0 + binding.audioMsgPlayerSeekBar.isEnabled = false } } } // audio msg player seek bar handler - audioMsgPlayerSeekBar.setOnSeekBarChangeListener( + binding.audioMsgPlayerSeekBar.setOnSeekBarChangeListener( object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(p0: SeekBar?, progress: Int, fromUser: Boolean) { // if seek was initiated by the user and this part is currently playing @@ -459,62 +410,62 @@ class ComposeActivity : QkThemedActivity(), ComposeView { else -> state.conversationtitle } - toolbarSubtitle.setVisible(state.query.isNotEmpty()) - toolbarSubtitle.text = getString(R.string.compose_subtitle_results, state.searchSelectionPosition, + binding.toolbarSubtitle.setVisible(state.query.isNotEmpty()) + binding.toolbarSubtitle.text = getString(R.string.compose_subtitle_results, state.searchSelectionPosition, state.searchResults) - toolbarTitle.setVisible(!state.editingMode) - chips.setVisible(state.editingMode) - composeBar.setVisible(!state.loading) + binding.toolbarTitle.setVisible(!state.editingMode) + binding.chips.setVisible(state.editingMode) + binding.composeBar.setVisible(!state.loading) // Don't set the adapters unless needed - if (state.editingMode && chips.adapter == null) chips.adapter = chipsAdapter + if (state.editingMode && binding.chips.adapter == null) binding.chips.adapter = chipsAdapter - toolbar.menu.findItem(R.id.viewScheduledMessages)?.isVisible = !state.editingMode && state.selectedMessages == 0 + binding.toolbar.menu.findItem(R.id.viewScheduledMessages)?.isVisible = !state.editingMode && state.selectedMessages == 0 && state.query.isEmpty() && state.hasScheduledMessages - toolbar.menu.findItem(R.id.select_all)?.isVisible = !state.editingMode && (messageAdapter.itemCount > 1) && state.selectedMessages != 0 - toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode - toolbar.menu.findItem(R.id.call)?.isVisible = !state.editingMode && state.selectedMessages == 0 + binding.toolbar.menu.findItem(R.id.select_all)?.isVisible = !state.editingMode && (messageAdapter.itemCount > 1) && state.selectedMessages != 0 + binding.toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode + binding.toolbar.menu.findItem(R.id.call)?.isVisible = !state.editingMode && state.selectedMessages == 0 && state.query.isEmpty() - toolbar.menu.findItem(R.id.info)?.isVisible = !state.editingMode && state.selectedMessages == 0 + binding.toolbar.menu.findItem(R.id.info)?.isVisible = !state.editingMode && state.selectedMessages == 0 && state.query.isEmpty() - toolbar.menu.findItem(R.id.copy)?.isVisible = + binding.toolbar.menu.findItem(R.id.copy)?.isVisible = !state.editingMode && state.selectedMessages > 0 && state.selectedMessagesHaveText - toolbar.menu.findItem(R.id.share)?.isVisible = + binding.toolbar.menu.findItem(R.id.share)?.isVisible = !state.editingMode && state.selectedMessages > 0 && state.selectedMessagesHaveText - toolbar.menu.findItem(R.id.details)?.isVisible = !state.editingMode && state.selectedMessages == 1 - toolbar.menu.findItem(R.id.delete)?.isVisible = !state.editingMode && ((state.selectedMessages > 0) || state.canSend) - toolbar.menu.findItem(R.id.forward)?.isVisible = !state.editingMode && state.selectedMessages == 1 - toolbar.menu.findItem(R.id.show_status)?.isVisible = !state.editingMode && state.selectedMessages > 0 - toolbar.menu.findItem(R.id.previous)?.isVisible = state.selectedMessages == 0 && state.query.isNotEmpty() - toolbar.menu.findItem(R.id.next)?.isVisible = state.selectedMessages == 0 && state.query.isNotEmpty() - toolbar.menu.findItem(R.id.clear)?.isVisible = state.selectedMessages == 0 && state.query.isNotEmpty() + binding.toolbar.menu.findItem(R.id.details)?.isVisible = !state.editingMode && state.selectedMessages == 1 + binding.toolbar.menu.findItem(R.id.delete)?.isVisible = !state.editingMode && ((state.selectedMessages > 0) || state.canSend) + binding.toolbar.menu.findItem(R.id.forward)?.isVisible = !state.editingMode && state.selectedMessages == 1 + binding.toolbar.menu.findItem(R.id.show_status)?.isVisible = !state.editingMode && state.selectedMessages > 0 + binding.toolbar.menu.findItem(R.id.previous)?.isVisible = state.selectedMessages == 0 && state.query.isNotEmpty() + binding.toolbar.menu.findItem(R.id.next)?.isVisible = state.selectedMessages == 0 && state.query.isNotEmpty() + binding.toolbar.menu.findItem(R.id.clear)?.isVisible = state.selectedMessages == 0 && state.query.isNotEmpty() chipsAdapter.data = state.selectedChips - loading.setVisible(state.loading) + binding.loading.setVisible(state.loading) - sendAsGroup.setVisible(state.recipientCount > 1) - sendAsGroupSwitch.isChecked = state.sendAsGroup - sendAsGroupSummary.setText( - if (sendAsGroupSwitch.isChecked) R.string.compose_send_group_summary_on + binding.sendAsGroup.setVisible(state.recipientCount > 1) + binding.sendAsGroupSwitch.isChecked = state.sendAsGroup + binding.sendAsGroupSummary.setText( + if (binding.sendAsGroupSwitch.isChecked) R.string.compose_send_group_summary_on else R.string.compose_send_group_summary_off ) - messageList.setVisible(!state.editingMode || state.sendAsGroup || state.selectedChips.size == 1) + binding.messageList.setVisible(!state.editingMode || state.sendAsGroup || state.selectedChips.size == 1) messageAdapter.data = state.messages messageAdapter.highlight = state.searchSelectionId - scheduledGroup.isVisible = state.scheduled != 0L - scheduledTime.text = dateFormatter.getScheduledTimestamp(state.scheduled) + binding.scheduledGroup.isVisible = state.scheduled != 0L + binding.scheduledTime.text = dateFormatter.getScheduledTimestamp(state.scheduled) - messageAttachments.setVisible(state.attachments.isNotEmpty()) + binding.messageAttachments.setVisible(state.attachments.isNotEmpty()) composeAttachmentAdapter.data = state.attachments - attach.animate().rotation(if (state.attaching) 135f else 0f).start() - attaching.isVisible = state.attaching + binding.attach.animate().rotation(if (state.attaching) 135f else 0f).start() + binding.attaching.isVisible = state.attaching - shadeBackground.apply { + binding.shadeBackground.apply { when { state.attaching -> { visibility = View.VISIBLE @@ -531,31 +482,31 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } // show or hide audio message recording panel and shade background - audioMsgBackground.isVisible = state.audioMsgRecording + binding.audioMsgBackground.isVisible = state.audioMsgRecording - counter.text = state.remaining - counter.setVisible(counter.text.isNotBlank()) + binding.counter.text = state.remaining + binding.counter.setVisible(binding.counter.text.isNotBlank()) - sim.setVisible(state.subscription != null) - sim.contentDescription = getString(R.string.compose_sim_cd, state.subscription?.displayName) - simIndex.text = state.subscription?.simSlotIndex?.plus(1)?.toString() + binding.sim.setVisible(state.subscription != null) + binding.sim.contentDescription = getString(R.string.compose_sim_cd, state.subscription?.displayName) + binding.simIndex.text = state.subscription?.simSlotIndex?.plus(1)?.toString() // show either send, audio msg record, or sendScheduled button - send.visibility = if (state.canSend && !state.loading && state.scheduled == 0L) View.VISIBLE else View.INVISIBLE - recordAudioMsg.visibility = if (state.canSend && !state.loading) View.INVISIBLE else View.VISIBLE - scheduledSend.visibility = if (state.canSend && (state.scheduled != 0L) && !state.loading) View.VISIBLE else View.INVISIBLE + binding.send.visibility = if (state.canSend && !state.loading && state.scheduled == 0L) View.VISIBLE else View.INVISIBLE + binding.recordAudioMsg.visibility = if (state.canSend && !state.loading) View.INVISIBLE else View.VISIBLE + binding.scheduledSend.visibility = if (state.canSend && (state.scheduled != 0L) && !state.loading) View.VISIBLE else View.INVISIBLE // if not in editing mode, and there are no non-me participants that can be sent to, // hide controls that allow constructing a reply and inform user no valid recipients if (!state.editingMode && (state.validRecipientNumbers == 0)) { - composeBar.visibility = View.GONE - sim.visibility = View.GONE - recordAudioMsg.visibility = View.GONE - noValidRecipients.visibility = View.VISIBLE + binding.composeBar.visibility = View.GONE + binding.sim.visibility = View.GONE + binding.recordAudioMsg.visibility = View.GONE + binding.noValidRecipients.visibility = View.VISIBLE // change constraint of messageList to constrain bottom to top of noValidRecipients ConstraintSet().apply { - clone(contentView) + clone(binding.contentView) connect( R.id.messageList, ConstraintSet.BOTTOM, @@ -563,7 +514,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { ConstraintSet.TOP, 0 ) - applyTo(contentView) + applyTo(binding.contentView) } } @@ -573,15 +524,15 @@ class ComposeActivity : QkThemedActivity(), ComposeView { // if stt is available and preference is set to show stt button if (isSpeechRecognitionAvailable() && prefs.showStt.get()) { - speechToTextFrame.isVisible = true + binding.speechToTextFrame.isVisible = true val xPercent = prefs.showSttOffsetX.get() val yPercent = prefs.showSttOffsetY.get() // if the stt icon has a custom position, move it if ((xPercent != Float.MAX_VALUE) && (yPercent != Float.MAX_VALUE)) { - speechToTextFrame.x = (contentView.x + (xPercent * contentView.width)) - speechToTextFrame.y = (contentView.y + (yPercent * contentView.height)) + binding.speechToTextFrame.x = (binding.contentView.x + (xPercent * binding.contentView.width)) + binding.speechToTextFrame.y = (binding.contentView.y + (yPercent * binding.contentView.height)) } } } @@ -654,7 +605,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)).show() // On some devices, the keyboard can cover the date picker - message.hideKeyboard() + binding.message.hideKeyboard() } override fun requestContact() { @@ -665,7 +616,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } override fun showContacts(sharing: Boolean, chips: List) { - message.hideKeyboard() + binding.message.hideKeyboard() val serialized = HashMap(chips.associate { chip -> chip.address to chip.contact?.lookupKey }) val intent = Intent(this, ContactsActivity::class.java) .putExtra(ContactsActivity.SHARING_KEY, sharing) @@ -687,12 +638,12 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } override fun themeChanged() { - messageList.scrapViews() + binding.messageList.scrapViews() } override fun showKeyboard() { - message.postDelayed({ - message.showKeyboard() + binding.message.postDelayed({ + binding.message.showKeyboard() }, 200) } @@ -717,19 +668,19 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } override fun setDraft(draft: String) { - message.setText(draft) - message.setSelection(draft.length) + binding.message.setText(draft) + binding.message.setSelection(draft.length) } override fun scrollToMessage(id: Long) { messageAdapter.data?.second ?.indexOfLast { message -> message.id == id } ?.takeIf { position -> position != -1 } - ?.let(messageList::scrollToPosition) + ?.let(binding.messageList::scrollToPosition) } override fun showQksmsPlusSnackbar(message: Int) { - Snackbar.make(contentView, message, Snackbar.LENGTH_LONG).run { + Snackbar.make(binding.contentView, message, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusIntent.onNext(Unit) } setActionTextColor(colors.theme().theme) show() @@ -817,14 +768,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { // check returned results are good val match = data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) if ((match !== null) && (match.size > 0) && (!match[0].isNullOrEmpty())) { - // get the edit text view - val messageEditBox = findViewById(R.id.message) - if (messageEditBox !== null) { - // populate message box with data returned by STT, set cursor to end, and focus - messageEditBox.append(match[0]) - messageEditBox.setSelection(messageEditBox.text?.length ?: 0) - messageEditBox.requestFocus() - } + // populate message box with data returned by STT, set cursor to end, and focus + binding.message.append(match[0]) + binding.message.setSelection(binding.message.text?.length ?: 0) + binding.message.requestFocus() } } @@ -845,6 +792,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun onBackPressed() = backPressedIntent.onNext(Unit) override fun focusMessage() { - message.requestFocus() + binding.message.requestFocus() } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeAttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeAttachmentAdapter.kt index 5de4efa5..f72ef3e7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeAttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeAttachmentAdapter.kt @@ -27,6 +27,8 @@ import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkAdapter import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.extensions.getDisplayName +import dev.octoshrimpy.quik.databinding.AttachmentContactListItemBinding +import dev.octoshrimpy.quik.databinding.AttachmentFileListItemBinding import dev.octoshrimpy.quik.extensions.getName import dev.octoshrimpy.quik.feature.extensions.LoadBestIconIntoImageView import dev.octoshrimpy.quik.feature.extensions.loadBestIconIntoImageView @@ -34,9 +36,6 @@ import dev.octoshrimpy.quik.model.Attachment import ezvcard.Ezvcard import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.attachment_contact_list_item.* -import kotlinx.android.synthetic.main.scheduled_message_image_list_item.fileName -import kotlinx.android.synthetic.main.scheduled_message_image_list_item.thumbnail import javax.inject.Inject @@ -54,12 +53,11 @@ class ComposeAttachmentAdapter @Inject constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val inflater = LayoutInflater.from(parent.context) - val view = inflater.inflate( - if (viewType == VIEW_TYPE_CONTACT) R.layout.attachment_contact_list_item - else R.layout.attachment_file_list_item, - parent, - false - ) + val view = if (viewType == VIEW_TYPE_CONTACT) { + AttachmentContactListItemBinding.inflate(inflater, parent, false).root + } else { + AttachmentFileListItemBinding.inflate(inflater, parent, false).root + } return QkViewHolder(view).apply { view.setOnClickListener { @@ -73,36 +71,38 @@ class ComposeAttachmentAdapter @Inject constructor( val attachment = getItem(position) if (attachment.isVCard(context)) { + val binding = AttachmentContactListItemBinding.bind(holder.itemView) try { val displayName = Ezvcard.parse( String(attachment.getResourceBytes(context)) ).first().getDisplayName() ?: "" - holder.name.text = displayName - holder.name.isVisible = displayName.isNotEmpty() + binding.name.text = displayName + binding.name.isVisible = displayName.isNotEmpty() } catch (e: Exception) { // npe from Ezvcard first() call above can be thrown if resource bytes cannot // be retrieved from contact resource provider - holder.vCardAvatar.setImageResource(android.R.drawable.ic_delete) - holder.name.text = context.getString(R.string.attachment_missing) - holder.name.isVisible = true + binding.vCardAvatar.setImageResource(android.R.drawable.ic_delete) + binding.name.text = context.getString(R.string.attachment_missing) + binding.name.isVisible = true } return } + val binding = AttachmentFileListItemBinding.bind(holder.itemView) // set best image and text to use for icon - when (attachment.uri.loadBestIconIntoImageView(context, holder.thumbnail)) { + when (attachment.uri.loadBestIconIntoImageView(context, binding.thumbnail)) { LoadBestIconIntoImageView.Missing -> { - holder.fileName.text = context.getString(R.string.attachment_missing) - holder.fileName.visibility = View.VISIBLE + binding.fileName.text = context.getString(R.string.attachment_missing) + binding.fileName.visibility = View.VISIBLE } LoadBestIconIntoImageView.ActivityIcon, LoadBestIconIntoImageView.DefaultAudioIcon, LoadBestIconIntoImageView.GenericIcon -> { // generic style icon used, also show name - holder.fileName.text = attachment.uri.getName(context) - holder.fileName.visibility = View.VISIBLE + binding.fileName.text = attachment.uri.getName(context) + binding.fileName.visibility = View.VISIBLE } - else -> holder.fileName.visibility = View.GONE + else -> binding.fileName.visibility = View.GONE } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt index b2e5d535..09bf6cab 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt @@ -37,6 +37,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.ProgressBar +import android.widget.TextView import androidx.core.net.toUri import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.common.QkMediaPlayer @@ -65,24 +66,14 @@ import dev.octoshrimpy.quik.feature.extensions.isEmojiOnly import dev.octoshrimpy.quik.model.Conversation import dev.octoshrimpy.quik.model.Message import dev.octoshrimpy.quik.model.Recipient +import dev.octoshrimpy.quik.databinding.MessageListItemInBinding +import dev.octoshrimpy.quik.databinding.MessageListItemOutBinding import dev.octoshrimpy.quik.util.PhoneNumberUtils import dev.octoshrimpy.quik.util.Preferences import io.reactivex.disposables.Disposable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import io.realm.RealmResults -import kotlinx.android.synthetic.main.message_list_item_in.* -import kotlinx.android.synthetic.main.message_list_item_in.body -import kotlinx.android.synthetic.main.message_list_item_in.reactionText -import kotlinx.android.synthetic.main.message_list_item_in.parts -import kotlinx.android.synthetic.main.message_list_item_in.reactions -import kotlinx.android.synthetic.main.message_list_item_in.sim -import kotlinx.android.synthetic.main.message_list_item_in.simIndex -import kotlinx.android.synthetic.main.message_list_item_in.status -import kotlinx.android.synthetic.main.message_list_item_in.timestamp -import kotlinx.android.synthetic.main.message_list_item_in.view.* -import kotlinx.android.synthetic.main.message_list_item_out.* -import kotlinx.android.synthetic.main.message_list_item_out.view.cancel import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -148,20 +139,33 @@ class MessagesAdapter @Inject constructor( // Use the parent's context to inflate the layout, otherwise link clicks will crash the app val inflater = LayoutInflater.from(parent.context) - val view = if (viewType == VIEW_TYPE_MESSAGE_OUT) { - inflater.inflate(R.layout.message_list_item_out, parent,false).apply { - findViewById(R.id.cancelIcon).setTint(theme.theme) - findViewById(R.id.cancel).setTint(theme.theme) - findViewById(R.id.sendNowIcon).setTint(theme.theme) - findViewById(R.id.resendIcon).setTint(theme.theme) - } - } else - inflater.inflate(R.layout.message_list_item_in, parent, false) + val view: View + val body: TextView + val parts: View + val status: View - view.body.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE + if (viewType == VIEW_TYPE_MESSAGE_OUT) { + val binding = MessageListItemOutBinding.inflate(inflater, parent, false) + view = binding.root + body = binding.body + parts = binding.parts + status = binding.status + binding.cancelIcon.setTint(theme.theme) + binding.cancel.setTint(theme.theme) + binding.sendNowIcon.setTint(theme.theme) + binding.resendIcon.setTint(theme.theme) + } else { + val binding = MessageListItemInBinding.inflate(inflater, parent, false) + view = binding.root + body = binding.body + parts = binding.parts + status = binding.status + } + + body.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE // register recycler view with compose activity for context menus - partContextMenuRegistrar.onNext(view.parts) + partContextMenuRegistrar.onNext(parts) return QkViewHolder(view).apply { view.setOnClickListener { @@ -169,7 +173,7 @@ class MessagesAdapter @Inject constructor( when (toggleSelection(it.id, false)) { true -> view.isActivated = isSelected(it.id) false -> { - expanded[it.id] = view.status.visibility != View.VISIBLE + expanded[it.id] = status.visibility != View.VISIBLE notifyItemChanged(adapterPosition) } } @@ -196,55 +200,108 @@ class MessagesAdapter @Inject constructor( } // Update the selected state - holder.containerView.isActivated = isSelected(message.id) || highlight == message.id + holder.itemView.isActivated = isSelected(message.id) || highlight == message.id - // bind the cancelFrame (cancel button) and send now button - holder.cancelFrame?.let { cancelFrame -> - holder.sendNowIcon?.let { sendNowIcon -> - val isCancellable = message.isSending() && message.date > System.currentTimeMillis() + // Get views based on message type + val isOutgoing = message.isMe() + val timestamp: TextView + val simIndex: TextView + val sim: ImageView + val body: TextView + val parts: androidx.recyclerview.widget.RecyclerView + val reactions: View + val reactionText: TextView + val status: TextView - if (isCancellable) { - cancelFrame.visibility = View.VISIBLE - sendNowIcon.visibility = View.VISIBLE + if (isOutgoing) { + val binding = MessageListItemOutBinding.bind(holder.itemView) + timestamp = binding.timestamp + simIndex = binding.simIndex + sim = binding.sim + body = binding.body + parts = binding.parts + reactions = binding.reactions + reactionText = binding.reactionText + status = binding.status - cancelFrame.setOnClickListener { cancelSendingClicks.onNext(message.id) } - sendNowIcon.setOnClickListener { sendNowClicks.onNext(message.id) } + // bind the cancelFrame (cancel button) and send now button + val isCancellable = message.isSending() && message.date > System.currentTimeMillis() - cancelFrame.cancel.progress = 2 + if (isCancellable) { + binding.cancelFrame.visibility = View.VISIBLE + binding.sendNowIcon.visibility = View.VISIBLE - val delay = when (prefs.sendDelay.get()) { - Preferences.SEND_DELAY_SHORT -> 3000 - Preferences.SEND_DELAY_MEDIUM -> 5000 - Preferences.SEND_DELAY_LONG -> 10000 - else -> 0 - } - val progress = - (1 - (message.date - System.currentTimeMillis()) / delay.toFloat()) * 100 + binding.cancelFrame.setOnClickListener { cancelSendingClicks.onNext(message.id) } + binding.sendNowIcon.setOnClickListener { sendNowClicks.onNext(message.id) } - ObjectAnimator.ofInt(cancelFrame.cancel, "progress", progress.toInt(), 100) - .setDuration(message.date - System.currentTimeMillis()) - .start() + binding.cancel.progress = 2 + + val delay = when (prefs.sendDelay.get()) { + Preferences.SEND_DELAY_SHORT -> 3000 + Preferences.SEND_DELAY_MEDIUM -> 5000 + Preferences.SEND_DELAY_LONG -> 10000 + else -> 0 } - else { - cancelFrame.visibility = View.GONE - sendNowIcon.visibility = View.GONE + val progress = + (1 - (message.date - System.currentTimeMillis()) / delay.toFloat()) * 100 - cancelFrame.setOnClickListener(null) - sendNowIcon.setOnClickListener(null) + ObjectAnimator.ofInt(binding.cancel, "progress", progress.toInt(), 100) + .setDuration(message.date - System.currentTimeMillis()) + .start() + } else { + binding.cancelFrame.visibility = View.GONE + binding.sendNowIcon.visibility = View.GONE + + binding.cancelFrame.setOnClickListener(null) + binding.sendNowIcon.setOnClickListener(null) + } + + // bind the resend icon view + if (message.isFailedMessage()) { + binding.resendIcon.visibility = View.VISIBLE + binding.resendIcon.clicks().subscribe { + resendClicks.onNext(message.id) + binding.resendIcon.visibility = View.GONE + } + } else { + binding.resendIcon.visibility = View.GONE + } + + body.apply { + highlightColor = theme.theme.withAlpha(0x5d) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + textSelectHandle?.setTint(theme.theme.withAlpha(0xad)) + textSelectHandleLeft?.setTint(theme.theme.withAlpha(0xad)) + textSelectHandleRight?.setTint(theme.theme.withAlpha(0xad)) } } - } + } else { + val binding = MessageListItemInBinding.bind(holder.itemView) + timestamp = binding.timestamp + simIndex = binding.simIndex + sim = binding.sim + body = binding.body + parts = binding.parts + reactions = binding.reactions + reactionText = binding.reactionText + status = binding.status - // bind the resend icon view - holder.resendIcon?.let { resendIcon -> - if (message.isFailedMessage()) { - resendIcon.visibility = View.VISIBLE - resendIcon.clicks().subscribe { - resendClicks.onNext(message.id) - resendIcon.visibility = View.GONE + // Bind the avatar and bubble colour + binding.avatar.apply { + setRecipient(contactCache[message.address]) + setVisible(!canGroup(message, next), View.INVISIBLE) + } + + body.apply { + setTextColor(theme.textPrimary) + setBackgroundTint(theme.theme) + highlightColor = R.attr.bubbleColor.withAlpha(0x5d) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + textSelectHandle?.setTint(R.attr.bubbleColor.withAlpha(0x7d)) + textSelectHandleLeft?.setTint(R.attr.bubbleColor.withAlpha(0x7d)) + textSelectHandleRight?.setTint(R.attr.bubbleColor.withAlpha(0x7d)) } - } else - resendIcon.visibility = View.GONE + } } val subject = message.getCleansedSubject() @@ -270,12 +327,12 @@ class MessagesAdapter @Inject constructor( } // Bind the message status - bindStatus(holder, isMsgTextTruncated, message, next) + bindStatus(status, isMsgTextTruncated, message, next) // Bind the timestamp val subscription = subs.find { it.subscriptionId == message.subId } - holder.timestamp.apply { + timestamp.apply { text = dateFormatter.getMessageTimestamp(message.date) setVisible( ((message.date - (previous?.date ?: 0)) @@ -285,49 +342,22 @@ class MessagesAdapter @Inject constructor( ) } - holder.simIndex.text = subscription?.simSlotIndex?.plus(1)?.toString() + simIndex.text = subscription?.simSlotIndex?.plus(1)?.toString() ((message.subId != previous?.subId) && (subscription != null) && (subs.size > 1)).also { - holder.sim.setVisible(it) - holder.simIndex.setVisible(it) + sim.setVisible(it) + simIndex.setVisible(it) } // Bind the grouping - holder.containerView.setPadding( + holder.itemView.setPadding( bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context) ) - // Bind the avatar and bubble colour - if (!message.isMe()) { - holder.avatar.apply { - setRecipient(contactCache[message.address]) - setVisible(!canGroup(message, next), View.INVISIBLE) - } - - holder.body.apply { - setTextColor(theme.textPrimary) - setBackgroundTint(theme.theme) - highlightColor = R.attr.bubbleColor.withAlpha(0x5d) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - textSelectHandle?.setTint(R.attr.bubbleColor.withAlpha(0x7d)) - textSelectHandleLeft?.setTint(R.attr.bubbleColor.withAlpha(0x7d)) - textSelectHandleRight?.setTint(R.attr.bubbleColor.withAlpha(0x7d)) - } - } - } else - holder.body.apply { - highlightColor = theme.theme.withAlpha(0x5d) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - textSelectHandle?.setTint(theme.theme.withAlpha(0xad)) - textSelectHandleLeft?.setTint(theme.theme.withAlpha(0xad)) - textSelectHandleRight?.setTint(theme.theme.withAlpha(0xad)) - } - } - // Bind the body text val emojiOnly = displayText.isEmojiOnly() textViewStyler.setTextSize( - holder.body, + body, when (emojiOnly) { true -> TextViewStyler.SIZE_EMOJI false -> TextViewStyler.SIZE_PRIMARY @@ -337,10 +367,10 @@ class MessagesAdapter @Inject constructor( val spanString = SpannableStringBuilder(displayText) when (prefs.messageLinkHandling.get()) { - Preferences.MESSAGE_LINK_HANDLING_BLOCK -> holder.body.autoLinkMask = 0 + Preferences.MESSAGE_LINK_HANDLING_BLOCK -> body.autoLinkMask = 0 Preferences.MESSAGE_LINK_HANDLING_ASK -> { // manually handle link clicks if user has set to ask before opening links - holder.body.apply { + body.apply { isClickable = false linksClickable = false movementMethod = LinkMovementMethod.getInstance() @@ -367,10 +397,10 @@ class MessagesAdapter @Inject constructor( } } } - else -> holder.body.movementMethod = LinkMovementMethod.getInstance() + else -> body.movementMethod = LinkMovementMethod.getInstance() } - holder.body.apply { + body.apply { text = spanString setVisible(message.isSms() || spanString.isNotBlank()) @@ -386,49 +416,47 @@ class MessagesAdapter @Inject constructor( } // Bind the parts - holder.parts.adapter = partsAdapterProvider.get().apply { + parts.adapter = partsAdapterProvider.get().apply { this.theme = theme setData(message, previous, next, holder, audioState) contextMenuValue = message.id clicks.subscribe(partClicks) // part clicks gets passed back to compose view model } - showEmojiReactions(holder, message) + showEmojiReactions(reactions, reactionText, message) } - private fun showEmojiReactions(holder: QkViewHolder, message: Message) { - holder.reactions?.let { reactionsContainer -> - val reactions = message.emojiReactions - val hasReactions = reactions.isNotEmpty() + private fun showEmojiReactions(reactionsContainer: View, reactionTextView: TextView, message: Message) { + val reactions = message.emojiReactions + val hasReactions = reactions.isNotEmpty() - if (hasReactions) { - val reactionCounts = reactions.groupBy { it.emoji } - .mapValues { it.value.size } - .toList() - .sortedByDescending { it.second } // Sort by count, most reactions first + if (hasReactions) { + val reactionCounts = reactions.groupBy { it.emoji } + .mapValues { it.value.size } + .toList() + .sortedByDescending { it.second } // Sort by count, most reactions first - // For now, show just the first (most popular) reaction - val topReaction = reactionCounts.first() - val reactionText = if (topReaction.second == 1) { - topReaction.first - } else { - // Use a non-breaking space to keep the emoji and count together - "${topReaction.first}\u00A0${topReaction.second}" - } - - holder.reactionText?.text = reactionText - reactionsContainer.setVisible(true) - makeRoomForEmojis(holder) + // For now, show just the first (most popular) reaction + val topReaction = reactionCounts.first() + val reactionText = if (topReaction.second == 1) { + topReaction.first } else { - reactionsContainer.setVisible(false) + // Use a non-breaking space to keep the emoji and count together + "${topReaction.first}\u00A0${topReaction.second}" } + + reactionTextView.text = reactionText + reactionsContainer.setVisible(true) + makeRoomForEmojis(reactionsContainer) + } else { + reactionsContainer.setVisible(false) } } - private fun makeRoomForEmojis(holder: QkViewHolder) { + private fun makeRoomForEmojis(reactionsContainer: View) { val paddingBottom = 25.dpToPx(context) - (holder.reactions?.parent?.parent as? ViewGroup)?.let { parent -> + (reactionsContainer.parent?.parent as? ViewGroup)?.let { parent -> parent.setPadding( parent.paddingLeft, parent.paddingTop, @@ -439,12 +467,12 @@ class MessagesAdapter @Inject constructor( } private fun bindStatus( - holder: QkViewHolder, + statusView: TextView, bodyTextTruncated: Boolean, message: Message, next: Message? ) { - holder.status.apply { + statusView.apply { text = when { message.isSending() -> context.getString(R.string.message_status_sending) message.isDelivered() -> context.getString( diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt index 6433ceac..b39670da 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt @@ -36,10 +36,9 @@ import dev.octoshrimpy.quik.model.Recipient import dev.octoshrimpy.quik.repository.ConversationRepository import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign +import dev.octoshrimpy.quik.databinding.ContactListItemBinding import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.contact_list_item.* -import kotlinx.android.synthetic.main.contact_list_item.view.* import javax.inject.Inject class ComposeItemAdapter @Inject constructor( @@ -61,20 +60,20 @@ class ComposeItemAdapter @Inject constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val layoutInflater = LayoutInflater.from(parent.context) - val view = layoutInflater.inflate(R.layout.contact_list_item, parent, false) + val binding = ContactListItemBinding.inflate(layoutInflater, parent, false) - view.icon.setTint(colors.theme().theme) + binding.icon.setTint(colors.theme().theme) - view.numbers.setRecycledViewPool(numbersViewPool) - view.numbers.adapter = PhoneNumberAdapter() - view.numbers.forwardTouches(view) + binding.numbers.setRecycledViewPool(numbersViewPool) + binding.numbers.adapter = PhoneNumberAdapter() + binding.numbers.forwardTouches(binding.root) - return QkViewHolder(view).apply { - view.setOnClickListener { + return QkViewHolder(binding.root).apply { + binding.root.setOnClickListener { val item = getItem(adapterPosition) clicks.onNext(item) } - view.setOnLongClickListener { + binding.root.setOnLongClickListener { val item = getItem(adapterPosition) longClicks.onNext(item) true @@ -83,102 +82,103 @@ class ComposeItemAdapter @Inject constructor( } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val binding = ContactListItemBinding.bind(holder.itemView) val prevItem = if (position > 0) getItem(position - 1) else null when (val item = getItem(position)) { - is ComposeItem.New -> bindNew(holder, item.value) - is ComposeItem.Recent -> bindRecent(holder, item.value, prevItem) - is ComposeItem.Starred -> bindStarred(holder, item.value, prevItem) - is ComposeItem.Person -> bindPerson(holder, item.value, prevItem) - is ComposeItem.Group -> bindGroup(holder, item.value, prevItem) + is ComposeItem.New -> bindNew(binding, item.value) + is ComposeItem.Recent -> bindRecent(binding, item.value, prevItem) + is ComposeItem.Starred -> bindStarred(binding, item.value, prevItem) + is ComposeItem.Person -> bindPerson(binding, item.value, prevItem) + is ComposeItem.Group -> bindGroup(binding, item.value, prevItem) } } - private fun bindNew(holder: QkViewHolder, contact: Contact) { - holder.index.isVisible = false + private fun bindNew(binding: ContactListItemBinding, contact: Contact) { + binding.index.isVisible = false - holder.icon.isVisible = false + binding.icon.isVisible = false - holder.avatar.recipients = listOf(createRecipient(contact)) + binding.avatar.recipients = listOf(createRecipient(contact)) - holder.title.text = contact.numbers.joinToString { it.address } + binding.title.text = contact.numbers.joinToString { it.address } - holder.subtitle.isVisible = false + binding.subtitle.isVisible = false - holder.numbers.isVisible = false + binding.numbers.isVisible = false } - private fun bindRecent(holder: QkViewHolder, conversation: Conversation, prev: ComposeItem?) { - holder.index.isVisible = false + private fun bindRecent(binding: ContactListItemBinding, conversation: Conversation, prev: ComposeItem?) { + binding.index.isVisible = false - holder.icon.isVisible = prev !is ComposeItem.Recent - holder.icon.setImageResource(R.drawable.ic_history_black_24dp) + binding.icon.isVisible = prev !is ComposeItem.Recent + binding.icon.setImageResource(R.drawable.ic_history_black_24dp) - holder.avatar.recipients = conversation.recipients + binding.avatar.recipients = conversation.recipients - holder.title.text = conversation.getTitle() + binding.title.text = conversation.getTitle() - holder.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() - holder.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> + binding.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() + binding.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> recipient.contact?.name ?: recipient.address } - holder.subtitle.collapseEnabled = conversation.recipients.size > 1 + binding.subtitle.collapseEnabled = conversation.recipients.size > 1 - holder.numbers.isVisible = conversation.recipients.size == 1 - (holder.numbers.adapter as PhoneNumberAdapter).data = conversation.recipients + binding.numbers.isVisible = conversation.recipients.size == 1 + (binding.numbers.adapter as PhoneNumberAdapter).data = conversation.recipients .mapNotNull { recipient -> recipient.contact } .flatMap { contact -> contact.numbers } } - private fun bindStarred(holder: QkViewHolder, contact: Contact, prev: ComposeItem?) { - holder.index.isVisible = false + private fun bindStarred(binding: ContactListItemBinding, contact: Contact, prev: ComposeItem?) { + binding.index.isVisible = false - holder.icon.isVisible = prev !is ComposeItem.Starred - holder.icon.setImageResource(R.drawable.ic_star_black_24dp) + binding.icon.isVisible = prev !is ComposeItem.Starred + binding.icon.setImageResource(R.drawable.ic_star_black_24dp) - holder.avatar.recipients = listOf(createRecipient(contact)) + binding.avatar.recipients = listOf(createRecipient(contact)) - holder.title.text = contact.name + binding.title.text = contact.name - holder.subtitle.isVisible = false + binding.subtitle.isVisible = false - holder.numbers.isVisible = true - (holder.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + binding.numbers.isVisible = true + (binding.numbers.adapter as PhoneNumberAdapter).data = contact.numbers } - private fun bindGroup(holder: QkViewHolder, group: ContactGroup, prev: ComposeItem?) { - holder.index.isVisible = false + private fun bindGroup(binding: ContactListItemBinding, group: ContactGroup, prev: ComposeItem?) { + binding.index.isVisible = false - holder.icon.isVisible = prev !is ComposeItem.Group - holder.icon.setImageResource(R.drawable.ic_people_black_24dp) + binding.icon.isVisible = prev !is ComposeItem.Group + binding.icon.setImageResource(R.drawable.ic_people_black_24dp) - holder.avatar.recipients = group.contacts.map(::createRecipient) + binding.avatar.recipients = group.contacts.map(::createRecipient) - holder.title.text = group.title + binding.title.text = group.title - holder.subtitle.isVisible = true - holder.subtitle.text = group.contacts.joinToString(", ") { it.name } - holder.subtitle.collapseEnabled = group.contacts.size > 1 + binding.subtitle.isVisible = true + binding.subtitle.text = group.contacts.joinToString(", ") { it.name } + binding.subtitle.collapseEnabled = group.contacts.size > 1 - holder.numbers.isVisible = false + binding.numbers.isVisible = false } - private fun bindPerson(holder: QkViewHolder, contact: Contact, prev: ComposeItem?) { - holder.index.isVisible = true - holder.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" - holder.index.isVisible = prev !is ComposeItem.Person || + private fun bindPerson(binding: ContactListItemBinding, contact: Contact, prev: ComposeItem?) { + binding.index.isVisible = true + binding.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" + binding.index.isVisible = prev !is ComposeItem.Person || (contact.name[0].isLetter() && !contact.name[0].equals(prev.value.name[0], ignoreCase = true)) || (!contact.name[0].isLetter() && prev.value.name[0].isLetter()) - holder.icon.isVisible = false + binding.icon.isVisible = false - holder.avatar.recipients = listOf(createRecipient(contact)) + binding.avatar.recipients = listOf(createRecipient(contact)) - holder.title.text = contact.name + binding.title.text = contact.name - holder.subtitle.isVisible = false + binding.subtitle.isVisible = false - holder.numbers.isVisible = true - (holder.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + binding.numbers.isVisible = true + (binding.numbers.adapter as PhoneNumberAdapter).data = contact.numbers } private fun createRecipient(contact: Contact): Recipient { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/DetailedChipView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/DetailedChipView.kt index 19e7bb41..e661216f 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/DetailedChipView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/DetailedChipView.kt @@ -19,24 +19,26 @@ package dev.octoshrimpy.quik.feature.compose.editing import android.content.Context +import android.view.LayoutInflater import android.view.View import android.view.animation.AlphaAnimation import android.widget.RelativeLayout -import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setTint +import dev.octoshrimpy.quik.databinding.ContactChipDetailedBinding import dev.octoshrimpy.quik.injection.appComponent import dev.octoshrimpy.quik.model.Recipient -import kotlinx.android.synthetic.main.contact_chip_detailed.view.* import javax.inject.Inject class DetailedChipView(context: Context) : RelativeLayout(context) { @Inject lateinit var colors: Colors + private val binding: ContactChipDetailedBinding + init { - View.inflate(context, R.layout.contact_chip_detailed, this) + binding = ContactChipDetailedBinding.inflate(LayoutInflater.from(context), this, true) appComponent.inject(this) setOnClickListener { hide() } @@ -48,15 +50,15 @@ class DetailedChipView(context: Context) : RelativeLayout(context) { } fun setRecipient(recipient: Recipient) { - avatar.setRecipient(recipient) - name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address - info.text = recipient.address + binding.avatar.setRecipient(recipient) + binding.name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address + binding.info.text = recipient.address colors.theme(recipient).let { theme -> - card.setBackgroundTint(theme.theme) - name.setTextColor(theme.textPrimary) - info.setTextColor(theme.textTertiary) - delete.setTint(theme.textPrimary) + binding.card.setBackgroundTint(theme.theme) + binding.name.setTextColor(theme.textPrimary) + binding.info.setTextColor(theme.textTertiary) + binding.delete.setTint(theme.textPrimary) } } @@ -77,7 +79,7 @@ class DetailedChipView(context: Context) : RelativeLayout(context) { } fun setOnDeleteListener(listener: (View) -> Unit) { - delete.setOnClickListener(listener) + binding.delete.setOnClickListener(listener) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt index 31130c8c..5208820d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt @@ -25,13 +25,11 @@ import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkAdapter import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.extensions.forwardTouches +import dev.octoshrimpy.quik.databinding.PhoneNumberListItemBinding import dev.octoshrimpy.quik.extensions.Optional import dev.octoshrimpy.quik.model.PhoneNumber import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.phone_number_list_item.* -import kotlinx.android.synthetic.main.radio_preference_view.* -import kotlinx.android.synthetic.main.radio_preference_view.view.* import javax.inject.Inject class PhoneNumberPickerAdapter @Inject constructor( @@ -50,11 +48,11 @@ class PhoneNumberPickerAdapter @Inject constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val inflater = LayoutInflater.from(parent.context) - val view = inflater.inflate(R.layout.phone_number_list_item, parent, false) - return QkViewHolder(view).apply { - radioButton.forwardTouches(itemView) + val binding = PhoneNumberListItemBinding.inflate(inflater, parent, false) + return QkViewHolder(binding.root).apply { + binding.number.radioButton.forwardTouches(itemView) - view.setOnClickListener { + binding.root.setOnClickListener { val phoneNumber = getItem(adapterPosition) selectedItem = phoneNumber.id } @@ -62,11 +60,12 @@ class PhoneNumberPickerAdapter @Inject constructor( } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val binding = PhoneNumberListItemBinding.bind(holder.itemView) val phoneNumber = getItem(position) - holder.number.radioButton.isChecked = phoneNumber.id == selectedItem - holder.number.titleView.text = phoneNumber.address - holder.number.summaryView.text = when (phoneNumber.isDefault) { + binding.number.radioButton.isChecked = phoneNumber.id == selectedItem + binding.number.titleView.text = phoneNumber.address + binding.number.summaryView.text = when (phoneNumber.isDefault) { true -> context.getString(R.string.compose_number_picker_default, phoneNumber.type) false -> phoneNumber.type } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/AudioBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/AudioBinder.kt index fb474812..b406b97d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/AudioBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/AudioBinder.kt @@ -35,6 +35,7 @@ import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setTint import dev.octoshrimpy.quik.common.util.extensions.withAlpha import dev.octoshrimpy.quik.common.widget.BubbleImageView +import dev.octoshrimpy.quik.databinding.MmsAudioPreviewListItemBinding import dev.octoshrimpy.quik.extensions.isAudio import dev.octoshrimpy.quik.extensions.resourceExists import dev.octoshrimpy.quik.feature.compose.MessagesAdapter @@ -44,8 +45,6 @@ import dev.octoshrimpy.quik.util.GlideApp import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers -import kotlinx.android.synthetic.main.mms_audio_preview_list_item.* -import kotlinx.android.synthetic.main.mms_image_preview_list_item.thumbnail import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -69,33 +68,36 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte .subscribeOn(Schedulers.single()) .observeOn(AndroidSchedulers.mainThread()) .doOnNext { - viewHolder?.seekBar?.progress = QkMediaPlayer.currentPosition + viewHolder?.let { holder -> + val binding = MmsAudioPreviewListItemBinding.bind(holder.itemView) + binding.seekBar.progress = QkMediaPlayer.currentPosition + } } .subscribe() } } - private fun uiToPlaying(viewHolder: QkViewHolder) { - viewHolder.seekBar.max = QkMediaPlayer.duration - viewHolder.seekBar.isEnabled = true - viewHolder.seekBar.progress = QkMediaPlayer.currentPosition - viewHolder.playPause.setImageResource(R.drawable.exo_icon_pause) - viewHolder.playPause.tag = QkMediaPlayer.PlayingState.Playing - viewHolder.metadataTitle.isSelected = true // start marquee + private fun uiToPlaying(binding: MmsAudioPreviewListItemBinding) { + binding.seekBar.max = QkMediaPlayer.duration + binding.seekBar.isEnabled = true + binding.seekBar.progress = QkMediaPlayer.currentPosition + binding.playPause.setImageResource(R.drawable.exo_icon_pause) + binding.playPause.tag = QkMediaPlayer.PlayingState.Playing + binding.metadataTitle.isSelected = true // start marquee } - private fun uiToPaused(viewHolder: QkViewHolder) { - viewHolder.playPause.setImageResource(R.drawable.exo_icon_play) - viewHolder.playPause.tag = QkMediaPlayer.PlayingState.Paused + private fun uiToPaused(binding: MmsAudioPreviewListItemBinding) { + binding.playPause.setImageResource(R.drawable.exo_icon_play) + binding.playPause.tag = QkMediaPlayer.PlayingState.Paused } - private fun uiToStopped(viewHolder: QkViewHolder) { - viewHolder.seekBar.progress = 0 - viewHolder.seekBar.max = 0 - viewHolder.seekBar.isEnabled = false - viewHolder.playPause.setImageResource(R.drawable.exo_icon_play) - viewHolder.playPause.tag = QkMediaPlayer.PlayingState.Stopped - viewHolder.metadataTitle.isSelected = false // stop marquee + private fun uiToStopped(binding: MmsAudioPreviewListItemBinding) { + binding.seekBar.progress = 0 + binding.seekBar.max = 0 + binding.seekBar.isEnabled = false + binding.playPause.setImageResource(R.drawable.exo_icon_play) + binding.playPause.tag = QkMediaPlayer.PlayingState.Stopped + binding.metadataTitle.isSelected = false // stop marquee } override fun bindPart( @@ -105,16 +107,18 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte canGroupWithPrevious: Boolean, canGroupWithNext: Boolean, ) { + val binding = MmsAudioPreviewListItemBinding.bind(holder.itemView) + // click on background - passes back to compose view model - holder.containerView.setOnClickListener { clicks.onNext(part.id) } + holder.itemView.setOnClickListener { clicks.onNext(part.id) } // play/pause button click handling - holder.playPause.setOnClickListener { - when (holder.playPause.tag) { + binding.playPause.setOnClickListener { + when (binding.playPause.tag) { QkMediaPlayer.PlayingState.Playing -> { if (audioState.partId == part.id) { QkMediaPlayer.pause() - uiToPaused(holder) + uiToPaused(binding) audioState.state = QkMediaPlayer.PlayingState.Paused // stop progress bar update timer @@ -124,7 +128,7 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte QkMediaPlayer.PlayingState.Paused -> { if (audioState.partId == part.id) { QkMediaPlayer.start() - uiToPlaying(holder) + uiToPlaying(binding) audioState.state = QkMediaPlayer.PlayingState.Playing // start progress bar update timer @@ -139,7 +143,7 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte // start media playing QkMediaPlayer.start() - uiToPlaying(holder) + uiToPlaying(binding) // set current view holder and part as active audioState.apply { @@ -155,8 +159,10 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte QkMediaPlayer.setOnCompletionListener { // also called on error because we don't have an onerrorlistener audioState.apply { // if this part is currently active, set it to stopped and inactive - if ((partId == part.id) && (viewHolder != null)) - uiToStopped(viewHolder!!) + if ((partId == part.id) && (viewHolder != null)) { + val stoppedBinding = MmsAudioPreviewListItemBinding.bind(viewHolder!!.itemView) + uiToStopped(stoppedBinding) + } state = QkMediaPlayer.PlayingState.Stopped partId = -1 @@ -192,18 +198,18 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte if (!message.isMe()) theme.theme else - holder.containerView.context.resolveThemeColor(R.attr.bubbleColor) + holder.itemView.context.resolveThemeColor(R.attr.bubbleColor) val primaryColor = if (!message.isMe()) theme.textPrimary else - holder.containerView.context.resolveThemeColor(android.R.attr.textColorPrimary) + holder.itemView.context.resolveThemeColor(android.R.attr.textColorPrimary) // sound wave - holder.soundWave.setTint(primaryColor) + binding.soundWave.setTint(primaryColor) // seek bar - holder.seekBar.apply { + binding.seekBar.apply { setTint(secondaryColor) thumbTintList = ColorStateList.valueOf(secondaryColor) @@ -219,15 +225,15 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte } // playPause button - holder.playPause. apply { + binding.playPause.apply { if ((audioState.partId == part.id) && (audioState.state == QkMediaPlayer.PlayingState.Playing)) - uiToPlaying(holder) + uiToPlaying(binding) else if ((audioState.partId == part.id) && (audioState.state == QkMediaPlayer.PlayingState.Paused)) - uiToPaused(holder) + uiToPaused(binding) else - uiToStopped(holder) + uiToStopped(binding) setTint(secondaryColor) setBackgroundTint(primaryColor) @@ -239,7 +245,7 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte setDataSource(context, part.getUri()) // metadata title - holder.metadataTitle.apply { + binding.metadataTitle.apply { text = extractMetadata(METADATA_KEY_TITLE) if (text.isEmpty()) @@ -252,7 +258,7 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte } // bubble / embedded image - holder.thumbnail.apply { + binding.thumbnail.apply { bubbleStyle = when { !canGroupWithPrevious && canGroupWithNext -> if (message.isMe()) BubbleImageView.Style.OUT_FIRST else BubbleImageView.Style.IN_FIRST @@ -268,18 +274,18 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte val embeddedPicture = embeddedPicture if (embeddedPicture == null) { - holder.frame.layoutParams.height = (holder.frame.layoutParams.width / 2) + binding.frame.layoutParams.height = (binding.frame.layoutParams.width / 2) setTint(secondaryColor) setImageResource(R.drawable.rectangle) } else { - holder.frame.layoutParams.height = holder.frame.layoutParams.width + binding.frame.layoutParams.height = binding.frame.layoutParams.width setTint(null) GlideApp.with(context) .asBitmap() .load(embeddedPicture) .override( - holder.frame.layoutParams.width, - holder.frame.layoutParams.height + binding.frame.layoutParams.width, + binding.frame.layoutParams.height ) .into(this) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt index e020dc13..ad5afbb5 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt @@ -27,6 +27,7 @@ import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setTint +import dev.octoshrimpy.quik.databinding.MmsFileListItemBinding import dev.octoshrimpy.quik.extensions.mapNotNull import dev.octoshrimpy.quik.feature.compose.BubbleUtils import dev.octoshrimpy.quik.model.Message @@ -35,7 +36,6 @@ import dev.octoshrimpy.quik.util.tryOrNull import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers -import kotlinx.android.synthetic.main.mms_file_list_item.* import javax.inject.Inject class FileBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { @@ -56,8 +56,9 @@ class FileBinder @Inject constructor(colors: Colors, private val context: Contex canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { + val binding = MmsFileListItemBinding.bind(holder.itemView) BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) - .let(holder.fileBackground::setBackgroundResource) + .let(binding.fileBackground::setBackgroundResource) tryOrNull(true) { Observable.just(part.getUri()) @@ -76,21 +77,21 @@ class FileBinder @Inject constructor(colors: Colors, private val context: Contex } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { size -> holder.size.text = size } + .subscribe { size -> binding.size.text = size } - holder.filename.text = part.getBestFilename() + binding.filename.text = part.getBestFilename() } if (!message.isMe()) { - holder.fileBackground.setBackgroundTint(theme.theme) - holder.icon.setTint(theme.textPrimary) - holder.filename.setTextColor(theme.textPrimary) - holder.size.setTextColor(theme.textTertiary) + binding.fileBackground.setBackgroundTint(theme.theme) + binding.icon.setTint(theme.textPrimary) + binding.filename.setTextColor(theme.textPrimary) + binding.size.setTextColor(theme.textTertiary) } else { - holder.fileBackground.setBackgroundTint(holder.containerView.context.resolveThemeColor(R.attr.bubbleColor)) - holder.icon.setTint(holder.containerView.context.resolveThemeColor(android.R.attr.textColorSecondary)) - holder.filename.setTextColor(holder.containerView.context.resolveThemeColor(android.R.attr.textColorPrimary)) - holder.size.setTextColor(holder.containerView.context.resolveThemeColor(android.R.attr.textColorTertiary)) + binding.fileBackground.setBackgroundTint(holder.itemView.context.resolveThemeColor(R.attr.bubbleColor)) + binding.icon.setTint(holder.itemView.context.resolveThemeColor(android.R.attr.textColorSecondary)) + binding.filename.setTextColor(holder.itemView.context.resolveThemeColor(android.R.attr.textColorPrimary)) + binding.size.setTextColor(holder.itemView.context.resolveThemeColor(android.R.attr.textColorTertiary)) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/ImageBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/ImageBinder.kt index c3cc8dff..ab5eb828 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/ImageBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/ImageBinder.kt @@ -24,13 +24,13 @@ import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.setVisible import dev.octoshrimpy.quik.common.widget.BubbleImageView +import dev.octoshrimpy.quik.databinding.MmsImagePreviewListItemBinding import dev.octoshrimpy.quik.extensions.isImage import dev.octoshrimpy.quik.extensions.isVideo import dev.octoshrimpy.quik.model.Message import dev.octoshrimpy.quik.model.MmsPart import dev.octoshrimpy.quik.util.GlideApp import dev.octoshrimpy.quik.util.tryOrNull -import kotlinx.android.synthetic.main.mms_image_preview_list_item.* import javax.inject.Inject class ImageBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { @@ -47,10 +47,11 @@ class ImageBinder @Inject constructor(colors: Colors, private val context: Conte canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { - holder.video.setVisible(part.isVideo()) - holder.containerView.setOnClickListener { clicks.onNext(part.id) } + val binding = MmsImagePreviewListItemBinding.bind(holder.itemView) + binding.video.setVisible(part.isVideo()) + holder.itemView.setOnClickListener { clicks.onNext(part.id) } - holder.thumbnail.bubbleStyle = when { + binding.thumbnail.bubbleStyle = when { !canGroupWithPrevious && canGroupWithNext -> if (message.isMe()) BubbleImageView.Style.OUT_FIRST else BubbleImageView.Style.IN_FIRST canGroupWithPrevious && canGroupWithNext -> if (message.isMe()) BubbleImageView.Style.OUT_MIDDLE else BubbleImageView.Style.IN_MIDDLE canGroupWithPrevious && !canGroupWithNext -> if (message.isMe()) BubbleImageView.Style.OUT_LAST else BubbleImageView.Style.IN_LAST @@ -58,7 +59,7 @@ class ImageBinder @Inject constructor(colors: Colors, private val context: Conte } tryOrNull(true) { - GlideApp.with(context).load(part.getUri()).fitCenter().into(holder.thumbnail) + GlideApp.with(context).load(part.getUri()).fitCenter().into(binding.thumbnail) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartsAdapter.kt index 292def32..1457b8e3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartsAdapter.kt @@ -24,6 +24,8 @@ import android.view.ViewGroup import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.widget.QkContextMenuRecyclerView +import dev.octoshrimpy.quik.databinding.MessageListItemInBinding +import dev.octoshrimpy.quik.databinding.MessageListItemOutBinding import dev.octoshrimpy.quik.extensions.isSmil import dev.octoshrimpy.quik.extensions.isText import dev.octoshrimpy.quik.feature.compose.BubbleUtils.canGroup @@ -31,7 +33,6 @@ import dev.octoshrimpy.quik.feature.compose.MessagesAdapter import dev.octoshrimpy.quik.model.Message import dev.octoshrimpy.quik.model.MmsPart import io.reactivex.Observable -import kotlinx.android.synthetic.main.message_list_item_in.* import javax.inject.Inject @@ -69,7 +70,12 @@ class PartsAdapter @Inject constructor( this.message = message this.previous = previous this.next = next - this.bodyVisible = holder.body.visibility == View.VISIBLE + // Get body visibility from appropriate binding based on message type + this.bodyVisible = if (message.isMe()) { + MessageListItemOutBinding.bind(holder.itemView).body.visibility == View.VISIBLE + } else { + MessageListItemInBinding.bind(holder.itemView).body.visibility == View.VISIBLE + } this.data = message.parts.filter { !it.isSmil() && !it.isText() } this.audioState = audioState } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/VCardBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/VCardBinder.kt index 3ab31a3b..7e975def 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/VCardBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/VCardBinder.kt @@ -27,6 +27,7 @@ import dev.octoshrimpy.quik.common.util.extensions.getDisplayName import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setTint +import dev.octoshrimpy.quik.databinding.MmsVcardListItemBinding import dev.octoshrimpy.quik.extensions.isVCard import dev.octoshrimpy.quik.extensions.mapNotNull import dev.octoshrimpy.quik.feature.compose.BubbleUtils @@ -37,7 +38,6 @@ import ezvcard.Ezvcard import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers -import kotlinx.android.synthetic.main.mms_vcard_list_item.* import javax.inject.Inject class VCardBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { @@ -54,10 +54,11 @@ class VCardBinder @Inject constructor(colors: Colors, private val context: Conte canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { + val binding = MmsVcardListItemBinding.bind(holder.itemView) BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) - .let(holder.vCardBackground::setBackgroundResource) + .let(binding.vCardBackground::setBackgroundResource) - holder.containerView.setOnClickListener { clicks.onNext(part.id) } + holder.itemView.setOnClickListener { clicks.onNext(part.id) } tryOrNull(true) { Observable.just(part.getUri()) @@ -67,21 +68,21 @@ class VCardBinder @Inject constructor(colors: Colors, private val context: Conte .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { displayName -> - holder.name?.text = displayName - holder.name.isVisible = displayName.isNotEmpty() + binding.name.text = displayName + binding.name.isVisible = displayName.isNotEmpty() } } if (!message.isMe()) { - holder.vCardBackground.setBackgroundTint(theme.theme) - holder.vCardAvatar.setTint(theme.textPrimary) - holder.name.setTextColor(theme.textPrimary) - holder.label.setTextColor(theme.textTertiary) + binding.vCardBackground.setBackgroundTint(theme.theme) + binding.vCardAvatar.setTint(theme.textPrimary) + binding.name.setTextColor(theme.textPrimary) + binding.label.setTextColor(theme.textTertiary) } else { - holder.vCardBackground.setBackgroundTint(holder.containerView.context.resolveThemeColor(R.attr.bubbleColor)) - holder.vCardAvatar.setTint(holder.containerView.context.resolveThemeColor(android.R.attr.textColorSecondary)) - holder.name.setTextColor(holder.containerView.context.resolveThemeColor(android.R.attr.textColorPrimary)) - holder.label.setTextColor(holder.containerView.context.resolveThemeColor(android.R.attr.textColorTertiary)) + binding.vCardBackground.setBackgroundTint(holder.itemView.context.resolveThemeColor(R.attr.bubbleColor)) + binding.vCardAvatar.setTint(holder.itemView.context.resolveThemeColor(android.R.attr.textColorSecondary)) + binding.name.setTextColor(holder.itemView.context.resolveThemeColor(android.R.attr.textColorPrimary)) + binding.label.setTextColor(holder.itemView.context.resolveThemeColor(android.R.attr.textColorTertiary)) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivity.kt index 89fdd34f..b74e200f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivity.kt @@ -35,6 +35,7 @@ import dev.octoshrimpy.quik.common.base.QkThemedActivity import dev.octoshrimpy.quik.common.util.extensions.hideKeyboard import dev.octoshrimpy.quik.common.util.extensions.showKeyboard import dev.octoshrimpy.quik.common.widget.QkDialog +import dev.octoshrimpy.quik.databinding.ContactsActivityBinding import dev.octoshrimpy.quik.extensions.Optional import dev.octoshrimpy.quik.feature.compose.editing.ComposeItem import dev.octoshrimpy.quik.feature.compose.editing.ComposeItemAdapter @@ -43,7 +44,6 @@ import dev.octoshrimpy.quik.feature.compose.editing.PhoneNumberPickerAdapter import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.contacts_activity.* import javax.inject.Inject class ContactsActivity : QkThemedActivity(), ContactsContract { @@ -53,14 +53,16 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { const val CHIPS_KEY = "chips" } + private lateinit var binding: ContactsActivityBinding + @Inject lateinit var contactsAdapter: ComposeItemAdapter @Inject lateinit var phoneNumberAdapter: PhoneNumberPickerAdapter @Inject lateinit var viewModelFactory: ViewModelFactory @Inject lateinit var navigator: Navigator - override val queryChangedIntent: Observable by lazy { search.textChanges() } - override val queryClearedIntent: Observable<*> by lazy { cancel.clicks() } - override val queryEditorActionIntent: Observable by lazy { search.editorActions() } + override val queryChangedIntent: Observable by lazy { binding.search.textChanges() } + override val queryClearedIntent: Observable<*> by lazy { binding.cancel.clicks() } + override val queryEditorActionIntent: Observable by lazy { binding.search.editorActions() } override val composeItemPressedIntent: Subject by lazy { contactsAdapter.clicks } override val composeItemLongPressedIntent: Subject by lazy { contactsAdapter.longClicks } override val phoneNumberSelectedIntent: Subject> by lazy { phoneNumberAdapter.selectedItemChanges } @@ -83,11 +85,12 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.contacts_activity) + binding = ContactsActivityBinding.inflate(layoutInflater) + setContentView(binding.root) showBackButton(true) viewModel.bindView(this) - contacts.adapter = contactsAdapter + binding.contacts.adapter = contactsAdapter val callback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { @@ -98,7 +101,7 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { } override fun render(state: ContactsState) { - cancel.isVisible = state.query.length > 1 + binding.cancel.isVisible = state.query.length > 1 contactsAdapter.data = state.composeItems @@ -112,17 +115,17 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { } override fun clearQuery() { - search.text = null + binding.search.text = null } override fun openKeyboard() { - search.postDelayed({ - search.showKeyboard() + binding.search.postDelayed({ + binding.search.showKeyboard() }, 200) } override fun finish(result: HashMap) { - search.hideKeyboard() + binding.search.hideKeyboard() val intent = Intent().putExtra(CHIPS_KEY, result) setResult(Activity.RESULT_OK, intent) finish() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoActivity.kt index 16069617..8970e304 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoActivity.kt @@ -23,20 +23,21 @@ import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction import dagger.android.AndroidInjection -import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkThemedActivity -import kotlinx.android.synthetic.main.container_activity.* +import dev.octoshrimpy.quik.databinding.ContainerActivityBinding class ConversationInfoActivity : QkThemedActivity() { + private lateinit var binding: ContainerActivityBinding private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.container_activity) + binding = ContainerActivityBinding.inflate(layoutInflater) + setContentView(binding.root) - router = Conductor.attachRouter(this, container, savedInstanceState) + router = Conductor.attachRouter(this, binding.container, savedInstanceState) if (!router.hasRootController()) { val threadId = intent.extras?.getLong("threadId") ?: 0L router.setRoot(RouterTransaction.with(ConversationInfoController(threadId))) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoAdapter.kt index e34663e9..332f7541 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoAdapter.kt @@ -11,14 +11,14 @@ import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.setTint import dev.octoshrimpy.quik.common.util.extensions.setVisible +import dev.octoshrimpy.quik.databinding.ConversationInfoSettingsBinding +import dev.octoshrimpy.quik.databinding.ConversationMediaListItemBinding +import dev.octoshrimpy.quik.databinding.ConversationRecipientListItemBinding import dev.octoshrimpy.quik.extensions.isVideo import dev.octoshrimpy.quik.feature.conversationinfo.ConversationInfoItem.* import dev.octoshrimpy.quik.util.GlideApp import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.conversation_info_settings.* -import kotlinx.android.synthetic.main.conversation_media_list_item.* -import kotlinx.android.synthetic.main.conversation_recipient_list_item.* import javax.inject.Inject class ConversationInfoAdapter @Inject constructor( @@ -40,37 +40,46 @@ class ConversationInfoAdapter @Inject constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val inflater = LayoutInflater.from(parent.context) return when (viewType) { - 0 -> QkViewHolder(inflater.inflate(R.layout.conversation_recipient_list_item, parent, false)).apply { - itemView.setOnClickListener { - val item = getItem(adapterPosition) as? ConversationInfoRecipient - item?.value?.id?.run(recipientClicks::onNext) - } + 0 -> { + val binding = ConversationRecipientListItemBinding.inflate(inflater, parent, false) + QkViewHolder(binding.root).apply { + itemView.setOnClickListener { + val item = getItem(adapterPosition) as? ConversationInfoRecipient + item?.value?.id?.run(recipientClicks::onNext) + } - itemView.setOnLongClickListener { - val item = getItem(adapterPosition) as? ConversationInfoRecipient - item?.value?.id?.run(recipientLongClicks::onNext) - true - } + itemView.setOnLongClickListener { + val item = getItem(adapterPosition) as? ConversationInfoRecipient + item?.value?.id?.run(recipientLongClicks::onNext) + true + } - theme.setOnClickListener { - val item = getItem(adapterPosition) as? ConversationInfoRecipient - item?.value?.id?.run(themeClicks::onNext) + binding.theme.setOnClickListener { + val item = getItem(adapterPosition) as? ConversationInfoRecipient + item?.value?.id?.run(themeClicks::onNext) + } } } - 1 -> QkViewHolder(inflater.inflate(R.layout.conversation_info_settings, parent, false)).apply { - groupName.clicks().subscribe(nameClicks) - notifications.clicks().subscribe(notificationClicks) - markUnread.clicks().subscribe(markUnreadClicks) - archive.clicks().subscribe(archiveClicks) - block.clicks().subscribe(blockClicks) - delete.clicks().subscribe(deleteClicks) + 1 -> { + val binding = ConversationInfoSettingsBinding.inflate(inflater, parent, false) + QkViewHolder(binding.root).apply { + binding.groupName.clicks().subscribe(nameClicks) + binding.notifications.clicks().subscribe(notificationClicks) + binding.markUnread.clicks().subscribe(markUnreadClicks) + binding.archive.clicks().subscribe(archiveClicks) + binding.block.clicks().subscribe(blockClicks) + binding.delete.clicks().subscribe(deleteClicks) + } } - 2 -> QkViewHolder(inflater.inflate(R.layout.conversation_media_list_item, parent, false)).apply { - itemView.setOnClickListener { - val item = getItem(adapterPosition) as? ConversationInfoMedia - item?.value?.id?.run(mediaClicks::onNext) + 2 -> { + val binding = ConversationMediaListItemBinding.inflate(inflater, parent, false) + QkViewHolder(binding.root).apply { + itemView.setOnClickListener { + val item = getItem(adapterPosition) as? ConversationInfoMedia + item?.value?.id?.run(mediaClicks::onNext) + } } } @@ -81,46 +90,49 @@ class ConversationInfoAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { when (val item = getItem(position)) { is ConversationInfoRecipient -> { + val binding = ConversationRecipientListItemBinding.bind(holder.itemView) val recipient = item.value - holder.avatar.setRecipient(recipient) + binding.avatar.setRecipient(recipient) - holder.name.text = recipient.contact?.name ?: recipient.address + binding.name.text = recipient.contact?.name ?: recipient.address - holder.address.text = recipient.address - holder.address.setVisible(recipient.contact != null) + binding.address.text = recipient.address + binding.address.setVisible(recipient.contact != null) - holder.add.setVisible(recipient.contact == null) + binding.add.setVisible(recipient.contact == null) val theme = colors.theme(recipient) - holder.theme.setTint(theme.theme) + binding.theme.setTint(theme.theme) } is ConversationInfoSettings -> { - holder.groupName.summary = item.name + val binding = ConversationInfoSettingsBinding.bind(holder.itemView) + binding.groupName.summary = item.name - holder.notifications.isEnabled = !item.blocked + binding.notifications.isEnabled = !item.blocked - holder.archive.isEnabled = !item.blocked - holder.archive.title = context.getString(when (item.archived) { + binding.archive.isEnabled = !item.blocked + binding.archive.title = context.getString(when (item.archived) { true -> R.string.info_unarchive false -> R.string.info_archive }) - holder.block.title = context.getString(when (item.blocked) { + binding.block.title = context.getString(when (item.blocked) { true -> R.string.info_unblock false -> R.string.info_block }) } is ConversationInfoMedia -> { + val binding = ConversationMediaListItemBinding.bind(holder.itemView) val part = item.value GlideApp.with(context) .load(part.getUri()) .fitCenter() - .into(holder.thumbnail) + .into(binding.thumbnail) - holder.video.isVisible = part.isVideo() + binding.video.isVisible = part.isVideo() } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt index e7c64578..556cc339 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt @@ -18,7 +18,9 @@ */ package dev.octoshrimpy.quik.feature.conversationinfo +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.GridLayoutManager import com.bluelinelabs.conductor.RouterTransaction @@ -30,6 +32,7 @@ import dev.octoshrimpy.quik.common.QkChangeHandler import dev.octoshrimpy.quik.common.base.QkController import dev.octoshrimpy.quik.common.util.extensions.scrapViews import dev.octoshrimpy.quik.common.widget.TextInputDialog +import dev.octoshrimpy.quik.databinding.ConversationInfoControllerBinding import dev.octoshrimpy.quik.feature.blocking.BlockingDialog import dev.octoshrimpy.quik.feature.conversationinfo.injection.ConversationInfoModule import dev.octoshrimpy.quik.feature.themepicker.ThemePickerController @@ -37,12 +40,14 @@ import dev.octoshrimpy.quik.injection.appComponent import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.conversation_info_controller.* import javax.inject.Inject class ConversationInfoController( val threadId: Long = 0 -) : QkController(), ConversationInfoView { +) : QkController(), ConversationInfoView { + + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): ConversationInfoControllerBinding = + ConversationInfoControllerBinding.inflate(inflater, container, false) @Inject override lateinit var presenter: ConversationInfoPresenter @Inject lateinit var blockingDialog: BlockingDialog @@ -62,14 +67,12 @@ class ConversationInfoController( .conversationInfoModule(ConversationInfoModule(this)) .build() .inject(this) - - layoutRes = R.layout.conversation_info_controller } override fun onViewCreated() { - recyclerView.adapter = adapter - recyclerView.addItemDecoration(GridSpacingItemDecoration(adapter, activity!!)) - recyclerView.layoutManager = GridLayoutManager(activity, 3).apply { + binding.recyclerView.adapter = adapter + binding.recyclerView.addItemDecoration(GridSpacingItemDecoration(adapter, activity!!)) + binding.recyclerView.layoutManager = GridLayoutManager(activity, 3).apply { spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int = if (adapter.getItemViewType(position) == 2) 1 else 3 } @@ -77,7 +80,7 @@ class ConversationInfoController( themedActivity?.theme ?.autoDisposable(scope()) - ?.subscribe { recyclerView.scrapViews() } + ?.subscribe { binding.recyclerView.scrapViews() } } override fun onAttach(view: View) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt index ffad063a..ce07a0ea 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt @@ -33,12 +33,11 @@ import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.DateFormatter import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor import dev.octoshrimpy.quik.common.util.extensions.setTint +import dev.octoshrimpy.quik.databinding.ConversationListItemBinding import dev.octoshrimpy.quik.model.Conversation import dev.octoshrimpy.quik.repository.ScheduledMessageRepository import dev.octoshrimpy.quik.util.PhoneNumberUtils import io.reactivex.disposables.CompositeDisposable -import kotlinx.android.synthetic.main.conversation_list_item.* -import kotlinx.android.synthetic.main.conversation_list_item.view.* import javax.inject.Inject class ConversationsAdapter @Inject constructor( @@ -58,35 +57,35 @@ class ConversationsAdapter @Inject constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val layoutInflater = LayoutInflater.from(parent.context) - val view = layoutInflater.inflate(R.layout.conversation_list_item, parent, false) + val binding = ConversationListItemBinding.inflate(layoutInflater, parent, false) if (viewType == 1) { val textColorPrimary = parent.context.resolveThemeColor(android.R.attr.textColorPrimary) - view.title.setTypeface(view.title.typeface, Typeface.BOLD) + binding.title.setTypeface(binding.title.typeface, Typeface.BOLD) - view.snippet.setTypeface(view.snippet.typeface, Typeface.BOLD) - view.snippet.setTextColor(textColorPrimary) - view.snippet.maxLines = 5 + binding.snippet.setTypeface(binding.snippet.typeface, Typeface.BOLD) + binding.snippet.setTextColor(textColorPrimary) + binding.snippet.maxLines = 5 - view.unread.isVisible = true + binding.unread.isVisible = true - view.date.setTypeface(view.date.typeface, Typeface.BOLD) - view.date.setTextColor(textColorPrimary) + binding.date.setTypeface(binding.date.typeface, Typeface.BOLD) + binding.date.setTextColor(textColorPrimary) } - return QkViewHolder(view).apply { - view.setOnClickListener { + return QkViewHolder(binding.root).apply { + binding.root.setOnClickListener { val conversation = getItem(adapterPosition) ?: return@setOnClickListener when (toggleSelection(conversation.id, false)) { - true -> view.isActivated = isSelected(conversation.id) + true -> binding.root.isActivated = isSelected(conversation.id) false -> navigator.showConversation(conversation.id) } } - view.setOnLongClickListener { + binding.root.setOnLongClickListener { val conversation = getItem(adapterPosition) ?: return@setOnLongClickListener true toggleSelection(conversation.id) - view.isActivated = isSelected(conversation.id) + binding.root.isActivated = isSelected(conversation.id) true } } @@ -94,6 +93,7 @@ class ConversationsAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return + val binding = ConversationListItemBinding.bind(holder.itemView) // If the last message wasn't incoming, then the colour doesn't really matter anyway val lastMessage = conversation.lastMessage @@ -105,22 +105,22 @@ class ConversationsAdapter @Inject constructor( } val theme = colors.theme(recipient).theme - holder.containerView.isActivated = isSelected(conversation.id) + holder.itemView.isActivated = isSelected(conversation.id) - holder.avatars.recipients = conversation.recipients - holder.title.collapseEnabled = conversation.recipients.size > 1 - holder.title.text = buildSpannedString { + binding.avatars.recipients = conversation.recipients + binding.title.collapseEnabled = conversation.recipients.size > 1 + binding.title.text = buildSpannedString { append(conversation.getTitle()) } - holder.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) - holder.snippet.text = when { + binding.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) + binding.snippet.text = when { conversation.draft.isNotEmpty() -> context.getString(R.string.main_sender_draft, conversation.draft) conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) else -> conversation.snippet } // Make the preview in italics if draft - if (conversation.draft.isNotEmpty()) holder.snippet.setTypeface(null, Typeface.ITALIC) + if (conversation.draft.isNotEmpty()) binding.snippet.setTypeface(null, Typeface.ITALIC) // Get Scheduled Messages val disposable = scheduledMessageRepo @@ -128,12 +128,12 @@ class ConversationsAdapter @Inject constructor( .asFlowable() .toObservable() .subscribe { messages -> - holder.scheduled.isVisible = messages.isNotEmpty() + binding.scheduled.isVisible = messages.isNotEmpty() } disposables.add(disposable) - holder.pinned.isVisible = conversation.pinned - holder.unread.setTint(theme) + binding.pinned.isVisible = conversation.pinned + binding.unread.setTint(theme) } override fun getItemId(position: Int): Long { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryActivity.kt index 2189035b..4610802e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryActivity.kt @@ -34,15 +34,17 @@ import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkActivity import dev.octoshrimpy.quik.common.util.DateFormatter import dev.octoshrimpy.quik.common.util.extensions.setVisible +import dev.octoshrimpy.quik.databinding.GalleryActivityBinding import dev.octoshrimpy.quik.model.MmsPart import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.gallery_activity.* import javax.inject.Inject class GalleryActivity : QkActivity(), GalleryView { + private lateinit var binding: GalleryActivityBinding + @Inject lateinit var dateFormatter: DateFormatter @Inject lateinit var viewModelFactory: ViewModelProvider.Factory @Inject lateinit var pagerAdapter: GalleryPagerAdapter @@ -57,12 +59,13 @@ class GalleryActivity : QkActivity(), GalleryView { delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.gallery_activity) + binding = GalleryActivityBinding.inflate(layoutInflater) + setContentView(binding.root) showBackButton(true) viewModel.bindView(this) - pager.adapter = pagerAdapter - pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + binding.pager.adapter = pagerAdapter + binding.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { this@GalleryActivity.onPageSelected(position) } @@ -74,7 +77,7 @@ class GalleryActivity : QkActivity(), GalleryView { ?.indexOfFirst { part -> part.id == partId } ?.let { index -> onPageSelected(index) - pager.setCurrentItem(index, false) + binding.pager.setCurrentItem(index, false) pagerAdapter.unregisterAdapterDataObserver(this) } } @@ -82,15 +85,15 @@ class GalleryActivity : QkActivity(), GalleryView { } fun onPageSelected(position: Int) { - toolbarSubtitle.text = pagerAdapter.getItem(position)?.messages?.firstOrNull()?.date + binding.toolbarSubtitle.text = pagerAdapter.getItem(position)?.messages?.firstOrNull()?.date ?.let(dateFormatter::getDetailedTimestamp) - toolbarSubtitle.isVisible = toolbarTitle.text.isNotBlank() + binding.toolbarSubtitle.isVisible = binding.toolbarTitle.text.isNotBlank() pagerAdapter.getItem(position)?.run(pageChangedSubject::onNext) } override fun render(state: GalleryState) { - toolbar.setVisible(state.navigationVisible) + toolbar?.setVisible(state.navigationVisible) title = state.title pagerAdapter.updateData(state.parts) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryPagerAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryPagerAdapter.kt index 65882e5b..1dc9f568 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryPagerAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryPagerAdapter.kt @@ -33,15 +33,14 @@ import com.google.android.mms.ContentType import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkRealmAdapter import dev.octoshrimpy.quik.common.base.QkViewHolder +import dev.octoshrimpy.quik.databinding.GalleryImagePageBinding +import dev.octoshrimpy.quik.databinding.GalleryVideoPageBinding import dev.octoshrimpy.quik.extensions.isImage import dev.octoshrimpy.quik.extensions.isVideo import dev.octoshrimpy.quik.model.MmsPart import dev.octoshrimpy.quik.util.GlideApp import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.gallery_image_page.* -import kotlinx.android.synthetic.main.gallery_image_page.view.* -import kotlinx.android.synthetic.main.gallery_video_page.* import java.util.* import javax.inject.Inject @@ -61,25 +60,26 @@ class GalleryPagerAdapter @Inject constructor(private val context: Context) : Qk override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val inflater = LayoutInflater.from(parent.context) return QkViewHolder(when (viewType) { - VIEW_TYPE_IMAGE -> inflater.inflate(R.layout.gallery_image_page, parent, false).apply { - + VIEW_TYPE_IMAGE -> { + val binding = GalleryImagePageBinding.inflate(inflater, parent, false) // When calling the public setter, it doesn't allow the midscale to be the same as the // maxscale or the minscale. We don't want 3 levels and we don't want to modify the library // so let's celebrate the invention of reflection! - image.attacher.run { + binding.image.attacher.run { javaClass.getDeclaredField("mMinScale").run { isAccessible = true - setFloat(image.attacher, 1f) + setFloat(binding.image.attacher, 1f) } javaClass.getDeclaredField("mMidScale").run { isAccessible = true - setFloat(image.attacher, 1f) + setFloat(binding.image.attacher, 1f) } javaClass.getDeclaredField("mMaxScale").run { isAccessible = true - setFloat(image.attacher, 3f) + setFloat(binding.image.attacher, 3f) } } + binding.root } VIEW_TYPE_VIDEO -> inflater.inflate(R.layout.gallery_video_page, parent, false) @@ -93,25 +93,27 @@ class GalleryPagerAdapter @Inject constructor(private val context: Context) : Qk val part = getItem(position) ?: return when (getItemViewType(position)) { VIEW_TYPE_IMAGE -> { + val binding = GalleryImagePageBinding.bind(holder.itemView) // We need to explicitly request a gif from glide for animations to work when (part.getUri().let(contentResolver::getType)) { ContentType.IMAGE_GIF -> GlideApp.with(context) .asGif() .load(part.getUri()) - .into(holder.image) + .into(binding.image) else -> GlideApp.with(context) .asBitmap() .load(part.getUri()) - .into(holder.image) + .into(binding.image) } } VIEW_TYPE_VIDEO -> { + val binding = GalleryVideoPageBinding.bind(holder.itemView) val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(null) val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory) val exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector) - holder.video.player = exoPlayer + binding.video.player = exoPlayer exoPlayers.add(exoPlayer) val dataSourceFactory = DefaultDataSourceFactory(context, Util.getUserAgent(context, "QUIK")) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt index 8ed8e245..e5b4c95e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt @@ -57,6 +57,9 @@ import dev.octoshrimpy.quik.common.util.extensions.setTint import dev.octoshrimpy.quik.common.util.extensions.setVisible import dev.octoshrimpy.quik.common.widget.TextInputDialog import dev.octoshrimpy.quik.feature.blocking.BlockingDialog +import dev.octoshrimpy.quik.databinding.MainActivityBinding +import dev.octoshrimpy.quik.databinding.MainPermissionHintBinding +import dev.octoshrimpy.quik.databinding.MainSyncingBinding import dev.octoshrimpy.quik.feature.changelog.ChangelogDialog import dev.octoshrimpy.quik.feature.conversations.ConversationItemTouchCallback import dev.octoshrimpy.quik.feature.conversations.ConversationsAdapter @@ -66,10 +69,6 @@ import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.drawer_view.* -import kotlinx.android.synthetic.main.main_activity.* -import kotlinx.android.synthetic.main.main_permission_hint.* -import kotlinx.android.synthetic.main.main_syncing.* import javax.inject.Inject class MainActivity : QkThemedActivity(), MainView { @@ -83,32 +82,36 @@ class MainActivity : QkThemedActivity(), MainView { @Inject lateinit var itemTouchCallback: ConversationItemTouchCallback @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + private lateinit var binding: MainActivityBinding + private lateinit var snackbarBinding: MainPermissionHintBinding + private lateinit var syncingBinding: MainSyncingBinding + override val onNewIntentIntent: Subject = PublishSubject.create() override val activityResumedIntent: Subject = PublishSubject.create() - override val queryChangedIntent by lazy { toolbarSearch.textChanges() } - override val composeIntent by lazy { compose.clicks() } + override val queryChangedIntent by lazy { binding.toolbarSearch.textChanges() } + override val composeIntent by lazy { binding.compose.clicks() } override val drawerToggledIntent: Observable by lazy { - drawerLayout.drawerOpen(Gravity.START) + binding.drawerLayout.drawerOpen(Gravity.START) } override val homeIntent: Subject = PublishSubject.create() override val navigationIntent: Observable by lazy { Observable.merge(listOf( backPressedSubject, - inbox.clicks().map { NavItem.INBOX }, - archived.clicks().map { NavItem.ARCHIVED }, - backup.clicks().map { NavItem.BACKUP }, - scheduled.clicks().map { NavItem.SCHEDULED }, - blocking.clicks().map { NavItem.BLOCKING }, - messageUtils.clicks().map { NavItem.MESSAGE_UTILS }, - settings.clicks().map { NavItem.SETTINGS }, + binding.drawer.inbox.clicks().map { NavItem.INBOX }, + binding.drawer.archived.clicks().map { NavItem.ARCHIVED }, + binding.drawer.backup.clicks().map { NavItem.BACKUP }, + binding.drawer.scheduled.clicks().map { NavItem.SCHEDULED }, + binding.drawer.blocking.clicks().map { NavItem.BLOCKING }, + binding.drawer.messageUtils.clicks().map { NavItem.MESSAGE_UTILS }, + binding.drawer.settings.clicks().map { NavItem.SETTINGS }, // plus.clicks().map { NavItem.PLUS }, // help.clicks().map { NavItem.HELP }, - invite.clicks().map { NavItem.INVITE })) + binding.drawer.invite.clicks().map { NavItem.INVITE })) } override val optionsItemIntent: Subject = PublishSubject.create() // override val plusBannerIntent by lazy { plusBanner.clicks() } - override val dismissRatingIntent by lazy { rateDismiss.clicks() } - override val rateIntent by lazy { rateOkay.clicks() } + override val dismissRatingIntent by lazy { binding.drawer.rateDismiss.clicks() } + override val rateIntent by lazy { binding.drawer.rateOkay.clicks() } override val conversationsSelectedIntent by lazy { conversationsAdapter.selectionChanges } override val confirmDeleteIntent: Subject> = PublishSubject.create() override val renameConversationIntent: Subject = PublishSubject.create() @@ -123,52 +126,49 @@ class MainActivity : QkThemedActivity(), MainView { private val toggle by lazy { ActionBarDrawerToggle( this, - drawerLayout, - toolbar, + binding.drawerLayout, + binding.toolbar, R.string.main_drawer_open_cd, 0 ) } private val itemTouchHelper by lazy { ItemTouchHelper(itemTouchCallback) } private val progressAnimator by lazy { - ObjectAnimator.ofInt(syncingProgress, "progress", 0, 0) + ObjectAnimator.ofInt(syncingBinding.syncingProgress, "progress", 0, 0) } private val changelogDialog by lazy { ChangelogDialog(this) } - private val snackbar by lazy { findViewById(R.id.snackbar) } - private val syncing by lazy { findViewById(R.id.syncing) } private val backPressedSubject: Subject = PublishSubject.create() override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.main_activity) + binding = MainActivityBinding.inflate(layoutInflater) + setContentView(binding.root) viewModel.bindView(this) onNewIntentIntent.onNext(intent) - (snackbar as? ViewStub)?.setOnInflateListener { _, _ -> - snackbarButton.clicks() - .autoDisposable(scope(Lifecycle.Event.ON_DESTROY)) - .subscribe(snackbarButtonIntent) + snackbarBinding = MainPermissionHintBinding.bind(binding.snackbar.inflate()).also { + it.snackbarButton.clicks() + .autoDisposable(scope(Lifecycle.Event.ON_DESTROY)) + .subscribe(snackbarButtonIntent) } - (syncing as? ViewStub)?.setOnInflateListener { _, _ -> - syncingProgress?.let { - it.progressTintList = ColorStateList.valueOf(theme.blockingFirst().theme) - it.indeterminateTintList = ColorStateList.valueOf(theme.blockingFirst().theme) - } + syncingBinding = MainSyncingBinding.bind(binding.syncing.inflate()).also { + it.syncingProgress.progressTintList = ColorStateList.valueOf(theme.blockingFirst().theme) + it.syncingProgress.indeterminateTintList = ColorStateList.valueOf(theme.blockingFirst().theme) } toggle.syncState() - toolbar.setNavigationOnClickListener { + binding.toolbar.setNavigationOnClickListener { dismissKeyboard() homeIntent.onNext(Unit) } itemTouchCallback.adapter = conversationsAdapter - conversationsAdapter.autoScrollToStart(recyclerView) + conversationsAdapter.autoScrollToStart(binding.recyclerView) // Don't allow clicks to pass through the drawer layout - drawer.clicks().autoDisposable(scope()).subscribe() + binding.drawer.root.clicks().autoDisposable(scope()).subscribe() // Set the theme color tint to the recyclerView, progressbar, and FAB theme @@ -183,23 +183,23 @@ class MainActivity : QkThemedActivity(), MainView { resolveThemeColor(android.R.attr.textColorSecondary) )) .let { tintList -> - inboxIcon.imageTintList = tintList - archivedIcon.imageTintList = tintList + binding.drawer.inboxIcon.imageTintList = tintList + binding.drawer.archivedIcon.imageTintList = tintList } // Miscellaneous views - listOf(plusBadge1, plusBadge2).forEach { badge -> + listOf(binding.drawer.plusBadge1, binding.drawer.plusBadge2).forEach { badge -> badge.setBackgroundTint(theme.theme) badge.setTextColor(theme.textPrimary) } - syncingProgress?.progressTintList = ColorStateList.valueOf(theme.theme) - syncingProgress?.indeterminateTintList = ColorStateList.valueOf(theme.theme) - plusIcon.setTint(theme.theme) - rateIcon.setTint(theme.theme) - compose.setBackgroundTint(theme.theme) + syncingBinding.syncingProgress.progressTintList = ColorStateList.valueOf(theme.theme) + syncingBinding.syncingProgress.indeterminateTintList = ColorStateList.valueOf(theme.theme) + binding.drawer.plusIcon.setTint(theme.theme) + binding.drawer.rateIcon.setTint(theme.theme) + binding.compose.setBackgroundTint(theme.theme) // Set the FAB compose icon color - compose.setTint(theme.textPrimary) + binding.compose.setTint(theme.textPrimary) } } @@ -239,13 +239,13 @@ class MainActivity : QkThemedActivity(), MainView { else -> 0 } - toolbarSearch.setVisible(state.page is Inbox && + binding.toolbarSearch.setVisible(state.page is Inbox && state.page.selected == 0 || state.page is Searching ) - toolbarTitle.setVisible(toolbarSearch.visibility != View.VISIBLE) + binding.toolbarTitle.setVisible(binding.toolbarSearch.visibility != View.VISIBLE) - toolbar.menu.apply { + binding.toolbar.menu.apply { findItem(R.id.select_all)?.isVisible = (conversationsAdapter.itemCount > 1) && selectedConversations != 0 findItem(R.id.archive)?.isVisible = @@ -264,36 +264,36 @@ class MainActivity : QkThemedActivity(), MainView { findItem(R.id.rename)?.isVisible = selectedConversations == 1 } - listOf(plusBadge1, plusBadge2).forEach { badge -> + listOf(binding.drawer.plusBadge1, binding.drawer.plusBadge2).forEach { badge -> badge.isVisible = drawerBadgesExperiment.variant && !state.upgraded } // plus.isVisible = state.upgraded - plusBanner.isVisible = !state.upgraded - rateLayout.setVisible(state.showRating) + binding.drawer.plusBanner.isVisible = !state.upgraded + binding.drawer.rateLayout.setVisible(state.showRating) - compose.setVisible(state.page is Inbox || state.page is Archived) - conversationsAdapter.emptyView = empty.takeIf { + binding.compose.setVisible(state.page is Inbox || state.page is Archived) + conversationsAdapter.emptyView = binding.empty.takeIf { state.page is Inbox || state.page is Archived } - searchAdapter.emptyView = empty.takeIf { state.page is Searching } + searchAdapter.emptyView = binding.empty.takeIf { state.page is Searching } when (state.page) { is Inbox -> { showBackButton(state.page.selected > 0) title = getString(R.string.main_title_selected, state.page.selected) - if (recyclerView.adapter !== conversationsAdapter) - recyclerView.adapter = conversationsAdapter + if (binding.recyclerView.adapter !== conversationsAdapter) + binding.recyclerView.adapter = conversationsAdapter conversationsAdapter.updateData(state.page.data) - itemTouchHelper.attachToRecyclerView(recyclerView) - empty.setText(R.string.inbox_empty_text) + itemTouchHelper.attachToRecyclerView(binding.recyclerView) + binding.empty.setText(R.string.inbox_empty_text) } is Searching -> { showBackButton(true) - if (recyclerView.adapter !== searchAdapter) recyclerView.adapter = searchAdapter + if (binding.recyclerView.adapter !== searchAdapter) binding.recyclerView.adapter = searchAdapter searchAdapter.data = state.page.data ?: listOf() itemTouchHelper.attachToRecyclerView(null) - empty.setText(R.string.inbox_search_empty_text) + binding.empty.setText(R.string.inbox_search_empty_text) } is Archived -> { @@ -302,78 +302,78 @@ class MainActivity : QkThemedActivity(), MainView { true -> getString(R.string.main_title_selected, state.page.selected) false -> getString(R.string.title_archived) } - if (recyclerView.adapter !== conversationsAdapter) - recyclerView.adapter = conversationsAdapter + if (binding.recyclerView.adapter !== conversationsAdapter) + binding.recyclerView.adapter = conversationsAdapter conversationsAdapter.updateData(state.page.data) itemTouchHelper.attachToRecyclerView(null) - empty.setText(R.string.archived_empty_text) + binding.empty.setText(R.string.archived_empty_text) } else -> {} } - inbox.isActivated = state.page is Inbox - archived.isActivated = state.page is Archived + binding.drawer.inbox.isActivated = state.page is Inbox + binding.drawer.archived.isActivated = state.page is Archived - if (drawerLayout.isDrawerOpen(GravityCompat.START) && !state.drawerOpen) - drawerLayout.closeDrawer(GravityCompat.START) - else if (!drawerLayout.isDrawerVisible(GravityCompat.START) && state.drawerOpen) - drawerLayout.openDrawer(GravityCompat.START) + if (binding.drawerLayout.isDrawerOpen(GravityCompat.START) && !state.drawerOpen) + binding.drawerLayout.closeDrawer(GravityCompat.START) + else if (!binding.drawerLayout.isDrawerVisible(GravityCompat.START) && state.drawerOpen) + binding.drawerLayout.openDrawer(GravityCompat.START) when (state.syncing) { is SyncRepository.SyncProgress.Idle -> { - syncing.isVisible = false - snackbar.isVisible = (!state.defaultSms || + syncingBinding.root.isVisible = false + snackbarBinding.root.isVisible = (!state.defaultSms || !state.smsPermission || !state.contactPermission || !state.notificationPermission) } is SyncRepository.SyncProgress.Running -> { - syncing.isVisible = true - syncingProgress.max = state.syncing.max + syncingBinding.root.isVisible = true + syncingBinding.syncingProgress.max = state.syncing.max progressAnimator.apply { - setIntValues(syncingProgress.progress, state.syncing.progress) + setIntValues(syncingBinding.syncingProgress.progress, state.syncing.progress) }.start() - syncingProgress.isIndeterminate = state.syncing.indeterminate - snackbar.isVisible = false + syncingBinding.syncingProgress.isIndeterminate = state.syncing.indeterminate + snackbarBinding.root.isVisible = false } is SyncRepository.SyncProgress.ParsingEmojis -> { - syncing.isVisible = true - syncingLabel.setText(getString(R.string.main_sync_emojis)) - syncingProgress.max = state.syncing.max + syncingBinding.root.isVisible = true + syncingBinding.syncingLabel.setText(getString(R.string.main_sync_emojis)) + syncingBinding.syncingProgress.max = state.syncing.max progressAnimator.apply { - setIntValues(syncingProgress.progress, state.syncing.progress) + setIntValues(syncingBinding.syncingProgress.progress, state.syncing.progress) }.start() - syncingProgress.isIndeterminate = state.syncing.indeterminate - snackbar.isVisible = false + syncingBinding.syncingProgress.isIndeterminate = state.syncing.indeterminate + snackbarBinding.root.isVisible = false } } when { !state.defaultSms -> { - snackbarTitle?.setText(R.string.main_default_sms_title) - snackbarMessage?.setText(R.string.main_default_sms_message) - snackbarButton?.setText(R.string.main_default_sms_change) + snackbarBinding.snackbarTitle.setText(R.string.main_default_sms_title) + snackbarBinding.snackbarMessage.setText(R.string.main_default_sms_message) + snackbarBinding.snackbarButton.setText(R.string.main_default_sms_change) } !state.smsPermission -> { - snackbarTitle?.setText(R.string.main_permission_required) - snackbarMessage?.setText(R.string.main_permission_sms) - snackbarButton?.setText(R.string.main_permission_allow) + snackbarBinding.snackbarTitle.setText(R.string.main_permission_required) + snackbarBinding.snackbarMessage.setText(R.string.main_permission_sms) + snackbarBinding.snackbarButton.setText(R.string.main_permission_allow) } !state.contactPermission -> { - snackbarTitle?.setText(R.string.main_permission_required) - snackbarMessage?.setText(R.string.main_permission_contacts) - snackbarButton?.setText(R.string.main_permission_allow) + snackbarBinding.snackbarTitle.setText(R.string.main_permission_required) + snackbarBinding.snackbarMessage.setText(R.string.main_permission_contacts) + snackbarBinding.snackbarButton.setText(R.string.main_permission_allow) } !state.notificationPermission -> { - snackbarTitle?.setText(R.string.main_permission_required) - snackbarMessage?.setText(R.string.main_permission_notifications) - snackbarButton?.setText(R.string.main_permission_allow) + snackbarBinding.snackbarTitle.setText(R.string.main_permission_required) + snackbarBinding.snackbarMessage.setText(R.string.main_permission_notifications) + snackbarBinding.snackbarButton.setText(R.string.main_permission_allow) } } } @@ -389,7 +389,7 @@ class MainActivity : QkThemedActivity(), MainView { override fun showBackButton(show: Boolean) = toggle.let { - it.onDrawerSlide(drawer, if (show) 1f else 0f) + it.onDrawerSlide(binding.drawer.root, if (show) 1f else 0f) it.drawerArrowDrawable.color = when (show) { true -> resolveThemeColor(android.R.attr.textColorSecondary) false -> resolveThemeColor(android.R.attr.textColorPrimary) @@ -414,14 +414,14 @@ class MainActivity : QkThemedActivity(), MainView { override fun clearSearch() { dismissKeyboard() - toolbarSearch.text = null + binding.toolbarSearch.text = null } override fun clearSelection() = conversationsAdapter.clearSelection() override fun toggleSelectAll() = conversationsAdapter.toggleSelectAll() - override fun themeChanged() = recyclerView.scrapViews() + override fun themeChanged() = binding.recyclerView.scrapViews() override fun showBlockingDialog(conversations: List, block: Boolean) { blockingDialog.show(this, conversations, block) @@ -456,7 +456,7 @@ class MainActivity : QkThemedActivity(), MainView { override fun showArchivedSnackbar(countConversationsArchived: Int, isArchiving: Boolean) = Snackbar.make( - drawerLayout, + binding.drawerLayout, if (isArchiving) { resources.getQuantityString(R.plurals.toast_archived, countConversationsArchived, countConversationsArchived) } else { @@ -484,9 +484,9 @@ class MainActivity : QkThemedActivity(), MainView { override fun drawerToggled(opened: Boolean) { if (opened) { dismissKeyboard() - if (!inbox.isInTouchMode) - inbox.requestFocus() + if (!binding.drawer.inbox.isInTouchMode) + binding.drawer.inbox.requestFocus() } else - toolbarSearch.requestFocus() + binding.toolbarSearch.requestFocus() } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/messageutils/MessageUtilsController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/messageutils/MessageUtilsController.kt index 32b275fb..e99ec1fe 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/messageutils/MessageUtilsController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/messageutils/MessageUtilsController.kt @@ -16,26 +16,25 @@ import dev.octoshrimpy.quik.injection.appComponent import dev.octoshrimpy.quik.repository.MessageRepository import io.reactivex.Single import javax.inject.Inject -class MessageUtilsController : QkController(), MessageUtilsView { + +class MessageUtilsController : QkController(), MessageUtilsView { + + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): MessageUtilsControllerBinding = + MessageUtilsControllerBinding.inflate(inflater, container, false) + @Inject override lateinit var presenter: MessageUtilsPresenter - private var binding: MessageUtilsControllerBinding? = null - override val autoDeduplicateClickIntent by lazy { binding!!.autoDeduplicate.clicks() } - override val deduplicateClickIntent by lazy { binding!!.deduplicateMessages.clicks() } + override val autoDeduplicateClickIntent by lazy { binding.autoDeduplicate.clicks() } + override val deduplicateClickIntent by lazy { binding.deduplicateMessages.clicks() } init { appComponent.inject(this) retainViewMode = RetainViewMode.RETAIN_DETACH } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = MessageUtilsControllerBinding.inflate(inflater, container, false) - return binding!!.root - } - override fun onViewCreated() { super.onViewCreated() - (binding?.root as? ViewGroup)?.postDelayed({ - binding?.parent?.animateLayoutChanges = true + binding.root.postDelayed({ + binding.parent.animateLayoutChanges = true }, 100) } @@ -46,17 +45,12 @@ class MessageUtilsController : QkController(R.id.checkbox) + binding.autoDeduplicate + .findViewById(R.id.checkbox) ?.isChecked = state.autoDeduplicateMessages - val deduplicationProgress = binding!!.deduplicationProgress + val deduplicationProgress = binding.deduplicationProgress val progressAnimator = ObjectAnimator.ofInt(deduplicationProgress, "progress", 0, 0) when (state.deduplicationProgress) { @@ -92,7 +86,7 @@ class MessageUtilsController : QkController= Build.VERSION_CODES.O - notificationsO.setVisible(hasOreo) - notifications.setVisible(!hasOreo) - vibration.setVisible(!hasOreo) - ringtone.setVisible(!hasOreo) + binding.notificationsO.setVisible(hasOreo) + binding.notifications.setVisible(!hasOreo) + binding.vibration.setVisible(!hasOreo) + binding.ringtone.setVisible(!hasOreo) previewModeDialog.setTitle(R.string.settings_notification_previews_title) previewModeDialog.adapter.setData(R.array.notification_preview_options) actionsDialog.adapter.setData(R.array.notification_actions) // Listen to clicks for all of the preferences - (0 until preferences.childCount) - .map { index -> preferences.getChildAt(index) } + (0 until binding.preferences.childCount) + .map { index -> binding.preferences.getChildAt(index) } .mapNotNull { view -> view as? PreferenceView } .map { preference -> preference.clicks().map { preference } } .let { Observable.merge(it) } @@ -95,31 +98,31 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { title = state.conversationTitle } - notifications.checkbox.isChecked = state.notificationsEnabled - previews.summary = state.previewSummary + binding.notifications.findViewById(R.id.checkbox)?.isChecked = state.notificationsEnabled + binding.previews.summary = state.previewSummary previewModeDialog.adapter.selectedItem = state.previewId - wake.checkbox.isChecked = state.wakeEnabled - silentNotContact.checkbox.isChecked = state.silentNotContact - silentNotContact.isVisible = state.threadId == 0L - vibration.checkbox.isChecked = state.vibrationEnabled - ringtone.summary = state.ringtoneName + binding.wake.findViewById(R.id.checkbox)?.isChecked = state.wakeEnabled + binding.silentNotContact.findViewById(R.id.checkbox)?.isChecked = state.silentNotContact + binding.silentNotContact.isVisible = state.threadId == 0L + binding.vibration.findViewById(R.id.checkbox)?.isChecked = state.vibrationEnabled + binding.ringtone.summary = state.ringtoneName - actionsDivider.isVisible = state.threadId == 0L - actionsTitle.isVisible = state.threadId == 0L - action1.isVisible = state.threadId == 0L - action1.summary = state.action1Summary - action2.isVisible = state.threadId == 0L - action2.summary = state.action2Summary - action3.isVisible = state.threadId == 0L - action3.summary = state.action3Summary + binding.actionsDivider.isVisible = state.threadId == 0L + binding.actionsTitle.isVisible = state.threadId == 0L + binding.action1.isVisible = state.threadId == 0L + binding.action1.summary = state.action1Summary + binding.action2.isVisible = state.threadId == 0L + binding.action2.summary = state.action2Summary + binding.action3.isVisible = state.threadId == 0L + binding.action3.summary = state.action3Summary - qkreplyDivider.isVisible = state.threadId == 0L - qkreplyTitle.isVisible = state.threadId == 0L - qkreply.checkbox.isChecked = state.qkReplyEnabled - qkreply.isVisible = state.threadId == 0L - qkreplyTapDismiss.isVisible = state.threadId == 0L - qkreplyTapDismiss.isEnabled = state.qkReplyEnabled - qkreplyTapDismiss.checkbox.isChecked = state.qkReplyTapDismiss + binding.qkreplyDivider.isVisible = state.threadId == 0L + binding.qkreplyTitle.isVisible = state.threadId == 0L + binding.qkreply.findViewById(R.id.checkbox)?.isChecked = state.qkReplyEnabled + binding.qkreply.isVisible = state.threadId == 0L + binding.qkreplyTapDismiss.isVisible = state.threadId == 0L + binding.qkreplyTapDismiss.isEnabled = state.qkReplyEnabled + binding.qkreplyTapDismiss.findViewById(R.id.checkbox)?.isChecked = state.qkReplyTapDismiss } override fun showPreviewModeDialog() = previewModeDialog.show(this) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivity.kt index 0a606f49..5ed78709 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivity.kt @@ -83,7 +83,7 @@ class PlusActivity : QkThemedActivity(), PlusView { // Make the list titles bold binding.linearLayout.children .mapNotNull { it as? PreferenceView } - .map { it.titleTextView } + .map { it.titleView } .forEach { it.setTypeface(it.typeface, Typeface.BOLD) } val textPrimary = resolveThemeColor(android.R.attr.textColorPrimary) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyActivity.kt index 9362fc76..fb6ca8bc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyActivity.kt @@ -45,22 +45,23 @@ import dev.octoshrimpy.quik.common.util.extensions.autoScrollToStart import dev.octoshrimpy.quik.common.util.extensions.setVisible import dev.octoshrimpy.quik.common.util.extensions.showKeyboard import dev.octoshrimpy.quik.common.widget.QkEditText +import dev.octoshrimpy.quik.databinding.QkreplyActivityBinding import dev.octoshrimpy.quik.feature.compose.MessagesAdapter import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.compose_activity.message -import kotlinx.android.synthetic.main.qkreply_activity.* import javax.inject.Inject class QkReplyActivity : QkThemedActivity(), QkReplyView { + private lateinit var binding: QkreplyActivityBinding + @Inject lateinit var adapter: MessagesAdapter @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override val menuItemIntent: Subject = PublishSubject.create() - override val textChangedIntent by lazy { message.textChanges() } - override val changeSimIntent by lazy { sim.clicks() } - override val sendIntent by lazy { send.clicks() } + override val textChangedIntent by lazy { binding.message.textChanges() } + override val changeSimIntent by lazy { binding.sim.clicks() } + override val sendIntent by lazy { binding.send.clicks() } private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[QkReplyViewModel::class.java] } @@ -90,21 +91,22 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { super.onCreate(savedInstanceState) setFinishOnTouchOutside(prefs.qkreplyTapDismiss.get()) - setContentView(R.layout.qkreply_activity) + binding = QkreplyActivityBinding.inflate(layoutInflater) + setContentView(binding.root) window.setBackgroundDrawable(null) window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) viewModel.bindView(this) - toolbar.clipToOutline = true + binding.toolbar.clipToOutline = true - messages.adapter = adapter - messages.adapter?.autoScrollToStart(messages) - messages.adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onChanged() = messages.scrollToPosition(adapter.itemCount - 1) + binding.messages.adapter = adapter + binding.messages.adapter?.autoScrollToStart(binding.messages) + binding.messages.adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onChanged() = binding.messages.scrollToPosition(adapter.itemCount - 1) }) - message.setOnTouchListener(object : OnTouchListener { + binding.message.setOnTouchListener(object : OnTouchListener { private val gestureDetector = GestureDetector(this@QkReplyActivity, object : SimpleOnGestureListener() { override fun onDoubleTap(e: MotionEvent): Boolean { @@ -119,7 +121,7 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { } override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - message.showKeyboard() + binding.message.showKeyboard() return true } @@ -143,24 +145,24 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { title = state.title - toolbar.menu.findItem(R.id.expand)?.isVisible = !state.expanded - toolbar.menu.findItem(R.id.collapse)?.isVisible = state.expanded + binding.toolbar.menu.findItem(R.id.expand)?.isVisible = !state.expanded + binding.toolbar.menu.findItem(R.id.collapse)?.isVisible = state.expanded adapter.data = state.data - counter.text = state.remaining - counter.setVisible(counter.text.isNotBlank()) + binding.counter.text = state.remaining + binding.counter.setVisible(binding.counter.text.isNotBlank()) - sim.setVisible(state.subscription != null) - sim.contentDescription = getString(R.string.compose_sim_cd, state.subscription?.displayName) - simIndex.text = "${state.subscription?.simSlotIndex?.plus(1)}" + binding.sim.setVisible(state.subscription != null) + binding.sim.contentDescription = getString(R.string.compose_sim_cd, state.subscription?.displayName) + binding.simIndex.text = "${state.subscription?.simSlotIndex?.plus(1)}" - send.isEnabled = state.canSend - send.imageAlpha = if (state.canSend) 255 else 128 + binding.send.isEnabled = state.canSend + binding.send.imageAlpha = if (state.canSend) 255 else 128 } override fun setDraft(draft: String) { - message.setText(draft) + binding.message.setText(draft) } override fun onCreateOptionsMenu(menu: Menu?): Boolean { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledActivity.kt index 10b90752..ce8aa19d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledActivity.kt @@ -31,20 +31,21 @@ import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkThemedActivity import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setTint +import dev.octoshrimpy.quik.databinding.ScheduledActivityBinding import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.main_activity.toolbar -import kotlinx.android.synthetic.main.scheduled_activity.* import javax.inject.Inject class ScheduledActivity : QkThemedActivity(), ScheduledView { + private lateinit var binding: ScheduledActivityBinding + @Inject lateinit var scheduledMessageAdapter: ScheduledMessageAdapter @Inject lateinit var viewModelFactory: ViewModelProvider.Factory - override val composeIntent by lazy { compose.clicks() } - override val upgradeIntent by lazy { upgrade.clicks() } + override val composeIntent by lazy { binding.compose.clicks() } + override val upgradeIntent by lazy { binding.upgrade.clicks() } override val messagesSelectedIntent by lazy { scheduledMessageAdapter.selectionChanges } override val optionsItemIntent: Subject = PublishSubject.create() override val deleteScheduledMessages: Subject> = PublishSubject.create() @@ -59,22 +60,23 @@ class ScheduledActivity : QkThemedActivity(), ScheduledView { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.scheduled_activity) + binding = ScheduledActivityBinding.inflate(layoutInflater) + setContentView(binding.root) setTitle(R.string.scheduled_title) showBackButton(true) viewModel.bindView(this) - scheduledMessageAdapter.emptyView = empty - messages.adapter = scheduledMessageAdapter + scheduledMessageAdapter.emptyView = binding.empty + binding.messages.adapter = scheduledMessageAdapter colors.theme().let { theme -> - sampleMessage.setBackgroundTint(theme.theme) - sampleMessage.setTextColor(theme.textPrimary) - compose.setTint(theme.textPrimary) - compose.setBackgroundTint(theme.theme) - upgrade.setBackgroundTint(theme.theme) - upgradeIcon.setTint(theme.textPrimary) - upgradeLabel.setTextColor(theme.textPrimary) + binding.sampleMessage.setBackgroundTint(theme.theme) + binding.sampleMessage.setTextColor(theme.textPrimary) + binding.compose.setTint(theme.textPrimary) + binding.compose.setBackgroundTint(theme.theme) + binding.upgrade.setBackgroundTint(theme.theme) + binding.upgradeIcon.setTint(theme.textPrimary) + binding.upgradeLabel.setTextColor(theme.textPrimary) } } @@ -88,20 +90,20 @@ class ScheduledActivity : QkThemedActivity(), ScheduledView { }) // show/hide menu items - toolbar.menu.findItem(R.id.select_all)?.isVisible = + toolbar?.menu?.findItem(R.id.select_all)?.isVisible = ((scheduledMessageAdapter.itemCount > 1) && (state.selectedMessages != 0)) - toolbar.menu.findItem(R.id.delete)?.isVisible = + toolbar?.menu?.findItem(R.id.delete)?.isVisible = ((scheduledMessageAdapter.itemCount != 0) && (state.selectedMessages != 0)) - toolbar.menu.findItem(R.id.copy)?.isVisible = + toolbar?.menu?.findItem(R.id.copy)?.isVisible = ((scheduledMessageAdapter.itemCount != 0) && (state.selectedMessages != 0)) - toolbar.menu.findItem(R.id.send_now)?.isVisible = + toolbar?.menu?.findItem(R.id.send_now)?.isVisible = ((scheduledMessageAdapter.itemCount != 0) && (state.selectedMessages != 0)) - toolbar.menu.findItem(R.id.edit_message)?.isVisible = + toolbar?.menu?.findItem(R.id.edit_message)?.isVisible = ((scheduledMessageAdapter.itemCount != 0) && (state.selectedMessages == 1)) // show compose button - compose.isVisible = state.upgraded && (state.conversationId == null) - upgrade.isVisible = !state.upgraded + binding.compose.isVisible = state.upgraded && (state.conversationId == null) + binding.upgrade.isVisible = !state.upgraded } override fun onBackPressed() = backPressedIntent.onNext(Unit) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsActivity.kt index c41ce9d6..afd8548e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsActivity.kt @@ -23,20 +23,21 @@ import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction import dagger.android.AndroidInjection -import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkThemedActivity -import kotlinx.android.synthetic.main.container_activity.* +import dev.octoshrimpy.quik.databinding.ContainerActivityBinding class SettingsActivity : QkThemedActivity() { + private lateinit var binding: ContainerActivityBinding private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.container_activity) + binding = ContainerActivityBinding.inflate(layoutInflater) + setContentView(binding.root) - router = Conductor.attachRouter(this, container, savedInstanceState) + router = Conductor.attachRouter(this, binding.container, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(SettingsController())) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt index d2b5bf9d..cff5d557 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt @@ -23,7 +23,9 @@ import android.app.TimePickerDialog import android.content.Context import android.os.Build import android.text.format.DateFormat +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.bluelinelabs.conductor.RouterTransaction @@ -43,7 +45,9 @@ import dev.octoshrimpy.quik.common.util.extensions.animateLayoutChanges import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setVisible import dev.octoshrimpy.quik.common.widget.PreferenceView +import dev.octoshrimpy.quik.common.widget.QkSwitch import dev.octoshrimpy.quik.common.widget.TextInputDialog +import dev.octoshrimpy.quik.databinding.SettingsControllerBinding import dev.octoshrimpy.quik.feature.settings.about.AboutController import dev.octoshrimpy.quik.feature.settings.autodelete.AutoDeleteDialog import dev.octoshrimpy.quik.feature.settings.swipe.SwipeActionsController @@ -54,17 +58,16 @@ import dev.octoshrimpy.quik.util.Preferences import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.settings_controller.* -import kotlinx.android.synthetic.main.settings_controller.view.* -import kotlinx.android.synthetic.main.settings_switch_widget.view.* -import kotlinx.android.synthetic.main.settings_theme_widget.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.coroutines.resume -class SettingsController : QkController(), SettingsView { +class SettingsController : QkController(), SettingsView { + + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): SettingsControllerBinding = + SettingsControllerBinding.inflate(inflater, container, false) @Inject lateinit var context: Context @Inject lateinit var colors: Colors @@ -89,12 +92,11 @@ class SettingsController : QkController = PublishSubject.create() private val autoDeleteSubject: Subject = PublishSubject.create() - private val progressAnimator by lazy { ObjectAnimator.ofInt(syncingProgress, "progress", 0, 0) } + private val progressAnimator by lazy { ObjectAnimator.ofInt(binding.syncingProgress, "progress", 0, 0) } init { appComponent.inject(this) retainViewMode = RetainViewMode.RETAIN_DETACH - layoutRes = R.layout.settings_controller colors.themeObservable() .autoDisposable(scope()) @@ -102,7 +104,7 @@ class SettingsController : QkController= 29) { true -> nightModeDialog.adapter.setData(R.array.night_modes) @@ -115,7 +117,7 @@ class SettingsController : QkController = (0 until preferences.childCount) - .map { index -> preferences.getChildAt(index) } + override fun preferenceClicks(): Observable = (0 until binding.preferences.childCount) + .map { index -> binding.preferences.getChildAt(index) } .mapNotNull { view -> view as? PreferenceView } .map { preference -> preference.clicks().map { preference } } .let { preferences -> Observable.merge(preferences) } - override fun aboutLongClicks(): Observable<*> = about.longClicks() + override fun aboutLongClicks(): Observable<*> = binding.about.longClicks() override fun viewQksmsPlusClicks(): Observable<*> = viewQksmsPlusSubject @@ -154,79 +156,79 @@ class SettingsController : QkController = messageLinkHandlingDialog.adapter.menuItemClicks override fun render(state: SettingsState) { - themePreview.setBackgroundTint(state.theme) - night.summary = state.nightModeSummary + binding.theme.findViewById(R.id.themePreview)?.setBackgroundTint(state.theme) + binding.night.summary = state.nightModeSummary nightModeDialog.adapter.selectedItem = state.nightModeId - nightStart.setVisible(state.nightModeId == Preferences.NIGHT_MODE_AUTO) - nightStart.summary = state.nightStart - nightEnd.setVisible(state.nightModeId == Preferences.NIGHT_MODE_AUTO) - nightEnd.summary = state.nightEnd + binding.nightStart.setVisible(state.nightModeId == Preferences.NIGHT_MODE_AUTO) + binding.nightStart.summary = state.nightStart + binding.nightEnd.setVisible(state.nightModeId == Preferences.NIGHT_MODE_AUTO) + binding.nightEnd.summary = state.nightEnd - black.setVisible(state.nightModeId != Preferences.NIGHT_MODE_OFF) - black.checkbox.isChecked = state.black + binding.black.setVisible(state.nightModeId != Preferences.NIGHT_MODE_OFF) + binding.black.findViewById(R.id.checkbox)?.isChecked = state.black - autoEmoji.checkbox.isChecked = state.autoEmojiEnabled + binding.autoEmoji.findViewById(R.id.checkbox)?.isChecked = state.autoEmojiEnabled - delayed.summary = state.sendDelaySummary + binding.delayed.summary = state.sendDelaySummary sendDelayDialog.adapter.selectedItem = state.sendDelayId - delivery.checkbox.isChecked = state.deliveryEnabled + binding.delivery.findViewById(R.id.checkbox)?.isChecked = state.deliveryEnabled - unreadAtTop.checkbox.isChecked = state.unreadAtTopEnabled + binding.unreadAtTop.findViewById(R.id.checkbox)?.isChecked = state.unreadAtTopEnabled - signature.summary = state.signature.takeIf { it.isNotBlank() } + binding.signature.summary = state.signature.takeIf { it.isNotBlank() } ?: context.getString(R.string.settings_signature_summary) - textSize.summary = state.textSizeSummary + binding.textSize.summary = state.textSizeSummary textSizeDialog.adapter.selectedItem = state.textSizeId - autoColor.checkbox.isChecked = state.autoColor + binding.autoColor.findViewById(R.id.checkbox)?.isChecked = state.autoColor - systemFont.checkbox.isChecked = state.systemFontEnabled + binding.systemFont.findViewById(R.id.checkbox)?.isChecked = state.systemFontEnabled - showStt.checkbox.isChecked = state.showStt + binding.showStt.findViewById(R.id.checkbox)?.isChecked = state.showStt - unicode.checkbox.isChecked = state.stripUnicodeEnabled - mobileOnly.checkbox.isChecked = state.mobileOnly + binding.unicode.findViewById(R.id.checkbox)?.isChecked = state.stripUnicodeEnabled + binding.mobileOnly.findViewById(R.id.checkbox)?.isChecked = state.mobileOnly - autoDelete.summary = when (state.autoDelete) { + binding.autoDelete.summary = when (state.autoDelete) { 0 -> context.getString(R.string.settings_auto_delete_never) else -> context.resources.getQuantityString( R.plurals.settings_auto_delete_summary, state.autoDelete, state.autoDelete) } - longAsMms.checkbox.isChecked = state.longAsMms + binding.longAsMms.findViewById(R.id.checkbox)?.isChecked = state.longAsMms - mmsSize.summary = state.maxMmsSizeSummary + binding.mmsSize.summary = state.maxMmsSizeSummary mmsSizeDialog.adapter.selectedItem = state.maxMmsSizeId - messsageLinkHandling.summary = state.messageLinkHandlingSummary + binding.messsageLinkHandling.summary = state.messageLinkHandlingSummary messageLinkHandlingDialog.adapter.selectedItem = state.messageLinkHandlingId - disableScreenshots.checkbox.isChecked = state.disableScreenshotsEnabled + binding.disableScreenshots.findViewById(R.id.checkbox)?.isChecked = state.disableScreenshotsEnabled when (state.syncProgress) { - is SyncRepository.SyncProgress.Idle -> syncingProgress.isVisible = false + is SyncRepository.SyncProgress.Idle -> binding.syncingProgress.isVisible = false is SyncRepository.SyncProgress.Running -> { - syncingProgress.isVisible = true - syncingProgress.max = state.syncProgress.max - progressAnimator.apply { setIntValues(syncingProgress.progress, state.syncProgress.progress) }.start() - syncingProgress.isIndeterminate = state.syncProgress.indeterminate + binding.syncingProgress.isVisible = true + binding.syncingProgress.max = state.syncProgress.max + progressAnimator.apply { setIntValues(binding.syncingProgress.progress, state.syncProgress.progress) }.start() + binding.syncingProgress.isIndeterminate = state.syncProgress.indeterminate } is SyncRepository.SyncProgress.ParsingEmojis -> { - syncingProgress.isVisible = true - syncingProgress.max = state.syncProgress.max - progressAnimator.apply { setIntValues(syncingProgress.progress, state.syncProgress.progress) }.start() - syncingProgress.isIndeterminate = state.syncProgress.indeterminate + binding.syncingProgress.isVisible = true + binding.syncingProgress.max = state.syncProgress.max + progressAnimator.apply { setIntValues(binding.syncingProgress.progress, state.syncProgress.progress) }.start() + binding.syncingProgress.isIndeterminate = state.syncProgress.indeterminate } } } override fun showQksmsPlusSnackbar() { view?.run { - Snackbar.make(contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { + Snackbar.make(binding.contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusSubject.onNext(Unit) } setActionTextColor(colors.theme().theme) show() @@ -291,4 +293,4 @@ class SettingsController : QkController(), AboutView { +class AboutController : QkController(), AboutView { + + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): AboutControllerBinding = + AboutControllerBinding.inflate(inflater, container, false) @Inject override lateinit var presenter: AboutPresenter init { appComponent.inject(this) - layoutRes = R.layout.about_controller } override fun onViewCreated() { - version.summary = BuildConfig.VERSION_NAME + binding.version.summary = BuildConfig.VERSION_NAME } override fun onAttach(view: View) { @@ -49,8 +53,8 @@ class AboutController : QkController(), AboutVi showBackButton(true) } - override fun preferenceClicks(): Observable = (0 until preferences.childCount) - .map { index -> preferences.getChildAt(index) } + override fun preferenceClicks(): Observable = (0 until binding.preferences.childCount) + .map { index -> binding.preferences.getChildAt(index) } .mapNotNull { view -> view as? PreferenceView } .map { preference -> preference.clicks().map { preference } } .let { preferences -> Observable.merge(preferences) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsController.kt index b8ac743d..4b8dc3fe 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsController.kt @@ -18,7 +18,9 @@ */ package dev.octoshrimpy.quik.feature.settings.swipe +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.core.view.isVisible import com.jakewharton.rxbinding2.view.clicks import com.uber.autodispose.android.lifecycle.scope @@ -30,14 +32,17 @@ import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.animateLayoutChanges import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setTint +import dev.octoshrimpy.quik.databinding.SwipeActionsControllerBinding import dev.octoshrimpy.quik.injection.appComponent import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.swipe_actions_controller.* import javax.inject.Inject -class SwipeActionsController : QkController(), SwipeActionsView { +class SwipeActionsController : QkController(), SwipeActionsView { + + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): SwipeActionsControllerBinding = + SwipeActionsControllerBinding.inflate(inflater, container, false) @Inject override lateinit var presenter: SwipeActionsPresenter @Inject lateinit var actionsDialog: QkDialog @@ -50,25 +55,24 @@ class SwipeActionsController : QkController - rightIcon.setBackgroundTint(theme.theme) - rightIcon.setTint(theme.textPrimary) - leftIcon.setBackgroundTint(theme.theme) - leftIcon.setTint(theme.textPrimary) + binding.rightIcon.setBackgroundTint(theme.theme) + binding.rightIcon.setTint(theme.textPrimary) + binding.leftIcon.setBackgroundTint(theme.theme) + binding.leftIcon.setTint(theme.textPrimary) } - right.postDelayed({ right?.animateLayoutChanges = true }, 100) - left.postDelayed({ left?.animateLayoutChanges = true }, 100) + binding.right.postDelayed({ binding.right.animateLayoutChanges = true }, 100) + binding.left.postDelayed({ binding.left.animateLayoutChanges = true }, 100) Observable.merge( - right.clicks().map { SwipeActionsView.Action.RIGHT }, - left.clicks().map { SwipeActionsView.Action.LEFT }) + binding.right.clicks().map { SwipeActionsView.Action.RIGHT }, + binding.left.clicks().map { SwipeActionsView.Action.LEFT }) .autoDisposable(scope()) .subscribe(actionClicks) } @@ -90,13 +94,13 @@ class SwipeActionsController : QkController + binding.saturation.setOnTouchListener { _, event -> setupBounds() when (event.action) { MotionEvent.ACTION_DOWN -> { @@ -68,8 +69,8 @@ class HSVPickerView @JvmOverloads constructor( MotionEvent.ACTION_MOVE -> { // Calculate the new x/y position - swatch.x = (event.rawX + swatchX + min).within(min, max) - swatch.y = (event.rawY + swatchY + min).within(min, max) + binding.swatch.x = (event.rawX + swatchX + min).within(min, max) + binding.swatch.y = (event.rawY + swatchY + min).within(min, max) updateSelectedColor() } @@ -85,7 +86,7 @@ class HSVPickerView @JvmOverloads constructor( var hueThumbX = 0f - hueGroup.setOnTouchListener { _, event -> + binding.hueGroup.setOnTouchListener { _, event -> setupBounds() when (event.action) { MotionEvent.ACTION_DOWN -> { @@ -96,8 +97,8 @@ class HSVPickerView @JvmOverloads constructor( MotionEvent.ACTION_MOVE -> { val x = (event.rawX + hueThumbX + min).within(min, max) - hueThumb.x = x - hue = (hueThumb.x - min) / (max - min) * 360 + binding.hueThumb.x = x + hue = (binding.hueThumb.x - min) / (max - min) * 360 updateSelectedColor() } @@ -111,14 +112,14 @@ class HSVPickerView @JvmOverloads constructor( true } - hueTrack.clipToOutline = true - hueTrack.setImageDrawable(GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, hues)) + binding.hueTrack.clipToOutline = true + binding.hueTrack.setImageDrawable(GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, hues)) } private fun setupBounds() { if (min == 0f || max == 0f) { - min = saturation.x - swatch.width / 2 - max = min + saturation.width + min = binding.saturation.x - binding.swatch.width / 2 + max = min + binding.saturation.width } } @@ -126,10 +127,10 @@ class HSVPickerView @JvmOverloads constructor( setupBounds() val range = max - min - val hsv = floatArrayOf(hue, (swatch.x - min) / range, 1 - (swatch.y - min) / range) + val hsv = floatArrayOf(hue, (binding.swatch.x - min) / range, 1 - (binding.swatch.y - min) / range) val color = Color.HSVToColor(hsv) - swatch.setTint(color) + binding.swatch.setTint(color) selectedColor.onNext(color) } @@ -145,9 +146,9 @@ class HSVPickerView @JvmOverloads constructor( setupBounds() val range = max - min - hueThumb.x = range * hsv[0] / 360 + min - swatch.x = range * hsv[1] + min - swatch.y = range * (1 - hsv[2]) + min + binding.hueThumb.x = range * hsv[0] / 360 + min + binding.swatch.x = range * hsv[1] + min + binding.swatch.y = range * (1 - hsv[2]) + min updateSelectedColor() } @@ -156,7 +157,7 @@ class HSVPickerView @JvmOverloads constructor( private fun updateHue() { val hsv = floatArrayOf(hue, 1f, 1f) val tint = Color.HSVToColor(hsv) - saturation.setBackgroundTint(tint) + binding.saturation.setBackgroundTint(tint) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemeAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemeAdapter.kt index 0d05e068..1c188e30 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemeAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemeAdapter.kt @@ -25,7 +25,6 @@ import android.view.ViewGroup import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayout -import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkAdapter import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.Colors @@ -33,11 +32,10 @@ import dev.octoshrimpy.quik.common.util.extensions.dpToPx import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setTint import dev.octoshrimpy.quik.common.util.extensions.setVisible +import dev.octoshrimpy.quik.databinding.ThemeListItemBinding +import dev.octoshrimpy.quik.databinding.ThemePaletteListItemBinding import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.theme_list_item.view.* -import kotlinx.android.synthetic.main.theme_palette_list_item.* -import kotlinx.android.synthetic.main.theme_palette_list_item.view.* import javax.inject.Inject class ThemeAdapter @Inject constructor( @@ -62,15 +60,16 @@ class ThemeAdapter @Inject constructor( private var iconTint = 0 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.theme_palette_list_item, parent, false) - view.palette.flexWrap = FlexWrap.WRAP - view.palette.flexDirection = FlexDirection.ROW + val binding = ThemePaletteListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + binding.palette.flexWrap = FlexWrap.WRAP + binding.palette.flexDirection = FlexDirection.ROW - return QkViewHolder(view) + return QkViewHolder(binding.root) } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val palette = getItem(position) + val binding = ThemePaletteListItemBinding.bind(holder.itemView) val screenWidth = Resources.getSystem().displayMetrics.widthPixels val minPadding = (16 * 6).dpToPx(context) @@ -81,33 +80,34 @@ class ThemeAdapter @Inject constructor( } val swatchPadding = (screenWidth - size * 5) / 12 - holder.palette.removeAllViews() - holder.palette.setPadding(swatchPadding, swatchPadding, swatchPadding, swatchPadding) + binding.palette.removeAllViews() + binding.palette.setPadding(swatchPadding, swatchPadding, swatchPadding, swatchPadding) (palette.subList(0, 5) + palette.subList(5, 10).reversed()) .mapIndexed { index, color -> - LayoutInflater.from(context).inflate(R.layout.theme_list_item, holder.palette, false).apply { + val itemBinding = ThemeListItemBinding.inflate(LayoutInflater.from(context), binding.palette, false) - // Send clicks to the selected subject - setOnClickListener { colorSelected.onNext(color) } + // Send clicks to the selected subject + itemBinding.root.setOnClickListener { colorSelected.onNext(color) } - // Apply the color to the view - theme.setBackgroundTint(color) + // Apply the color to the view + itemBinding.theme.setBackgroundTint(color) - // Control the check visibility and tint - check.setVisible(color == selectedColor) - check.setTint(iconTint) + // Control the check visibility and tint + itemBinding.check.setVisible(color == selectedColor) + itemBinding.check.setTint(iconTint) - // Update the size so that the spacing is perfectly even - layoutParams = (layoutParams as FlexboxLayout.LayoutParams).apply { - height = size - width = size - isWrapBefore = index % 5 == 0 - setMargins(swatchPadding, swatchPadding, swatchPadding, swatchPadding) - } + // Update the size so that the spacing is perfectly even + itemBinding.root.layoutParams = (itemBinding.root.layoutParams as FlexboxLayout.LayoutParams).apply { + height = size + width = size + isWrapBefore = index % 5 == 0 + setMargins(swatchPadding, swatchPadding, swatchPadding, swatchPadding) } + + itemBinding.root } - .forEach { theme -> holder.palette.addView(theme) } + .forEach { theme -> binding.palette.addView(theme) } } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerController.kt index 247ae489..1bc1ea26 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerController.kt @@ -19,7 +19,9 @@ package dev.octoshrimpy.quik.feature.themepicker import android.animation.ObjectAnimator +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import com.jakewharton.rxbinding2.view.clicks @@ -29,18 +31,20 @@ import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.dpToPx import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setVisible +import dev.octoshrimpy.quik.databinding.ThemePickerControllerBinding import dev.octoshrimpy.quik.feature.themepicker.injection.ThemePickerModule import dev.octoshrimpy.quik.injection.appComponent import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.theme_picker_controller.* -import kotlinx.android.synthetic.main.theme_picker_hsv.* import javax.inject.Inject class ThemePickerController( val recipientId: Long = 0L -) : QkController(), ThemePickerView { +) : QkController(), ThemePickerView { + + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup): ThemePickerControllerBinding = + ThemePickerControllerBinding.inflate(inflater, container, false) @Inject override lateinit var presenter: ThemePickerPresenter @@ -56,19 +60,17 @@ class ThemePickerController( .themePickerModule(ThemePickerModule(this)) .build() .inject(this) - - layoutRes = R.layout.theme_picker_controller } override fun onViewCreated() { - pager.offscreenPageLimit = 1 - pager.adapter = themePagerAdapter - tabs.pager = pager + binding.pager.offscreenPageLimit = 1 + binding.pager.adapter = themePagerAdapter + binding.tabs.pager = binding.pager themeAdapter.data = colors.materialColors - materialColors.layoutManager = LinearLayoutManager(activity) - materialColors.adapter = themeAdapter + binding.materialColors.layoutManager = LinearLayoutManager(activity) + binding.materialColors.adapter = themeAdapter } override fun onAttach(view: View) { @@ -91,7 +93,7 @@ class ThemePickerController( } override fun showQksmsPlusSnackbar() { - Snackbar.make(contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { + Snackbar.make(binding.contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusSubject.onNext(Unit) } setActionTextColor(colors.theme().theme) show() @@ -100,27 +102,27 @@ class ThemePickerController( override fun themeSelected(): Observable = themeAdapter.colorSelected - override fun hsvThemeSelected(): Observable = picker.selectedColor + override fun hsvThemeSelected(): Observable = binding.hsvPicker.picker.selectedColor - override fun clearHsvThemeClicks(): Observable<*> = clear.clicks() + override fun clearHsvThemeClicks(): Observable<*> = binding.hsvPicker.clear.clicks() - override fun applyHsvThemeClicks(): Observable<*> = apply.clicks() + override fun applyHsvThemeClicks(): Observable<*> = binding.hsvPicker.apply.clicks() override fun viewQksmsPlusClicks(): Observable<*> = viewQksmsPlusSubject override fun render(state: ThemePickerState) { - tabs.setRecipientId(state.recipientId) + binding.tabs.setRecipientId(state.recipientId) - hex.setText(Integer.toHexString(state.newColor).takeLast(6)) + binding.hsvPicker.hex.setText(Integer.toHexString(state.newColor).takeLast(6)) - applyGroup.setVisible(state.applyThemeVisible) - apply.setBackgroundTint(state.newColor) - apply.setTextColor(state.newTextColor) + binding.hsvPicker.applyGroup.setVisible(state.applyThemeVisible) + binding.hsvPicker.apply.setBackgroundTint(state.newColor) + binding.hsvPicker.apply.setTextColor(state.newTextColor) } override fun setCurrentTheme(color: Int) { - picker.setColor(color) + binding.hsvPicker.picker.setColor(color) themeAdapter.selectedColor = color } -} \ No newline at end of file +} From 6f3d6cc679eae97c2e8d8e2e4e9e4bc4e6dfca5f Mon Sep 17 00:00:00 2001 From: Ben Iofel Date: Mon, 12 Jan 2026 02:06:18 -0500 Subject: [PATCH 2/4] Simplify checkbox access in PreferenceView Add widget and checkbox properties to PreferenceView, eliminating repetitive findViewById(R.id.checkbox) calls throughout the codebase. Co-Authored-By: Claude Opus 4.5 --- .../QKSMS/common/widget/PreferenceView.kt | 6 +++++ .../feature/blocking/BlockingController.kt | 3 +-- .../MessageContentFiltersController.kt | 12 ++++------ .../messageutils/MessageUtilsController.kt | 5 +--- .../NotificationPrefsActivity.kt | 13 +++++------ .../feature/settings/SettingsController.kt | 23 +++++++++---------- 6 files changed, 30 insertions(+), 32 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt index abaf1788..a401ba6f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt @@ -40,6 +40,12 @@ class PreferenceView @JvmOverloads constructor( val titleView: TextView get() = layout.titleView + /** Returns the widget view inflated into the widget frame, if any */ + val widget: View? get() = layout.widgetFrame.getChildAt(0) + + /** Returns the checkbox widget if this preference has one */ + val checkbox: QkSwitch? get() = widget as? QkSwitch + var title: String? = null set(value) { field = value diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt index c543f775..cd447cb3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt @@ -34,7 +34,6 @@ import dev.octoshrimpy.quik.feature.blocking.numbers.BlockedNumbersController import dev.octoshrimpy.quik.feature.blocking.filters.MessageContentFiltersController import dev.octoshrimpy.quik.injection.appComponent import dev.octoshrimpy.quik.databinding.BlockingControllerBinding -import dev.octoshrimpy.quik.common.widget.QkSwitch import javax.inject.Inject class BlockingController : QkController(), BlockingView { @@ -70,7 +69,7 @@ class BlockingController : QkController(R.id.checkbox)?.isChecked = state.dropEnabled + binding.drop.checkbox?.isChecked = state.dropEnabled binding.blockedMessages.isEnabled = !state.dropEnabled } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt index 34afb5e9..eacc717c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt @@ -34,7 +34,6 @@ import dev.octoshrimpy.quik.injection.appComponent import dev.octoshrimpy.quik.model.MessageContentFilterData import dev.octoshrimpy.quik.databinding.MessageContentFiltersControllerBinding import dev.octoshrimpy.quik.databinding.MessageContentFiltersAddDialogBinding -import dev.octoshrimpy.quik.common.widget.QkSwitch import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject @@ -90,9 +89,8 @@ class MessageContentFiltersController : QkController - val checkbox = pref.findViewById(R.id.checkbox) - checkbox?.isChecked = !(checkbox?.isChecked ?: false) - val regexChecked = layout.regexp.findViewById(R.id.checkbox)?.isChecked ?: false + pref.checkbox?.let { it.isChecked = !it.isChecked } + val regexChecked = layout.regexp.checkbox?.isChecked ?: false layout.caseSensitivity.isEnabled = !regexChecked } @@ -101,14 +99,14 @@ class MessageContentFiltersController : QkController var text = layout.input.text.toString(); if (!text.isBlank()) { - val regexChecked = layout.regexp.findViewById(R.id.checkbox)?.isChecked ?: false + val regexChecked = layout.regexp.checkbox?.isChecked ?: false if (!regexChecked) text = text.trim() saveFilterSubject.onNext( MessageContentFilterData( text, - (layout.caseSensitivity.findViewById(R.id.checkbox)?.isChecked == true) && !regexChecked, + (layout.caseSensitivity.checkbox?.isChecked == true) && !regexChecked, regexChecked, - layout.contacts.findViewById(R.id.checkbox)?.isChecked == true + layout.contacts.checkbox?.isChecked == true ) ) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/messageutils/MessageUtilsController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/messageutils/MessageUtilsController.kt index e99ec1fe..a665bafb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/messageutils/MessageUtilsController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/messageutils/MessageUtilsController.kt @@ -10,7 +10,6 @@ import com.jakewharton.rxbinding2.view.clicks import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkController import dev.octoshrimpy.quik.common.util.extensions.animateLayoutChanges -import dev.octoshrimpy.quik.common.widget.QkSwitch import dev.octoshrimpy.quik.databinding.MessageUtilsControllerBinding import dev.octoshrimpy.quik.injection.appComponent import dev.octoshrimpy.quik.repository.MessageRepository @@ -46,9 +45,7 @@ class MessageUtilsController : QkController(R.id.checkbox) - ?.isChecked = state.autoDeduplicateMessages + binding.autoDeduplicate.checkbox?.isChecked = state.autoDeduplicateMessages val deduplicationProgress = binding.deduplicationProgress val progressAnimator = ObjectAnimator.ofInt(deduplicationProgress, "progress", 0, 0) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt index 96a91093..b96bdabc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt @@ -37,7 +37,6 @@ import dev.octoshrimpy.quik.common.base.QkThemedActivity import dev.octoshrimpy.quik.common.util.extensions.animateLayoutChanges import dev.octoshrimpy.quik.common.util.extensions.setVisible import dev.octoshrimpy.quik.common.widget.PreferenceView -import dev.octoshrimpy.quik.common.widget.QkSwitch import dev.octoshrimpy.quik.databinding.NotificationPrefsActivityBinding import io.reactivex.Observable import io.reactivex.subjects.PublishSubject @@ -98,13 +97,13 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { title = state.conversationTitle } - binding.notifications.findViewById(R.id.checkbox)?.isChecked = state.notificationsEnabled + binding.notifications.checkbox?.isChecked = state.notificationsEnabled binding.previews.summary = state.previewSummary previewModeDialog.adapter.selectedItem = state.previewId - binding.wake.findViewById(R.id.checkbox)?.isChecked = state.wakeEnabled - binding.silentNotContact.findViewById(R.id.checkbox)?.isChecked = state.silentNotContact + binding.wake.checkbox?.isChecked = state.wakeEnabled + binding.silentNotContact.checkbox?.isChecked = state.silentNotContact binding.silentNotContact.isVisible = state.threadId == 0L - binding.vibration.findViewById(R.id.checkbox)?.isChecked = state.vibrationEnabled + binding.vibration.checkbox?.isChecked = state.vibrationEnabled binding.ringtone.summary = state.ringtoneName binding.actionsDivider.isVisible = state.threadId == 0L @@ -118,11 +117,11 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { binding.qkreplyDivider.isVisible = state.threadId == 0L binding.qkreplyTitle.isVisible = state.threadId == 0L - binding.qkreply.findViewById(R.id.checkbox)?.isChecked = state.qkReplyEnabled + binding.qkreply.checkbox?.isChecked = state.qkReplyEnabled binding.qkreply.isVisible = state.threadId == 0L binding.qkreplyTapDismiss.isVisible = state.threadId == 0L binding.qkreplyTapDismiss.isEnabled = state.qkReplyEnabled - binding.qkreplyTapDismiss.findViewById(R.id.checkbox)?.isChecked = state.qkReplyTapDismiss + binding.qkreplyTapDismiss.checkbox?.isChecked = state.qkReplyTapDismiss } override fun showPreviewModeDialog() = previewModeDialog.show(this) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt index cff5d557..2b494212 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt @@ -45,7 +45,6 @@ import dev.octoshrimpy.quik.common.util.extensions.animateLayoutChanges import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint import dev.octoshrimpy.quik.common.util.extensions.setVisible import dev.octoshrimpy.quik.common.widget.PreferenceView -import dev.octoshrimpy.quik.common.widget.QkSwitch import dev.octoshrimpy.quik.common.widget.TextInputDialog import dev.octoshrimpy.quik.databinding.SettingsControllerBinding import dev.octoshrimpy.quik.feature.settings.about.AboutController @@ -165,16 +164,16 @@ class SettingsController : QkController(R.id.checkbox)?.isChecked = state.black + binding.black.checkbox?.isChecked = state.black - binding.autoEmoji.findViewById(R.id.checkbox)?.isChecked = state.autoEmojiEnabled + binding.autoEmoji.checkbox?.isChecked = state.autoEmojiEnabled binding.delayed.summary = state.sendDelaySummary sendDelayDialog.adapter.selectedItem = state.sendDelayId - binding.delivery.findViewById(R.id.checkbox)?.isChecked = state.deliveryEnabled + binding.delivery.checkbox?.isChecked = state.deliveryEnabled - binding.unreadAtTop.findViewById(R.id.checkbox)?.isChecked = state.unreadAtTopEnabled + binding.unreadAtTop.checkbox?.isChecked = state.unreadAtTopEnabled binding.signature.summary = state.signature.takeIf { it.isNotBlank() } ?: context.getString(R.string.settings_signature_summary) @@ -182,14 +181,14 @@ class SettingsController : QkController(R.id.checkbox)?.isChecked = state.autoColor + binding.autoColor.checkbox?.isChecked = state.autoColor - binding.systemFont.findViewById(R.id.checkbox)?.isChecked = state.systemFontEnabled + binding.systemFont.checkbox?.isChecked = state.systemFontEnabled - binding.showStt.findViewById(R.id.checkbox)?.isChecked = state.showStt + binding.showStt.checkbox?.isChecked = state.showStt - binding.unicode.findViewById(R.id.checkbox)?.isChecked = state.stripUnicodeEnabled - binding.mobileOnly.findViewById(R.id.checkbox)?.isChecked = state.mobileOnly + binding.unicode.checkbox?.isChecked = state.stripUnicodeEnabled + binding.mobileOnly.checkbox?.isChecked = state.mobileOnly binding.autoDelete.summary = when (state.autoDelete) { 0 -> context.getString(R.string.settings_auto_delete_never) @@ -197,7 +196,7 @@ class SettingsController : QkController(R.id.checkbox)?.isChecked = state.longAsMms + binding.longAsMms.checkbox?.isChecked = state.longAsMms binding.mmsSize.summary = state.maxMmsSizeSummary mmsSizeDialog.adapter.selectedItem = state.maxMmsSizeId @@ -205,7 +204,7 @@ class SettingsController : QkController(R.id.checkbox)?.isChecked = state.disableScreenshotsEnabled + binding.disableScreenshots.checkbox?.isChecked = state.disableScreenshotsEnabled when (state.syncProgress) { is SyncRepository.SyncProgress.Idle -> binding.syncingProgress.isVisible = false From 109c3c2c0a7b17b600197b7f3f3a6c4af521fdc1 Mon Sep 17 00:00:00 2001 From: Ben Iofel Date: Mon, 12 Jan 2026 02:09:53 -0500 Subject: [PATCH 3/4] Use QkBindingViewHolder in single-type adapters Store ViewBinding in the ViewHolder instead of recreating it via .bind() on every onBindViewHolder call. This reduces allocations and simplifies the code. Co-Authored-By: Claude Opus 4.5 --- .../blocking/messages/BlockedMessagesAdapter.kt | 12 ++++++------ .../feature/compose/editing/ComposeItemAdapter.kt | 12 ++++++------ .../compose/editing/PhoneNumberPickerAdapter.kt | 12 ++++++------ .../feature/conversations/ConversationsAdapter.kt | 12 ++++++------ .../moez/QKSMS/feature/themepicker/ThemeAdapter.kt | 12 ++++++------ 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt index a51617e3..e5244dce 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt @@ -24,8 +24,8 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible import dev.octoshrimpy.quik.R +import dev.octoshrimpy.quik.common.base.QkBindingViewHolder import dev.octoshrimpy.quik.common.base.QkRealmAdapter -import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.DateFormatter import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor import dev.octoshrimpy.quik.databinding.BlockedListItemBinding @@ -37,11 +37,11 @@ import javax.inject.Inject class BlockedMessagesAdapter @Inject constructor( private val context: Context, private val dateFormatter: DateFormatter -) : QkRealmAdapter() { +) : QkRealmAdapter>() { val clicks: PublishSubject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkBindingViewHolder { val binding = BlockedListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) if (viewType == 0) { @@ -50,7 +50,7 @@ class BlockedMessagesAdapter @Inject constructor( binding.date.setTextColor(binding.root.context.resolveThemeColor(android.R.attr.textColorPrimary)) } - return QkViewHolder(binding.root).apply { + return QkBindingViewHolder(binding).apply { binding.root.setOnClickListener { val conversation = getItem(adapterPosition) ?: return@setOnClickListener when (toggleSelection(conversation.id, false)) { @@ -67,9 +67,9 @@ class BlockedMessagesAdapter @Inject constructor( } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkBindingViewHolder, position: Int) { val conversation = getItem(position) ?: return - val binding = BlockedListItemBinding.bind(holder.itemView) + val binding = holder.binding holder.itemView.isActivated = isSelected(conversation.id) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt index b39670da..359b6545 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt @@ -24,7 +24,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkAdapter -import dev.octoshrimpy.quik.common.base.QkViewHolder +import dev.octoshrimpy.quik.common.base.QkBindingViewHolder import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.forwardTouches import dev.octoshrimpy.quik.common.util.extensions.setTint @@ -44,7 +44,7 @@ import javax.inject.Inject class ComposeItemAdapter @Inject constructor( private val colors: Colors, private val conversationRepo: ConversationRepository -) : QkAdapter() { +) : QkAdapter>() { val clicks: Subject = PublishSubject.create() val longClicks: Subject = PublishSubject.create() @@ -58,7 +58,7 @@ class ComposeItemAdapter @Inject constructor( notifyDataSetChanged() } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkBindingViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = ContactListItemBinding.inflate(layoutInflater, parent, false) @@ -68,7 +68,7 @@ class ComposeItemAdapter @Inject constructor( binding.numbers.adapter = PhoneNumberAdapter() binding.numbers.forwardTouches(binding.root) - return QkViewHolder(binding.root).apply { + return QkBindingViewHolder(binding).apply { binding.root.setOnClickListener { val item = getItem(adapterPosition) clicks.onNext(item) @@ -81,8 +81,8 @@ class ComposeItemAdapter @Inject constructor( } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val binding = ContactListItemBinding.bind(holder.itemView) + override fun onBindViewHolder(holder: QkBindingViewHolder, position: Int) { + val binding = holder.binding val prevItem = if (position > 0) getItem(position - 1) else null when (val item = getItem(position)) { is ComposeItem.New -> bindNew(binding, item.value) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt index 5208820d..a689b5d6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt @@ -23,7 +23,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.base.QkAdapter -import dev.octoshrimpy.quik.common.base.QkViewHolder +import dev.octoshrimpy.quik.common.base.QkBindingViewHolder import dev.octoshrimpy.quik.common.util.extensions.forwardTouches import dev.octoshrimpy.quik.databinding.PhoneNumberListItemBinding import dev.octoshrimpy.quik.extensions.Optional @@ -34,7 +34,7 @@ import javax.inject.Inject class PhoneNumberPickerAdapter @Inject constructor( private val context: Context -) : QkAdapter() { +) : QkAdapter>() { val selectedItemChanges: Subject> = BehaviorSubject.create() @@ -46,10 +46,10 @@ class PhoneNumberPickerAdapter @Inject constructor( selectedItemChanges.onNext(Optional(value)) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkBindingViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = PhoneNumberListItemBinding.inflate(inflater, parent, false) - return QkViewHolder(binding.root).apply { + return QkBindingViewHolder(binding).apply { binding.number.radioButton.forwardTouches(itemView) binding.root.setOnClickListener { @@ -59,8 +59,8 @@ class PhoneNumberPickerAdapter @Inject constructor( } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val binding = PhoneNumberListItemBinding.bind(holder.itemView) + override fun onBindViewHolder(holder: QkBindingViewHolder, position: Int) { + val binding = holder.binding val phoneNumber = getItem(position) binding.number.radioButton.isChecked = phoneNumber.id == selectedItem diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt index ce07a0ea..515012a2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt @@ -27,8 +27,8 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import dev.octoshrimpy.quik.R import dev.octoshrimpy.quik.common.Navigator +import dev.octoshrimpy.quik.common.base.QkBindingViewHolder import dev.octoshrimpy.quik.common.base.QkRealmAdapter -import dev.octoshrimpy.quik.common.base.QkViewHolder import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.DateFormatter import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor @@ -47,7 +47,7 @@ class ConversationsAdapter @Inject constructor( private val scheduledMessageRepo: ScheduledMessageRepository, private val navigator: Navigator, private val phoneNumberUtils: PhoneNumberUtils -) : QkRealmAdapter() { +) : QkRealmAdapter>() { private val disposables = CompositeDisposable() init { @@ -55,7 +55,7 @@ class ConversationsAdapter @Inject constructor( setHasStableIds(true) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkBindingViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = ConversationListItemBinding.inflate(layoutInflater, parent, false) @@ -74,7 +74,7 @@ class ConversationsAdapter @Inject constructor( binding.date.setTextColor(textColorPrimary) } - return QkViewHolder(binding.root).apply { + return QkBindingViewHolder(binding).apply { binding.root.setOnClickListener { val conversation = getItem(adapterPosition) ?: return@setOnClickListener when (toggleSelection(conversation.id, false)) { @@ -91,9 +91,9 @@ class ConversationsAdapter @Inject constructor( } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkBindingViewHolder, position: Int) { val conversation = getItem(position) ?: return - val binding = ConversationListItemBinding.bind(holder.itemView) + val binding = holder.binding // If the last message wasn't incoming, then the colour doesn't really matter anyway val lastMessage = conversation.lastMessage diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemeAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemeAdapter.kt index 1c188e30..37f6e86f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemeAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemeAdapter.kt @@ -26,7 +26,7 @@ import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayout import dev.octoshrimpy.quik.common.base.QkAdapter -import dev.octoshrimpy.quik.common.base.QkViewHolder +import dev.octoshrimpy.quik.common.base.QkBindingViewHolder import dev.octoshrimpy.quik.common.util.Colors import dev.octoshrimpy.quik.common.util.extensions.dpToPx import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint @@ -41,7 +41,7 @@ import javax.inject.Inject class ThemeAdapter @Inject constructor( private val context: Context, private val colors: Colors -) : QkAdapter, QkViewHolder>() { +) : QkAdapter, QkBindingViewHolder>() { val colorSelected: Subject = PublishSubject.create() @@ -59,17 +59,17 @@ class ThemeAdapter @Inject constructor( private var iconTint = 0 - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkBindingViewHolder { val binding = ThemePaletteListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) binding.palette.flexWrap = FlexWrap.WRAP binding.palette.flexDirection = FlexDirection.ROW - return QkViewHolder(binding.root) + return QkBindingViewHolder(binding) } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkBindingViewHolder, position: Int) { val palette = getItem(position) - val binding = ThemePaletteListItemBinding.bind(holder.itemView) + val binding = holder.binding val screenWidth = Resources.getSystem().displayMetrics.widthPixels val minPadding = (16 * 6).dpToPx(context) From 434b9d610b4ead40dca16a144501e845d0892067 Mon Sep 17 00:00:00 2001 From: Ben Iofel Date: Tue, 3 Feb 2026 01:40:27 -0500 Subject: [PATCH 4/4] cleanup --- .../main/java/com/moez/QKSMS/common/widget/PreferenceView.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt index a401ba6f..9efc9a26 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt @@ -40,10 +40,8 @@ class PreferenceView @JvmOverloads constructor( val titleView: TextView get() = layout.titleView - /** Returns the widget view inflated into the widget frame, if any */ val widget: View? get() = layout.widgetFrame.getChildAt(0) - /** Returns the checkbox widget if this preference has one */ val checkbox: QkSwitch? get() = widget as? QkSwitch var title: String? = null