From 0dadcfb3dca556605b26292857ec9c75d85c9f83 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Thu, 26 Dec 2019 18:20:14 -0500 Subject: [PATCH 001/213] Respect threadId when sending MMS --- .../com/android/mms/service_alt/DownloadRequest.java | 2 +- .../android/mms/transaction/NotificationTransaction.java | 9 ++++----- .../java/com/android/mms/transaction/PushReceiver.java | 7 ++++--- .../com/android/mms/transaction/RetrieveTransaction.java | 4 ++-- .../com/google/android/mms/pdu_alt/PduPersister.java | 8 ++++---- .../java/com/klinker/android/send_message/Transaction.kt | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/DownloadRequest.java b/android-smsmms/src/main/java/com/android/mms/service_alt/DownloadRequest.java index c413ca74e..b70679497 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/DownloadRequest.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/DownloadRequest.java @@ -169,7 +169,7 @@ public class DownloadRequest extends MmsRequest { // } // Store the downloaded message final PduPersister persister = PduPersister.getPduPersister(context); - final Uri messageUri = persister.persist(pdu, Telephony.Mms.Inbox.CONTENT_URI, true, true, null); + final Uri messageUri = persister.persist(pdu, Telephony.Mms.Inbox.CONTENT_URI, PduPersister.DUMMY_THREAD_ID, true, true, null); if (messageUri == null) { Timber.e("DownloadRequest.persistIfRequired: can not persist message"); return null; diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/NotificationTransaction.java b/android-smsmms/src/main/java/com/android/mms/transaction/NotificationTransaction.java index 074586bda..1b03c0bab 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/NotificationTransaction.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/NotificationTransaction.java @@ -110,9 +110,8 @@ public class NotificationTransaction extends Transaction implements Runnable { try { // Save the pdu. If we can start downloading the real pdu immediately, don't allow // persist() to create a thread for the notificationInd because it causes UI jank. - mUri = PduPersister.getPduPersister(context).persist( - ind, Inbox.CONTENT_URI, !allowAutoDownload(mContext), - true, null); + mUri = PduPersister.getPduPersister(context).persist(ind, Inbox.CONTENT_URI, + PduPersister.DUMMY_THREAD_ID, !allowAutoDownload(mContext), true, null); } catch (MmsException e) { Timber.e(e, "Failed to save NotificationInd in constructor."); throw new IllegalArgumentException(); @@ -185,8 +184,8 @@ public class NotificationTransaction extends Transaction implements Runnable { } else { // Save the received PDU (must be a M-RETRIEVE.CONF). PduPersister p = PduPersister.getPduPersister(mContext); - Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true, - true, null); + Uri uri = p.persist(pdu, Inbox.CONTENT_URI, PduPersister.DUMMY_THREAD_ID, + true, true, null); RetrieveConf retrieveConf = (RetrieveConf) pdu; diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/PushReceiver.java b/android-smsmms/src/main/java/com/android/mms/transaction/PushReceiver.java index a701be4b7..722b2f834 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/PushReceiver.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/PushReceiver.java @@ -107,8 +107,8 @@ public class PushReceiver extends BroadcastReceiver { break; } - Uri uri = p.persist(pdu, Uri.parse("content://mms/inbox"), true, - true, null); + Uri uri = p.persist(pdu, Uri.parse("content://mms/inbox"), + PduPersister.DUMMY_THREAD_ID, true, true, null); // Update thread ID for ReadOrigInd & DeliveryInd. ContentValues values = new ContentValues(1); values.put(Mms.THREAD_ID, threadId); @@ -136,7 +136,8 @@ public class PushReceiver extends BroadcastReceiver { // Save the pdu. If we can start downloading the real pdu immediately, // don't allow persist() to create a thread for the notificationInd // because it causes UI jank. - Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true, true, null); + Uri uri = p.persist(pdu, Inbox.CONTENT_URI, + PduPersister.DUMMY_THREAD_ID, true, true, null); String location = getContentLocation(mContext, uri); if (downloadedUrls.contains(location)) { diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/RetrieveTransaction.java b/android-smsmms/src/main/java/com/android/mms/transaction/RetrieveTransaction.java index a9ea47b21..4de1c9b8a 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/RetrieveTransaction.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/RetrieveTransaction.java @@ -153,8 +153,8 @@ public class RetrieveTransaction extends Transaction implements Runnable { } else { // Store M-Retrieve.conf into Inbox PduPersister persister = PduPersister.getPduPersister(mContext); - msgUri = persister.persist(retrieveConf, Inbox.CONTENT_URI, true, - true, null); + msgUri = persister.persist(retrieveConf, Inbox.CONTENT_URI, + PduPersister.DUMMY_THREAD_ID, true, true, null); // Use local time instead of PDU time ContentValues values = new ContentValues(3); diff --git a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPersister.java b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPersister.java index 638b4c9ab..07bcb551f 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPersister.java +++ b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPersister.java @@ -67,7 +67,7 @@ import java.util.Set; public class PduPersister { private static final boolean LOCAL_LOGV = false; - private static final long DUMMY_THREAD_ID = Long.MAX_VALUE; + public static final long DUMMY_THREAD_ID = Long.MAX_VALUE; private static final int DEFAULT_SUBSCRIPTION = 0; private static final int MAX_TEXT_BODY_SIZE = 300 * 1024; @@ -1263,6 +1263,7 @@ public class PduPersister { * * @param pdu The PDU object to be stored. * @param uri Where to store the given PDU object. + * @param threadId * @param createThreadId if true, this function may create a thread id for the recipients * @param groupMmsEnabled if true, all of the recipients addressed in the PDU will be used * to create the associated thread. When false, only the sender will be used in finding or @@ -1271,7 +1272,7 @@ public class PduPersister { * @return A Uri which can be used to access the stored PDU. */ - public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled, + public Uri persist(GenericPdu pdu, Uri uri, long threadId, boolean createThreadId, boolean groupMmsEnabled, HashMap preOpenedFiles) throws MmsException { if (uri == null) { @@ -1400,8 +1401,7 @@ public class PduPersister { loadRecipients(PduHeaders.TO, recipients, addressMap, false); break; } - long threadId = DUMMY_THREAD_ID; - if (createThreadId && !recipients.isEmpty()) { + if (threadId == DUMMY_THREAD_ID && createThreadId && !recipients.isEmpty()) { // Given all the recipients associated with this message, find (or create) the // correct thread. threadId = Threads.getOrCreateThreadId(mContext, recipients); diff --git a/android-smsmms/src/main/java/com/klinker/android/send_message/Transaction.kt b/android-smsmms/src/main/java/com/klinker/android/send_message/Transaction.kt index 79b237f91..e7169cbef 100755 --- a/android-smsmms/src/main/java/com/klinker/android/send_message/Transaction.kt +++ b/android-smsmms/src/main/java/com/klinker/android/send_message/Transaction.kt @@ -94,7 +94,7 @@ class Transaction @JvmOverloads constructor(private val context: Context, settin val sendReq = buildPdu(context, addresses, subject, parts) val persister = PduPersister.getPduPersister(context) - val messageUri = existingUri ?: persister.persist(sendReq, Uri.parse("content://mms/outbox"), true, true, null) + val messageUri = existingUri ?: persister.persist(sendReq, Uri.parse("content://mms/outbox"), threadId, true, true, null) val sentIntent = Intent(MMS_SENT) BroadcastUtils.addClassName(context, sentIntent, MMS_SENT) -- GitLab From 1e5f8485f28f4418301cea27e0afa74a53d8272c Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 29 Sep 2019 15:04:42 -0400 Subject: [PATCH 002/213] Stop flashing for unchanged numbers --- .../com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt index 3e8689434..94d62e381 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt @@ -51,4 +51,12 @@ class PhoneNumberAdapter( view.type.text = number.type } + override fun areItemsTheSame(old: PhoneNumber, new: PhoneNumber): Boolean { + return old.type == new.type && old.address == new.address + } + + override fun areContentsTheSame(old: PhoneNumber, new: PhoneNumber): Boolean { + return old.type == new.type && old.address == new.address + } + } \ No newline at end of file -- GitLab From a8efaa19bebc0d31bf8d2f9f5d1dba59c665aef8 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 29 Sep 2019 17:43:51 -0400 Subject: [PATCH 003/213] Update contact list stype to show indices --- .../QKSMS/feature/compose/ContactAdapter.kt | 28 +++---- .../feature/compose/PhoneNumberAdapter.kt | 10 +-- .../src/main/res/layout/contact_list_item.xml | 84 +++++++++---------- .../res/layout/contact_number_list_item.xml | 29 +++---- 4 files changed, 70 insertions(+), 81 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt index 524bb783c..0f8b7875e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt @@ -20,10 +20,12 @@ package com.moez.QKSMS.feature.compose import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.extensions.forwardTouches import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.model.Contact import io.reactivex.subjects.PublishSubject @@ -42,32 +44,34 @@ class ContactAdapter @Inject constructor() : QkAdapter() { val view = layoutInflater.inflate(R.layout.contact_list_item, parent, false) view.addresses.setRecycledViewPool(numbersViewPool) + view.addresses.adapter = PhoneNumberAdapter() + view.addresses.forwardTouches(view) return QkViewHolder(view).apply { - view.primary.setOnClickListener { + view.setOnClickListener { val contact = getItem(adapterPosition) - contactSelected.onNext(copyContact(contact, 0)) - } - - view.addresses.adapter = PhoneNumberAdapter { contact, index -> - contactSelected.onNext(copyContact(contact, index + 1)) + contactSelected.onNext(contact) } } } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val prevContact = if (position > 0) getItem(position - 1) else null val contact = getItem(position) val view = holder.containerView + view.index.text = if (contact.name[0].isLetter()) contact.name[0].toString() else "#" + view.index.isVisible = prevContact == null || + (contact.name[0].isLetter() && contact.name[0] != prevContact.name[0]) || + (!contact.name[0].isLetter() && prevContact.name[0].isLetter()) + view.avatar.setContact(contact) view.name.text = contact.name view.name.setVisible(view.name.text.isNotEmpty()) - view.address.text = contact.numbers.firstOrNull()?.address ?: "" - view.type.text = contact.numbers.firstOrNull()?.type ?: "" val adapter = view.addresses.adapter as PhoneNumberAdapter adapter.contact = contact - adapter.data = contact.numbers.drop(Math.min(contact.numbers.size, 1)) + adapter.data = contact.numbers } /** @@ -80,8 +84,4 @@ class ContactAdapter @Inject constructor() : QkAdapter() { numbers.add(contact.numbers[numberIndex]) } - override fun areItemsTheSame(old: Contact, new: Contact): Boolean { - return old.lookupKey == new.lookupKey - } - -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt index 94d62e381..14db217c9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt @@ -25,11 +25,9 @@ import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.PhoneNumber -import kotlinx.android.synthetic.main.contact_list_item.view.* +import kotlinx.android.synthetic.main.contact_number_list_item.view.* -class PhoneNumberAdapter( - private val numberClicked: (Contact, Int) -> Unit -) : QkAdapter() { +class PhoneNumberAdapter : QkAdapter() { lateinit var contact: Contact @@ -43,10 +41,6 @@ class PhoneNumberAdapter( val number = getItem(position) val view = holder.containerView - // Setting this in onCreateViewHolder causes a crash sometimes. [contact] returns the - // contact from a different row, I'm not sure why - view.setOnClickListener { numberClicked(contact, position) } - view.address.text = number.address view.type.text = number.type } diff --git a/presentation/src/main/res/layout/contact_list_item.xml b/presentation/src/main/res/layout/contact_list_item.xml index a2c3bd384..7ccfbbe5a 100644 --- a/presentation/src/main/res/layout/contact_list_item.xml +++ b/presentation/src/main/res/layout/contact_list_item.xml @@ -21,16 +21,45 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:paddingTop="8dp" + android:paddingBottom="8dp"> + + + + @@ -41,59 +70,24 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" - app:layout_constraintBottom_toTopOf="@id/address" + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@id/addresses" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0" app:layout_constraintStart_toEndOf="@id/avatar" - app:layout_constraintTop_toTopOf="@id/avatar" + app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" tools:text="Moez Bhatti" /> - - - - - - \ No newline at end of file diff --git a/presentation/src/main/res/layout/contact_number_list_item.xml b/presentation/src/main/res/layout/contact_number_list_item.xml index 5aa2b23d9..0ea6fab36 100644 --- a/presentation/src/main/res/layout/contact_number_list_item.xml +++ b/presentation/src/main/res/layout/contact_number_list_item.xml @@ -22,26 +22,27 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground"> + android:paddingTop="2dp"> + tools:text="Mobile" /> + android:textColor="?android:attr/textColorTertiary" + app:textSize="secondary" + tools:text="(123) 456-7890" /> \ No newline at end of file -- GitLab From 4da154e3faeeec6951eb9689f73950015d4a2f88 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 29 Sep 2019 18:16:28 -0400 Subject: [PATCH 004/213] Show two initials for avatar --- .../java/com/moez/QKSMS/common/widget/AvatarView.kt | 10 ++++++++-- presentation/src/main/res/layout/avatar_view.xml | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index ba4e854a3..12fcf47e0 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -35,7 +35,9 @@ import com.moez.QKSMS.util.GlideApp import kotlinx.android.synthetic.main.avatar_view.view.* import javax.inject.Inject -class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { +class AvatarView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : FrameLayout(context, attrs) { @Inject lateinit var colors: Colors @Inject lateinit var navigator: Navigator @@ -116,7 +118,11 @@ class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet private fun updateView() { if (name?.isNotEmpty() == true) { - initial.text = name?.substring(0, 1) + val initials = name?.split(" ").orEmpty() + .filter { name -> name.isNotEmpty() } + .map { name -> name[0].toString() } + + initial.text = if (initials.size > 1) initials.first() + initials.last() else initials.first() icon.visibility = GONE } else { initial.text = null diff --git a/presentation/src/main/res/layout/avatar_view.xml b/presentation/src/main/res/layout/avatar_view.xml index c0896e8f6..8f8e2aa9f 100644 --- a/presentation/src/main/res/layout/avatar_view.xml +++ b/presentation/src/main/res/layout/avatar_view.xml @@ -18,6 +18,7 @@ ~ along with QKSMS. If not, see . --> + app:autoSizeMaxTextSize="22sp" + app:autoSizeTextType="uniform" /> Date: Sun, 27 Oct 2019 12:14:33 -0400 Subject: [PATCH 005/213] Don't crash when name is empty --- .../main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt index 0f8b7875e..934c21ca8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt @@ -60,7 +60,7 @@ class ContactAdapter @Inject constructor() : QkAdapter() { val contact = getItem(position) val view = holder.containerView - view.index.text = if (contact.name[0].isLetter()) contact.name[0].toString() else "#" + view.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" view.index.isVisible = prevContact == null || (contact.name[0].isLetter() && contact.name[0] != prevContact.name[0]) || (!contact.name[0].isLetter() && prevContact.name[0].isLetter()) -- GitLab From fdf2e2dbcb1c670bb6355b808a42e1577ad63da7 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 27 Oct 2019 13:07:25 -0400 Subject: [PATCH 006/213] Make sure indices get updated --- .../java/com/moez/QKSMS/feature/compose/ContactAdapter.kt | 6 +++--- .../com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt index 934c21ca8..a7cb4ce4d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt @@ -69,9 +69,7 @@ class ContactAdapter @Inject constructor() : QkAdapter() { view.name.text = contact.name view.name.setVisible(view.name.text.isNotEmpty()) - val adapter = view.addresses.adapter as PhoneNumberAdapter - adapter.contact = contact - adapter.data = contact.numbers + (view.addresses.adapter as PhoneNumberAdapter).data = contact.numbers } /** @@ -84,4 +82,6 @@ class ContactAdapter @Inject constructor() : QkAdapter() { numbers.add(contact.numbers[numberIndex]) } + override fun areContentsTheSame(old: Contact, new: Contact): Boolean = false + } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt index 14db217c9..ca7df4496 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt @@ -23,14 +23,11 @@ import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.PhoneNumber import kotlinx.android.synthetic.main.contact_number_list_item.view.* class PhoneNumberAdapter : QkAdapter() { - lateinit var contact: Contact - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val inflater = LayoutInflater.from(parent.context) val view = inflater.inflate(R.layout.contact_number_list_item, parent, false) -- GitLab From 28cad9bbc96eba00348dea578e2a32da14d67f55 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 27 Oct 2019 13:24:48 -0400 Subject: [PATCH 007/213] Sort contacts with letter names first --- .../QKSMS/repository/ContactRepositoryImpl.kt | 20 +++++++++++++++---- .../QKSMS/feature/compose/ContactAdapter.kt | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt index 848e7d38a..45248ff9e 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt @@ -77,10 +77,9 @@ class ContactRepositoryImpl @Inject constructor( Phone.getTypeLabel(context.resources, Phone.TYPE_MOBILE, "Mobile").toString() } - return when (prefs.mobileOnly.get()) { + val contactsFlowable = when (prefs.mobileOnly.get()) { true -> realm.where(Contact::class.java) .contains("numbers.type", mobileLabel) - .sort("name") .findAllAsync() .asFlowable() .filter { it.isLoaded } @@ -98,7 +97,6 @@ class ContactRepositoryImpl @Inject constructor( } false -> realm.where(Contact::class.java) - .sort("name") .findAllAsync() .asFlowable() .filter { it.isLoaded } @@ -107,6 +105,20 @@ class ContactRepositoryImpl @Inject constructor( .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io()) } + + return contactsFlowable.map { contacts -> + contacts.sortedWith(Comparator { c1, c2 -> + val initial = c1.name.firstOrNull() + val other = c2.name.firstOrNull() + if (initial?.isLetter() == true && other?.isLetter() != true) { + -1 + } else if (initial?.isLetter() != true && other?.isLetter() == true) { + 1 + } else { + c1.name.compareTo(c2.name, ignoreCase = true) + } + }) + } } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt index a7cb4ce4d..595f69187 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt @@ -62,7 +62,7 @@ class ContactAdapter @Inject constructor() : QkAdapter() { view.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" view.index.isVisible = prevContact == null || - (contact.name[0].isLetter() && contact.name[0] != prevContact.name[0]) || + (contact.name[0].isLetter() && !contact.name[0].equals(prevContact.name[0], ignoreCase = true)) || (!contact.name[0].isLetter() && prevContact.name[0].isLetter()) view.avatar.setContact(contact) -- GitLab From 8f67de736a582c8f1134ba471377618affee5bf7 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 27 Oct 2019 17:50:27 -0400 Subject: [PATCH 008/213] Sync starred contacts and groups --- .../moez/QKSMS/extensions/CursorExtensions.kt | 37 +++--------- .../QKSMS/mapper/CursorToContactGroupImpl.kt | 52 ++++++++++++++++ .../mapper/CursorToContactGroupMemberImpl.kt | 52 ++++++++++++++++ .../moez/QKSMS/mapper/CursorToContactImpl.kt | 7 ++- .../moez/QKSMS/migration/QkRealmMigration.kt | 14 ++++- .../QKSMS/repository/SyncRepositoryImpl.kt | 60 +++++++++++++------ .../moez/QKSMS/mapper/CursorToContactGroup.kt | 28 +++++++++ .../mapper/CursorToContactGroupMember.kt | 29 +++++++++ .../main/java/com/moez/QKSMS/model/Contact.kt | 1 + .../java/com/moez/QKSMS/model/ContactGroup.kt | 29 +++++++++ .../com/moez/QKSMS/injection/AppModule.kt | 10 ++++ 11 files changed, 268 insertions(+), 51 deletions(-) create mode 100644 data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt create mode 100644 data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupMemberImpl.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroup.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupMember.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/model/ContactGroup.kt diff --git a/data/src/main/java/com/moez/QKSMS/extensions/CursorExtensions.kt b/data/src/main/java/com/moez/QKSMS/extensions/CursorExtensions.kt index 1214d0be8..a23e95b25 100644 --- a/data/src/main/java/com/moez/QKSMS/extensions/CursorExtensions.kt +++ b/data/src/main/java/com/moez/QKSMS/extensions/CursorExtensions.kt @@ -20,8 +20,6 @@ package com.moez.QKSMS.extensions import android.database.Cursor import io.reactivex.Flowable -import io.reactivex.Maybe -import io.reactivex.subjects.MaybeSubject fun Cursor.forEach(closeOnComplete: Boolean = true, method: (Cursor) -> Unit = {}) { moveToPosition(-1) @@ -41,21 +39,6 @@ fun Cursor.map(map: (Cursor) -> T): List { } } -fun Cursor.mapWhile(map: (Cursor) -> T, predicate: (T) -> Boolean): ArrayList { - val result = ArrayList() - - moveToPosition(-1) - while (moveToNext()) { - val item = map(this) - - if (!predicate(item)) break - - result.add(item) - } - - return result -} - /** * We're using this simple implementation with .range() because of the * complexities of dealing with Backpressure with a Cursor. We can't simply @@ -72,18 +55,14 @@ fun Cursor.asFlowable(): Flowable { .doOnComplete { close() } } -fun Cursor.asMaybe(): Maybe { - val subject = MaybeSubject.create() +/** + * Dumps the contents of the cursor as a CSV string + */ +fun Cursor.dump(): String { + val lines = mutableListOf() - if (moveToFirst()) { - subject.onSuccess(this) - } else { - subject.onError(IndexOutOfBoundsException("The cursor has no items")) - } + lines += columnNames.joinToString(",") + forEach { lines += (0 until columnCount).joinToString(",", transform = ::getString) } - subject.doOnComplete { close() } - return subject + return lines.joinToString("\n") } - - - diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt new file mode 100644 index 000000000..f2f290b5d --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.mapper + +import android.content.Context +import android.database.Cursor +import android.provider.ContactsContract +import com.moez.QKSMS.model.ContactGroup +import javax.inject.Inject + +class CursorToContactGroupImpl @Inject constructor( + private val context: Context +) : CursorToContactGroup { + + companion object { + private val URI = ContactsContract.Groups.CONTENT_URI + private val PROJECTION = arrayOf( + ContactsContract.Groups._ID, + ContactsContract.Groups.TITLE) + private const val SELECTION = "${ContactsContract.Groups.AUTO_ADD}=0 " + + "AND ${ContactsContract.Groups.DELETED}=0 " + + "AND ${ContactsContract.Groups.FAVORITES}=0" + + private const val ID = 0 + private const val TITLE = 1 + } + + override fun map(from: Cursor): ContactGroup { + return ContactGroup(from.getLong(ID), from.getString(TITLE)) + } + + override fun getContactGroupsCursor(): Cursor? { + return context.contentResolver.query(URI, PROJECTION, SELECTION, null, null) + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupMemberImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupMemberImpl.kt new file mode 100644 index 000000000..7eb72aab7 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupMemberImpl.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.mapper + +import android.content.Context +import android.database.Cursor +import android.provider.ContactsContract +import javax.inject.Inject + +class CursorToContactGroupMemberImpl @Inject constructor( + private val context: Context +) : CursorToContactGroupMember { + + companion object { + private val URI = ContactsContract.Data.CONTENT_URI + private val PROJECTION = arrayOf( + ContactsContract.Data.LOOKUP_KEY, + ContactsContract.Data.DATA1) + + private const val SELECTION = "${ContactsContract.Data.MIMETYPE}=?" + private val SELECTION_ARGS = arrayOf( + ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE) + + private const val LOOKUP_KEY = 0 + private const val GROUP_ID = 1 + } + + override fun map(from: Cursor): CursorToContactGroupMember.GroupMember { + return CursorToContactGroupMember.GroupMember(from.getString(LOOKUP_KEY), from.getLong(GROUP_ID)) + } + + override fun getGroupMembersCursor(): Cursor? { + return context.contentResolver.query(URI, PROJECTION, SELECTION, SELECTION_ARGS, null) + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt index 82d50ca9b..072e777ee 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt @@ -39,6 +39,7 @@ class CursorToContactImpl @Inject constructor( Phone.TYPE, Phone.LABEL, Phone.DISPLAY_NAME, + Phone.STARRED, Phone.CONTACT_LAST_UPDATED_TIMESTAMP ) @@ -47,7 +48,8 @@ class CursorToContactImpl @Inject constructor( const val COLUMN_TYPE = 2 const val COLUMN_LABEL = 3 const val COLUMN_DISPLAY_NAME = 4 - const val CONTACT_LAST_UPDATED = 5 + const val COLUMN_STARRED = 5 + const val CONTACT_LAST_UPDATED = 6 } override fun map(from: Cursor) = Contact().apply { @@ -58,6 +60,7 @@ class CursorToContactImpl @Inject constructor( type = Phone.getTypeLabel(context.resources, from.getInt(COLUMN_TYPE), from.getString(COLUMN_LABEL)).toString() )) + starred = from.getInt(COLUMN_STARRED) != 0 lastUpdate = from.getLong(CONTACT_LAST_UPDATED) } @@ -68,4 +71,4 @@ class CursorToContactImpl @Inject constructor( } } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index 0d5ea8178..1461fa9bb 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -26,7 +26,7 @@ import io.realm.Sort class QkRealmMigration : RealmMigration { companion object { - const val SCHEMA_VERSION: Long = 8 + const val SCHEMA_VERSION: Long = 9 } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -118,6 +118,18 @@ class QkRealmMigration : RealmMigration { version++ } + if (version == 8L) { + realm.schema.create("ContactGroup") + .addField("id", Long::class.java, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED) + .addField("title", String::class.java, FieldAttribute.REQUIRED) + .addRealmListField("contacts", realm.schema.get("Contact")) + + realm.schema.get("Contact") + ?.addField("starred", Boolean::class.java, FieldAttribute.REQUIRED) + + version++ + } + check(version >= newVersion) { "Migration missing from v$oldVersion to v$newVersion" } } diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index 46bc532d8..679f592dc 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -27,10 +27,13 @@ import com.moez.QKSMS.extensions.insertOrUpdate import com.moez.QKSMS.extensions.map import com.moez.QKSMS.manager.KeyManager import com.moez.QKSMS.mapper.CursorToContact +import com.moez.QKSMS.mapper.CursorToContactGroup +import com.moez.QKSMS.mapper.CursorToContactGroupMember import com.moez.QKSMS.mapper.CursorToConversation import com.moez.QKSMS.mapper.CursorToMessage import com.moez.QKSMS.mapper.CursorToRecipient import com.moez.QKSMS.model.Contact +import com.moez.QKSMS.model.ContactGroup import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart @@ -53,6 +56,8 @@ class SyncRepositoryImpl @Inject constructor( private val cursorToMessage: CursorToMessage, private val cursorToRecipient: CursorToRecipient, private val cursorToContact: CursorToContact, + private val cursorToContactGroup: CursorToContactGroup, + private val cursorToContactGroupMember: CursorToContactGroupMember, private val keys: KeyManager, private val phoneNumberUtils: PhoneNumberUtils, private val rxPrefs: RxSharedPreferences @@ -89,6 +94,7 @@ class SyncRepositoryImpl @Inject constructor( .toMutableMap() realm.delete(Contact::class.java) + realm.delete(ContactGroup::class.java) realm.delete(Conversation::class.java) realm.delete(Message::class.java) realm.delete(MmsPart::class.java) @@ -234,19 +240,19 @@ class SyncRepositoryImpl @Inject constructor( realm.executeTransaction { realm.delete(Contact::class.java) + realm.delete(ContactGroup::class.java) contacts = realm.copyToRealm(contacts) + realm.insertOrUpdate(getContactGroups(contacts)) // Update all the recipients with the new contacts - val updatedRecipients = recipients.map { recipient -> - recipient.apply { - contact = contacts.firstOrNull { - it.numbers.any { phoneNumberUtils.compare(recipient.address, it.address) } - } + recipients.forEach { recipient -> + recipient.contact = contacts.find { contact -> + contact.numbers.any { phoneNumberUtils.compare(recipient.address, it.address) } } } - realm.insertOrUpdate(updatedRecipients) + realm.insertOrUpdate(recipients) } } @@ -254,26 +260,24 @@ class SyncRepositoryImpl @Inject constructor( override fun syncContact(address: String): Boolean { // See if there's a contact that matches this phone number - var contact = getContacts().firstOrNull { - it.numbers.any { number -> phoneNumberUtils.compare(number.address, address) } + var contact = getContacts().find { contact -> + contact.numbers.any { number -> phoneNumberUtils.compare(number.address, address) } } ?: return false Realm.getDefaultInstance().use { realm -> - val recipients = realm.where(Recipient::class.java).findAll() + val recipients = realm.where(Recipient::class.java).findAll().filter { recipient -> + contact.numbers.any { number -> + phoneNumberUtils.compare(recipient.address, number.address) + } + } realm.executeTransaction { contact = realm.copyToRealmOrUpdate(contact) // Update all the matching recipients with the new contact - val updatedRecipients = recipients - .filter { recipient -> - contact.numbers.any { number -> - phoneNumberUtils.compare(recipient.address, number.address) - } - } - .map { recipient -> recipient.apply { this.contact = contact } } - - realm.insertOrUpdate(updatedRecipients) + recipients.forEach { recipient -> recipient.contact = contact } + + realm.insertOrUpdate(recipients) } } @@ -293,4 +297,22 @@ class SyncRepositoryImpl @Inject constructor( } ?: listOf() } -} \ No newline at end of file + private fun getContactGroups(contacts: List): List { + val groupMembers = cursorToContactGroupMember.getGroupMembersCursor() + ?.map(cursorToContactGroupMember::map) + .orEmpty() + + val groups = cursorToContactGroup.getContactGroupsCursor() + ?.map(cursorToContactGroup::map) + .orEmpty() + + groups.forEach { group -> + group.contacts.addAll(groupMembers + .filter { member -> member.groupId == group.id } + .mapNotNull { member -> contacts.find { contact -> contact.lookupKey == member.lookupKey } }) + } + + return groups + } + +} diff --git a/domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroup.kt b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroup.kt new file mode 100644 index 000000000..2de28c6f4 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroup.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.mapper + +import android.database.Cursor +import com.moez.QKSMS.model.ContactGroup + +interface CursorToContactGroup : Mapper { + + fun getContactGroupsCursor(): Cursor? + +} diff --git a/domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupMember.kt b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupMember.kt new file mode 100644 index 000000000..22861fdd4 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupMember.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.mapper + +import android.database.Cursor + +interface CursorToContactGroupMember : Mapper { + + data class GroupMember(val lookupKey: String, val groupId: Long) + + fun getGroupMembersCursor(): Cursor? + +} diff --git a/domain/src/main/java/com/moez/QKSMS/model/Contact.kt b/domain/src/main/java/com/moez/QKSMS/model/Contact.kt index e7e256379..dd325dacf 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Contact.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Contact.kt @@ -26,5 +26,6 @@ open class Contact( @PrimaryKey var lookupKey: String = "", var numbers: RealmList = RealmList(), var name: String = "", + var starred: Boolean = false, var lastUpdate: Long = 0 ) : RealmObject() \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/model/ContactGroup.kt b/domain/src/main/java/com/moez/QKSMS/model/ContactGroup.kt new file mode 100644 index 000000000..fd0e4363f --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/model/ContactGroup.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.model + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +open class ContactGroup( + @PrimaryKey var id: Long = 0, + var title: String = "", + var contacts: RealmList = RealmList() +) : RealmObject() diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index 8862b1894..c80d6fd19 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -51,6 +51,10 @@ import com.moez.QKSMS.manager.ShortcutManager import com.moez.QKSMS.manager.WidgetManager import com.moez.QKSMS.manager.WidgetManagerImpl import com.moez.QKSMS.mapper.CursorToContact +import com.moez.QKSMS.mapper.CursorToContactGroup +import com.moez.QKSMS.mapper.CursorToContactGroupImpl +import com.moez.QKSMS.mapper.CursorToContactGroupMember +import com.moez.QKSMS.mapper.CursorToContactGroupMemberImpl import com.moez.QKSMS.mapper.CursorToContactImpl import com.moez.QKSMS.mapper.CursorToConversation import com.moez.QKSMS.mapper.CursorToConversationImpl @@ -158,6 +162,12 @@ class AppModule(private var application: Application) { @Provides fun provideCursorToContact(mapper: CursorToContactImpl): CursorToContact = mapper + @Provides + fun provideCursorToContactGroup(mapper: CursorToContactGroupImpl): CursorToContactGroup = mapper + + @Provides + fun provideCursorToContactGroupMember(mapper: CursorToContactGroupMemberImpl): CursorToContactGroupMember = mapper + @Provides fun provideCursorToConversation(mapper: CursorToConversationImpl): CursorToConversation = mapper -- GitLab From a91643e882d9743ff70921db13edb52f78187786 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 27 Oct 2019 22:04:43 -0400 Subject: [PATCH 009/213] Display recent, starred, and contact groups in compose --- .../com/moez/QKSMS/filter/ContactFilter.kt | 2 +- .../moez/QKSMS/filter/ContactGroupFilter.kt | 32 ++++ .../QKSMS/repository/ContactRepositoryImpl.kt | 90 ++++++---- .../repository/ConversationRepositoryImpl.kt | 23 +++ .../QKSMS/repository/ContactRepository.kt | 7 +- .../repository/ConversationRepository.kt | 3 + .../QKSMS/feature/compose/ComposeActivity.kt | 6 +- .../moez/QKSMS/feature/compose/ComposeItem.kt | 52 ++++++ .../feature/compose/ComposeItemAdapter.kt | 167 ++++++++++++++++++ .../QKSMS/feature/compose/ComposeState.kt | 2 +- .../moez/QKSMS/feature/compose/ComposeView.kt | 2 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 76 +++++--- .../QKSMS/feature/compose/ContactAdapter.kt | 87 --------- .../src/main/res/layout/contact_list_item.xml | 32 ++-- 14 files changed, 414 insertions(+), 167 deletions(-) create mode 100644 data/src/main/java/com/moez/QKSMS/filter/ContactGroupFilter.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItem.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItemAdapter.kt delete mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt diff --git a/data/src/main/java/com/moez/QKSMS/filter/ContactFilter.kt b/data/src/main/java/com/moez/QKSMS/filter/ContactFilter.kt index 8e77da1af..6010c7b30 100644 --- a/data/src/main/java/com/moez/QKSMS/filter/ContactFilter.kt +++ b/data/src/main/java/com/moez/QKSMS/filter/ContactFilter.kt @@ -29,4 +29,4 @@ class ContactFilter @Inject constructor(private val phoneNumberFilter: PhoneNumb item.numbers.map { it.address }.any { address -> phoneNumberFilter.filter(address, query) } // Number } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/filter/ContactGroupFilter.kt b/data/src/main/java/com/moez/QKSMS/filter/ContactGroupFilter.kt new file mode 100644 index 000000000..903e16fd8 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/filter/ContactGroupFilter.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.filter + +import com.moez.QKSMS.extensions.removeAccents +import com.moez.QKSMS.model.ContactGroup +import javax.inject.Inject + +class ContactGroupFilter @Inject constructor(private val contactFilter: ContactFilter) : Filter() { + + override fun filter(item: ContactGroup, query: CharSequence): Boolean { + return item.title.removeAccents().contains(query, true) || // Name + item.contacts.any { contact -> contactFilter.filter(contact, query) } // Contacts + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt index 45248ff9e..842a423c1 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt @@ -25,10 +25,13 @@ import android.provider.ContactsContract import android.provider.ContactsContract.CommonDataKinds.Email import android.provider.ContactsContract.CommonDataKinds.Phone import com.moez.QKSMS.extensions.asFlowable +import com.moez.QKSMS.extensions.asObservable import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.model.Contact +import com.moez.QKSMS.model.ContactGroup import com.moez.QKSMS.util.Preferences import io.reactivex.Flowable +import io.reactivex.Observable import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers @@ -70,55 +73,68 @@ class ContactRepositoryImpl @Inject constructor( .findAll() } - override fun getUnmanagedContacts(): Flowable> { + override fun getUnmanagedContacts(starred: Boolean): Observable> { val realm = Realm.getDefaultInstance() - val mobileLabel by lazy { - Phone.getTypeLabel(context.resources, Phone.TYPE_MOBILE, "Mobile").toString() + val mobileOnly = prefs.mobileOnly.get() + val mobileLabel by lazy { Phone.getTypeLabel(context.resources, Phone.TYPE_MOBILE, "Mobile").toString() } + + var query = realm.where(Contact::class.java) + + if (mobileOnly) { + query = query.contains("numbers.type", mobileLabel) + } + + if (starred) { + query = query.equalTo("starred", true) } - val contactsFlowable = when (prefs.mobileOnly.get()) { - true -> realm.where(Contact::class.java) - .contains("numbers.type", mobileLabel) - .findAllAsync() - .asFlowable() - .filter { it.isLoaded } - .filter { it.isValid } - .map { realm.copyFromRealm(it) } - .subscribeOn(AndroidSchedulers.mainThread()) - .observeOn(Schedulers.io()) - .map { contacts -> + return query + .findAllAsync() + .asObservable() + .filter { it.isLoaded } + .filter { it.isValid } + .map { realm.copyFromRealm(it) } + .subscribeOn(AndroidSchedulers.mainThread()) + .observeOn(Schedulers.io()) + .map { contacts -> + if (mobileOnly) { contacts.map { contact -> val filteredNumbers = contact.numbers.filter { number -> number.type == mobileLabel } contact.numbers.clear() contact.numbers.addAll(filteredNumbers) contact } + } else { + contacts } - - false -> realm.where(Contact::class.java) - .findAllAsync() - .asFlowable() - .filter { it.isLoaded } - .filter { it.isValid } - .map { realm.copyFromRealm(it) } - .subscribeOn(AndroidSchedulers.mainThread()) - .observeOn(Schedulers.io()) - } - - return contactsFlowable.map { contacts -> - contacts.sortedWith(Comparator { c1, c2 -> - val initial = c1.name.firstOrNull() - val other = c2.name.firstOrNull() - if (initial?.isLetter() == true && other?.isLetter() != true) { - -1 - } else if (initial?.isLetter() != true && other?.isLetter() == true) { - 1 - } else { - c1.name.compareTo(c2.name, ignoreCase = true) } - }) - } + .map { contacts -> + contacts.sortedWith(Comparator { c1, c2 -> + val initial = c1.name.firstOrNull() + val other = c2.name.firstOrNull() + if (initial?.isLetter() == true && other?.isLetter() != true) { + -1 + } else if (initial?.isLetter() != true && other?.isLetter() == true) { + 1 + } else { + c1.name.compareTo(c2.name, ignoreCase = true) + } + }) + } + } + + override fun getUnmanagedContactGroups(): Observable> { + val realm = Realm.getDefaultInstance() + return realm.where(ContactGroup::class.java) + .isNotEmpty("contacts") + .findAllAsync() + .asObservable() + .filter { it.isLoaded } + .filter { it.isValid } + .map { realm.copyFromRealm(it) } + .subscribeOn(AndroidSchedulers.mainThread()) + .observeOn(Schedulers.io()) } } diff --git a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt index 0c05e2227..988b5ce4a 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt @@ -23,6 +23,7 @@ import android.content.Context import android.provider.Telephony import com.moez.QKSMS.compat.TelephonyCompat import com.moez.QKSMS.extensions.anyOf +import com.moez.QKSMS.extensions.asObservable import com.moez.QKSMS.extensions.map import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.filter.ConversationFilter @@ -35,6 +36,9 @@ import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.SearchResult import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.tryOrNull +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers import io.realm.Case import io.realm.Realm import io.realm.RealmResults @@ -183,6 +187,25 @@ class ConversationRepositoryImpl @Inject constructor( .findAll() } + override fun getUnmanagedConversations(): Observable> { + val realm = Realm.getDefaultInstance() + return realm.where(Conversation::class.java) + .sort("date", Sort.DESCENDING) + .notEqualTo("id", 0L) + .greaterThan("count", 0) + .equalTo("archived", false) + .equalTo("blocked", false) + .isNotEmpty("recipients") + .limit(5) + .findAllAsync() + .asObservable() + .filter { it.isLoaded } + .filter { it.isValid } + .map { realm.copyFromRealm(it) } + .subscribeOn(AndroidSchedulers.mainThread()) + .observeOn(Schedulers.io()) + } + override fun getRecipient(recipientId: Long): Recipient? { return Realm.getDefaultInstance() .where(Recipient::class.java) diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt index 037336261..0bc8471bd 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt @@ -20,7 +20,8 @@ package com.moez.QKSMS.repository import android.net.Uri import com.moez.QKSMS.model.Contact -import io.reactivex.Flowable +import com.moez.QKSMS.model.ContactGroup +import io.reactivex.Observable import io.reactivex.Single import io.realm.RealmResults @@ -30,6 +31,8 @@ interface ContactRepository { fun getContacts(): RealmResults - fun getUnmanagedContacts(): Flowable> + fun getUnmanagedContacts(starred: Boolean = false): Observable> + + fun getUnmanagedContactGroups(): Observable> } \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt index 7c517c105..a6f1a0711 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt @@ -21,6 +21,7 @@ package com.moez.QKSMS.repository import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.SearchResult +import io.reactivex.Observable import io.realm.RealmResults interface ConversationRepository { @@ -51,6 +52,8 @@ interface ConversationRepository { */ fun getConversations(vararg threadIds: Long): RealmResults + fun getUnmanagedConversations(): Observable> + fun getRecipient(recipientId: Long): Recipient? fun getThreadId(recipient: String): Long? 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 a0c38ad08..94706ccfc 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 @@ -76,7 +76,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { @Inject lateinit var attachmentAdapter: AttachmentAdapter @Inject lateinit var chipsAdapter: ChipsAdapter - @Inject lateinit var contactsAdapter: ContactAdapter + @Inject lateinit var contactsAdapter: ComposeItemAdapter @Inject lateinit var dateFormatter: DateFormatter @Inject lateinit var messageAdapter: MessagesAdapter @Inject lateinit var navigator: Navigator @@ -86,7 +86,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val queryChangedIntent: Observable by lazy { chipsAdapter.textChanges } override val queryBackspaceIntent: Observable<*> by lazy { chipsAdapter.backspaces } override val queryEditorActionIntent: Observable by lazy { chipsAdapter.actions } - override val chipSelectedIntent: Subject by lazy { contactsAdapter.contactSelected } + override val chipSelectedIntent: Subject by lazy { contactsAdapter.itemSelected } override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } override val menuReadyIntent: Observable = menu.map { Unit } override val optionsItemIntent: Subject = PublishSubject.create() @@ -211,7 +211,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } chipsAdapter.data = state.selectedContacts - contactsAdapter.data = state.contacts + contactsAdapter.data = state.composeItems loading.setVisible(state.loading) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItem.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItem.kt new file mode 100644 index 000000000..e089b689e --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItem.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.compose + +import com.moez.QKSMS.model.Contact +import com.moez.QKSMS.model.ContactGroup +import com.moez.QKSMS.model.Conversation +import com.moez.QKSMS.model.PhoneNumber +import io.realm.RealmList + +sealed class ComposeItem { + + abstract fun getContacts(): List + + data class New(val value: Contact) : ComposeItem() { + override fun getContacts(): List = listOf(value) + } + + data class Recent(val value: Conversation) : ComposeItem() { + override fun getContacts(): List = value.recipients.map { recipient -> + Contact(numbers = RealmList(PhoneNumber(address = recipient.address))) + } + } + + data class Starred(val value: Contact) : ComposeItem() { + override fun getContacts(): List = listOf(value) + } + + data class Group(val value: ContactGroup) : ComposeItem() { + override fun getContacts(): List = value.contacts + } + + data class Person(val value: Contact) : ComposeItem() { + override fun getContacts(): List = listOf(value) + } +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItemAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItemAdapter.kt new file mode 100644 index 000000000..8294fea6a --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItemAdapter.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.compose + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkAdapter +import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.forwardTouches +import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.model.Contact +import com.moez.QKSMS.model.ContactGroup +import com.moez.QKSMS.model.Conversation +import com.moez.QKSMS.model.Recipient +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.contact_list_item.view.* +import javax.inject.Inject + +class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAdapter() { + + val itemSelected: Subject = PublishSubject.create() + + private val numbersViewPool = RecyclerView.RecycledViewPool() + + 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) + + view.icon.setTint(colors.theme().theme) + + view.numbers.setRecycledViewPool(numbersViewPool) + view.numbers.adapter = PhoneNumberAdapter() + view.numbers.forwardTouches(view) + + return QkViewHolder(view).apply { + view.setOnClickListener { + val item = getItem(adapterPosition) + itemSelected.onNext(item) + } + } + } + + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val prevItem = if (position > 0) getItem(position - 1) else null + val item = getItem(position) + val view = holder.containerView + + when (item) { + is ComposeItem.New -> bindNew(view, item.value) + is ComposeItem.Recent -> bindRecent(view, item.value, prevItem) + is ComposeItem.Starred -> bindStarred(view, item.value, prevItem) + is ComposeItem.Person -> bindPerson(view, item.value, prevItem) + is ComposeItem.Group -> bindGroup(view, item.value, prevItem) + } + } + + private fun bindNew(view: View, contact: Contact) { + view.index.isVisible = false + + view.icon.isVisible = false + + view.avatar.contacts = listOf(Recipient(contact = contact)) + + view.title.text = contact.numbers.joinToString { it.address } + + view.subtitle.isVisible = false + + view.numbers.isVisible = false + } + + private fun bindRecent(view: View, conversation: Conversation, prev: ComposeItem?) { + view.index.isVisible = false + + view.icon.isVisible = prev !is ComposeItem.Recent + view.icon.setImageResource(R.drawable.ic_history_black_24dp) + + view.avatar.contacts = conversation.recipients + + view.title.text = conversation.getTitle() + + view.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() + view.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> + recipient.contact?.name ?: recipient.address + } + + view.numbers.isVisible = conversation.recipients.size == 1 + (view.numbers.adapter as PhoneNumberAdapter).data = conversation.recipients + .mapNotNull { recipient -> recipient.contact } + .flatMap { contact -> contact.numbers } + } + + private fun bindStarred(view: View, contact: Contact, prev: ComposeItem?) { + view.index.isVisible = false + + view.icon.isVisible = prev !is ComposeItem.Starred + view.icon.setImageResource(R.drawable.ic_star_black_24dp) + + view.avatar.contacts = listOf(Recipient(contact = contact)) + + view.title.text = contact.name + + view.subtitle.isVisible = false + + view.numbers.isVisible = true + (view.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + } + + private fun bindGroup(view: View, group: ContactGroup, prev: ComposeItem?) { + view.index.isVisible = false + + view.icon.isVisible = prev !is ComposeItem.Group + view.icon.setImageResource(R.drawable.ic_people_black_24dp) + + view.avatar.contacts = group.contacts.map { contact -> Recipient(contact = contact) } + + view.title.text = group.title + + view.subtitle.isVisible = true + view.subtitle.text = group.contacts.joinToString(", ") { it.name } + + view.numbers.isVisible = false + } + + private fun bindPerson(view: View, contact: Contact, prev: ComposeItem?) { + view.index.isVisible = true + view.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" + view.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()) + + view.icon.isVisible = false + + view.avatar.contacts = listOf(Recipient(contact = contact)) + + view.title.text = contact.name + + view.subtitle.isVisible = false + + view.numbers.isVisible = true + (view.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + } + + override fun areContentsTheSame(old: ComposeItem, new: ComposeItem): Boolean = false + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt index 29862e2ff..08312ef05 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt @@ -28,7 +28,7 @@ import io.realm.RealmResults data class ComposeState( val hasError: Boolean = false, val editingMode: Boolean = false, - val contacts: List = ArrayList(), + val composeItems: List = ArrayList(), val contactsVisible: Boolean = false, val selectedConversation: Long = 0, val selectedContacts: List = ArrayList(), diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index 137a2aae5..e420dac5f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -33,7 +33,7 @@ interface ComposeView : QkView { val queryChangedIntent: Observable val queryBackspaceIntent: Observable<*> val queryEditorActionIntent: Observable - val chipSelectedIntent: Subject + val chipSelectedIntent: Subject val chipDeletedIntent: Subject val menuReadyIntent: Observable val optionsItemIntent: Observable diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 9d54191ba..9da74c039 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -37,7 +37,9 @@ import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.extensions.removeAccents +import com.moez.QKSMS.feature.compose.ComposeItem.* import com.moez.QKSMS.filter.ContactFilter +import com.moez.QKSMS.filter.ContactGroupFilter import com.moez.QKSMS.interactor.AddScheduledMessage import com.moez.QKSMS.interactor.CancelDelayedMessage import com.moez.QKSMS.interactor.ContactSync @@ -50,6 +52,7 @@ import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Attachments import com.moez.QKSMS.model.Contact +import com.moez.QKSMS.model.ContactGroup import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.PhoneNumber @@ -89,6 +92,7 @@ class ComposeViewModel @Inject constructor( private val billingManager: BillingManager, private val cancelMessage: CancelDelayedMessage, private val contactFilter: ContactFilter, + private val contactGroupFilter: ContactGroupFilter, private val contactsRepo: ContactRepository, private val conversationRepo: ConversationRepository, private val deleteMessages: DeleteMessages, @@ -110,13 +114,16 @@ class ComposeViewModel @Inject constructor( ) { private val attachments: Subject> = BehaviorSubject.createDefault(sharedAttachments) - private val contacts: Observable> by lazy { contactsRepo.getUnmanagedContacts().toObservable() } + private val contactGroups: Observable> by lazy { contactsRepo.getUnmanagedContactGroups() } + private val contacts: Observable> by lazy { contactsRepo.getUnmanagedContacts() } private val contactsReducer: Subject<(List) -> List> = PublishSubject.create() + private val conversation: Subject = BehaviorSubject.create() + private val messages: Subject> = BehaviorSubject.create() + private val recents: Observable> by lazy { conversationRepo.getUnmanagedConversations() } private val selectedContacts: Subject> = BehaviorSubject.createDefault(listOf()) private val searchResults: Subject> = BehaviorSubject.create() private val searchSelection: Subject = BehaviorSubject.createDefault(-1) - private val conversation: Subject = BehaviorSubject.create() - private val messages: Subject> = BehaviorSubject.create() + private val starredContacts: Observable> by lazy { contactsRepo.getUnmanagedContacts(true) } init { val initialConversation = threadId.takeIf { it != 0L } @@ -250,31 +257,48 @@ class ComposeViewModel @Inject constructor( // Update the list of contact suggestions based on the query input, while also filtering out any contacts // that have already been selected Observables - .combineLatest(view.queryChangedIntent, contacts, selectedContacts) { query, contacts, - selectedContacts -> - - // Strip the accents from the query. This can be an expensive operation, so - // cache the result instead of doing it for each contact - val normalizedQuery = query.removeAccents() - - var filteredContacts = contacts - .filterNot { contact -> selectedContacts.contains(contact) } - .filter { contact -> contactFilter.filter(contact, normalizedQuery) } - - // If the entry is a valid destination, allow it as a recipient - if (phoneNumberUtils.isPossibleNumber(query.toString())) { - val newAddress = phoneNumberUtils.formatNumber(query) - val newContact = Contact(numbers = RealmList(PhoneNumber(address = newAddress))) - filteredContacts = listOf(newContact) + filteredContacts + .combineLatest( + view.queryChangedIntent, recents, starredContacts, contactGroups, contacts, selectedContacts + ) { query, recents, starredContacts, contactGroups, contacts, selectedContacts -> + val composeItems = mutableListOf() + if (query.isBlank()) { + composeItems += recents.map(::Recent) + composeItems += starredContacts.map(::Starred) + composeItems += contactGroups.map(::Group) + composeItems += contacts.map(::Person) + } else { + // If the entry is a valid destination, allow it as a recipient + if (phoneNumberUtils.isPossibleNumber(query.toString())) { + val newAddress = phoneNumberUtils.formatNumber(query) + val newContact = Contact(numbers = RealmList(PhoneNumber(address = newAddress))) + composeItems += New(newContact) + } + + // Strip the accents from the query. This can be an expensive operation, so + // cache the result instead of doing it for each contact + val normalizedQuery = query.removeAccents() + composeItems += starredContacts + .filterNot { contact -> selectedContacts.contains(contact) } + .filter { contact -> contactFilter.filter(contact, normalizedQuery) } + .map(::Starred) + + composeItems += contactGroups + .filter { group -> contactGroupFilter.filter(group, normalizedQuery) } + .map(::Group) + + composeItems += contacts + .filterNot { contact -> selectedContacts.contains(contact) } + .filter { contact -> contactFilter.filter(contact, normalizedQuery) } + .map(::Person) } - filteredContacts + composeItems } .skipUntil(state.filter { state -> state.editingMode }) .takeUntil(state.filter { state -> !state.editingMode }) .subscribeOn(Schedulers.computation()) .autoDisposable(view.scope()) - .subscribe { contacts -> newState { copy(contacts = contacts) } } + .subscribe { items -> newState { copy(composeItems = items) } } // Backspaces should delete the most recent contact if there's no text input // Close the activity if user presses back @@ -293,8 +317,8 @@ class ComposeViewModel @Inject constructor( .withLatestFrom(state) { _, state -> state } .autoDisposable(view.scope()) .subscribe { state -> - state.contacts.firstOrNull()?.let { contact -> - contactsReducer.onNext { contacts -> contacts + contact } + state.composeItems.firstOrNull()?.let { composeItem -> + contactsReducer.onNext { contacts -> contacts + composeItem.getContacts() } } } @@ -303,8 +327,10 @@ class ComposeViewModel @Inject constructor( view.chipDeletedIntent.doOnNext { contact -> contactsReducer.onNext { contacts -> contacts.filterNot { it == contact } } }, - view.chipSelectedIntent.doOnNext { contact -> - contactsReducer.onNext { contacts -> contacts.toMutableList().apply { add(contact) } } + view.chipSelectedIntent.doOnNext { composeItem -> + contactsReducer.onNext { contacts -> + contacts.toMutableList().apply { addAll(composeItem.getContacts()) } + } }) .skipUntil(state.filter { state -> state.editingMode }) .takeUntil(state.filter { state -> !state.editingMode }) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt deleted file mode 100644 index 595f69187..000000000 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.feature.compose - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView -import com.moez.QKSMS.R -import com.moez.QKSMS.common.base.QkAdapter -import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.common.util.extensions.forwardTouches -import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.model.Contact -import io.reactivex.subjects.PublishSubject -import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.contact_list_item.view.* -import javax.inject.Inject - -class ContactAdapter @Inject constructor() : QkAdapter() { - - val contactSelected: Subject = PublishSubject.create() - - private val numbersViewPool = RecyclerView.RecycledViewPool() - - 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) - - view.addresses.setRecycledViewPool(numbersViewPool) - view.addresses.adapter = PhoneNumberAdapter() - view.addresses.forwardTouches(view) - - return QkViewHolder(view).apply { - view.setOnClickListener { - val contact = getItem(adapterPosition) - contactSelected.onNext(contact) - } - } - } - - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val prevContact = if (position > 0) getItem(position - 1) else null - val contact = getItem(position) - val view = holder.containerView - - view.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" - view.index.isVisible = prevContact == null || - (contact.name[0].isLetter() && !contact.name[0].equals(prevContact.name[0], ignoreCase = true)) || - (!contact.name[0].isLetter() && prevContact.name[0].isLetter()) - - view.avatar.setContact(contact) - view.name.text = contact.name - view.name.setVisible(view.name.text.isNotEmpty()) - - (view.addresses.adapter as PhoneNumberAdapter).data = contact.numbers - } - - /** - * Creates a copy of the contact with only one phone number, so that the chips - * view can still display the name/photo, and not get confused about which phone number to use - */ - private fun copyContact(contact: Contact, numberIndex: Int) = Contact().apply { - lookupKey = contact.lookupKey - name = contact.name - numbers.add(contact.numbers[numberIndex]) - } - - override fun areContentsTheSame(old: Contact, new: Contact): Boolean = false - -} diff --git a/presentation/src/main/res/layout/contact_list_item.xml b/presentation/src/main/res/layout/contact_list_item.xml index 7ccfbbe5a..3b669e330 100644 --- a/presentation/src/main/res/layout/contact_list_item.xml +++ b/presentation/src/main/res/layout/contact_list_item.xml @@ -30,7 +30,7 @@ android:id="@+id/index" android:layout_width="24dp" android:layout_height="wrap_content" - android:layout_marginStart="12dp" + android:layout_marginStart="16dp" android:gravity="center_horizontal" android:maxLength="1" android:textStyle="bold" @@ -47,31 +47,31 @@ android:id="@+id/icon" android:layout_width="24dp" android:layout_height="24dp" - android:layout_marginStart="12dp" + android:layout_marginStart="16dp" android:visibility="invisible" app:layout_constraintBottom_toBottomOf="@id/avatar" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/avatar" /> - + + \ No newline at end of file -- GitLab From 73a36740e024e0211ffc89942a0519410859115d Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 27 Oct 2019 22:07:38 -0400 Subject: [PATCH 010/213] Organize compose package --- .../java/com/moez/QKSMS/feature/compose/ComposeActivity.kt | 3 +++ .../java/com/moez/QKSMS/feature/compose/ComposeState.kt | 1 + .../main/java/com/moez/QKSMS/feature/compose/ComposeView.kt | 1 + .../java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt | 3 ++- .../com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt | 6 +++++- .../QKSMS/feature/compose/{ => editing}/ChipsAdapter.kt | 4 ++-- .../moez/QKSMS/feature/compose/{ => editing}/ComposeItem.kt | 2 +- .../feature/compose/{ => editing}/ComposeItemAdapter.kt | 4 ++-- .../QKSMS/feature/compose/{ => editing}/DetailedChipView.kt | 5 ++--- .../feature/compose/{ => editing}/PhoneNumberAdapter.kt | 2 +- .../src/main/java/com/moez/QKSMS/injection/AppComponent.kt | 2 +- 11 files changed, 21 insertions(+), 12 deletions(-) rename presentation/src/main/java/com/moez/QKSMS/feature/compose/{ => editing}/ChipsAdapter.kt (98%) rename presentation/src/main/java/com/moez/QKSMS/feature/compose/{ => editing}/ComposeItem.kt (97%) rename presentation/src/main/java/com/moez/QKSMS/feature/compose/{ => editing}/ComposeItemAdapter.kt (98%) rename presentation/src/main/java/com/moez/QKSMS/feature/compose/{ => editing}/DetailedChipView.kt (95%) rename presentation/src/main/java/com/moez/QKSMS/feature/compose/{ => editing}/PhoneNumberAdapter.kt (97%) 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 94706ccfc..7f0f60bd6 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 @@ -53,6 +53,9 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.util.extensions.showKeyboard +import com.moez.QKSMS.feature.compose.editing.ChipsAdapter +import com.moez.QKSMS.feature.compose.editing.ComposeItem +import com.moez.QKSMS.feature.compose.editing.ComposeItemAdapter import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Contact import com.uber.autodispose.android.lifecycle.scope diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt index 08312ef05..9b4b7e6c9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt @@ -19,6 +19,7 @@ package com.moez.QKSMS.feature.compose import com.moez.QKSMS.compat.SubscriptionInfoCompat +import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Conversation diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index e420dac5f..7dd8cc5d4 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -22,6 +22,7 @@ import android.net.Uri import androidx.annotation.StringRes import androidx.core.view.inputmethod.InputContentInfoCompat import com.moez.QKSMS.common.base.QkView +import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Contact import io.reactivex.Observable diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 9da74c039..fd0b6b181 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -37,7 +37,8 @@ import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.extensions.removeAccents -import com.moez.QKSMS.feature.compose.ComposeItem.* +import com.moez.QKSMS.feature.compose.editing.ComposeItem +import com.moez.QKSMS.feature.compose.editing.ComposeItem.* import com.moez.QKSMS.filter.ContactFilter import com.moez.QKSMS.filter.ContactGroupFilter import com.moez.QKSMS.interactor.AddScheduledMessage diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt index b3f8acbfe..0ea9eab79 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt @@ -32,8 +32,12 @@ import android.view.Window import android.view.WindowManager import android.view.accessibility.AccessibilityEvent import androidx.annotation.RequiresApi +import com.moez.QKSMS.feature.compose.editing.DetailedChipView -class ComposeWindowCallback(private val localCallback: Window.Callback, private val activity: Activity) : Window.Callback { +class ComposeWindowCallback( + private val localCallback: Window.Callback, + private val activity: Activity +) : Window.Callback { override fun dispatchKeyEvent(keyEvent: KeyEvent): Boolean { return localCallback.dispatchKeyEvent(keyEvent) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ChipsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt similarity index 98% rename from presentation/src/main/java/com/moez/QKSMS/feature/compose/ChipsAdapter.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt index 2d7d7d50a..5bd9f7377 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ChipsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2019 Moez Bhatti * * This file is part of QKSMS. * @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.compose +package com.moez.QKSMS.feature.compose.editing import android.content.Context import android.view.LayoutInflater diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItem.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItem.kt similarity index 97% rename from presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItem.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItem.kt index e089b689e..894daa1e2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItem.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItem.kt @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.compose +package com.moez.QKSMS.feature.compose.editing import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.ContactGroup diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItemAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt similarity index 98% rename from presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItemAdapter.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt index 8294fea6a..8a4a4dcbe 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeItemAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItemAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2019 Moez Bhatti * * This file is part of QKSMS. * @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.compose +package com.moez.QKSMS.feature.compose.editing import android.view.LayoutInflater import android.view.View diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/DetailedChipView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/DetailedChipView.kt similarity index 95% rename from presentation/src/main/java/com/moez/QKSMS/feature/compose/DetailedChipView.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/DetailedChipView.kt index 5bcca0dae..731d9c9da 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/DetailedChipView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/DetailedChipView.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2019 Moez Bhatti * * This file is part of QKSMS. * @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.compose +package com.moez.QKSMS.feature.compose.editing import android.content.Context import android.view.View @@ -31,7 +31,6 @@ import com.moez.QKSMS.model.Contact import kotlinx.android.synthetic.main.contact_chip_detailed.view.* import javax.inject.Inject - class DetailedChipView(context: Context) : RelativeLayout(context) { @Inject lateinit var colors: Colors diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt similarity index 97% rename from presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt index ca7df4496..565bd1b9f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/PhoneNumberAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.compose +package com.moez.QKSMS.feature.compose.editing import android.view.LayoutInflater import android.view.ViewGroup diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt index 755152147..ae5481274 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt @@ -33,7 +33,7 @@ import com.moez.QKSMS.feature.blocking.BlockingController import com.moez.QKSMS.feature.blocking.manager.BlockingManagerController import com.moez.QKSMS.feature.blocking.messages.BlockedMessagesController import com.moez.QKSMS.feature.blocking.numbers.BlockedNumbersController -import com.moez.QKSMS.feature.compose.DetailedChipView +import com.moez.QKSMS.feature.compose.editing.DetailedChipView import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoComponent import com.moez.QKSMS.feature.settings.SettingsController import com.moez.QKSMS.feature.settings.about.AboutController -- GitLab From cd368dddfca0ff0f7056427f48e45c9e27bebee3 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Wed, 30 Oct 2019 21:05:41 -0400 Subject: [PATCH 011/213] Show button to add new contact --- .../QKSMS/feature/compose/ComposeActivity.kt | 15 ++-- .../QKSMS/feature/compose/ComposeState.kt | 2 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 15 ++-- .../feature/compose/editing/ChipsAdapter.kt | 88 ++++--------------- .../src/main/res/layout/compose_activity.xml | 14 +++ presentation/src/main/res/menu/compose.xml | 7 ++ presentation/src/main/res/values/strings.xml | 1 + 7 files changed, 56 insertions(+), 86 deletions(-) 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 7f0f60bd6..841a99616 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 @@ -41,6 +41,7 @@ import androidx.lifecycle.ViewModelProviders import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.material.snackbar.Snackbar import com.jakewharton.rxbinding2.view.clicks +import com.jakewharton.rxbinding2.widget.editorActions import com.jakewharton.rxbinding2.widget.textChanges import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator @@ -86,9 +87,9 @@ class ComposeActivity : QkThemedActivity(), ComposeView { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override val activityVisibleIntent: Subject = PublishSubject.create() - override val queryChangedIntent: Observable by lazy { chipsAdapter.textChanges } - override val queryBackspaceIntent: Observable<*> by lazy { chipsAdapter.backspaces } - override val queryEditorActionIntent: Observable by lazy { chipsAdapter.actions } + override val queryChangedIntent: Observable by lazy { search.textChanges() } + override val queryBackspaceIntent: Observable<*> by lazy { search.backspaces } + override val queryEditorActionIntent: Observable by lazy { search.editorActions() } override val chipSelectedIntent: Subject by lazy { contactsAdapter.itemSelected } override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } override val menuReadyIntent: Observable = menu.map { Unit } @@ -191,14 +192,16 @@ class ComposeActivity : QkThemedActivity(), ComposeView { toolbarSubtitle.text = getString(R.string.compose_subtitle_results, state.searchSelectionPosition, state.searchResults) toolbarTitle.setVisible(!state.editingMode) - chips.setVisible(state.editingMode) - contacts.setVisible(state.contactsVisible) - composeBar.setVisible(!state.contactsVisible && !state.loading) + chips.setVisible(state.editingMode && !state.searching) + search.setVisible(state.editingMode && state.searching) + contacts.setVisible(state.editingMode && state.searching) + composeBar.setVisible(!state.searching && !state.loading) // Don't set the adapters unless needed if (state.editingMode && chips.adapter == null) chips.adapter = chipsAdapter if (state.editingMode && contacts.adapter == null) contacts.adapter = contactsAdapter + toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode && !state.searching 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 && state.query.isEmpty() toolbar.menu.findItem(R.id.copy)?.isVisible = !state.editingMode && state.selectedMessages == 1 diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt index 9b4b7e6c9..2502442d9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt @@ -29,8 +29,8 @@ import io.realm.RealmResults data class ComposeState( val hasError: Boolean = false, val editingMode: Boolean = false, + val searching: Boolean = false, val composeItems: List = ArrayList(), - val contactsVisible: Boolean = false, val selectedConversation: Long = 0, val selectedContacts: List = ArrayList(), val sendAsGroup: Boolean = true, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index fd0b6b181..93602cb18 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -110,6 +110,7 @@ class ComposeViewModel @Inject constructor( private val syncContacts: ContactSync ) : QkViewModel(ComposeState( editingMode = threadId == 0L && address.isBlank(), + searching = threadId == 0L && address.isBlank(), selectedConversation = threadId, query = query) ) { @@ -243,17 +244,13 @@ class ComposeViewModel @Inject constructor( override fun bindView(view: ComposeView) { super.bindView(view) - // Set the contact suggestions list to visible at all times when in editing mode and there are no contacts - // selected yet, and also visible while in editing mode and there is text entered in the query field - Observables - .combineLatest(view.queryChangedIntent, selectedContacts) { query, selectedContacts -> - selectedContacts.isEmpty() || query.isNotEmpty() - } + // Set the contact suggestions list to visible when the add button is pressed + view.optionsItemIntent + .filter { it == R.id.add } .skipUntil(state.filter { state -> state.editingMode }) .takeUntil(state.filter { state -> !state.editingMode }) - .distinctUntilChanged() .autoDisposable(view.scope()) - .subscribe { contactsVisible -> newState { copy(contactsVisible = contactsVisible && editingMode) } } + .subscribe { newState { copy(searching = true) } } // Update the list of contact suggestions based on the query input, while also filtering out any contacts // that have already been selected @@ -318,6 +315,7 @@ class ComposeViewModel @Inject constructor( .withLatestFrom(state) { _, state -> state } .autoDisposable(view.scope()) .subscribe { state -> + newState { copy(searching = false) } state.composeItems.firstOrNull()?.let { composeItem -> contactsReducer.onNext { contacts -> contacts + composeItem.getContacts() } } @@ -329,6 +327,7 @@ class ComposeViewModel @Inject constructor( contactsReducer.onNext { contacts -> contacts.filterNot { it == contact } } }, view.chipSelectedIntent.doOnNext { composeItem -> + newState { copy(searching = false) } contactsReducer.onNext { contacts -> contacts.toMutableList().apply { addAll(composeItem.getContacts()) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt index 5bd9f7377..ca5b5364a 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt @@ -20,103 +20,49 @@ package com.moez.QKSMS.feature.compose.editing import android.content.Context import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.widget.RelativeLayout import androidx.recyclerview.widget.RecyclerView -import com.google.android.flexbox.FlexboxLayoutManager -import com.jakewharton.rxbinding2.widget.editorActions -import com.jakewharton.rxbinding2.widget.textChanges import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.extensions.dpToPx -import com.moez.QKSMS.common.util.extensions.resolveThemeColor -import com.moez.QKSMS.common.util.extensions.showKeyboard -import com.moez.QKSMS.common.widget.QkEditText import com.moez.QKSMS.model.Contact import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.contact_chip.view.* import javax.inject.Inject -class ChipsAdapter @Inject constructor(private val context: Context) : QkAdapter() { - - companion object { - private const val TYPE_EDIT_TEXT = 0 - private const val TYPE_ITEM = 1 - } - - private val hint: String = context.getString(R.string.title_compose) - private val editText = View.inflate(context, R.layout.chip_input_list_item, null) as QkEditText +class ChipsAdapter @Inject constructor() : QkAdapter() { var view: RecyclerView? = null val chipDeleted: PublishSubject = PublishSubject.create() - val textChanges = editText.textChanges() - val actions = editText.editorActions() - val backspaces = editText.backspaces - - init { - val wrap = ViewGroup.LayoutParams.WRAP_CONTENT - editText.layoutParams = FlexboxLayoutManager.LayoutParams(wrap, wrap).apply { - minHeight = 36.dpToPx(context) - minWidth = 56.dpToPx(context) - flexGrow = 8f - } - - editText.hint = hint - } - - override fun onDatasetChanged() { - editText.text = null - editText.hint = if (itemCount == 1) hint else null - if (itemCount != 2) { - editText.showKeyboard() - } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) { - TYPE_EDIT_TEXT -> { - editText.setTextColor(parent.context.resolveThemeColor(android.R.attr.textColorPrimary)) - editText.setHintTextColor(parent.context.resolveThemeColor(android.R.attr.textColorTertiary)) - QkViewHolder(editText) - } - - else -> { - val inflater = LayoutInflater.from(parent.context) - val view = inflater.inflate(R.layout.contact_chip, parent, false) - QkViewHolder(view).apply { - view.setOnClickListener { - val contact = getItem(adapterPosition) - showDetailedChip(view.context, contact) - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.contact_chip, parent, false) + return QkViewHolder(view).apply { + view.setOnClickListener { + val contact = getItem(adapterPosition) + showDetailedChip(view.context, contact) } } } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - when (getItemViewType(position)) { - TYPE_ITEM -> { - val contact = getItem(position) - val view = holder.containerView + val contact = getItem(position) + val view = holder.containerView - view.avatar.setContact(contact) + view.avatar.setContact(contact) - // If the contact's name is empty, try to display a phone number instead - // The contacts provided here should only have one number - view.name.text = if (contact.name.isNotBlank()) { - contact.name - } else { - contact.numbers.firstOrNull { it.address.isNotBlank() }?.address ?: "" - } - } + // If the contact's name is empty, try to display a phone number instead + // The contacts provided here should only have one number + view.name.text = if (contact.name.isNotBlank()) { + contact.name + } else { + contact.numbers.firstOrNull { it.address.isNotBlank() }?.address ?: "" } } - override fun getItemCount() = super.getItemCount() + 1 - - override fun getItemViewType(position: Int) = if (position == itemCount - 1) TYPE_EDIT_TEXT else TYPE_ITEM - /** * The [context] has to come from a view, because we're inflating a view that used themed attrs */ diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index 274da5dc8..16207e2b0 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -376,6 +376,20 @@ android:scrollbars="vertical" tools:visibility="gone" /> + + diff --git a/presentation/src/main/res/menu/compose.xml b/presentation/src/main/res/menu/compose.xml index 85d05b57f..c39f25ac2 100644 --- a/presentation/src/main/res/menu/compose.xml +++ b/presentation/src/main/res/menu/compose.xml @@ -20,6 +20,13 @@ + + Skip Continue + Add person Call Details Save to gallery -- GitLab From 132a6543b7532b85ec39924ed0907119cc34a9a6 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Wed, 30 Oct 2019 21:26:43 -0400 Subject: [PATCH 012/213] Go back to search mode if all contacts are removed --- .../java/com/moez/QKSMS/common/widget/GroupAvatarView.kt | 4 +++- .../com/moez/QKSMS/feature/compose/ComposeViewModel.kt | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt index 0021a7e42..d3f2d9afd 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt @@ -27,7 +27,9 @@ import com.moez.QKSMS.R import com.moez.QKSMS.model.Recipient import kotlinx.android.synthetic.main.group_avatar_view.view.* -class GroupAvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { +class GroupAvatarView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : ConstraintLayout(context, attrs) { var contacts: List = ArrayList() set(value) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 93602cb18..f96f163f9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -324,7 +324,13 @@ class ComposeViewModel @Inject constructor( // Update the list of selected contacts when a new contact is selected or an existing one is deselected Observable.merge( view.chipDeletedIntent.doOnNext { contact -> - contactsReducer.onNext { contacts -> contacts.filterNot { it == contact } } + contactsReducer.onNext { contacts -> + val result = contacts.filterNot { it == contact } + if (result.isEmpty()) { + newState { copy(searching = true) } + } + result + } }, view.chipSelectedIntent.doOnNext { composeItem -> newState { copy(searching = false) } -- GitLab From 9d08be9a5644200b2a6881ea1e04e8a702901ae8 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Thu, 31 Oct 2019 00:09:40 -0400 Subject: [PATCH 013/213] Updated group avatar style Closes #1492, closes #1093 --- .../QKSMS/common/widget/GroupAvatarView.kt | 34 ++++++------- .../src/main/res/layout/blocked_list_item.xml | 16 +++--- .../src/main/res/layout/contact_list_item.xml | 8 +-- .../res/layout/conversation_list_item.xml | 12 ++--- .../src/main/res/layout/group_avatar_view.xml | 50 ++++++++++++------- .../layout/scheduled_message_list_item.xml | 6 +-- .../src/main/res/layout/search_list_item.xml | 12 ++--- 7 files changed, 76 insertions(+), 62 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt index d3f2d9afd..904ec4608 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt @@ -19,11 +19,15 @@ package com.moez.QKSMS.common.widget import android.content.Context -import android.os.Build import android.util.AttributeSet import android.view.View import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import com.moez.QKSMS.R +import com.moez.QKSMS.common.util.extensions.getColorCompat +import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.model.Recipient import kotlinx.android.synthetic.main.group_avatar_view.view.* @@ -37,36 +41,30 @@ class GroupAvatarView @JvmOverloads constructor( updateView() } - private val avatars by lazy { listOf(avatar1, avatar2, avatar3) } - init { View.inflate(context, R.layout.group_avatar_view, this) - setBackgroundResource(R.drawable.circle) - clipToOutline = true } override fun onFinishInflate() { super.onFinishInflate() - avatars.forEach { avatar -> - avatar.setBackgroundResource(R.drawable.rectangle) - - // If we're on API 21 we need to reapply the tint after changing the background - if (Build.VERSION.SDK_INT < 22) { - avatar.applyTheme(0) - } - } - if (!isInEditMode) { updateView() } } private fun updateView() { - avatars.forEachIndexed { index, avatar -> - avatar.visibility = if (contacts.size > index) View.VISIBLE else View.GONE - avatar.setContact(contacts.getOrNull(index)) + avatar1Frame.setBackgroundTint(when (contacts.size > 1) { + true -> context.resolveThemeColor(android.R.attr.windowBackground) + false -> context.getColorCompat(android.R.color.transparent) + }) + avatar1Frame.updateLayoutParams { + matchConstraintPercentWidth = if (contacts.size > 1) 0.75f else 1.0f } + avatar2.isVisible = contacts.size > 1 + + avatar1.setContact(contacts.getOrNull(0)) + avatar2.setContact(contacts.getOrNull(1)) } -} \ No newline at end of file +} diff --git a/presentation/src/main/res/layout/blocked_list_item.xml b/presentation/src/main/res/layout/blocked_list_item.xml index 5c41be73c..a70b945eb 100644 --- a/presentation/src/main/res/layout/blocked_list_item.xml +++ b/presentation/src/main/res/layout/blocked_list_item.xml @@ -23,15 +23,15 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" - android:paddingTop="8dp" - android:paddingBottom="8dp" + android:paddingTop="4dp" + android:paddingBottom="4dp" app:layout_constraintVertical_chainStyle="packed"> @@ -69,12 +70,11 @@ android:id="@+id/blocker" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" android:layout_marginTop="2dp" android:textColor="?android:attr/textColorTertiary" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toEndOf="@id/avatars" + app:layout_constraintStart_toStartOf="@id/title" app:layout_constraintTop_toBottomOf="@id/title" app:textSize="secondary" tools:text="Call Control" /> diff --git a/presentation/src/main/res/layout/contact_list_item.xml b/presentation/src/main/res/layout/contact_list_item.xml index 3b669e330..9c10ef533 100644 --- a/presentation/src/main/res/layout/contact_list_item.xml +++ b/presentation/src/main/res/layout/contact_list_item.xml @@ -55,9 +55,9 @@ + android:paddingBottom="8dp"> - + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0" + app:layout_constraintWidth_percent=".75"> - + - + + + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="1" + app:layout_constraintWidth_percent=".75"> + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/scheduled_message_list_item.xml b/presentation/src/main/res/layout/scheduled_message_list_item.xml index 46e6733ae..6520ea741 100644 --- a/presentation/src/main/res/layout/scheduled_message_list_item.xml +++ b/presentation/src/main/res/layout/scheduled_message_list_item.xml @@ -25,12 +25,12 @@ android:background="?attr/selectableItemBackground" android:gravity="center_vertical" android:orientation="horizontal" - android:padding="16dp"> + android:padding="12dp"> Date: Thu, 31 Oct 2019 00:32:50 -0400 Subject: [PATCH 014/213] Fix avatars not working --- .../src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 12fcf47e0..a5b2ca156 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -94,7 +94,7 @@ class AvatarView @JvmOverloads constructor( lookupKey = contact?.lookupKey name = contact?.name // If a contactAddress has been given, we use it. Use the contact address otherwise. - address = contactAddress ?: contact?.numbers?.firstOrNull()?.address + address = contactAddress?.takeIf { it.isNotEmpty() } ?: contact?.numbers?.firstOrNull()?.address lastUpdated = contact?.lastUpdate updateView() } -- GitLab From b4ef834df07be5e55512b3453440039af30aa6a5 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Thu, 31 Oct 2019 01:17:59 -0400 Subject: [PATCH 015/213] Fix vertical padding for contact list item --- presentation/src/main/res/layout/contact_list_item.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/res/layout/contact_list_item.xml b/presentation/src/main/res/layout/contact_list_item.xml index 9c10ef533..0fc94716e 100644 --- a/presentation/src/main/res/layout/contact_list_item.xml +++ b/presentation/src/main/res/layout/contact_list_item.xml @@ -22,9 +22,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground" - android:paddingTop="8dp" - android:paddingBottom="8dp"> + android:background="?attr/selectableItemBackground"> Date: Thu, 31 Oct 2019 01:19:47 -0400 Subject: [PATCH 016/213] Filter out duplicate phone number entries that come from multiple contacts providers Closes #1285 --- .../moez/QKSMS/mapper/CursorToContactImpl.kt | 15 ++++++++------ .../moez/QKSMS/migration/QkRealmMigration.kt | 3 +++ .../QKSMS/repository/SyncRepositoryImpl.kt | 20 +++++++++++++++++-- .../java/com/moez/QKSMS/model/PhoneNumber.kt | 1 + 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt index 072e777ee..62f1602d3 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt @@ -35,6 +35,7 @@ class CursorToContactImpl @Inject constructor( val URI = Phone.CONTENT_URI val PROJECTION = arrayOf( Phone.LOOKUP_KEY, + Phone.ACCOUNT_TYPE_AND_DATA_SET, Phone.NUMBER, Phone.TYPE, Phone.LABEL, @@ -44,18 +45,20 @@ class CursorToContactImpl @Inject constructor( ) const val COLUMN_LOOKUP_KEY = 0 - const val COLUMN_NUMBER = 1 - const val COLUMN_TYPE = 2 - const val COLUMN_LABEL = 3 - const val COLUMN_DISPLAY_NAME = 4 - const val COLUMN_STARRED = 5 - const val CONTACT_LAST_UPDATED = 6 + const val COLUMN_ACCOUNT_TYPE = 1 + const val COLUMN_NUMBER = 2 + const val COLUMN_TYPE = 3 + const val COLUMN_LABEL = 4 + const val COLUMN_DISPLAY_NAME = 5 + const val COLUMN_STARRED = 6 + const val CONTACT_LAST_UPDATED = 7 } override fun map(from: Cursor) = Contact().apply { lookupKey = from.getString(COLUMN_LOOKUP_KEY) name = from.getString(COLUMN_DISPLAY_NAME) ?: "" numbers.add(PhoneNumber( + accountType = from.getString(COLUMN_ACCOUNT_TYPE), address = from.getString(COLUMN_NUMBER) ?: "", type = Phone.getTypeLabel(context.resources, from.getInt(COLUMN_TYPE), from.getString(COLUMN_LABEL)).toString() diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index 1461fa9bb..2cf98f1f8 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -127,6 +127,9 @@ class QkRealmMigration : RealmMigration { realm.schema.get("Contact") ?.addField("starred", Boolean::class.java, FieldAttribute.REQUIRED) + realm.schema.get("PhoneNumber") + ?.addField("accountType", String::class.java, FieldAttribute.REQUIRED) + version++ } diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index 679f592dc..77bd2f9e4 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -37,6 +37,7 @@ import com.moez.QKSMS.model.ContactGroup import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart +import com.moez.QKSMS.model.PhoneNumber import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.SyncLog import com.moez.QKSMS.util.PhoneNumberUtils @@ -289,10 +290,25 @@ class SyncRepositoryImpl @Inject constructor( ?.map { cursor -> cursorToContact.map(cursor) } ?.groupBy { contact -> contact.lookupKey } ?.map { contacts -> - val allNumbers = contacts.value.map { it.numbers }.flatten() + // Sometimes, contacts providers on the phone will create duplicate phone number entries. This + // commonly happens with Whatsapp. Let's try to detect these duplicate entries and filter them out + val uniqueNumbers = mutableListOf() + contacts.value + .flatMap { it.numbers } + .sortedBy { it.accountType } + .forEach { number -> + val duplicate = uniqueNumbers.any { other -> + number.accountType != other.accountType + && phoneNumberUtils.compare(number.address, other.address) + } + if (!duplicate) { + uniqueNumbers += number + } + } + contacts.value.first().apply { numbers.clear() - numbers.addAll(allNumbers) + numbers.addAll(uniqueNumbers) } } ?: listOf() } diff --git a/domain/src/main/java/com/moez/QKSMS/model/PhoneNumber.kt b/domain/src/main/java/com/moez/QKSMS/model/PhoneNumber.kt index 070fb1306..db02b9ef6 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/PhoneNumber.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/PhoneNumber.kt @@ -21,6 +21,7 @@ package com.moez.QKSMS.model import io.realm.RealmObject open class PhoneNumber( + var accountType: String = "", var address: String = "", var type: String = "" ) : RealmObject() \ No newline at end of file -- GitLab From c348fba3b7c5cbdf7eac2c9d24f106afb6113a3a Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Thu, 31 Oct 2019 01:28:40 -0400 Subject: [PATCH 017/213] Fix top margin for subtitle --- presentation/src/main/res/layout/contact_list_item.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/presentation/src/main/res/layout/contact_list_item.xml b/presentation/src/main/res/layout/contact_list_item.xml index 0fc94716e..d445cf22c 100644 --- a/presentation/src/main/res/layout/contact_list_item.xml +++ b/presentation/src/main/res/layout/contact_list_item.xml @@ -84,6 +84,7 @@ android:id="@+id/subtitle" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginTop="2dp" android:textColor="?android:attr/textColorTertiary" app:layout_constraintBottom_toTopOf="@id/numbers" app:layout_constraintEnd_toEndOf="parent" -- GitLab From 058d12d3bbe817bd5dee158b2a646d1c040d5b61 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Thu, 31 Oct 2019 02:06:53 -0400 Subject: [PATCH 018/213] Create chip model to keep original contact and know which number was selected --- .../QKSMS/feature/compose/ComposeActivity.kt | 10 +-- .../QKSMS/feature/compose/ComposeState.kt | 4 +- .../moez/QKSMS/feature/compose/ComposeView.kt | 4 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 68 +++++++++---------- .../QKSMS/feature/compose/editing/Chip.kt | 26 +++++++ .../feature/compose/editing/ChipsAdapter.kt | 32 +++------ .../feature/compose/editing/ComposeItem.kt | 2 +- .../compose/editing/DetailedChipView.kt | 9 ++- 8 files changed, 82 insertions(+), 73 deletions(-) create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/Chip.kt 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 841a99616..d8aa4bcd0 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 @@ -54,11 +54,11 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.util.extensions.showKeyboard +import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ChipsAdapter import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.ComposeItemAdapter import com.moez.QKSMS.model.Attachment -import com.moez.QKSMS.model.Contact import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable import dagger.android.AndroidInjection @@ -91,7 +91,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val queryBackspaceIntent: Observable<*> by lazy { search.backspaces } override val queryEditorActionIntent: Observable by lazy { search.editorActions() } override val chipSelectedIntent: Subject by lazy { contactsAdapter.itemSelected } - override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } + override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } override val menuReadyIntent: Observable = menu.map { Unit } override val optionsItemIntent: Subject = PublishSubject.create() override val sendAsGroupIntent by lazy { sendAsGroupBackground.clicks() } @@ -212,16 +212,16 @@ class ComposeActivity : QkThemedActivity(), ComposeView { 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() - if (chipsAdapter.data.isEmpty() && state.selectedContacts.isNotEmpty()) { + if (chipsAdapter.data.isEmpty() && state.selectedChips.isNotEmpty()) { message.showKeyboard() } - chipsAdapter.data = state.selectedContacts + chipsAdapter.data = state.selectedChips contactsAdapter.data = state.composeItems loading.setVisible(state.loading) - sendAsGroup.setVisible(state.editingMode && state.selectedContacts.size >= 2) + sendAsGroup.setVisible(state.editingMode && state.selectedChips.size >= 2) sendAsGroupSwitch.isChecked = state.sendAsGroup messageList.setVisible(state.sendAsGroup) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt index 2502442d9..b2f55b0ee 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt @@ -19,9 +19,9 @@ package com.moez.QKSMS.feature.compose import com.moez.QKSMS.compat.SubscriptionInfoCompat +import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.model.Attachment -import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import io.realm.RealmResults @@ -32,7 +32,7 @@ data class ComposeState( val searching: Boolean = false, val composeItems: List = ArrayList(), val selectedConversation: Long = 0, - val selectedContacts: List = ArrayList(), + val selectedChips: List = ArrayList(), val sendAsGroup: Boolean = true, val conversationtitle: String = "", val loading: Boolean = false, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index 7dd8cc5d4..78f1cbe82 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -22,9 +22,9 @@ import android.net.Uri import androidx.annotation.StringRes import androidx.core.view.inputmethod.InputContentInfoCompat import com.moez.QKSMS.common.base.QkView +import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.model.Attachment -import com.moez.QKSMS.model.Contact import io.reactivex.Observable import io.reactivex.subjects.Subject @@ -35,7 +35,7 @@ interface ComposeView : QkView { val queryBackspaceIntent: Observable<*> val queryEditorActionIntent: Observable val chipSelectedIntent: Subject - val chipDeletedIntent: Subject + val chipDeletedIntent: Subject val menuReadyIntent: Observable val optionsItemIntent: Observable val sendAsGroupIntent: Observable<*> diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index f96f163f9..d28fea7c6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -37,6 +37,7 @@ import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.extensions.removeAccents +import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.ComposeItem.* import com.moez.QKSMS.filter.ContactFilter @@ -118,11 +119,11 @@ class ComposeViewModel @Inject constructor( private val attachments: Subject> = BehaviorSubject.createDefault(sharedAttachments) private val contactGroups: Observable> by lazy { contactsRepo.getUnmanagedContactGroups() } private val contacts: Observable> by lazy { contactsRepo.getUnmanagedContacts() } - private val contactsReducer: Subject<(List) -> List> = PublishSubject.create() + private val chipsReducer: Subject<(List) -> List> = PublishSubject.create() private val conversation: Subject = BehaviorSubject.create() private val messages: Subject> = BehaviorSubject.create() private val recents: Observable> by lazy { conversationRepo.getUnmanagedConversations() } - private val selectedContacts: Subject> = BehaviorSubject.createDefault(listOf()) + private val selectedChips: Subject> = BehaviorSubject.createDefault(listOf()) private val searchResults: Subject> = BehaviorSubject.create() private val searchSelection: Subject = BehaviorSubject.createDefault(-1) private val starredContacts: Observable> by lazy { contactsRepo.getUnmanagedContacts(true) } @@ -133,9 +134,9 @@ class ComposeViewModel @Inject constructor( ?.asObservable() ?: Observable.empty() - val selectedConversation = selectedContacts + val selectedConversation = selectedChips .skipWhile { it.isEmpty() } - .map { contacts -> contacts.map { it.numbers.firstOrNull()?.address ?: "" } } + .map { chips -> chips.map { it.address } } .distinctUntilChanged() .doOnNext { newState { copy(loading = true) } } .observeOn(Schedulers.io()) @@ -177,15 +178,15 @@ class ComposeViewModel @Inject constructor( .subscribe(conversation::onNext) if (address.isNotBlank()) { - selectedContacts.onNext(listOf(Contact(numbers = RealmList(PhoneNumber(address))))) + selectedChips.onNext(listOf(Chip(address))) } - disposables += contactsReducer - .scan(listOf()) { previousState, reducer -> reducer(previousState) } - .doOnNext { contacts -> newState { copy(selectedContacts = contacts) } } + disposables += chipsReducer + .scan(listOf()) { previousState, reducer -> reducer(previousState) } + .doOnNext { chips -> newState { copy(selectedChips = chips) } } .skipUntil(state.filter { state -> state.editingMode }) .takeUntil(state.filter { state -> !state.editingMode }) - .subscribe(selectedContacts::onNext) + .subscribe(selectedChips::onNext) // When the conversation changes, mark read, and update the threadId and the messages for the adapter disposables += conversation @@ -256,8 +257,8 @@ class ComposeViewModel @Inject constructor( // that have already been selected Observables .combineLatest( - view.queryChangedIntent, recents, starredContacts, contactGroups, contacts, selectedContacts - ) { query, recents, starredContacts, contactGroups, contacts, selectedContacts -> + view.queryChangedIntent, recents, starredContacts, contactGroups, contacts, selectedChips + ) { query, recents, starredContacts, contactGroups, contacts, selectedChips -> val composeItems = mutableListOf() if (query.isBlank()) { composeItems += recents.map(::Recent) @@ -276,7 +277,7 @@ class ComposeViewModel @Inject constructor( // cache the result instead of doing it for each contact val normalizedQuery = query.removeAccents() composeItems += starredContacts - .filterNot { contact -> selectedContacts.contains(contact) } + .filterNot { contact -> selectedChips.map { it.contact }.contains(contact) } .filter { contact -> contactFilter.filter(contact, normalizedQuery) } .map(::Starred) @@ -285,7 +286,7 @@ class ComposeViewModel @Inject constructor( .map(::Group) composeItems += contacts - .filterNot { contact -> selectedContacts.contains(contact) } + .filterNot { contact -> selectedChips.map { it.contact }.contains(contact) } .filter { contact -> contactFilter.filter(contact, normalizedQuery) } .map(::Person) } @@ -301,47 +302,42 @@ class ComposeViewModel @Inject constructor( // Backspaces should delete the most recent contact if there's no text input // Close the activity if user presses back view.queryBackspaceIntent - .withLatestFrom(selectedContacts, view.queryChangedIntent) { event, contacts, query -> + .withLatestFrom(selectedChips, view.queryChangedIntent) { event, contacts, query -> if (contacts.isNotEmpty() && query.isEmpty()) { - contactsReducer.onNext { it.dropLast(1) } + chipsReducer.onNext { it.dropLast(1) } } } .autoDisposable(view.scope()) .subscribe() - // Enter the first contact suggestion if the enter button is pressed + // Enter the first contact suggestion if the enter button is pressed, and enter contacts that are selected view.queryEditorActionIntent .filter { actionId -> actionId == EditorInfo.IME_ACTION_DONE } .withLatestFrom(state) { _, state -> state } + .mapNotNull { state -> state.composeItems.firstOrNull() } + .mergeWith(view.chipSelectedIntent) .autoDisposable(view.scope()) - .subscribe { state -> + .subscribe { composeItem -> newState { copy(searching = false) } - state.composeItems.firstOrNull()?.let { composeItem -> - contactsReducer.onNext { contacts -> contacts + composeItem.getContacts() } + chipsReducer.onNext { chips -> + chips + composeItem.getContacts().map { Chip(it.numbers.first()?.address.orEmpty(), it) } } } // Update the list of selected contacts when a new contact is selected or an existing one is deselected - Observable.merge( - view.chipDeletedIntent.doOnNext { contact -> - contactsReducer.onNext { contacts -> + view.chipDeletedIntent + .skipUntil(state.filter { state -> state.editingMode }) + .takeUntil(state.filter { state -> !state.editingMode }) + .autoDisposable(view.scope()) + .subscribe { contact -> + chipsReducer.onNext { contacts -> val result = contacts.filterNot { it == contact } if (result.isEmpty()) { newState { copy(searching = true) } } result } - }, - view.chipSelectedIntent.doOnNext { composeItem -> - newState { copy(searching = false) } - contactsReducer.onNext { contacts -> - contacts.toMutableList().apply { addAll(composeItem.getContacts()) } - } - }) - .skipUntil(state.filter { state -> state.editingMode }) - .takeUntil(state.filter { state -> !state.editingMode }) - .autoDisposable(view.scope()) - .subscribe() + } // When the menu is loaded, trigger a new state so that the menu options can be rendered correctly view.menuReadyIntent @@ -667,12 +663,12 @@ class ComposeViewModel @Inject constructor( .filter { permissionManager.hasSendSms().also { if (!it) view.requestSmsPermission() } } .withLatestFrom(view.textChangedIntent) { _, body -> body } .map { body -> body.toString() } - .withLatestFrom(state, attachments, conversation, selectedContacts) { body, state, attachments, - conversation, contacts -> + .withLatestFrom(state, attachments, conversation, selectedChips) { body, state, attachments, + conversation, chips -> val subId = state.subscription?.subscriptionId ?: -1 val addresses = when (conversation.recipients.isNotEmpty()) { true -> conversation.recipients.map { it.address } - false -> contacts.mapNotNull { it.numbers.firstOrNull()?.address } + false -> chips.map { chip -> chip.address } } val delay = when (prefs.sendDelay.get()) { Preferences.SEND_DELAY_SHORT -> 3000 diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/Chip.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/Chip.kt new file mode 100644 index 000000000..31846fbdb --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/Chip.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.compose.editing + +import com.moez.QKSMS.model.Contact + +data class Chip( + val address: String, + val contact: Contact? = null +) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt index ca5b5364a..f0dc7935f 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt @@ -27,48 +27,40 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.extensions.dpToPx -import com.moez.QKSMS.model.Contact import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.contact_chip.view.* import javax.inject.Inject -class ChipsAdapter @Inject constructor() : QkAdapter() { +class ChipsAdapter @Inject constructor() : QkAdapter() { var view: RecyclerView? = null - val chipDeleted: PublishSubject = PublishSubject.create() + val chipDeleted: PublishSubject = PublishSubject.create() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val inflater = LayoutInflater.from(parent.context) val view = inflater.inflate(R.layout.contact_chip, parent, false) return QkViewHolder(view).apply { view.setOnClickListener { - val contact = getItem(adapterPosition) - showDetailedChip(view.context, contact) + val chip = getItem(adapterPosition) + showDetailedChip(view.context, chip) } } } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val contact = getItem(position) + val chip = getItem(position) val view = holder.containerView - view.avatar.setContact(contact) - - // If the contact's name is empty, try to display a phone number instead - // The contacts provided here should only have one number - view.name.text = if (contact.name.isNotBlank()) { - contact.name - } else { - contact.numbers.firstOrNull { it.address.isNotBlank() }?.address ?: "" - } + view.avatar.setContact(chip.contact, chip.address) + view.name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address } /** * The [context] has to come from a view, because we're inflating a view that used themed attrs */ - private fun showDetailedChip(context: Context, contact: Contact) { + private fun showDetailedChip(context: Context, chip: Chip) { val detailedChipView = DetailedChipView(context) - detailedChipView.setContact(contact) + detailedChipView.setChip(chip) val rootView = view?.rootView as ViewGroup @@ -83,12 +75,8 @@ class ChipsAdapter @Inject constructor() : QkAdapter() { detailedChipView.show() detailedChipView.setOnDeleteListener { - chipDeleted.onNext(contact) + chipDeleted.onNext(chip) detailedChipView.hide() } } - - override fun areItemsTheSame(old: Contact, new: Contact): Boolean { - return old.lookupKey == new.lookupKey - } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItem.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItem.kt index 894daa1e2..421418bf8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItem.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ComposeItem.kt @@ -34,7 +34,7 @@ sealed class ComposeItem { data class Recent(val value: Conversation) : ComposeItem() { override fun getContacts(): List = value.recipients.map { recipient -> - Contact(numbers = RealmList(PhoneNumber(address = recipient.address))) + recipient.contact ?: Contact(numbers = RealmList(PhoneNumber(address = recipient.address))) } } 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 731d9c9da..262d0ab6b 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 @@ -27,7 +27,6 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.injection.appComponent -import com.moez.QKSMS.model.Contact import kotlinx.android.synthetic.main.contact_chip_detailed.view.* import javax.inject.Inject @@ -54,10 +53,10 @@ class DetailedChipView(context: Context) : RelativeLayout(context) { } } - fun setContact(contact: Contact) { - avatar.setContact(contact) - name.text = contact.name - info.text = contact.numbers.joinToString(", ") { it.address } + fun setChip(chip: Chip) { + avatar.setContact(chip.contact, chip.address) + name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address + info.text = chip.address } fun show() { -- GitLab From bb816209ba00be5e4cd6dabca700e9a5a29934aa Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Thu, 7 Nov 2019 02:44:19 -0500 Subject: [PATCH 019/213] Build phone number picker --- data/build.gradle | 1 - .../moez/QKSMS/mapper/CursorToContactImpl.kt | 19 ++-- .../moez/QKSMS/migration/QkRealmMigration.kt | 2 + .../QKSMS/repository/ContactRepositoryImpl.kt | 16 +++ .../QKSMS/repository/SyncRepositoryImpl.kt | 11 +- domain/build.gradle | 1 - .../QKSMS/interactor/SetDefaultPhoneNumber.kt | 38 +++++++ .../main/java/com/moez/QKSMS/model/Contact.kt | 6 +- .../java/com/moez/QKSMS/model/PhoneNumber.kt | 7 +- .../QKSMS/repository/ContactRepository.kt | 4 +- .../com/moez/QKSMS/common/widget/QkDialog.kt | 105 ++++++++++++++++++ .../QKSMS/feature/compose/ComposeActivity.kt | 39 ++++++- .../QKSMS/feature/compose/ComposeState.kt | 2 + .../moez/QKSMS/feature/compose/ComposeView.kt | 7 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 48 +++++++- .../compose/editing/ComposeItemAdapter.kt | 10 +- .../compose/editing/PhoneNumberAction.kt | 25 +++++ .../editing/PhoneNumberPickerAdapter.kt | 77 +++++++++++++ .../res/layout/phone_number_list_item.xml | 33 ++++++ .../src/main/res/layout/qk_dialog.xml | 104 +++++++++++++++++ presentation/src/main/res/values/strings.xml | 4 + 21 files changed, 531 insertions(+), 28 deletions(-) create mode 100644 domain/src/main/java/com/moez/QKSMS/interactor/SetDefaultPhoneNumber.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAction.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt create mode 100644 presentation/src/main/res/layout/phone_number_list_item.xml create mode 100644 presentation/src/main/res/layout/qk_dialog.xml diff --git a/data/build.gradle b/data/build.gradle index ee2b5fe7a..e023ae7a5 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -79,7 +79,6 @@ dependencies { // coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version" diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt index 62f1602d3..23ba4e5fb 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt @@ -34,6 +34,7 @@ class CursorToContactImpl @Inject constructor( companion object { val URI = Phone.CONTENT_URI val PROJECTION = arrayOf( + Phone._ID, Phone.LOOKUP_KEY, Phone.ACCOUNT_TYPE_AND_DATA_SET, Phone.NUMBER, @@ -44,20 +45,22 @@ class CursorToContactImpl @Inject constructor( Phone.CONTACT_LAST_UPDATED_TIMESTAMP ) - const val COLUMN_LOOKUP_KEY = 0 - const val COLUMN_ACCOUNT_TYPE = 1 - const val COLUMN_NUMBER = 2 - const val COLUMN_TYPE = 3 - const val COLUMN_LABEL = 4 - const val COLUMN_DISPLAY_NAME = 5 - const val COLUMN_STARRED = 6 - const val CONTACT_LAST_UPDATED = 7 + const val COLUMN_ID = 0 + const val COLUMN_LOOKUP_KEY = 1 + const val COLUMN_ACCOUNT_TYPE = 2 + const val COLUMN_NUMBER = 3 + const val COLUMN_TYPE = 4 + const val COLUMN_LABEL = 5 + const val COLUMN_DISPLAY_NAME = 6 + const val COLUMN_STARRED = 7 + const val CONTACT_LAST_UPDATED = 8 } override fun map(from: Cursor) = Contact().apply { lookupKey = from.getString(COLUMN_LOOKUP_KEY) name = from.getString(COLUMN_DISPLAY_NAME) ?: "" numbers.add(PhoneNumber( + id = from.getLong(COLUMN_ID), accountType = from.getString(COLUMN_ACCOUNT_TYPE), address = from.getString(COLUMN_NUMBER) ?: "", type = Phone.getTypeLabel(context.resources, from.getInt(COLUMN_TYPE), diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index 2cf98f1f8..06a10c53f 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -128,7 +128,9 @@ class QkRealmMigration : RealmMigration { ?.addField("starred", Boolean::class.java, FieldAttribute.REQUIRED) realm.schema.get("PhoneNumber") + ?.addField("id", Long::class.java, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED) ?.addField("accountType", String::class.java, FieldAttribute.REQUIRED) + ?.addField("isDefault", Boolean::class.java, FieldAttribute.REQUIRED) version++ } diff --git a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt index 842a423c1..268913a68 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt @@ -137,4 +137,20 @@ class ContactRepositoryImpl @Inject constructor( .observeOn(Schedulers.io()) } + override fun setDefaultPhoneNumber(lookupKey: String, phoneNumberId: Long) { + Realm.getDefaultInstance().use { realm -> + realm.refresh() + val contact = realm.where(Contact::class.java) + .equalTo("lookupKey", lookupKey) + .findFirst() + ?: return + + realm.executeTransaction { + contact.numbers.forEach { number -> + number.isDefault = number.id == phoneNumberId + } + } + } + } + } diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index 77bd2f9e4..3748bdcb9 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -243,7 +243,7 @@ class SyncRepositoryImpl @Inject constructor( realm.delete(Contact::class.java) realm.delete(ContactGroup::class.java) - contacts = realm.copyToRealm(contacts) + contacts = realm.copyToRealmOrUpdate(contacts) realm.insertOrUpdate(getContactGroups(contacts)) // Update all the recipients with the new contacts @@ -286,6 +286,13 @@ class SyncRepositoryImpl @Inject constructor( } private fun getContacts(): List { + val defaultNumberIds = Realm.getDefaultInstance().use { realm -> + realm.where(PhoneNumber::class.java) + .equalTo("isDefault", true) + .findAll() + .map { number -> number.id } + } + return cursorToContact.getContactsCursor() ?.map { cursor -> cursorToContact.map(cursor) } ?.groupBy { contact -> contact.lookupKey } @@ -297,10 +304,12 @@ class SyncRepositoryImpl @Inject constructor( .flatMap { it.numbers } .sortedBy { it.accountType } .forEach { number -> + number.isDefault = defaultNumberIds.any { id -> id == number.id } val duplicate = uniqueNumbers.any { other -> number.accountType != other.accountType && phoneNumberUtils.compare(number.address, other.address) } + if (!duplicate) { uniqueNumbers += number } diff --git a/domain/build.gradle b/domain/build.gradle index f1098d77b..97817fa4b 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -53,7 +53,6 @@ dependencies { // coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version" diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/SetDefaultPhoneNumber.kt b/domain/src/main/java/com/moez/QKSMS/interactor/SetDefaultPhoneNumber.kt new file mode 100644 index 000000000..639c15426 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/interactor/SetDefaultPhoneNumber.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.interactor + +import com.moez.QKSMS.repository.ContactRepository +import io.reactivex.Flowable +import javax.inject.Inject + +class SetDefaultPhoneNumber @Inject constructor( + private val contactRepo: ContactRepository +) : Interactor() { + + data class Params(val lookupKey: String, val phoneNumberId: Long) + + override fun buildObservable(params: Params): Flowable<*> { + return Flowable.just(params) + .doOnNext { (lookupKey, phoneNumberId) -> + contactRepo.setDefaultPhoneNumber(lookupKey, phoneNumberId) + } + } + +} diff --git a/domain/src/main/java/com/moez/QKSMS/model/Contact.kt b/domain/src/main/java/com/moez/QKSMS/model/Contact.kt index dd325dacf..7e8cd27c9 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Contact.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Contact.kt @@ -28,4 +28,8 @@ open class Contact( var name: String = "", var starred: Boolean = false, var lastUpdate: Long = 0 -) : RealmObject() \ No newline at end of file +) : RealmObject() { + + fun getDefaultNumber(): PhoneNumber? = numbers.find { number -> number.isDefault } + +} diff --git a/domain/src/main/java/com/moez/QKSMS/model/PhoneNumber.kt b/domain/src/main/java/com/moez/QKSMS/model/PhoneNumber.kt index db02b9ef6..2669049df 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/PhoneNumber.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/PhoneNumber.kt @@ -19,9 +19,12 @@ package com.moez.QKSMS.model import io.realm.RealmObject +import io.realm.annotations.PrimaryKey open class PhoneNumber( + @PrimaryKey var id: Long = 0, var accountType: String = "", var address: String = "", - var type: String = "" -) : RealmObject() \ No newline at end of file + var type: String = "", + var isDefault: Boolean = false +) : RealmObject() diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt index 0bc8471bd..4242afb52 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt @@ -35,4 +35,6 @@ interface ContactRepository { fun getUnmanagedContactGroups(): Observable> -} \ No newline at end of file + fun setDefaultPhoneNumber(lookupKey: String, phoneNumberId: Long) + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt new file mode 100644 index 000000000..f275c8a0c --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.common.widget + +import android.app.Activity +import android.view.LayoutInflater +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkAdapter +import kotlinx.android.synthetic.main.qk_dialog.view.* + +class QkDialog(private val context: Activity) : AlertDialog(context) { + + private val view = LayoutInflater.from(context).inflate(R.layout.qk_dialog, null) + + @StringRes + var titleRes: Int? = null + set(value) { + field = value + title = value?.let(context::getString) + } + + var title: String? = null + set(value) { + field = value + view.title.text = value + view.title.isVisible = !value.isNullOrBlank() + } + + @StringRes + var subtitleRes: Int? = null + set(value) { + field = value + subtitle = value?.let(context::getString) + } + + var subtitle: String? = null + set(value) { + field = value + view.subtitle.text = value + view.subtitle.isVisible = !value.isNullOrBlank() + } + + var adapter: QkAdapter<*>? = null + set(value) { + field = value + view.list.isVisible = value != null + view.list.adapter = value + } + + var positiveButtonListener: (() -> Unit)? = null + + @StringRes + var positiveButton: Int? = null + set(value) { + field = value + value?.run(view.positiveButton::setText) + view.positiveButton.isVisible = value != null + view.positiveButton.setOnClickListener { + positiveButtonListener?.invoke() ?: dismiss() + } + } + + var negativeButtonListener: (() -> Unit)? = null + + @StringRes + var negativeButton: Int? = null + set(value) { + field = value + value?.run(view.negativeButton::setText) + view.negativeButton.isVisible = value != null + view.negativeButton.setOnClickListener { + negativeButtonListener?.invoke() ?: dismiss() + } + } + + var cancelListener: (() -> Unit)? = null + set(value) { + field = value + setOnCancelListener { value?.invoke() } + } + + init { + setView(view) + } + +} 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 d8aa4bcd0..02d395436 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 @@ -54,10 +54,14 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.util.extensions.showKeyboard +import com.moez.QKSMS.common.widget.QkDialog +import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ChipsAdapter import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.ComposeItemAdapter +import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction +import com.moez.QKSMS.feature.compose.editing.PhoneNumberPickerAdapter import com.moez.QKSMS.model.Attachment import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable @@ -84,13 +88,17 @@ class ComposeActivity : QkThemedActivity(), ComposeView { @Inject lateinit var dateFormatter: DateFormatter @Inject lateinit var messageAdapter: MessagesAdapter @Inject lateinit var navigator: Navigator + @Inject lateinit var phoneNumberAdapter: PhoneNumberPickerAdapter @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override val activityVisibleIntent: Subject = PublishSubject.create() override val queryChangedIntent: Observable by lazy { search.textChanges() } override val queryBackspaceIntent: Observable<*> by lazy { search.backspaces } override val queryEditorActionIntent: Observable by lazy { search.editorActions() } - override val chipSelectedIntent: Subject by lazy { contactsAdapter.itemSelected } + override val composeItemPressedIntent: Subject by lazy { contactsAdapter.clicks } + override val composeItemLongPressedIntent: Subject by lazy { contactsAdapter.longClicks } + override val phoneNumberSelectedIntent: Subject> by lazy { phoneNumberAdapter.selectedItemChanges } + override val phoneNumberActionIntent: Subject = PublishSubject.create() override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } override val menuReadyIntent: Observable = menu.map { Unit } override val optionsItemIntent: Subject = PublishSubject.create() @@ -116,6 +124,18 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val viewQksmsPlusIntent: Subject = PublishSubject.create() override val backPressedIntent: Subject = PublishSubject.create() + private val phoneNumberDialog by lazy { + QkDialog(this).apply { + titleRes = R.string.compose_number_picker_title + adapter = phoneNumberAdapter + positiveButton = R.string.compose_number_picker_always + positiveButtonListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.ALWAYS) } + negativeButton = R.string.compose_number_picker_once + negativeButtonListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.JUST_ONCE) } + cancelListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.CANCEL) } + } + } + private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ComposeViewModel::class.java] } private var cameraDestination: Uri? = null @@ -202,8 +222,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { if (state.editingMode && contacts.adapter == null) contacts.adapter = contactsAdapter toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode && !state.searching - 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 && state.query.isEmpty() + 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 + && state.query.isEmpty() toolbar.menu.findItem(R.id.copy)?.isVisible = !state.editingMode && state.selectedMessages == 1 toolbar.menu.findItem(R.id.details)?.isVisible = !state.editingMode && state.selectedMessages == 1 toolbar.menu.findItem(R.id.delete)?.isVisible = !state.editingMode && state.selectedMessages > 0 @@ -219,6 +241,14 @@ class ComposeActivity : QkThemedActivity(), ComposeView { chipsAdapter.data = state.selectedChips contactsAdapter.data = state.composeItems + if (state.selectedContact != null && !phoneNumberDialog.isShowing) { + phoneNumberAdapter.data = state.selectedContact.numbers + phoneNumberDialog.subtitle = state.selectedContact.name + phoneNumberDialog.show() + } else if (state.selectedContact == null && phoneNumberDialog.isShowing) { + phoneNumberDialog.dismiss() + } + loading.setVisible(state.loading) sendAsGroup.setVisible(state.editingMode && state.selectedChips.size >= 2) @@ -282,7 +312,8 @@ class ComposeActivity : QkThemedActivity(), ComposeView { calendar.set(Calendar.HOUR_OF_DAY, hour) calendar.set(Calendar.MINUTE, minute) scheduleSelectedIntent.onNext(calendar.timeInMillis) - }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), DateFormat.is24HourFormat(this)).show() + }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), DateFormat.is24HourFormat(this)) + .show() }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)).show() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt index b2f55b0ee..f7b7e7b88 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt @@ -22,6 +22,7 @@ import com.moez.QKSMS.compat.SubscriptionInfoCompat import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.model.Attachment +import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import io.realm.RealmResults @@ -32,6 +33,7 @@ data class ComposeState( val searching: Boolean = false, val composeItems: List = ArrayList(), val selectedConversation: Long = 0, + val selectedContact: Contact? = null, // For phone number picker val selectedChips: List = ArrayList(), val sendAsGroup: Boolean = true, val conversationtitle: String = "", diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index 78f1cbe82..4c4af90f6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -22,8 +22,10 @@ import android.net.Uri import androidx.annotation.StringRes import androidx.core.view.inputmethod.InputContentInfoCompat import com.moez.QKSMS.common.base.QkView +import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ComposeItem +import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction import com.moez.QKSMS.model.Attachment import io.reactivex.Observable import io.reactivex.subjects.Subject @@ -34,7 +36,10 @@ interface ComposeView : QkView { val queryChangedIntent: Observable val queryBackspaceIntent: Observable<*> val queryEditorActionIntent: Observable - val chipSelectedIntent: Subject + val composeItemPressedIntent: Subject + val composeItemLongPressedIntent: Subject + val phoneNumberSelectedIntent: Subject> + val phoneNumberActionIntent: Subject val chipDeletedIntent: Subject val menuReadyIntent: Observable val optionsItemIntent: Observable diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index d28fea7c6..3d00fcddd 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -40,6 +40,7 @@ import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.ComposeItem.* +import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction import com.moez.QKSMS.filter.ContactFilter import com.moez.QKSMS.filter.ContactGroupFilter import com.moez.QKSMS.interactor.AddScheduledMessage @@ -49,6 +50,7 @@ import com.moez.QKSMS.interactor.DeleteMessages import com.moez.QKSMS.interactor.MarkRead import com.moez.QKSMS.interactor.RetrySending import com.moez.QKSMS.interactor.SendMessage +import com.moez.QKSMS.interactor.SetDefaultPhoneNumber import com.moez.QKSMS.manager.ActiveConversationManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.Attachment @@ -77,6 +79,8 @@ import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import io.realm.RealmList +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.rx2.awaitFirst import timber.log.Timber import java.util.* import javax.inject.Inject @@ -107,6 +111,7 @@ class ComposeViewModel @Inject constructor( private val prefs: Preferences, private val retrySending: RetrySending, private val sendMessage: SendMessage, + private val setDefaultPhoneNumber: SetDefaultPhoneNumber, private val subscriptionManager: SubscriptionManagerCompat, private val syncContacts: ContactSync ) : QkViewModel(ComposeState( @@ -310,18 +315,49 @@ class ComposeViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe() - // Enter the first contact suggestion if the enter button is pressed, and enter contacts that are selected + // Listen for ComposeItems being selected, and then send them off to the number picker dialog in case + // the user needs to select a phone number view.queryEditorActionIntent .filter { actionId -> actionId == EditorInfo.IME_ACTION_DONE } .withLatestFrom(state) { _, state -> state } .mapNotNull { state -> state.composeItems.firstOrNull() } - .mergeWith(view.chipSelectedIntent) + .mergeWith(view.composeItemPressedIntent) + .map { composeItem -> composeItem to false } + .mergeWith(view.composeItemLongPressedIntent.map { composeItem -> composeItem to true }) + .observeOn(Schedulers.io()) + .skipUntil(state.filter { state -> state.editingMode }) + .takeUntil(state.filter { state -> !state.editingMode }) .autoDisposable(view.scope()) - .subscribe { composeItem -> - newState { copy(searching = false) } - chipsReducer.onNext { chips -> - chips + composeItem.getContacts().map { Chip(it.numbers.first()?.address.orEmpty(), it) } + .subscribe { (composeItem, force) -> + val contacts = composeItem.getContacts() + val newChips = contacts.map { contact -> + if (contact.numbers.size == 1 || contact.getDefaultNumber() != null && !force) { + val number = contact.getDefaultNumber() ?: contact.numbers[0]!! + Chip(number.address, contact) + } else { + runBlocking { + newState { copy(selectedContact = contact) } + val action = view.phoneNumberActionIntent.awaitFirst() + newState { copy(selectedContact = null) } + val numberId = view.phoneNumberSelectedIntent.awaitFirst().value + val number = contact.numbers.find { number -> number.id == numberId } + + if (action == PhoneNumberAction.CANCEL || number == null) { + return@runBlocking null + } + + if (action == PhoneNumberAction.ALWAYS) { + val params = SetDefaultPhoneNumber.Params(contact.lookupKey, number.id) + setDefaultPhoneNumber.execute(params) + } + + Chip(number.address, contact) + } ?: return@subscribe + } } + + newState { copy(searching = false) } + chipsReducer.onNext { chips -> chips + newChips } } // Update the list of selected contacts when a new contact is selected or an existing one is deselected 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 8a4a4dcbe..b411b2143 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 @@ -40,7 +40,8 @@ import javax.inject.Inject class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAdapter() { - val itemSelected: Subject = PublishSubject.create() + val clicks: Subject = PublishSubject.create() + val longClicks: Subject = PublishSubject.create() private val numbersViewPool = RecyclerView.RecycledViewPool() @@ -57,7 +58,12 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda return QkViewHolder(view).apply { view.setOnClickListener { val item = getItem(adapterPosition) - itemSelected.onNext(item) + clicks.onNext(item) + } + view.setOnLongClickListener { + val item = getItem(adapterPosition) + longClicks.onNext(item) + true } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAction.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAction.kt new file mode 100644 index 000000000..7464cefd3 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAction.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.compose.editing + +enum class PhoneNumberAction { + CANCEL, + JUST_ONCE, + ALWAYS +} 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 new file mode 100644 index 000000000..3464241f7 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberPickerAdapter.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.compose.editing + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkAdapter +import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.extensions.Optional +import com.moez.QKSMS.model.PhoneNumber +import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.phone_number_list_item.view.* +import kotlinx.android.synthetic.main.radio_preference_view.view.* +import javax.inject.Inject + +class PhoneNumberPickerAdapter @Inject constructor( + private val context: Context +) : QkAdapter() { + + val selectedItemChanges: Subject> = BehaviorSubject.create() + + private var selectedItem: Long? = null + set(value) { + data.indexOfFirst { number -> number.id == field }.takeIf { it != -1 }?.run(::notifyItemChanged) + field = value + data.indexOfFirst { number -> number.id == field }.takeIf { it != -1 }?.run(::notifyItemChanged) + selectedItemChanges.onNext(Optional(value)) + } + + 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 { + view.setOnClickListener { + val phoneNumber = getItem(adapterPosition) + selectedItem = phoneNumber.id + } + } + } + + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val phoneNumber = getItem(position) + val view = holder.itemView + + view.number.radioButton.isChecked = phoneNumber.id == selectedItem + view.number.titleView.text = phoneNumber.address + view.number.summaryView.text = when (phoneNumber.isDefault) { + true -> context.getString(R.string.compose_number_picker_default, phoneNumber.type) + false -> phoneNumber.type + } + } + + override fun onDatasetChanged() { + super.onDatasetChanged() + selectedItem = data.find { number -> number.isDefault }?.id ?: data.firstOrNull()?.id + } + +} diff --git a/presentation/src/main/res/layout/phone_number_list_item.xml b/presentation/src/main/res/layout/phone_number_list_item.xml new file mode 100644 index 000000000..c5a850483 --- /dev/null +++ b/presentation/src/main/res/layout/phone_number_list_item.xml @@ -0,0 +1,33 @@ + + + + + + + diff --git a/presentation/src/main/res/layout/qk_dialog.xml b/presentation/src/main/res/layout/qk_dialog.xml new file mode 100644 index 000000000..f38decc29 --- /dev/null +++ b/presentation/src/main/res/layout/qk_dialog.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index ac18e9279..01be513b2 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -98,6 +98,10 @@ Delete + Choose a phone number + %s ∙ Default + Just once + Always %d selected %1$d of %2$d results Send as group message -- GitLab From 29068f52c9f7d2239e9221013fde47ec81769d90 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Tue, 12 Nov 2019 23:27:31 -0500 Subject: [PATCH 020/213] Merge issues --- .../com/moez/QKSMS/repository/ConversationRepositoryImpl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt index 988b5ce4a..24935ee6b 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt @@ -190,9 +190,9 @@ class ConversationRepositoryImpl @Inject constructor( override fun getUnmanagedConversations(): Observable> { val realm = Realm.getDefaultInstance() return realm.where(Conversation::class.java) - .sort("date", Sort.DESCENDING) + .sort("lastMessage.date", Sort.DESCENDING) .notEqualTo("id", 0L) - .greaterThan("count", 0) + .isNotNull("lastMessage") .equalTo("archived", false) .equalTo("blocked", false) .isNotEmpty("recipients") -- GitLab From af2e550ea46b63df836088ea148e324ff61d8929 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Tue, 12 Nov 2019 23:50:44 -0500 Subject: [PATCH 021/213] Guard against duplicate phone numbers --- .../main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index 3748bdcb9..0b1f2362f 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -162,7 +162,7 @@ class SyncRepositoryImpl @Inject constructor( // Sync recipients recipientCursor?.use { - val contacts = realm.copyToRealm(getContacts()) + val contacts = realm.copyToRealmOrUpdate(getContacts()) val recipients = recipientCursor.map { cursor -> progress++ syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) -- GitLab From 8d43d60e5603a0025471ecbd0e797d1350059164 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 17 Nov 2019 01:17:01 -0500 Subject: [PATCH 022/213] Split contacts to new screen, allow attaching multiple photos --- .../QKSMS/repository/ContactRepositoryImpl.kt | 9 + domain/build.gradle | 1 + .../QKSMS/repository/ContactRepository.kt | 2 + presentation/src/main/AndroidManifest.xml | 1 + .../QKSMS/feature/compose/ComposeActivity.kt | 101 ++++----- .../QKSMS/feature/compose/ComposeState.kt | 5 - .../moez/QKSMS/feature/compose/ComposeView.kt | 12 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 164 +++----------- .../feature/contacts/ContactsActivity.kt | 104 +++++++++ .../contacts/ContactsActivityModule.kt | 42 ++++ .../feature/contacts/ContactsContract.kt | 40 ++++ .../QKSMS/feature/contacts/ContactsState.kt | 27 +++ .../feature/contacts/ContactsViewModel.kt | 201 ++++++++++++++++++ .../android/ActivityBuilderModule.kt | 6 + .../src/main/res/layout/compose_activity.xml | 46 +--- .../src/main/res/layout/contacts_activity.xml | 80 +++++++ 16 files changed, 592 insertions(+), 249 deletions(-) create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivity.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivityModule.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsState.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt create mode 100644 presentation/src/main/res/layout/contacts_activity.xml diff --git a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt index 268913a68..ee3d25fa2 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt @@ -73,6 +73,15 @@ class ContactRepositoryImpl @Inject constructor( .findAll() } + override fun getUnmanagedContact(lookupKey: String): Contact? { + return Realm.getDefaultInstance().use { realm -> + realm.where(Contact::class.java) + .equalTo("lookupKey", lookupKey) + .findFirst() + ?.let(realm::copyFromRealm) + } + } + override fun getUnmanagedContacts(starred: Boolean): Observable> { val realm = Realm.getDefaultInstance() diff --git a/domain/build.gradle b/domain/build.gradle index 97817fa4b..a018510e7 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -19,6 +19,7 @@ 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/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt index 4242afb52..b6f8011b1 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt @@ -31,6 +31,8 @@ interface ContactRepository { fun getContacts(): RealmResults + fun getUnmanagedContact(lookupKey: String): Contact? + fun getUnmanagedContacts(starred: Boolean = false): Observable> fun getUnmanagedContactGroups(): Observable> diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index 9301275cc..08e36b7ab 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -109,6 +109,7 @@ android:windowSoftInputMode="adjustResize" /> + 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 02d395436..be4028a56 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 @@ -41,7 +41,6 @@ import androidx.lifecycle.ViewModelProviders import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.material.snackbar.Snackbar import com.jakewharton.rxbinding2.view.clicks -import com.jakewharton.rxbinding2.widget.editorActions import com.jakewharton.rxbinding2.widget.textChanges import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator @@ -54,14 +53,9 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.util.extensions.showKeyboard -import com.moez.QKSMS.common.widget.QkDialog -import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ChipsAdapter -import com.moez.QKSMS.feature.compose.editing.ComposeItem -import com.moez.QKSMS.feature.compose.editing.ComposeItemAdapter -import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction -import com.moez.QKSMS.feature.compose.editing.PhoneNumberPickerAdapter +import com.moez.QKSMS.feature.contacts.ContactsActivity import com.moez.QKSMS.model.Attachment import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable @@ -73,32 +67,26 @@ import kotlinx.android.synthetic.main.compose_activity.* import java.text.SimpleDateFormat import java.util.* import javax.inject.Inject +import kotlin.collections.HashMap class ComposeActivity : QkThemedActivity(), ComposeView { companion object { - private const val CAMERA_REQUEST_CODE = 0 - private const val GALLERY_REQUEST_CODE = 1 - private const val CONTACT_REQUEST_CODE = 2 + private const val SELECT_CONTACT_REQUEST_CODE = 0 + private const val TAKE_PHOTO_REQUEST_CODE = 1 + private const val ATTACH_PHOTO_REQUEST_CODE = 2 + private const val ATTACH_CONTACT_REQUEST_CODE = 3 } @Inject lateinit var attachmentAdapter: AttachmentAdapter @Inject lateinit var chipsAdapter: ChipsAdapter - @Inject lateinit var contactsAdapter: ComposeItemAdapter @Inject lateinit var dateFormatter: DateFormatter @Inject lateinit var messageAdapter: MessagesAdapter @Inject lateinit var navigator: Navigator - @Inject lateinit var phoneNumberAdapter: PhoneNumberPickerAdapter @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override val activityVisibleIntent: Subject = PublishSubject.create() - override val queryChangedIntent: Observable by lazy { search.textChanges() } - override val queryBackspaceIntent: Observable<*> by lazy { search.backspaces } - override val queryEditorActionIntent: Observable by lazy { 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 } - override val phoneNumberActionIntent: Subject = PublishSubject.create() + override val chipsSelectedIntent: Subject> = PublishSubject.create() override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } override val menuReadyIntent: Observable = menu.map { Unit } override val optionsItemIntent: Subject = PublishSubject.create() @@ -124,18 +112,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val viewQksmsPlusIntent: Subject = PublishSubject.create() override val backPressedIntent: Subject = PublishSubject.create() - private val phoneNumberDialog by lazy { - QkDialog(this).apply { - titleRes = R.string.compose_number_picker_title - adapter = phoneNumberAdapter - positiveButton = R.string.compose_number_picker_always - positiveButtonListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.ALWAYS) } - negativeButton = R.string.compose_number_picker_once - negativeButtonListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.JUST_ONCE) } - cancelListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.CANCEL) } - } - } - private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ComposeViewModel::class.java] } private var cameraDestination: Uri? = null @@ -153,7 +129,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { chipsAdapter.view = chips - contacts.itemAnimator = null chips.itemAnimator = null chips.layoutManager = FlexboxLayoutManager(this) @@ -209,19 +184,17 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } toolbarSubtitle.setVisible(state.query.isNotEmpty()) - toolbarSubtitle.text = getString(R.string.compose_subtitle_results, state.searchSelectionPosition, state.searchResults) + toolbarSubtitle.text = getString(R.string.compose_subtitle_results, state.searchSelectionPosition, + state.searchResults) toolbarTitle.setVisible(!state.editingMode) - chips.setVisible(state.editingMode && !state.searching) - search.setVisible(state.editingMode && state.searching) - contacts.setVisible(state.editingMode && state.searching) - composeBar.setVisible(!state.searching && !state.loading) + chips.setVisible(state.editingMode) + composeBar.setVisible(!state.loading) // Don't set the adapters unless needed if (state.editingMode && chips.adapter == null) chips.adapter = chipsAdapter - if (state.editingMode && contacts.adapter == null) contacts.adapter = contactsAdapter - toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode && !state.searching + toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode 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 @@ -239,15 +212,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } chipsAdapter.data = state.selectedChips - contactsAdapter.data = state.composeItems - - if (state.selectedContact != null && !phoneNumberDialog.isShowing) { - phoneNumberAdapter.data = state.selectedContact.numbers - phoneNumberDialog.subtitle = state.selectedContact.name - phoneNumberDialog.show() - } else if (state.selectedContact == null && phoneNumberDialog.isShowing) { - phoneNumberDialog.dismiss() - } loading.setVisible(state.loading) @@ -321,7 +285,14 @@ class ComposeActivity : QkThemedActivity(), ComposeView { val intent = Intent(Intent.ACTION_PICK) .setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE) - startActivityForResult(Intent.createChooser(intent, null), CONTACT_REQUEST_CODE) + startActivityForResult(Intent.createChooser(intent, null), ATTACH_CONTACT_REQUEST_CODE) + } + + override fun showContacts(chips: List) { + val serialized = HashMap(chips.associate { chip -> chip.address to chip.contact?.lookupKey }) + val intent = Intent(this, ContactsActivity::class.java) + .putExtra(ContactsActivity.ChipsKey, serialized) + startActivityForResult(intent, SELECT_CONTACT_REQUEST_CODE) } override fun requestCamera() { @@ -331,17 +302,17 @@ class ComposeActivity : QkThemedActivity(), ComposeView { val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) .putExtra(MediaStore.EXTRA_OUTPUT, cameraDestination) - startActivityForResult(Intent.createChooser(intent, null), CAMERA_REQUEST_CODE) + startActivityForResult(Intent.createChooser(intent, null), TAKE_PHOTO_REQUEST_CODE) } override fun requestGallery() { val intent = Intent(Intent.ACTION_PICK) - .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) + .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) .putExtra(Intent.EXTRA_LOCAL_ONLY, false) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .setType("image/*") - startActivityForResult(Intent.createChooser(intent, null), GALLERY_REQUEST_CODE) + startActivityForResult(Intent.createChooser(intent, null), ATTACH_PHOTO_REQUEST_CODE) } override fun setDraft(draft: String) = message.setText(draft) @@ -376,15 +347,29 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode == Activity.RESULT_OK) { - when (requestCode) { - CAMERA_REQUEST_CODE -> cameraDestination?.let(attachmentSelectedIntent::onNext) - GALLERY_REQUEST_CODE -> data?.data?.let(attachmentSelectedIntent::onNext) - CONTACT_REQUEST_CODE -> data?.data?.let(contactSelectedIntent::onNext) + when { + requestCode == SELECT_CONTACT_REQUEST_CODE -> { + chipsSelectedIntent.onNext(data?.getSerializableExtra(ContactsActivity.ChipsKey) + ?.let { serializable -> serializable as? HashMap } + ?: hashMapOf()) + } + requestCode == TAKE_PHOTO_REQUEST_CODE && resultCode == Activity.RESULT_OK -> { + cameraDestination?.let(attachmentSelectedIntent::onNext) + } + requestCode == ATTACH_PHOTO_REQUEST_CODE && resultCode == Activity.RESULT_OK -> { + data?.clipData?.itemCount + ?.let { count -> 0 until count } + ?.mapNotNull { i -> data.clipData?.getItemAt(i)?.uri } + ?.forEach(attachmentSelectedIntent::onNext) + ?: data?.data?.let(attachmentSelectedIntent::onNext) + } + requestCode == ATTACH_CONTACT_REQUEST_CODE && resultCode == Activity.RESULT_OK -> { + data?.data?.let(contactSelectedIntent::onNext) } + else -> super.onActivityResult(requestCode, resultCode, data) } } override fun onBackPressed() = backPressedIntent.onNext(Unit) -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt index f7b7e7b88..3d6babaf0 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt @@ -20,9 +20,7 @@ package com.moez.QKSMS.feature.compose import com.moez.QKSMS.compat.SubscriptionInfoCompat import com.moez.QKSMS.feature.compose.editing.Chip -import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.model.Attachment -import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import io.realm.RealmResults @@ -30,10 +28,7 @@ import io.realm.RealmResults data class ComposeState( val hasError: Boolean = false, val editingMode: Boolean = false, - val searching: Boolean = false, - val composeItems: List = ArrayList(), val selectedConversation: Long = 0, - val selectedContact: Contact? = null, // For phone number picker val selectedChips: List = ArrayList(), val sendAsGroup: Boolean = true, val conversationtitle: String = "", diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index 4c4af90f6..aa54543ee 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -22,10 +22,7 @@ import android.net.Uri import androidx.annotation.StringRes import androidx.core.view.inputmethod.InputContentInfoCompat import com.moez.QKSMS.common.base.QkView -import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.feature.compose.editing.Chip -import com.moez.QKSMS.feature.compose.editing.ComposeItem -import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction import com.moez.QKSMS.model.Attachment import io.reactivex.Observable import io.reactivex.subjects.Subject @@ -33,13 +30,7 @@ import io.reactivex.subjects.Subject interface ComposeView : QkView { val activityVisibleIntent: Observable - val queryChangedIntent: Observable - val queryBackspaceIntent: Observable<*> - val queryEditorActionIntent: Observable - val composeItemPressedIntent: Subject - val composeItemLongPressedIntent: Subject - val phoneNumberSelectedIntent: Subject> - val phoneNumberActionIntent: Subject + val chipsSelectedIntent: Subject> val chipDeletedIntent: Subject val menuReadyIntent: Observable val optionsItemIntent: Observable @@ -70,6 +61,7 @@ interface ComposeView : QkView { fun requestDefaultSms() fun requestStoragePermission() fun requestSmsPermission() + fun showContacts(chips: List) fun requestCamera() fun requestGallery() fun requestDatePicker() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 3d00fcddd..fb8db5b00 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -22,7 +22,6 @@ import android.content.Context import android.net.Uri import android.provider.ContactsContract import android.telephony.SmsMessage -import android.view.inputmethod.EditorInfo import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel @@ -36,35 +35,23 @@ import com.moez.QKSMS.extensions.asObservable import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.extensions.mapNotNull -import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.feature.compose.editing.Chip -import com.moez.QKSMS.feature.compose.editing.ComposeItem -import com.moez.QKSMS.feature.compose.editing.ComposeItem.* -import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction -import com.moez.QKSMS.filter.ContactFilter -import com.moez.QKSMS.filter.ContactGroupFilter import com.moez.QKSMS.interactor.AddScheduledMessage import com.moez.QKSMS.interactor.CancelDelayedMessage -import com.moez.QKSMS.interactor.ContactSync import com.moez.QKSMS.interactor.DeleteMessages import com.moez.QKSMS.interactor.MarkRead import com.moez.QKSMS.interactor.RetrySending import com.moez.QKSMS.interactor.SendMessage -import com.moez.QKSMS.interactor.SetDefaultPhoneNumber import com.moez.QKSMS.manager.ActiveConversationManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Attachments -import com.moez.QKSMS.model.Contact -import com.moez.QKSMS.model.ContactGroup import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message -import com.moez.QKSMS.model.PhoneNumber import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.util.ActiveSubscriptionObservable -import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.Preferences import com.moez.QKSMS.util.tryOrNull import com.uber.autodispose.android.lifecycle.scope @@ -78,9 +65,6 @@ import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import io.realm.RealmList -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.rx2.awaitFirst import timber.log.Timber import java.util.* import javax.inject.Inject @@ -92,14 +76,12 @@ class ComposeViewModel @Inject constructor( @Named("address") private val address: String, @Named("text") private val sharedText: String, @Named("attachments") private val sharedAttachments: Attachments, + private val contactRepo: ContactRepository, private val context: Context, private val activeConversationManager: ActiveConversationManager, private val addScheduledMessage: AddScheduledMessage, private val billingManager: BillingManager, private val cancelMessage: CancelDelayedMessage, - private val contactFilter: ContactFilter, - private val contactGroupFilter: ContactGroupFilter, - private val contactsRepo: ContactRepository, private val conversationRepo: ConversationRepository, private val deleteMessages: DeleteMessages, private val markRead: MarkRead, @@ -107,31 +89,25 @@ class ComposeViewModel @Inject constructor( private val messageRepo: MessageRepository, private val navigator: Navigator, private val permissionManager: PermissionManager, - private val phoneNumberUtils: PhoneNumberUtils, private val prefs: Preferences, private val retrySending: RetrySending, private val sendMessage: SendMessage, - private val setDefaultPhoneNumber: SetDefaultPhoneNumber, - private val subscriptionManager: SubscriptionManagerCompat, - private val syncContacts: ContactSync + private val subscriptionManager: SubscriptionManagerCompat ) : QkViewModel(ComposeState( editingMode = threadId == 0L && address.isBlank(), - searching = threadId == 0L && address.isBlank(), selectedConversation = threadId, query = query) ) { private val attachments: Subject> = BehaviorSubject.createDefault(sharedAttachments) - private val contactGroups: Observable> by lazy { contactsRepo.getUnmanagedContactGroups() } - private val contacts: Observable> by lazy { contactsRepo.getUnmanagedContacts() } private val chipsReducer: Subject<(List) -> List> = PublishSubject.create() private val conversation: Subject = BehaviorSubject.create() private val messages: Subject> = BehaviorSubject.create() - private val recents: Observable> by lazy { conversationRepo.getUnmanagedConversations() } private val selectedChips: Subject> = BehaviorSubject.createDefault(listOf()) private val searchResults: Subject> = BehaviorSubject.create() private val searchSelection: Subject = BehaviorSubject.createDefault(-1) - private val starredContacts: Observable> by lazy { contactsRepo.getUnmanagedContacts(true) } + + private var shouldShowContacts = threadId == 0L && address.isBlank() init { val initialConversation = threadId.takeIf { it != 0L } @@ -241,135 +217,51 @@ class ComposeViewModel @Inject constructor( val sub = if (subs.size > 1) subs.firstOrNull { it.subscriptionId == subId } ?: subs[0] else null newState { copy(subscription = sub) } }.subscribe() - - if (threadId == 0L) { - syncContacts.execute(Unit) - } } override fun bindView(view: ComposeView) { super.bindView(view) - // Set the contact suggestions list to visible when the add button is pressed - view.optionsItemIntent - .filter { it == R.id.add } - .skipUntil(state.filter { state -> state.editingMode }) - .takeUntil(state.filter { state -> !state.editingMode }) - .autoDisposable(view.scope()) - .subscribe { newState { copy(searching = true) } } - - // Update the list of contact suggestions based on the query input, while also filtering out any contacts - // that have already been selected - Observables - .combineLatest( - view.queryChangedIntent, recents, starredContacts, contactGroups, contacts, selectedChips - ) { query, recents, starredContacts, contactGroups, contacts, selectedChips -> - val composeItems = mutableListOf() - if (query.isBlank()) { - composeItems += recents.map(::Recent) - composeItems += starredContacts.map(::Starred) - composeItems += contactGroups.map(::Group) - composeItems += contacts.map(::Person) - } else { - // If the entry is a valid destination, allow it as a recipient - if (phoneNumberUtils.isPossibleNumber(query.toString())) { - val newAddress = phoneNumberUtils.formatNumber(query) - val newContact = Contact(numbers = RealmList(PhoneNumber(address = newAddress))) - composeItems += New(newContact) - } + if (shouldShowContacts) { + shouldShowContacts = false + view.showContacts(selectedChips.blockingFirst()) + } - // Strip the accents from the query. This can be an expensive operation, so - // cache the result instead of doing it for each contact - val normalizedQuery = query.removeAccents() - composeItems += starredContacts - .filterNot { contact -> selectedChips.map { it.contact }.contains(contact) } - .filter { contact -> contactFilter.filter(contact, normalizedQuery) } - .map(::Starred) - - composeItems += contactGroups - .filter { group -> contactGroupFilter.filter(group, normalizedQuery) } - .map(::Group) - - composeItems += contacts - .filterNot { contact -> selectedChips.map { it.contact }.contains(contact) } - .filter { contact -> contactFilter.filter(contact, normalizedQuery) } - .map(::Person) + view.chipsSelectedIntent + .withLatestFrom(selectedChips) { hashmap, chips -> + if (hashmap.isEmpty() && chips.isEmpty()) { + newState { copy(hasError = true) } } - - composeItems + hashmap } - .skipUntil(state.filter { state -> state.editingMode }) - .takeUntil(state.filter { state -> !state.editingMode }) - .subscribeOn(Schedulers.computation()) - .autoDisposable(view.scope()) - .subscribe { items -> newState { copy(composeItems = items) } } - - // Backspaces should delete the most recent contact if there's no text input - // Close the activity if user presses back - view.queryBackspaceIntent - .withLatestFrom(selectedChips, view.queryChangedIntent) { event, contacts, query -> - if (contacts.isNotEmpty() && query.isEmpty()) { - chipsReducer.onNext { it.dropLast(1) } + .filter { hashmap -> hashmap.isNotEmpty() } + .map { hashmap -> + hashmap.map { (address, lookupKey) -> + Chip(address, lookupKey?.let(contactRepo::getUnmanagedContact)) } } .autoDisposable(view.scope()) - .subscribe() - - // Listen for ComposeItems being selected, and then send them off to the number picker dialog in case - // the user needs to select a phone number - view.queryEditorActionIntent - .filter { actionId -> actionId == EditorInfo.IME_ACTION_DONE } - .withLatestFrom(state) { _, state -> state } - .mapNotNull { state -> state.composeItems.firstOrNull() } - .mergeWith(view.composeItemPressedIntent) - .map { composeItem -> composeItem to false } - .mergeWith(view.composeItemLongPressedIntent.map { composeItem -> composeItem to true }) - .observeOn(Schedulers.io()) - .skipUntil(state.filter { state -> state.editingMode }) - .takeUntil(state.filter { state -> !state.editingMode }) - .autoDisposable(view.scope()) - .subscribe { (composeItem, force) -> - val contacts = composeItem.getContacts() - val newChips = contacts.map { contact -> - if (contact.numbers.size == 1 || contact.getDefaultNumber() != null && !force) { - val number = contact.getDefaultNumber() ?: contact.numbers[0]!! - Chip(number.address, contact) - } else { - runBlocking { - newState { copy(selectedContact = contact) } - val action = view.phoneNumberActionIntent.awaitFirst() - newState { copy(selectedContact = null) } - val numberId = view.phoneNumberSelectedIntent.awaitFirst().value - val number = contact.numbers.find { number -> number.id == numberId } - - if (action == PhoneNumberAction.CANCEL || number == null) { - return@runBlocking null - } - - if (action == PhoneNumberAction.ALWAYS) { - val params = SetDefaultPhoneNumber.Params(contact.lookupKey, number.id) - setDefaultPhoneNumber.execute(params) - } - - Chip(number.address, contact) - } ?: return@subscribe - } - } + .subscribe { chips -> + chipsReducer.onNext { list -> list + chips } + } - newState { copy(searching = false) } - chipsReducer.onNext { chips -> chips + newChips } + // Set the contact suggestions list to visible when the add button is pressed + view.optionsItemIntent + .filter { it == R.id.add } + .withLatestFrom(selectedChips) { _, chips -> + view.showContacts(chips) } + .autoDisposable(view.scope()) + .subscribe() // Update the list of selected contacts when a new contact is selected or an existing one is deselected view.chipDeletedIntent - .skipUntil(state.filter { state -> state.editingMode }) - .takeUntil(state.filter { state -> !state.editingMode }) .autoDisposable(view.scope()) .subscribe { contact -> chipsReducer.onNext { contacts -> val result = contacts.filterNot { it == contact } if (result.isEmpty()) { - newState { copy(searching = true) } + view.showContacts(result) } result } 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 new file mode 100644 index 000000000..3685941cc --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivity.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.contacts + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import androidx.lifecycle.ViewModelProviders +import com.jakewharton.rxbinding2.widget.editorActions +import com.jakewharton.rxbinding2.widget.textChanges +import com.moez.QKSMS.R +import com.moez.QKSMS.common.ViewModelFactory +import com.moez.QKSMS.common.base.QkThemedActivity +import com.moez.QKSMS.common.widget.QkDialog +import com.moez.QKSMS.extensions.Optional +import com.moez.QKSMS.feature.compose.editing.ComposeItem +import com.moez.QKSMS.feature.compose.editing.ComposeItemAdapter +import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction +import com.moez.QKSMS.feature.compose.editing.PhoneNumberPickerAdapter +import dagger.android.AndroidInjection +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 { + + companion object { + const val ChipsKey = "chips" + } + + @Inject lateinit var contactsAdapter: ComposeItemAdapter + @Inject lateinit var phoneNumberAdapter: PhoneNumberPickerAdapter + @Inject lateinit var viewModelFactory: ViewModelFactory + + override val queryChangedIntent: Observable by lazy { search.textChanges() } + override val queryBackspaceIntent: Observable<*> by lazy { search.backspaces } + override val queryEditorActionIntent: Observable by lazy { 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 } + override val phoneNumberActionIntent: Subject = PublishSubject.create() + + private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ContactsViewModel::class.java] } + + private val phoneNumberDialog by lazy { + QkDialog(this).apply { + titleRes = R.string.compose_number_picker_title + adapter = phoneNumberAdapter + positiveButton = R.string.compose_number_picker_always + positiveButtonListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.ALWAYS) } + negativeButton = R.string.compose_number_picker_once + negativeButtonListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.JUST_ONCE) } + cancelListener = { phoneNumberActionIntent.onNext(PhoneNumberAction.CANCEL) } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + AndroidInjection.inject(this) + super.onCreate(savedInstanceState) + setContentView(R.layout.contacts_activity) + showBackButton(true) + viewModel.bindView(this) + + contacts.itemAnimator = null + contacts.adapter = contactsAdapter + } + + override fun render(state: ContactsState) { + contactsAdapter.data = state.composeItems + + if (state.selectedContact != null && !phoneNumberDialog.isShowing) { + phoneNumberAdapter.data = state.selectedContact.numbers + phoneNumberDialog.subtitle = state.selectedContact.name + phoneNumberDialog.show() + } else if (state.selectedContact == null && phoneNumberDialog.isShowing) { + phoneNumberDialog.dismiss() + } + } + + override fun finish(result: HashMap) { + val intent = Intent().putExtra(ChipsKey, result) + setResult(Activity.RESULT_OK, intent) + finish() + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivityModule.kt new file mode 100644 index 000000000..d8b832f85 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsActivityModule.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.contacts + +import androidx.lifecycle.ViewModel +import com.moez.QKSMS.injection.ViewModelKey +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap + +@Module +class ContactsActivityModule { + + @Provides + fun provideChips(activity: ContactsActivity): HashMap { + return activity.intent.extras?.getSerializable(ContactsActivity.ChipsKey) + ?.let { serializable -> serializable as? HashMap } + ?: hashMapOf() + } + + @Provides + @IntoMap + @ViewModelKey(ContactsViewModel::class) + fun provideContactsViewModel(viewModel: ContactsViewModel): ViewModel = viewModel + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt new file mode 100644 index 000000000..09628b38b --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.contacts + +import com.moez.QKSMS.common.base.QkView +import com.moez.QKSMS.extensions.Optional +import com.moez.QKSMS.feature.compose.editing.ComposeItem +import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction +import io.reactivex.Observable +import io.reactivex.subjects.Subject + +interface ContactsContract : QkView { + + val queryChangedIntent: Observable + val queryBackspaceIntent: Observable<*> + val queryEditorActionIntent: Observable + val composeItemPressedIntent: Subject + val composeItemLongPressedIntent: Subject + val phoneNumberSelectedIntent: Subject> + val phoneNumberActionIntent: Subject + + fun finish(result: HashMap) + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsState.kt new file mode 100644 index 000000000..9d5baad08 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.contacts + +import com.moez.QKSMS.feature.compose.editing.ComposeItem +import com.moez.QKSMS.model.Contact + +data class ContactsState( + val composeItems: List = ArrayList(), + val selectedContact: Contact? = null // For phone number picker +) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt new file mode 100644 index 000000000..99c948134 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.contacts + +import android.view.inputmethod.EditorInfo +import com.moez.QKSMS.common.base.QkViewModel +import com.moez.QKSMS.extensions.mapNotNull +import com.moez.QKSMS.extensions.removeAccents +import com.moez.QKSMS.feature.compose.editing.Chip +import com.moez.QKSMS.feature.compose.editing.ComposeItem +import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction +import com.moez.QKSMS.filter.ContactFilter +import com.moez.QKSMS.filter.ContactGroupFilter +import com.moez.QKSMS.interactor.ContactSync +import com.moez.QKSMS.interactor.SetDefaultPhoneNumber +import com.moez.QKSMS.model.Contact +import com.moez.QKSMS.model.ContactGroup +import com.moez.QKSMS.model.Conversation +import com.moez.QKSMS.model.PhoneNumber +import com.moez.QKSMS.repository.ContactRepository +import com.moez.QKSMS.repository.ConversationRepository +import com.moez.QKSMS.util.PhoneNumberUtils +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import io.reactivex.Observable +import io.reactivex.rxkotlin.Observables +import io.reactivex.rxkotlin.withLatestFrom +import io.reactivex.schedulers.Schedulers +import io.realm.RealmList +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.rx2.awaitFirst +import javax.inject.Inject + +class ContactsViewModel @Inject constructor( + serializedChips: HashMap, + syncContacts: ContactSync, + private val contactFilter: ContactFilter, + private val contactGroupFilter: ContactGroupFilter, + private val contactsRepo: ContactRepository, + private val conversationRepo: ConversationRepository, + private val phoneNumberUtils: PhoneNumberUtils, + private val setDefaultPhoneNumber: SetDefaultPhoneNumber +) : QkViewModel(ContactsState()) { + + private val contactGroups: Observable> by lazy { contactsRepo.getUnmanagedContactGroups() } + private val contacts: Observable> by lazy { contactsRepo.getUnmanagedContacts() } + private val recents: Observable> by lazy { conversationRepo.getUnmanagedConversations() } + private val starredContacts: Observable> by lazy { contactsRepo.getUnmanagedContacts(true) } + + private val selectedChips = Observable.just(serializedChips) + .observeOn(Schedulers.io()) + .map { hashmap -> + hashmap.map { (address, lookupKey) -> + Chip(address, lookupKey?.let(contactsRepo::getUnmanagedContact)) + } + } + + init { + syncContacts.execute(Unit) + } + + override fun bindView(view: ContactsContract) { + super.bindView(view) + + // Update the list of contact suggestions based on the query input, while also filtering out any contacts + // that have already been selected + Observables + .combineLatest( + view.queryChangedIntent, recents, starredContacts, contactGroups, contacts, selectedChips + ) { query, recents, starredContacts, contactGroups, contacts, selectedChips -> + val composeItems = mutableListOf() + if (query.isBlank()) { + composeItems += recents + .filter { conversation -> + conversation.recipients.any { recipient -> + selectedChips.none { chip -> + if (recipient.contact == null) { + chip.address == recipient.address + } else { + chip.contact?.lookupKey == recipient.contact?.lookupKey + } + } + } + } + .map(ComposeItem::Recent) + + composeItems += starredContacts + .filter { contact -> selectedChips.none { it.contact?.lookupKey == contact.lookupKey } } + .map(ComposeItem::Starred) + + composeItems += contactGroups + .filter { group -> + group.contacts.any { contact -> + selectedChips.none { chip -> chip.contact?.lookupKey == contact.lookupKey } + } + } + .map(ComposeItem::Group) + + composeItems += contacts + .filter { contact -> selectedChips.none { it.contact?.lookupKey == contact.lookupKey } } + .map(ComposeItem::Person) + } else { + // If the entry is a valid destination, allow it as a recipient + if (phoneNumberUtils.isPossibleNumber(query.toString())) { + val newAddress = phoneNumberUtils.formatNumber(query) + val newContact = Contact(numbers = RealmList(PhoneNumber(address = newAddress))) + composeItems += ComposeItem.New(newContact) + } + + // Strip the accents from the query. This can be an expensive operation, so + // cache the result instead of doing it for each contact + val normalizedQuery = query.removeAccents() + composeItems += starredContacts + .asSequence() + .filter { contact -> selectedChips.none { it.contact?.lookupKey == contact.lookupKey } } + .filter { contact -> contactFilter.filter(contact, normalizedQuery) } + .map(ComposeItem::Starred) + + composeItems += contactGroups + .asSequence() + .filter { group -> + group.contacts.any { contact -> + selectedChips.none { chip -> chip.contact?.lookupKey == contact.lookupKey } + } + } + .filter { group -> contactGroupFilter.filter(group, normalizedQuery) } + .map(ComposeItem::Group) + + composeItems += contacts + .asSequence() + .filter { contact -> selectedChips.none { it.contact?.lookupKey == contact.lookupKey } } + .filter { contact -> contactFilter.filter(contact, normalizedQuery) } + .map(ComposeItem::Person) + } + + composeItems + } + .subscribeOn(Schedulers.computation()) + .autoDisposable(view.scope()) + .subscribe { items -> newState { copy(composeItems = items) } } + + // Listen for ComposeItems being selected, and then send them off to the number picker dialog in case + // the user needs to select a phone number + view.queryEditorActionIntent + .filter { actionId -> actionId == EditorInfo.IME_ACTION_DONE } + .withLatestFrom(state) { _, state -> state } + .mapNotNull { state -> state.composeItems.firstOrNull() } + .mergeWith(view.composeItemPressedIntent) + .map { composeItem -> composeItem to false } + .mergeWith(view.composeItemLongPressedIntent.map { composeItem -> composeItem to true }) + .observeOn(Schedulers.io()) + .autoDisposable(view.scope()) + .subscribe { (composeItem, force) -> + val contacts = composeItem.getContacts() + val newChips = contacts.map { contact -> + if (contact.numbers.size == 1 || contact.getDefaultNumber() != null && !force) { + val number = contact.getDefaultNumber() ?: contact.numbers[0]!! + Chip(number.address, contact) + } else { + runBlocking { + newState { copy(selectedContact = contact) } + val action = view.phoneNumberActionIntent.awaitFirst() + newState { copy(selectedContact = null) } + val numberId = view.phoneNumberSelectedIntent.awaitFirst().value + val number = contact.numbers.find { number -> number.id == numberId } + + if (action == PhoneNumberAction.CANCEL || number == null) { + return@runBlocking null + } + + if (action == PhoneNumberAction.ALWAYS) { + val params = SetDefaultPhoneNumber.Params(contact.lookupKey, number.id) + setDefaultPhoneNumber.execute(params) + } + + Chip(number.address, contact) + } ?: return@subscribe + } + } + + view.finish(HashMap(newChips.associate { chip -> chip.address to chip.contact?.lookupKey })) + } + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/android/ActivityBuilderModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/android/ActivityBuilderModule.kt index 44deebeeb..808345092 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/android/ActivityBuilderModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/android/ActivityBuilderModule.kt @@ -22,6 +22,8 @@ import com.moez.QKSMS.feature.backup.BackupActivity import com.moez.QKSMS.feature.blocking.BlockingActivity import com.moez.QKSMS.feature.compose.ComposeActivity import com.moez.QKSMS.feature.compose.ComposeActivityModule +import com.moez.QKSMS.feature.contacts.ContactsActivity +import com.moez.QKSMS.feature.contacts.ContactsActivityModule import com.moez.QKSMS.feature.conversationinfo.ConversationInfoActivity import com.moez.QKSMS.feature.gallery.GalleryActivity import com.moez.QKSMS.feature.gallery.GalleryActivityModule @@ -59,6 +61,10 @@ abstract class ActivityBuilderModule { @ContributesAndroidInjector(modules = [ComposeActivityModule::class]) abstract fun bindComposeActivity(): ComposeActivity + @ActivityScope + @ContributesAndroidInjector(modules = [ContactsActivityModule::class]) + abstract fun bindContactsActivity(): ContactsActivity + @ActivityScope @ContributesAndroidInjector(modules = []) abstract fun bindConversationInfoActivity(): ConversationInfoActivity diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index 16207e2b0..3a85a1385 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -54,7 +54,7 @@ android:text="@string/compose_messages_empty" android:textColor="?android:attr/textColorTertiary" android:visibility="gone" - app:layout_constraintTop_toBottomOf="@id/toolbar" + app:layout_constraintTop_toBottomOf="@id/sendAsGroupBackground" app:textSize="secondary" /> - - + app:constraint_referenced_ids="composeBackground,messageBackground,attachments,attach,message,counter,send" /> + app:constraint_referenced_ids="scheduledTitle,scheduledTime,scheduledCancel,scheduledSeparator" /> - - @@ -411,8 +378,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" - app:constraint_referenced_ids="contact, contactLabel, schedule, scheduleLabel, gallery, galleryLabel, camera, - cameraLabel, attachingBackground" /> + app:constraint_referenced_ids="contact,contactLabel,schedule,scheduleLabel,gallery,galleryLabel,camera,cameraLabel,attachingBackground" /> + + + + + + + + + + + + + + + + -- GitLab From 30126523aefe6d53dcda3efe4ec0cf21816c8a0e Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 17 Nov 2019 02:10:55 -0500 Subject: [PATCH 023/213] Show keyboard when selecting contact and contact selected --- .../common/util/extensions/ActivityExtensions.kt | 3 +-- .../QKSMS/common/util/extensions/ViewExtensions.kt | 9 +++++++-- .../moez/QKSMS/feature/compose/ComposeActivity.kt | 12 ++++++++---- .../com/moez/QKSMS/feature/compose/ComposeView.kt | 1 + .../moez/QKSMS/feature/compose/ComposeViewModel.kt | 1 + .../moez/QKSMS/feature/contacts/ContactsActivity.kt | 9 +++++++++ .../moez/QKSMS/feature/contacts/ContactsContract.kt | 1 + .../moez/QKSMS/feature/contacts/ContactsViewModel.kt | 7 +++++++ 8 files changed, 35 insertions(+), 8 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt index 83c103adf..3d4f59f59 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt @@ -24,8 +24,7 @@ import android.view.inputmethod.InputMethodManager fun Activity.dismissKeyboard() { window.currentFocus?.let { focus -> - val imm = getSystemService( - Context.INPUT_METHOD_SERVICE) as InputMethodManager + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(focus.windowToken, 0) focus.clearFocus() diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt index 3adb55dc8..3dea161c2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt @@ -39,11 +39,16 @@ var ViewGroup.animateLayoutChanges: Boolean layoutTransition = if (value) LayoutTransition() else null } - fun EditText.showKeyboard() { requestFocus() val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.showSoftInput(this, 0) + imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) +} + +fun EditText.hideKeyboard() { + requestFocus() + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(windowToken, 0) } fun ImageView.setTint(color: Int) { 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 be4028a56..e5eb83baf 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 @@ -47,6 +47,7 @@ import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.autoScrollToStart +import com.moez.QKSMS.common.util.extensions.hideKeyboard import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.scrapViews import com.moez.QKSMS.common.util.extensions.setBackgroundTint @@ -207,10 +208,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { 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() - if (chipsAdapter.data.isEmpty() && state.selectedChips.isNotEmpty()) { - message.showKeyboard() - } - chipsAdapter.data = state.selectedChips loading.setVisible(state.loading) @@ -289,12 +286,19 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } override fun showContacts(chips: List) { + message.hideKeyboard() val serialized = HashMap(chips.associate { chip -> chip.address to chip.contact?.lookupKey }) val intent = Intent(this, ContactsActivity::class.java) .putExtra(ContactsActivity.ChipsKey, serialized) startActivityForResult(intent, SELECT_CONTACT_REQUEST_CODE) } + override fun showKeyboard() { + message.postDelayed({ + message.showKeyboard() + }, 200) + } + override fun requestCamera() { cameraDestination = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) .let { timestamp -> ContentValues().apply { put(MediaStore.Images.Media.TITLE, timestamp) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index aa54543ee..1f7efef61 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -62,6 +62,7 @@ interface ComposeView : QkView { fun requestStoragePermission() fun requestSmsPermission() fun showContacts(chips: List) + fun showKeyboard() fun requestCamera() fun requestGallery() fun requestDatePicker() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index fb8db5b00..ba36b0fd7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -243,6 +243,7 @@ class ComposeViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe { chips -> chipsReducer.onNext { list -> list + chips } + view.showKeyboard() } // Set the contact suggestions list to visible when the add button is pressed 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 3685941cc..424ae592b 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 @@ -27,6 +27,8 @@ import com.jakewharton.rxbinding2.widget.textChanges import com.moez.QKSMS.R import com.moez.QKSMS.common.ViewModelFactory import com.moez.QKSMS.common.base.QkThemedActivity +import com.moez.QKSMS.common.util.extensions.hideKeyboard +import com.moez.QKSMS.common.util.extensions.showKeyboard import com.moez.QKSMS.common.widget.QkDialog import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.feature.compose.editing.ComposeItem @@ -95,7 +97,14 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { } } + override fun openKeyboard() { + search.postDelayed({ + search.showKeyboard() + }, 200) + } + override fun finish(result: HashMap) { + search.hideKeyboard() val intent = Intent().putExtra(ChipsKey, result) setResult(Activity.RESULT_OK, intent) finish() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt index 09628b38b..65ed5ebe7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt @@ -35,6 +35,7 @@ interface ContactsContract : QkView { val phoneNumberSelectedIntent: Subject> val phoneNumberActionIntent: Subject + fun openKeyboard() fun finish(result: HashMap) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt index 99c948134..3a52671d5 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt @@ -71,6 +71,8 @@ class ContactsViewModel @Inject constructor( } } + private var shouldOpenKeyboard: Boolean = true + init { syncContacts.execute(Unit) } @@ -78,6 +80,11 @@ class ContactsViewModel @Inject constructor( override fun bindView(view: ContactsContract) { super.bindView(view) + if (shouldOpenKeyboard) { + view.openKeyboard() + shouldOpenKeyboard = false + } + // Update the list of contact suggestions based on the query input, while also filtering out any contacts // that have already been selected Observables -- GitLab From 056e992b2bd902e734ace29961ad70237fc02a6d Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Sun, 17 Nov 2019 02:11:06 -0500 Subject: [PATCH 024/213] Make sure group messages don't get hidden by banner --- presentation/src/main/res/layout/compose_activity.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index 3a85a1385..39d1d0e2d 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -39,7 +39,7 @@ app:layout_constraintBottom_toBottomOf="@id/composeBackground" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toolbar" + app:layout_constraintTop_toBottomOf="@id/sendAsGroupBackground" app:stackFromEnd="true" tools:listitem="@layout/message_list_item_in" /> -- GitLab From 66560b6f46228c86ce9944d2ab4ac905c06ae195 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 23 Nov 2019 19:47:29 -0500 Subject: [PATCH 025/213] White background for compose --- .../QKSMS/feature/compose/ComposeActivity.kt | 1 - .../QKSMS/feature/qkreply/QkReplyActivity.kt | 7 ++- .../res/drawable/compose_bar_background.xml | 28 --------- .../res/drawable/rounded_rectangle_22dp.xml | 2 +- .../src/main/res/layout/compose_activity.xml | 58 ++++++++----------- .../src/main/res/layout/contact_chip.xml | 2 +- .../src/main/res/layout/qkreply_activity.xml | 28 ++++----- .../src/main/res/values-night/themes.xml | 7 +-- presentation/src/main/res/values/colors.xml | 4 +- presentation/src/main/res/values/themes.xml | 9 +-- 10 files changed, 47 insertions(+), 99 deletions(-) delete mode 100644 presentation/src/main/res/drawable/compose_bar_background.xml 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 e5eb83baf..5f1b44b23 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 @@ -156,7 +156,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) - composeBackground.setBackgroundTint(resolveThemeColor(R.attr.composeBackground)) } } 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 ead2fe26e..9612bcce4 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 @@ -62,6 +62,7 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { setFinishOnTouchOutside(prefs.qkreplyTapDismiss.get()) setContentView(R.layout.qkreply_activity) + window.setBackgroundDrawable(null) window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) viewModel.bindView(this) @@ -77,10 +78,10 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { toolbar.setBackgroundTint(resolveThemeColor(R.attr.colorPrimary)) - background.setBackgroundTint(resolveThemeColor(R.attr.composeBackground)) + background.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) - composeBackgroundGradient.setBackgroundTint(resolveThemeColor(R.attr.composeBackground)) - composeBackgroundSolid.setBackgroundTint(resolveThemeColor(R.attr.composeBackground)) + composeBackgroundGradient.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) + composeBackgroundSolid.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) } } diff --git a/presentation/src/main/res/drawable/compose_bar_background.xml b/presentation/src/main/res/drawable/compose_bar_background.xml deleted file mode 100644 index 4c963a183..000000000 --- a/presentation/src/main/res/drawable/compose_bar_background.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/presentation/src/main/res/drawable/rounded_rectangle_22dp.xml b/presentation/src/main/res/drawable/rounded_rectangle_22dp.xml index d1754416e..7a6af7bb2 100755 --- a/presentation/src/main/res/drawable/rounded_rectangle_22dp.xml +++ b/presentation/src/main/res/drawable/rounded_rectangle_22dp.xml @@ -24,4 +24,4 @@ - \ No newline at end of file + diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index 39d1d0e2d..0e6c4a305 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -23,7 +23,7 @@ android:id="@+id/contentView" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?attr/composeBackground" + android:background="?android:attr/windowBackground" android:orientation="vertical" tools:context="com.moez.QKSMS.feature.compose.ComposeActivity"> @@ -31,12 +31,12 @@ android:id="@+id/messageList" android:layout_width="0dp" android:layout_height="0dp" + android:layout_marginBottom="12dp" android:clipChildren="false" android:clipToPadding="false" android:paddingTop="8dp" - android:paddingBottom="16dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:layout_constraintBottom_toBottomOf="@id/composeBackground" + app:layout_constraintBottom_toTopOf="@id/messageBackground" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/sendAsGroupBackground" @@ -135,31 +135,29 @@ android:id="@+id/composeBar" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:constraint_referenced_ids="composeBackground,messageBackground,attachments,attach,message,counter,send" /> + app:constraint_referenced_ids="messageBackground,attachments,attach,message,counter,send" /> + android:id="@+id/composeDivider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginBottom="12dp" + android:background="?android:attr/divider" + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@id/messageBackground" + app:layout_goneMarginBottom="12dp" /> @@ -174,7 +172,6 @@ android:id="@+id/scheduledTitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:elevation="4dp" android:paddingStart="16dp" android:paddingEnd="16dp" android:text="@string/compose_scheduled_for" @@ -191,7 +188,6 @@ android:id="@+id/scheduledTime" android:layout_width="0dp" android:layout_height="wrap_content" - android:elevation="4dp" android:paddingStart="16dp" android:paddingEnd="16dp" android:textColor="?android:attr/textColorTertiary" @@ -206,7 +202,6 @@ android:id="@+id/scheduledCancel" android:layout_width="44dp" android:layout_height="56dp" - android:elevation="4dp" android:padding="10dp" android:src="@drawable/ic_cancel_black_24dp" android:tint="?android:attr/textColorSecondary" @@ -218,7 +213,6 @@ android:layout_width="0dp" android:layout_height="1dp" android:background="?android:attr/divider" - android:elevation="4dp" app:layout_constraintBottom_toTopOf="@id/attachments" app:layout_constraintEnd_toEndOf="@id/messageBackground" app:layout_constraintStart_toStartOf="@id/messageBackground" /> @@ -229,7 +223,6 @@ android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" - android:elevation="4dp" android:orientation="horizontal" android:paddingStart="8dp" android:paddingEnd="8dp" @@ -246,7 +239,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:background="@null" - android:elevation="4dp" android:gravity="center_vertical" android:hint="@string/compose_hint" android:inputType="textLongMessage|textCapSentences|textMultiLine" @@ -263,12 +255,11 @@ + app:layout_constraintEnd_toEndOf="parent" /> diff --git a/presentation/src/main/res/layout/contact_chip.xml b/presentation/src/main/res/layout/contact_chip.xml index 76805b05d..6627a4434 100755 --- a/presentation/src/main/res/layout/contact_chip.xml +++ b/presentation/src/main/res/layout/contact_chip.xml @@ -30,7 +30,7 @@ android:layout_height="36dp" android:layout_margin="4dp" android:background="@drawable/chip_background" - android:backgroundTint="?attr/composeBackground" + android:backgroundTint="?attr/bubbleColor" android:orientation="horizontal"> + tools:context=".feature.qkreply.QkReplyActivity"> @@ -83,10 +81,9 @@ style="@style/TextPrimary" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginBottom="8dp" + android:layout_marginStart="12dp" + android:layout_marginBottom="12dp" android:background="@null" - android:elevation="4dp" android:gravity="center_vertical" android:hint="@string/compose_hint" android:inputType="textLongMessage|textCapSentences|textMultiLine" @@ -107,18 +104,16 @@ android:layout_height="44dp" android:background="?attr/selectableItemBackground" android:contentDescription="@string/compose_sim_cd" - android:elevation="4dp" android:padding="10dp" android:src="@drawable/ic_sim_card_black_24dp" android:tint="?android:attr/textColorSecondary" app:layout_constraintBottom_toBottomOf="@id/message" - app:layout_constraintEnd_toStartOf="@id/send" /> + app:layout_constraintEnd_toEndOf="@id/messageBackground" /> @color/bubbleDark @color/toolbarDark @color/statusBarDark - @color/backgroundDark @drawable/ripple_dark @color/switchThumbEnabledDark @color/switchThumbDisabledDark @@ -48,12 +47,11 @@ @color/text_primary @color/text_secondary @color/text_tertiary - @android:color/transparent + @color/backgroundDark @style/PopupTheme @color/bubbleDark @color/toolbarDark @color/statusBarDark - @color/backgroundDark @drawable/ripple_dark false @@ -70,14 +68,13 @@ @color/black @color/black @color/black - @color/black -- GitLab From cd22b2a8afbdd0b3b1fdaf090c5d66c34bf323d8 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 23 Nov 2019 19:47:41 -0500 Subject: [PATCH 026/213] More rotation for attach button --- .../main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5f1b44b23..87ae0133c 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 @@ -224,7 +224,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { attachments.setVisible(state.attachments.isNotEmpty()) attachmentAdapter.data = state.attachments - attach.animate().rotation(if (state.attaching) 45f else 0f).start() + attach.animate().rotation(if (state.attaching) 135f else 0f).start() attaching.isVisible = state.attaching counter.text = state.remaining -- GitLab From a6344cb7854d1bf501c80afd6e15acd22b44c04d Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 23 Nov 2019 20:06:26 -0500 Subject: [PATCH 027/213] More padding above empty messages --- presentation/src/main/res/layout/compose_activity.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index 0e6c4a305..3015b5750 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -48,7 +48,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="56dp" - android:layout_marginTop="16dp" + android:layout_marginTop="32dp" android:layout_marginEnd="56dp" android:gravity="center" android:text="@string/compose_messages_empty" -- GitLab From 5c883323dbef76af7a95cd8e152e0aa14aa6ec23 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 23 Nov 2019 23:31:25 -0500 Subject: [PATCH 028/213] Show cancel button when searching --- .../feature/contacts/ContactsActivity.kt | 10 +++- .../feature/contacts/ContactsContract.kt | 3 +- .../QKSMS/feature/contacts/ContactsState.kt | 1 + .../feature/contacts/ContactsViewModel.kt | 10 ++++ .../src/main/res/layout/contacts_activity.xml | 46 +++++++++++++++---- .../src/main/res/layout/main_activity.xml | 2 +- .../src/main/res/values-night/themes.xml | 2 +- presentation/src/main/res/values/colors.xml | 2 - presentation/src/main/res/values/themes.xml | 2 +- 9 files changed, 61 insertions(+), 17 deletions(-) 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 424ae592b..c0680d7e0 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 @@ -21,7 +21,9 @@ package com.moez.QKSMS.feature.contacts import android.app.Activity import android.content.Intent import android.os.Bundle +import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProviders +import com.jakewharton.rxbinding2.view.clicks import com.jakewharton.rxbinding2.widget.editorActions import com.jakewharton.rxbinding2.widget.textChanges import com.moez.QKSMS.R @@ -53,7 +55,7 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { @Inject lateinit var viewModelFactory: ViewModelFactory override val queryChangedIntent: Observable by lazy { search.textChanges() } - override val queryBackspaceIntent: Observable<*> by lazy { search.backspaces } + override val queryClearedIntent: Observable<*> by lazy { cancel.clicks() } override val queryEditorActionIntent: Observable by lazy { search.editorActions() } override val composeItemPressedIntent: Subject by lazy { contactsAdapter.clicks } override val composeItemLongPressedIntent: Subject by lazy { contactsAdapter.longClicks } @@ -86,6 +88,8 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { } override fun render(state: ContactsState) { + cancel.isVisible = state.query.length > 1 + contactsAdapter.data = state.composeItems if (state.selectedContact != null && !phoneNumberDialog.isShowing) { @@ -97,6 +101,10 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { } } + override fun clearQuery() { + search.text = null + } + override fun openKeyboard() { search.postDelayed({ search.showKeyboard() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt index 65ed5ebe7..cf6c55095 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsContract.kt @@ -28,13 +28,14 @@ import io.reactivex.subjects.Subject interface ContactsContract : QkView { val queryChangedIntent: Observable - val queryBackspaceIntent: Observable<*> + val queryClearedIntent: Observable<*> val queryEditorActionIntent: Observable val composeItemPressedIntent: Subject val composeItemLongPressedIntent: Subject val phoneNumberSelectedIntent: Subject> val phoneNumberActionIntent: Subject + fun clearQuery() fun openKeyboard() fun finish(result: HashMap) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsState.kt index 9d5baad08..9829d905b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsState.kt @@ -22,6 +22,7 @@ import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.model.Contact data class ContactsState( + val query: String = "", val composeItems: List = ArrayList(), val selectedContact: Contact? = null // For phone number picker ) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt index 3a52671d5..39ad99af9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt @@ -85,6 +85,16 @@ class ContactsViewModel @Inject constructor( shouldOpenKeyboard = false } + // Update the state's query, so we know if we should show the cancel button + view.queryChangedIntent + .autoDisposable(view.scope()) + .subscribe { query -> newState { copy(query = query.toString()) } } + + // Clear the query + view.queryClearedIntent + .autoDisposable(view.scope()) + .subscribe { view.clearQuery() } + // Update the list of contact suggestions based on the query input, while also filtering out any contacts // that have already been selected Observables diff --git a/presentation/src/main/res/layout/contacts_activity.xml b/presentation/src/main/res/layout/contacts_activity.xml index 1bbcbb503..89181ebff 100644 --- a/presentation/src/main/res/layout/contacts_activity.xml +++ b/presentation/src/main/res/layout/contacts_activity.xml @@ -47,21 +47,47 @@ style="@style/Toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" + app:contentInsetStartWithNavigation="0dp" app:layout_constrainedHeight="true" app:layout_constraintTop_toTopOf="parent"> - + android:animateLayoutChanges="true"> + + + + + + diff --git a/presentation/src/main/res/layout/main_activity.xml b/presentation/src/main/res/layout/main_activity.xml index 0e52fde04..ab58cee80 100644 --- a/presentation/src/main/res/layout/main_activity.xml +++ b/presentation/src/main/res/layout/main_activity.xml @@ -53,7 +53,7 @@ android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:background="@drawable/rounded_rectangle_24dp" - android:backgroundTint="?android:attr/queryBackground" + android:backgroundTint="?attr/bubbleColor" android:hint="@string/title_conversations" android:paddingStart="16dp" android:paddingEnd="16dp" diff --git a/presentation/src/main/res/values-night/themes.xml b/presentation/src/main/res/values-night/themes.xml index 37deb86e3..cf5054e5b 100644 --- a/presentation/src/main/res/values-night/themes.xml +++ b/presentation/src/main/res/values-night/themes.xml @@ -24,7 +24,7 @@ @@ -66,6 +66,7 @@ @color/black @color/black @color/black + @color/bubbleBlack @color/black @color/black @@ -79,7 +80,7 @@ diff --git a/presentation/src/main/res/values-v23/themes.xml b/presentation/src/main/res/values-v23/themes.xml index b789222d5..c27a31ed1 100644 --- a/presentation/src/main/res/values-v23/themes.xml +++ b/presentation/src/main/res/values-v23/themes.xml @@ -21,7 +21,7 @@ diff --git a/presentation/src/main/res/values-v27/themes.xml b/presentation/src/main/res/values-v27/themes.xml index 6acad9c94..1a7fd8cae 100644 --- a/presentation/src/main/res/values-v27/themes.xml +++ b/presentation/src/main/res/values-v27/themes.xml @@ -22,7 +22,7 @@ diff --git a/presentation/src/main/res/values/colors.xml b/presentation/src/main/res/values/colors.xml index 0193967ef..46f6941e1 100644 --- a/presentation/src/main/res/values/colors.xml +++ b/presentation/src/main/res/values/colors.xml @@ -24,9 +24,6 @@ #1f000000 #33ffffff - #FFFFFF - #1d262b - #FFFFFF #1d262b #88000000 @@ -47,7 +44,8 @@ #80ffffff #ECEFF1 - #242a2f + #151B1F + #0F1113 #0C000000 #1AFFFFFF diff --git a/presentation/src/main/res/values/themes.xml b/presentation/src/main/res/values/themes.xml index 4252e6b40..16db69160 100644 --- a/presentation/src/main/res/values/themes.xml +++ b/presentation/src/main/res/values/themes.xml @@ -44,8 +44,8 @@ true @style/PopupTheme @color/bubbleLight - @color/toolbarLight - @color/statusBarLight + @color/backgroundLight + @color/backgroundLight @drawable/ripple @color/switchThumbEnabledLight @color/switchThumbDisabledLight @@ -61,8 +61,8 @@ @color/backgroundLight @style/PopupTheme @color/bubbleLight - @color/toolbarLight - @color/statusBarLight + @color/backgroundLight + @color/backgroundLight @drawable/ripple false -- GitLab From e3bb7d9079899011e29bf6cbfd1e535d716390ed Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 03:20:46 -0500 Subject: [PATCH 038/213] Contact list animations --- .../feature/compose/editing/ComposeItemAdapter.kt | 10 +++++++++- .../moez/QKSMS/feature/contacts/ContactsActivity.kt | 1 - 2 files changed, 9 insertions(+), 2 deletions(-) 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 b411b2143..1c5cf36c2 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 @@ -168,6 +168,14 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda (view.numbers.adapter as PhoneNumberAdapter).data = contact.numbers } - override fun areContentsTheSame(old: ComposeItem, new: ComposeItem): Boolean = false + override fun areItemsTheSame(old: ComposeItem, new: ComposeItem): Boolean { + val oldIds = old.getContacts().map { contact -> contact.lookupKey } + val newIds = new.getContacts().map { contact -> contact.lookupKey } + return oldIds == newIds + } + + override fun areContentsTheSame(old: ComposeItem, new: ComposeItem): Boolean { + return false + } } 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 7dda49b53..f5ca9f71d 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 @@ -87,7 +87,6 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { showBackButton(true) viewModel.bindView(this) - contacts.itemAnimator = null contacts.adapter = contactsAdapter // These theme attributes don't apply themselves on API 21 -- GitLab From 13a418be4585eaf25c11f78fe996cd4caa74a2fb Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 11:16:12 -0500 Subject: [PATCH 039/213] Support sharing to multiple addresses Fixes #1371 --- .../QKSMS/feature/compose/ComposeActivityModule.kt | 12 ++++++------ .../moez/QKSMS/feature/compose/ComposeViewModel.kt | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index d9b0504a2..5d7b789ce 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -42,13 +42,11 @@ class ComposeActivityModule { fun provideThreadId(activity: ComposeActivity): Long = activity.intent.extras?.getLong("threadId") ?: 0L @Provides - @Named("address") - fun provideAddress(activity: ComposeActivity): String { - var address = "" - + @Named("addresses") + fun provideAddresses(activity: ComposeActivity): List { activity.intent.data?.let { val data = it.toString() - address = when { + var address = when { it.scheme?.startsWith("smsto") == true -> data.replace("smsto:", "") it.scheme?.startsWith("mmsto") == true -> data.replace("mmsto:", "") it.scheme?.startsWith("sms") == true -> data.replace("sms:", "") @@ -58,9 +56,11 @@ class ComposeActivityModule { // The dialer app on Oreo sends a URL encoded string, make sure to decode it if (address.contains('%')) address = URLDecoder.decode(address, "UTF-8") + + return address.split(",") } - return address + return listOf() } @Provides diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 61181d2fe..ded142e50 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -74,7 +74,7 @@ import javax.inject.Named class ComposeViewModel @Inject constructor( @Named("query") private val query: String, @Named("threadId") private val threadId: Long, - @Named("address") private val address: String, + @Named("addresses") private val addresses: List, @Named("text") private val sharedText: String, @Named("attachments") private val sharedAttachments: Attachments, private val contactRepo: ContactRepository, @@ -96,7 +96,7 @@ class ComposeViewModel @Inject constructor( private val sendMessage: SendMessage, private val subscriptionManager: SubscriptionManagerCompat ) : QkViewModel(ComposeState( - editingMode = threadId == 0L && address.isBlank(), + editingMode = threadId == 0L && addresses.isEmpty(), selectedConversation = threadId, query = query) ) { @@ -109,7 +109,7 @@ class ComposeViewModel @Inject constructor( private val searchResults: Subject> = BehaviorSubject.create() private val searchSelection: Subject = BehaviorSubject.createDefault(-1) - private var shouldShowContacts = threadId == 0L && address.isBlank() + private var shouldShowContacts = threadId == 0L && addresses.isEmpty() init { val initialConversation = threadId.takeIf { it != 0L } @@ -160,8 +160,8 @@ class ComposeViewModel @Inject constructor( .filter { conversation -> conversation.isValid } .subscribe(conversation::onNext) - if (address.isNotBlank()) { - selectedChips.onNext(listOf(Chip(address))) + if (addresses.isNotEmpty()) { + selectedChips.onNext(addresses.map { address -> Chip(address) }) } disposables += chipsReducer -- GitLab From 65fd1cacdba72aad929c852b016dff1e361dfeb3 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 11:48:53 -0500 Subject: [PATCH 040/213] Parse body param in uri query Fixes 1321 --- .../feature/compose/ComposeActivityModule.kt | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index 5d7b789ce..dcf9af1fb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -44,23 +44,12 @@ class ComposeActivityModule { @Provides @Named("addresses") fun provideAddresses(activity: ComposeActivity): List { - activity.intent.data?.let { - val data = it.toString() - var address = when { - it.scheme?.startsWith("smsto") == true -> data.replace("smsto:", "") - it.scheme?.startsWith("mmsto") == true -> data.replace("mmsto:", "") - it.scheme?.startsWith("sms") == true -> data.replace("sms:", "") - it.scheme?.startsWith("mms") == true -> data.replace("mms:", "") - else -> "" - } - - // The dialer app on Oreo sends a URL encoded string, make sure to decode it - if (address.contains('%')) address = URLDecoder.decode(address, "UTF-8") - - return address.split(",") - } - - return listOf() + return activity.intent + ?.decodedDataString() + ?.substringAfter(':') // Remove scheme + ?.replaceAfter("?", "") // Remove query + ?.split(",") + ?: listOf() } @Provides @@ -68,6 +57,11 @@ class ComposeActivityModule { fun provideSharedText(activity: ComposeActivity): String { return activity.intent.extras?.getString(Intent.EXTRA_TEXT) ?: activity.intent.extras?.getString("sms_body") + ?: activity.intent?.decodedDataString() + ?.substringAfter('?') // Query string + ?.split(',') + ?.firstOrNull { param -> param.startsWith("body") } + ?.substringAfter('=') ?: "" } @@ -85,4 +79,13 @@ class ComposeActivityModule { @ViewModelKey(ComposeViewModel::class) fun provideComposeViewModel(viewModel: ComposeViewModel): ViewModel = viewModel + // The dialer app on Oreo sends a URL encoded string, make sure to decode it + private fun Intent.decodedDataString(): String? { + val data = data?.toString() + if (data?.contains('%') == true) { + return URLDecoder.decode(data, "UTF-8") + } + return data + } + } \ No newline at end of file -- GitLab From 8e4ff3ec8d7121a56016d2354137242371932950 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 13:46:56 -0500 Subject: [PATCH 041/213] Persist camera destination uri Fixes #1457 --- .../QKSMS/feature/compose/ComposeActivity.kt | 36 ++++++++++++------- .../QKSMS/feature/compose/ComposeViewModel.kt | 2 +- 2 files changed, 25 insertions(+), 13 deletions(-) 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 c85dfdb54..86c097286 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 @@ -73,10 +73,12 @@ import kotlin.collections.HashMap class ComposeActivity : QkThemedActivity(), ComposeView { companion object { - private const val SELECT_CONTACT_REQUEST_CODE = 0 - private const val TAKE_PHOTO_REQUEST_CODE = 1 - private const val ATTACH_PHOTO_REQUEST_CODE = 2 - private const val ATTACH_CONTACT_REQUEST_CODE = 3 + private const val SelectContactRequestCode = 0 + private const val TakePhotoRequestCode = 1 + private const val AttachPhotoRequestCode = 2 + private const val AttachContactRequestCode = 3 + + private const val CameraDestinationKey = "camera_destination" } @Inject lateinit var attachmentAdapter: AttachmentAdapter @@ -281,7 +283,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { val intent = Intent(Intent.ACTION_PICK) .setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE) - startActivityForResult(Intent.createChooser(intent, null), ATTACH_CONTACT_REQUEST_CODE) + startActivityForResult(Intent.createChooser(intent, null), AttachContactRequestCode) } override fun showContacts(sharing: Boolean, chips: List) { @@ -290,7 +292,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { val intent = Intent(this, ContactsActivity::class.java) .putExtra(ContactsActivity.SharingKey, sharing) .putExtra(ContactsActivity.ChipsKey, serialized) - startActivityForResult(intent, SELECT_CONTACT_REQUEST_CODE) + startActivityForResult(intent, SelectContactRequestCode) } override fun showKeyboard() { @@ -306,7 +308,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) .putExtra(MediaStore.EXTRA_OUTPUT, cameraDestination) - startActivityForResult(Intent.createChooser(intent, null), TAKE_PHOTO_REQUEST_CODE) + startActivityForResult(Intent.createChooser(intent, null), TakePhotoRequestCode) } override fun requestGallery() { @@ -316,7 +318,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .putExtra(Intent.EXTRA_LOCAL_ONLY, false) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .setType("image/*") - startActivityForResult(Intent.createChooser(intent, null), ATTACH_PHOTO_REQUEST_CODE) + startActivityForResult(Intent.createChooser(intent, null), AttachPhotoRequestCode) } override fun setDraft(draft: String) = message.setText(draft) @@ -352,28 +354,38 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when { - requestCode == SELECT_CONTACT_REQUEST_CODE -> { + requestCode == SelectContactRequestCode -> { chipsSelectedIntent.onNext(data?.getSerializableExtra(ContactsActivity.ChipsKey) ?.let { serializable -> serializable as? HashMap } ?: hashMapOf()) } - requestCode == TAKE_PHOTO_REQUEST_CODE && resultCode == Activity.RESULT_OK -> { + requestCode == TakePhotoRequestCode && resultCode == Activity.RESULT_OK -> { cameraDestination?.let(attachmentSelectedIntent::onNext) } - requestCode == ATTACH_PHOTO_REQUEST_CODE && resultCode == Activity.RESULT_OK -> { + requestCode == AttachPhotoRequestCode && resultCode == Activity.RESULT_OK -> { data?.clipData?.itemCount ?.let { count -> 0 until count } ?.mapNotNull { i -> data.clipData?.getItemAt(i)?.uri } ?.forEach(attachmentSelectedIntent::onNext) ?: data?.data?.let(attachmentSelectedIntent::onNext) } - requestCode == ATTACH_CONTACT_REQUEST_CODE && resultCode == Activity.RESULT_OK -> { + requestCode == AttachContactRequestCode && resultCode == Activity.RESULT_OK -> { data?.data?.let(contactSelectedIntent::onNext) } else -> super.onActivityResult(requestCode, resultCode, data) } } + override fun onSaveInstanceState(outState: Bundle) { + outState.putParcelable(CameraDestinationKey, cameraDestination) + super.onSaveInstanceState(outState) + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle?) { + cameraDestination = savedInstanceState?.getParcelable(CameraDestinationKey) + super.onRestoreInstanceState(savedInstanceState) + } + override fun onBackPressed() = backPressedIntent.onNext(Unit) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index ded142e50..4687d388d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -493,7 +493,7 @@ class ComposeViewModel @Inject constructor( view.attachmentSelectedIntent.map { uri -> Attachment.Image(uri) }, view.inputContentIntent.map { inputContent -> Attachment.Image(inputContent = inputContent) }) .withLatestFrom(attachments) { attachment, attachments -> attachments + attachment } - .doOnNext { attachments.onNext(it) } + .doOnNext(attachments::onNext) .autoDisposable(view.scope()) .subscribe { newState { copy(attaching = false) } } -- GitLab From 889d298f96d6e3102998fb38733c4cf0757825d3 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 14:06:36 -0500 Subject: [PATCH 042/213] Copy text from multiple messages Closes #1122 --- .../QKSMS/feature/compose/ComposeActivity.kt | 2 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) 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 86c097286..ab1b0fa1d 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 @@ -201,7 +201,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { && state.query.isEmpty() toolbar.menu.findItem(R.id.info)?.isVisible = !state.editingMode && state.selectedMessages == 0 && state.query.isEmpty() - toolbar.menu.findItem(R.id.copy)?.isVisible = !state.editingMode && state.selectedMessages == 1 + toolbar.menu.findItem(R.id.copy)?.isVisible = !state.editingMode && state.selectedMessages > 0 toolbar.menu.findItem(R.id.details)?.isVisible = !state.editingMode && state.selectedMessages == 1 toolbar.menu.findItem(R.id.delete)?.isVisible = !state.editingMode && state.selectedMessages > 0 toolbar.menu.findItem(R.id.forward)?.isVisible = !state.editingMode && state.selectedMessages == 1 diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 4687d388d..22b2b79c2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -300,11 +300,20 @@ class ComposeViewModel @Inject constructor( // Copy the message contents view.optionsItemIntent .filter { it == R.id.copy } - .withLatestFrom(view.messagesSelectedIntent) { _, messages -> - messages?.firstOrNull()?.let { messageRepo.getMessage(it) }?.let { message -> - ClipboardUtils.copy(context, message.getText()) - context.makeToast(R.string.toast_copied) + .withLatestFrom(view.messagesSelectedIntent) { _, messageIds -> + val messages = messageIds.mapNotNull(messageRepo::getMessage).sortedBy { it.date } + val text = when (messages.size) { + 1 -> messages.first().getText() + else -> messages.foldIndexed("") { index, acc, message -> + when { + index == 0 -> message.getText() + messages[index - 1].compareSender(message) -> "$acc\n${message.getText()}" + else -> "$acc\n\n${message.getText()}" + } + } } + + ClipboardUtils.copy(context, text) } .autoDisposable(view.scope()) .subscribe { view.clearSelection() } -- GitLab From c2db9b66ad8c5a477502ac5236af62c9d8ff64a7 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 14:20:30 -0500 Subject: [PATCH 043/213] Share photos externally Closes #1442 --- .../main/java/com/moez/QKSMS/common/Navigator.kt | 11 +++++++++++ .../moez/QKSMS/feature/gallery/GalleryActivity.kt | 2 +- .../moez/QKSMS/feature/gallery/GalleryViewModel.kt | 14 ++++++++++++-- presentation/src/main/res/menu/gallery.xml | 6 +++++- presentation/src/main/res/values/strings.xml | 1 + 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt index 2b6372715..4cd9e69b7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt @@ -266,6 +266,17 @@ class Navigator @Inject constructor( startActivityExternal(intent) } + fun shareFile(file: File) { + val data = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) + val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.name.split(".").last()) + val intent = Intent(Intent.ACTION_SEND) + .setType(type) + .putExtra(Intent.EXTRA_STREAM, data) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + + startActivityExternal(intent) + } + fun showNotificationSettings(threadId: Long = 0) { val intent = Intent(context, NotificationPrefsActivity::class.java) intent.putExtra("threadId", threadId) 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 5edc8b829..f2c261b68 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 @@ -54,7 +54,7 @@ class GalleryActivity : QkActivity(), GalleryView { private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[GalleryViewModel::class.java] } override fun onCreate(savedInstanceState: Bundle?) { - delegate.setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES) + delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES AndroidInjection.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.gallery_activity) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt index 9d72c07da..d660d6cec 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt @@ -20,6 +20,7 @@ package com.moez.QKSMS.feature.gallery import android.content.Context import com.moez.QKSMS.R +import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.extensions.mapNotNull @@ -37,9 +38,10 @@ import javax.inject.Named class GalleryViewModel @Inject constructor( conversationRepo: ConversationRepository, - messageRepo: MessageRepository, @Named("partId") private val partId: Long, private val context: Context, + private val messageRepo: MessageRepository, + private val navigator: Navigator, private val saveImage: SaveImage, private val permissions: PermissionManager ) : QkViewModel(GalleryState()) { @@ -74,6 +76,14 @@ class GalleryViewModel @Inject constructor( .withLatestFrom(view.pageChanged()) { _, part -> part.id } .autoDisposable(view.scope()) .subscribe { partId -> saveImage.execute(partId) { context.makeToast(R.string.gallery_toast_saved) } } + + // Share image externally + view.optionsItemSelected() + .filter { itemId -> itemId == R.id.share } + .filter { permissions.hasStorage().also { if (!it) view.requestStoragePermission() } } + .withLatestFrom(view.pageChanged()) { _, part -> part.id } + .autoDisposable(view.scope()) + .subscribe { partId -> messageRepo.savePart(partId)?.let(navigator::shareFile) } } -} \ No newline at end of file +} diff --git a/presentation/src/main/res/menu/gallery.xml b/presentation/src/main/res/menu/gallery.xml index 00793241b..9cd668b49 100644 --- a/presentation/src/main/res/menu/gallery.xml +++ b/presentation/src/main/res/menu/gallery.xml @@ -23,4 +23,8 @@ android:id="@+id/save" android:title="@string/menu_save" /> - \ No newline at end of file + + + diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 01be513b2..27041d0cc 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ Call Details Save to gallery + Share Open navigation drawer %d selected -- GitLab From 25cff7e346e349b288cf03d4a570b22beab981c2 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 14:38:18 -0500 Subject: [PATCH 044/213] Refactor ContactSync to SyncContacts --- .../moez/QKSMS/interactor/{ContactSync.kt => SyncContacts.kt} | 2 +- .../java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename domain/src/main/java/com/moez/QKSMS/interactor/{ContactSync.kt => SyncContacts.kt} (92%) diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/ContactSync.kt b/domain/src/main/java/com/moez/QKSMS/interactor/SyncContacts.kt similarity index 92% rename from domain/src/main/java/com/moez/QKSMS/interactor/ContactSync.kt rename to domain/src/main/java/com/moez/QKSMS/interactor/SyncContacts.kt index 7cd97fc73..ba79168a8 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ContactSync.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/SyncContacts.kt @@ -24,7 +24,7 @@ import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject -open class ContactSync @Inject constructor(private val syncManager: SyncRepository) : Interactor() { +class SyncContacts @Inject constructor(private val syncManager: SyncRepository) : Interactor() { override fun buildObservable(params: Unit): Flowable { return Flowable.just(System.currentTimeMillis()) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt index f5869ff1f..108b0ce5b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt @@ -27,7 +27,7 @@ import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction import com.moez.QKSMS.filter.ContactFilter import com.moez.QKSMS.filter.ContactGroupFilter -import com.moez.QKSMS.interactor.ContactSync +import com.moez.QKSMS.interactor.SyncContacts import com.moez.QKSMS.interactor.SetDefaultPhoneNumber import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.ContactGroup @@ -50,7 +50,7 @@ import javax.inject.Inject class ContactsViewModel @Inject constructor( sharing: Boolean, serializedChips: HashMap, - syncContacts: ContactSync, + syncContacts: SyncContacts, private val contactFilter: ContactFilter, private val contactGroupFilter: ContactGroupFilter, private val contactsRepo: ContactRepository, -- GitLab From 13c8ba3e7813d266316cd1f026b947c208a78b6f Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 15:30:25 -0500 Subject: [PATCH 045/213] Keep contacts in sync Fixes #329 --- .../listener/ContactAddedListenerImpl.kt | 15 ++++------ .../QKSMS/repository/SyncRepositoryImpl.kt | 30 ++----------------- .../com/moez/QKSMS/interactor/SyncContacts.kt | 4 +-- .../QKSMS/listener/ContactAddedListener.kt | 4 +-- .../moez/QKSMS/repository/SyncRepository.kt | 11 ++----- .../feature/contacts/ContactsViewModel.kt | 6 ---- .../ConversationInfoPresenter.kt | 16 ++-------- .../com/moez/QKSMS/feature/main/MainState.kt | 2 +- .../moez/QKSMS/feature/main/MainViewModel.kt | 12 ++++++-- .../QKSMS/feature/settings/SettingsState.kt | 2 +- 10 files changed, 27 insertions(+), 75 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/listener/ContactAddedListenerImpl.kt b/data/src/main/java/com/moez/QKSMS/listener/ContactAddedListenerImpl.kt index 605b623c9..1d1f74339 100644 --- a/data/src/main/java/com/moez/QKSMS/listener/ContactAddedListenerImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/listener/ContactAddedListenerImpl.kt @@ -23,33 +23,28 @@ import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.provider.ContactsContract -import com.moez.QKSMS.repository.SyncRepository import io.reactivex.Observable -import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.BehaviorSubject import javax.inject.Inject /** * Listens for a contact being added, and then syncs it to Realm - * - * TODO: Stop listening automatically. Currently, this will only happen if the contact is added */ class ContactAddedListenerImpl @Inject constructor( - private val context: Context, - private val syncRepo: SyncRepository + private val context: Context ) : ContactAddedListener { companion object { private val URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI } - override fun listen(address: String): Observable<*> { + override fun listen(): Observable<*> { return ContactContentObserver(context).observable - .filter { syncRepo.syncContact(address) } } private class ContactContentObserver(context: Context) : ContentObserver(Handler()) { - private val subject = PublishSubject.create() + private val subject = BehaviorSubject.createDefault(Unit) val observable: Observable = subject .doOnSubscribe { context.contentResolver.registerContentObserver(URI, true, this) } @@ -66,4 +61,4 @@ class ContactAddedListenerImpl @Inject constructor( } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index 0b1f2362f..8cb37238b 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -65,7 +65,7 @@ class SyncRepositoryImpl @Inject constructor( ) : SyncRepository { override val syncProgress: Subject = - BehaviorSubject.createDefault(SyncRepository.SyncProgress.Idle()) + BehaviorSubject.createDefault(SyncRepository.SyncProgress.Idle) override fun syncMessages() { @@ -185,7 +185,7 @@ class SyncRepositoryImpl @Inject constructor( // Only delete this after the sync has successfully completed oldBlockedSenders.delete() - syncProgress.onNext(SyncRepository.SyncProgress.Idle()) + syncProgress.onNext(SyncRepository.SyncProgress.Idle) } override fun syncMessage(uri: Uri): Message? { @@ -259,32 +259,6 @@ class SyncRepositoryImpl @Inject constructor( } } - override fun syncContact(address: String): Boolean { - // See if there's a contact that matches this phone number - var contact = getContacts().find { contact -> - contact.numbers.any { number -> phoneNumberUtils.compare(number.address, address) } - } ?: return false - - Realm.getDefaultInstance().use { realm -> - val recipients = realm.where(Recipient::class.java).findAll().filter { recipient -> - contact.numbers.any { number -> - phoneNumberUtils.compare(recipient.address, number.address) - } - } - - realm.executeTransaction { - contact = realm.copyToRealmOrUpdate(contact) - - // Update all the matching recipients with the new contact - recipients.forEach { recipient -> recipient.contact = contact } - - realm.insertOrUpdate(recipients) - } - } - - return true - } - private fun getContacts(): List { val defaultNumberIds = Realm.getDefaultInstance().use { realm -> realm.where(PhoneNumber::class.java) diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/SyncContacts.kt b/domain/src/main/java/com/moez/QKSMS/interactor/SyncContacts.kt index ba79168a8..45fd8874e 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/SyncContacts.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/SyncContacts.kt @@ -21,7 +21,6 @@ package com.moez.QKSMS.interactor import com.moez.QKSMS.repository.SyncRepository import io.reactivex.Flowable import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject class SyncContacts @Inject constructor(private val syncManager: SyncRepository) : Interactor() { @@ -30,8 +29,7 @@ class SyncContacts @Inject constructor(private val syncManager: SyncRepository) return Flowable.just(System.currentTimeMillis()) .doOnNext { syncManager.syncContacts() } .map { startTime -> System.currentTimeMillis() - startTime } - .map { elapsed -> TimeUnit.MILLISECONDS.toSeconds(elapsed) } - .doOnNext { seconds -> Timber.v("Completed sync in $seconds seconds") } + .doOnNext { duration -> Timber.v("Completed sync in ${duration}ms") } } } \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/listener/ContactAddedListener.kt b/domain/src/main/java/com/moez/QKSMS/listener/ContactAddedListener.kt index 1d2d1c507..800d83460 100644 --- a/domain/src/main/java/com/moez/QKSMS/listener/ContactAddedListener.kt +++ b/domain/src/main/java/com/moez/QKSMS/listener/ContactAddedListener.kt @@ -22,6 +22,6 @@ import io.reactivex.Observable interface ContactAddedListener { - fun listen(address: String): Observable<*> + fun listen(): Observable<*> -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/repository/SyncRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/SyncRepository.kt index 0638557a8..f8f8b7def 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/SyncRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/SyncRepository.kt @@ -25,7 +25,7 @@ import io.reactivex.Observable interface SyncRepository { sealed class SyncProgress { - class Idle : SyncProgress() + object Idle : SyncProgress() data class Running(val max: Int, val progress: Int, val indeterminate: Boolean) : SyncProgress() } @@ -37,11 +37,4 @@ interface SyncRepository { fun syncContacts() - /** - * Syncs a single contact to the Realm - * - * Return false if the contact couldn't be found - */ - fun syncContact(address: String): Boolean - -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt index 108b0ce5b..cb9386f29 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt @@ -27,7 +27,6 @@ import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction import com.moez.QKSMS.filter.ContactFilter import com.moez.QKSMS.filter.ContactGroupFilter -import com.moez.QKSMS.interactor.SyncContacts import com.moez.QKSMS.interactor.SetDefaultPhoneNumber import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.ContactGroup @@ -50,7 +49,6 @@ import javax.inject.Inject class ContactsViewModel @Inject constructor( sharing: Boolean, serializedChips: HashMap, - syncContacts: SyncContacts, private val contactFilter: ContactFilter, private val contactGroupFilter: ContactGroupFilter, private val contactsRepo: ContactRepository, @@ -76,10 +74,6 @@ class ContactsViewModel @Inject constructor( private var shouldOpenKeyboard: Boolean = true - init { - syncContacts.execute(Unit) - } - override fun bindView(view: ContactsContract) { super.bindView(view) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt index 4f6029021..06c44e0ad 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt @@ -26,14 +26,12 @@ import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.interactor.DeleteConversations import com.moez.QKSMS.interactor.MarkArchived import com.moez.QKSMS.interactor.MarkUnarchived -import com.moez.QKSMS.listener.ContactAddedListener import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable -import io.reactivex.Observable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.subjects.BehaviorSubject @@ -44,7 +42,6 @@ import javax.inject.Named class ConversationInfoPresenter @Inject constructor( @Named("threadId") threadId: Long, messageRepo: MessageRepository, - private val contactAddedListener: ContactAddedListener, private val conversationRepo: ConversationRepository, private val deleteConversations: DeleteConversations, private val markArchived: MarkArchived, @@ -105,16 +102,9 @@ class ConversationInfoPresenter @Inject constructor( // Add or display the contact view.recipientClicks() .mapNotNull(conversationRepo::getRecipient) - .flatMap { recipient -> - val lookupKey = recipient.contact?.lookupKey - if (lookupKey != null) { - navigator.showContact(lookupKey) - Observable.empty() - } else { - // Allow the user to add the contact, then listen for changes - navigator.addContact(recipient.address) - contactAddedListener.listen(recipient.address) - } + .doOnNext { recipient -> + recipient.contact?.lookupKey?.let(navigator::showContact) + ?: navigator.addContact(recipient.address) } .autoDisposable(view.scope(Lifecycle.Event.ON_DESTROY)) // ... this should be the default .subscribe() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt index b905f0912..01697633a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt @@ -29,7 +29,7 @@ data class MainState( val drawerOpen: Boolean = false, val upgraded: Boolean = true, val showRating: Boolean = false, - val syncing: SyncRepository.SyncProgress = SyncRepository.SyncProgress.Idle(), + val syncing: SyncRepository.SyncProgress = SyncRepository.SyncProgress.Idle, val defaultSms: Boolean = true, val smsPermission: Boolean = true, val contactPermission: Boolean = true diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index b5b1c8e3b..dd9340845 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -33,6 +33,7 @@ import com.moez.QKSMS.interactor.MarkUnarchived import com.moez.QKSMS.interactor.MarkUnpinned import com.moez.QKSMS.interactor.MarkUnread import com.moez.QKSMS.interactor.MigratePreferences +import com.moez.QKSMS.interactor.SyncContacts import com.moez.QKSMS.interactor.SyncMessages import com.moez.QKSMS.listener.ContactAddedListener import com.moez.QKSMS.manager.ChangelogManager @@ -55,10 +56,10 @@ import javax.inject.Inject class MainViewModel @Inject constructor( billingManager: BillingManager, + contactAddedListener: ContactAddedListener, markAllSeen: MarkAllSeen, migratePreferences: MigratePreferences, syncRepository: SyncRepository, - private val contactAddedListener: ContactAddedListener, private val changelogManager: ChangelogManager, private val conversationRepo: ConversationRepository, private val deleteConversations: DeleteConversations, @@ -72,6 +73,7 @@ class MainViewModel @Inject constructor( private val permissionManager: PermissionManager, private val prefs: Preferences, private val ratingManager: RatingManager, + private val syncContacts: SyncContacts, private val syncMessages: SyncMessages ) : QkViewModel(MainState(page = Inbox(data = conversationRepo.getConversations()))) { @@ -81,6 +83,7 @@ class MainViewModel @Inject constructor( disposables += markArchived disposables += markUnarchived disposables += migratePreferences + disposables += syncContacts disposables += syncMessages // Show the syncing UI @@ -109,6 +112,12 @@ class MainViewModel @Inject constructor( syncMessages.execute(Unit) } + // Sync contacts when we detect a change + disposables += contactAddedListener.listen() + .debounce(1, TimeUnit.SECONDS) + .subscribeOn(Schedulers.io()) + .subscribe { syncContacts.execute(Unit) } + ratingManager.addSession() markAllSeen.execute(Unit) } @@ -287,7 +296,6 @@ class MainViewModel @Inject constructor( .map { conversation -> conversation.recipients } .mapNotNull { recipients -> recipients[0]?.address?.takeIf { recipients.size == 1 } } .doOnNext(navigator::addContact) - .flatMap(contactAddedListener::listen) .autoDisposable(view.scope()) .subscribe() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt index d2a359957..f78a841f5 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt @@ -42,5 +42,5 @@ data class SettingsState( val mobileOnly: Boolean = false, val maxMmsSizeSummary: String = "100KB", val maxMmsSizeId: Int = 100, - val syncProgress: SyncRepository.SyncProgress = SyncRepository.SyncProgress.Idle() + val syncProgress: SyncRepository.SyncProgress = SyncRepository.SyncProgress.Idle ) \ No newline at end of file -- GitLab From f9ef47fcb87f2cad58d583f1ae7e587c1d62532d Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 16:37:39 -0500 Subject: [PATCH 046/213] Fix notification sounds not working on 7.0 --- .../QKSMS/common/util/NotificationManagerImpl.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index 4fa3a3921..1f142e79f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -19,6 +19,7 @@ package com.moez.QKSMS.common.util import android.annotation.SuppressLint +import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent @@ -26,6 +27,7 @@ import android.content.ContentUris import android.content.Context import android.content.Intent import android.graphics.Color +import android.media.AudioAttributes import android.net.Uri import android.os.Build import android.provider.ContactsContract @@ -112,8 +114,8 @@ class NotificationManagerImpl @Inject constructor( val contentIntent = Intent(context, ComposeActivity::class.java).putExtra("threadId", threadId) val taskStackBuilder = TaskStackBuilder.create(context) - taskStackBuilder.addParentStack(ComposeActivity::class.java) - taskStackBuilder.addNextIntent(contentIntent) + .addParentStack(ComposeActivity::class.java) + .addNextIntent(contentIntent) val contentPI = taskStackBuilder.getPendingIntent(threadId.toInt() + 10000, PendingIntent.FLAG_UPDATE_CURRENT) val seenIntent = Intent(context, MarkSeenReceiver::class.java).putExtra("threadId", threadId) @@ -123,6 +125,10 @@ class NotificationManagerImpl @Inject constructor( val ringtone = prefs.ringtone(threadId).get() .takeIf { it.isNotEmpty() } ?.let(Uri::parse) + ?.also { uri -> + // https://commonsware.com/blog/2016/09/07/notifications-sounds-android-7p0-aggravation.html + context.grantUriPermission("com.android.systemui", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + } val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId)) .setCategory(NotificationCompat.CATEGORY_MESSAGE) @@ -342,6 +348,11 @@ class NotificationManagerImpl @Inject constructor( lightColor = Color.WHITE enableVibration(true) vibrationPattern = VIBRATE_PATTERN + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + setSound(prefs.ringtone().get().let(Uri::parse), AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .build()) } notificationManager.createNotificationChannel(channel) -- GitLab From f412ccd4423e66166c4cbf82dcdca29c82572278 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 16:53:40 -0500 Subject: [PATCH 047/213] Darker bubbles --- presentation/src/main/res/values/colors.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/res/values/colors.xml b/presentation/src/main/res/values/colors.xml index 46f6941e1..f359c4c05 100644 --- a/presentation/src/main/res/values/colors.xml +++ b/presentation/src/main/res/values/colors.xml @@ -44,7 +44,7 @@ #80ffffff #ECEFF1 - #151B1F + #11171B #0F1113 #0C000000 -- GitLab From 5d6291250827d54a8a90598c6f4daa47715eb268 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 24 Nov 2019 17:20:04 -0500 Subject: [PATCH 048/213] Add setting to wake screen Closes #1048 --- .../main/java/com/moez/QKSMS/util/Preferences.kt | 9 +++++++++ .../QKSMS/common/util/NotificationManagerImpl.kt | 13 +++++++++++++ .../notificationprefs/NotificationPrefsActivity.kt | 1 + .../notificationprefs/NotificationPrefsState.kt | 1 + .../notificationprefs/NotificationPrefsViewModel.kt | 6 ++++++ .../main/res/layout/notification_prefs_activity.xml | 7 +++++++ presentation/src/main/res/values/strings.xml | 1 + 7 files changed, 38 insertions(+) diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index 5e73c2896..2acfe37f3 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -145,6 +145,15 @@ class Preferences @Inject constructor(context: Context, private val rxPrefs: RxS } } + fun wakeScreen(threadId: Long = 0): Preference { + val default = rxPrefs.getBoolean("wake", false) + + return when (threadId) { + 0L -> default + else -> rxPrefs.getBoolean("wake_$threadId", default.get()) + } + } + fun vibration(threadId: Long = 0): Preference { val default = rxPrefs.getBoolean("vibration", true) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index 1f142e79f..7bbcb6aeb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -30,12 +30,14 @@ import android.graphics.Color import android.media.AudioAttributes import android.net.Uri import android.os.Build +import android.os.PowerManager import android.provider.ContactsContract import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.Person import androidx.core.app.RemoteInput import androidx.core.app.TaskStackBuilder +import androidx.core.content.getSystemService import androidx.core.graphics.drawable.IconCompat import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.dpToPx @@ -279,6 +281,17 @@ class NotificationManagerImpl @Inject constructor( } notificationManager.notify(threadId.toInt(), notification.build()) + + // Wake screen + if (prefs.wakeScreen(threadId).get()) { + context.getSystemService()?.let { powerManager -> + if (!powerManager.isInteractive) { + val flags = PowerManager.SCREEN_DIM_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP + val wakeLock = powerManager.newWakeLock(flags, context.packageName) + wakeLock.acquire(5000) + } + } + } } override fun notifyFailed(msgId: Long) { 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 b998c1d97..210b5d3fe 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 @@ -98,6 +98,7 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { notifications.checkbox.isChecked = state.notificationsEnabled previews.summary = state.previewSummary previewModeDialog.adapter.selectedItem = state.previewId + wake.checkbox.isChecked = state.wakeEnabled vibration.checkbox.isChecked = state.vibrationEnabled ringtone.summary = state.ringtoneName diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsState.kt index e5e077ea2..c0dcd0f24 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsState.kt @@ -27,6 +27,7 @@ data class NotificationPrefsState( val notificationsEnabled: Boolean = true, val previewSummary: String = "", val previewId: Int = Preferences.NOTIFICATION_PREVIEWS_ALL, + val wakeEnabled: Boolean = false, val action1Summary: String = "", val action2Summary: String = "", val action3Summary: String = "", diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt index 6639d4f70..596423bb3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt @@ -46,6 +46,7 @@ class NotificationPrefsViewModel @Inject constructor( private val notifications = prefs.notifications(threadId) private val previews = prefs.notificationPreviews(threadId) + private val wake = prefs.wakeScreen(threadId) private val vibration = prefs.vibration(threadId) private val ringtone = prefs.ringtone(threadId) @@ -75,6 +76,9 @@ class NotificationPrefsViewModel @Inject constructor( disposables += prefs.notifAction3.asObservable() .subscribe { previewId -> newState { copy(action3Summary = actionLabels[previewId]) } } + disposables += wake.asObservable() + .subscribe { enabled -> newState { copy(wakeEnabled = enabled) } } + disposables += vibration.asObservable() .subscribe { enabled -> newState { copy(vibrationEnabled = enabled) } } @@ -107,6 +111,8 @@ class NotificationPrefsViewModel @Inject constructor( R.id.previews -> view.showPreviewModeDialog() + R.id.wake -> wake.set(!wake.get()) + R.id.vibration -> vibration.set(!vibration.get()) R.id.ringtone -> view.showRingtonePicker(ringtone.get().takeIf { it.isNotEmpty() }?.let(Uri::parse)) diff --git a/presentation/src/main/res/layout/notification_prefs_activity.xml b/presentation/src/main/res/layout/notification_prefs_activity.xml index 5f130300d..497490339 100644 --- a/presentation/src/main/res/layout/notification_prefs_activity.xml +++ b/presentation/src/main/res/layout/notification_prefs_activity.xml @@ -72,6 +72,13 @@ app:title="@string/settings_notification_previews_title" tools:summary="Show name and message" /> + + Button 2 Button 3 Notification previews + Wake screen Vibration Sound None -- GitLab From 76e0f775d61c0b97b6549c04e04e27960aea1608 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 15 Dec 2019 20:46:33 -0500 Subject: [PATCH 049/213] Per-contact colours Closes #1120 --- .../moez/QKSMS/migration/QkRealmMigration.kt | 23 ++++- .../QKSMS/repository/MessageRepositoryImpl.kt | 95 +++++++++++-------- .../moez/QKSMS/manager/NotificationManager.kt | 2 +- .../QKSMS/repository/MessageRepository.kt | 2 + .../java/com/moez/QKSMS/util/Preferences.kt | 32 ++++++- .../QKSMS/common/base/QkThemedActivity.kt | 42 +++++++- .../java/com/moez/QKSMS/common/util/Colors.kt | 6 +- .../common/util/NotificationManagerImpl.kt | 20 +++- .../moez/QKSMS/common/widget/AvatarView.kt | 8 +- .../QKSMS/common/widget/GroupAvatarView.kt | 20 ++-- .../QKSMS/common/widget/PagerTitleView.kt | 10 +- .../messages/BlockedMessagesAdapter.kt | 2 +- .../QKSMS/feature/compose/ComposeActivity.kt | 6 +- .../QKSMS/feature/compose/ComposeState.kt | 2 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 8 +- .../QKSMS/feature/compose/MessagesAdapter.kt | 18 ++-- .../compose/editing/ComposeItemAdapter.kt | 10 +- .../ConversationInfoController.kt | 19 ++-- .../ConversationInfoPresenter.kt | 13 ++- .../conversationinfo/ConversationInfoView.kt | 6 +- .../ConversationRecipientAdapter.kt | 29 +++--- .../conversations/ConversationsAdapter.kt | 20 +++- .../moez/QKSMS/feature/main/MainActivity.kt | 5 +- .../moez/QKSMS/feature/main/SearchAdapter.kt | 2 +- .../QKSMS/feature/qkreply/QkReplyActivity.kt | 2 +- .../QKSMS/feature/qkreply/QkReplyState.kt | 2 +- .../QKSMS/feature/qkreply/QkReplyViewModel.kt | 2 +- .../scheduled/ScheduledMessageAdapter.kt | 2 +- .../themepicker/ThemePickerController.kt | 6 +- .../themepicker/ThemePickerPresenter.kt | 10 +- .../feature/themepicker/ThemePickerState.kt | 2 +- .../injection/ThemePickerModule.kt | 4 +- .../com/moez/QKSMS/injection/AppModule.kt | 10 +- .../layout/conversation_info_controller.xml | 9 +- .../conversation_recipient_list_item.xml | 19 +++- 35 files changed, 315 insertions(+), 153 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index 5dcfffc0e..be9f96872 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -18,6 +18,8 @@ */ package com.moez.QKSMS.migration +import android.annotation.SuppressLint +import com.f2prateek.rx.preferences2.RxSharedPreferences import com.moez.QKSMS.extensions.map import com.moez.QKSMS.mapper.CursorToContactImpl import io.realm.DynamicRealm @@ -29,13 +31,15 @@ import io.realm.Sort import javax.inject.Inject class QkRealmMigration @Inject constructor( - private val cursorToContact: CursorToContactImpl + private val cursorToContact: CursorToContactImpl, + private val prefs: RxSharedPreferences ) : RealmMigration { companion object { const val SchemaVersion: Long = 9 } + @SuppressLint("ApplySharedPref") override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { var version = oldVersion @@ -169,6 +173,23 @@ class QkRealmMigration @Inject constructor( realmContact.setString("photoUri", photoUri) } + // Migrate conversation themes + val recipients = mutableMapOf() // Map of recipientId:theme + realm.where("Conversation").findAll().forEach { conversation -> + val pref = prefs.getInteger("theme_${conversation.getLong("id")}") + if (pref.isSet) { + conversation.getList("Recipient").forEach { recipient -> + recipients[recipient.getLong("id")] = pref.get() + } + + pref.delete() + } + } + + recipients.forEach { (recipientId, theme) -> + prefs.getInteger("theme_$recipientId").set(theme) + } + version++ } diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index 9ec414cea..2b4ba0687 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -28,6 +28,8 @@ import android.media.MediaScannerConnection import android.os.Build import android.os.Environment import android.provider.Telephony +import android.provider.Telephony.Mms +import android.provider.Telephony.Sms import android.telephony.SmsManager import android.webkit.MimeTypeMap import androidx.core.content.contentValuesOf @@ -110,6 +112,25 @@ class MessageRepositoryImpl @Inject constructor( .findFirst() } + override fun getLastIncomingMessage(threadId: Long): RealmResults { + return Realm.getDefaultInstance() + .where(Message::class.java) + .equalTo("threadId", threadId) + .beginGroup() + .beginGroup() + .equalTo("type", "sms") + .`in`("boxId", arrayOf(Sms.MESSAGE_TYPE_INBOX, Sms.MESSAGE_TYPE_ALL)) + .endGroup() + .or() + .beginGroup() + .equalTo("type", "mms") + .`in`("boxId", arrayOf(Mms.MESSAGE_BOX_INBOX, Mms.MESSAGE_BOX_ALL)) + .endGroup() + .endGroup() + .sort("date", Sort.DESCENDING) + .findAll() + } + override fun getUnreadCount(): Long { return Realm.getDefaultInstance().use { realm -> realm.refresh() @@ -239,13 +260,13 @@ class MessageRepositoryImpl @Inject constructor( } val values = ContentValues() - values.put(Telephony.Sms.SEEN, true) - values.put(Telephony.Sms.READ, true) + values.put(Sms.SEEN, true) + values.put(Sms.READ, true) threadIds.forEach { threadId -> try { val uri = ContentUris.withAppendedId(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI, threadId) - context.contentResolver.update(uri, values, "${Telephony.Sms.READ} = 0", null) + context.contentResolver.update(uri, values, "${Sms.READ} = 0", null) } catch (exception: Exception) { Timber.w(exception) } @@ -421,7 +442,7 @@ class MessageRepositoryImpl @Inject constructor( this.subId = subId id = messageIds.newId() - boxId = Telephony.Sms.MESSAGE_TYPE_OUTBOX + boxId = Sms.MESSAGE_TYPE_OUTBOX type = "sms" read = true seen = true @@ -432,20 +453,20 @@ class MessageRepositoryImpl @Inject constructor( // Insert the message to the native content provider val values = contentValuesOf( - Telephony.Sms.ADDRESS to address, - Telephony.Sms.BODY to body, - Telephony.Sms.DATE to System.currentTimeMillis(), - Telephony.Sms.READ to true, - Telephony.Sms.SEEN to true, - Telephony.Sms.TYPE to Telephony.Sms.MESSAGE_TYPE_OUTBOX, - Telephony.Sms.THREAD_ID to threadId + Sms.ADDRESS to address, + Sms.BODY to body, + Sms.DATE to System.currentTimeMillis(), + Sms.READ to true, + Sms.SEEN to true, + Sms.TYPE to Sms.MESSAGE_TYPE_OUTBOX, + Sms.THREAD_ID to threadId ) if (prefs.canUseSubId.get()) { - values.put(Telephony.Sms.SUBSCRIPTION_ID, message.subId) + values.put(Sms.SUBSCRIPTION_ID, message.subId) } - val uri = context.contentResolver.insert(Telephony.Sms.CONTENT_URI, values) + val uri = context.contentResolver.insert(Sms.CONTENT_URI, values) // Update the contentId after the message has been inserted to the content provider // The message might have been deleted by now, so only proceed if it's valid @@ -479,7 +500,7 @@ class MessageRepositoryImpl @Inject constructor( id = messageIds.newId() threadId = TelephonyCompat.getOrCreateThreadId(context, address) - boxId = Telephony.Sms.MESSAGE_TYPE_INBOX + boxId = Sms.MESSAGE_TYPE_INBOX type = "sms" read = activeConversationManager.getActiveConversation() == threadId } @@ -489,16 +510,16 @@ class MessageRepositoryImpl @Inject constructor( // Insert the message to the native content provider val values = contentValuesOf( - Telephony.Sms.ADDRESS to address, - Telephony.Sms.BODY to body, - Telephony.Sms.DATE_SENT to sentTime + Sms.ADDRESS to address, + Sms.BODY to body, + Sms.DATE_SENT to sentTime ) if (prefs.canUseSubId.get()) { - values.put(Telephony.Sms.SUBSCRIPTION_ID, message.subId) + values.put(Sms.SUBSCRIPTION_ID, message.subId) } - context.contentResolver.insert(Telephony.Sms.Inbox.CONTENT_URI, values)?.lastPathSegment?.toLong()?.let { id -> + context.contentResolver.insert(Sms.Inbox.CONTENT_URI, values)?.lastPathSegment?.toLong()?.let { id -> // Update the contentId after the message has been inserted to the content provider realm.executeTransaction { managedMessage?.contentId = id } } @@ -520,15 +541,15 @@ class MessageRepositoryImpl @Inject constructor( // Update the message in realm realm.executeTransaction { message.boxId = when (message.isSms()) { - true -> Telephony.Sms.MESSAGE_TYPE_OUTBOX - false -> Telephony.Mms.MESSAGE_BOX_OUTBOX + true -> Sms.MESSAGE_TYPE_OUTBOX + false -> Mms.MESSAGE_BOX_OUTBOX } } // Update the message in the native ContentProvider val values = when (message.isSms()) { - true -> contentValuesOf(Telephony.Sms.TYPE to Telephony.Sms.MESSAGE_TYPE_OUTBOX) - false -> contentValuesOf(Telephony.Mms.MESSAGE_BOX to Telephony.Mms.MESSAGE_BOX_OUTBOX) + true -> contentValuesOf(Sms.TYPE to Sms.MESSAGE_TYPE_OUTBOX) + false -> contentValuesOf(Mms.MESSAGE_BOX to Mms.MESSAGE_BOX_OUTBOX) } context.contentResolver.update(message.getUri(), values, null, null) } @@ -543,12 +564,12 @@ class MessageRepositoryImpl @Inject constructor( message?.let { // Update the message in realm realm.executeTransaction { - message.boxId = Telephony.Sms.MESSAGE_TYPE_SENT + message.boxId = Sms.MESSAGE_TYPE_SENT } // Update the message in the native ContentProvider val values = ContentValues() - values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_SENT) + values.put(Sms.TYPE, Sms.MESSAGE_TYPE_SENT) context.contentResolver.update(message.getUri(), values, null, null) } } @@ -562,14 +583,14 @@ class MessageRepositoryImpl @Inject constructor( message?.let { // Update the message in realm realm.executeTransaction { - message.boxId = Telephony.Sms.MESSAGE_TYPE_FAILED + message.boxId = Sms.MESSAGE_TYPE_FAILED message.errorCode = resultCode } // Update the message in the native ContentProvider val values = ContentValues() - values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_FAILED) - values.put(Telephony.Sms.ERROR_CODE, resultCode) + values.put(Sms.TYPE, Sms.MESSAGE_TYPE_FAILED) + values.put(Sms.ERROR_CODE, resultCode) context.contentResolver.update(message.getUri(), values, null, null) } } @@ -583,16 +604,16 @@ class MessageRepositoryImpl @Inject constructor( message?.let { // Update the message in realm realm.executeTransaction { - message.deliveryStatus = Telephony.Sms.STATUS_COMPLETE + message.deliveryStatus = Sms.STATUS_COMPLETE message.dateSent = System.currentTimeMillis() message.read = true } // Update the message in the native ContentProvider val values = ContentValues() - values.put(Telephony.Sms.STATUS, Telephony.Sms.STATUS_COMPLETE) - values.put(Telephony.Sms.DATE_SENT, System.currentTimeMillis()) - values.put(Telephony.Sms.READ, true) + values.put(Sms.STATUS, Sms.STATUS_COMPLETE) + values.put(Sms.DATE_SENT, System.currentTimeMillis()) + values.put(Sms.READ, true) context.contentResolver.update(message.getUri(), values, null, null) } } @@ -606,7 +627,7 @@ class MessageRepositoryImpl @Inject constructor( message?.let { // Update the message in realm realm.executeTransaction { - message.deliveryStatus = Telephony.Sms.STATUS_FAILED + message.deliveryStatus = Sms.STATUS_FAILED message.dateSent = System.currentTimeMillis() message.read = true message.errorCode = resultCode @@ -614,10 +635,10 @@ class MessageRepositoryImpl @Inject constructor( // Update the message in the native ContentProvider val values = ContentValues() - values.put(Telephony.Sms.STATUS, Telephony.Sms.STATUS_FAILED) - values.put(Telephony.Sms.DATE_SENT, System.currentTimeMillis()) - values.put(Telephony.Sms.READ, true) - values.put(Telephony.Sms.ERROR_CODE, resultCode) + values.put(Sms.STATUS, Sms.STATUS_FAILED) + values.put(Sms.DATE_SENT, System.currentTimeMillis()) + values.put(Sms.READ, true) + values.put(Sms.ERROR_CODE, resultCode) context.contentResolver.update(message.getUri(), values, null, null) } } diff --git a/domain/src/main/java/com/moez/QKSMS/manager/NotificationManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/NotificationManager.kt index 0a31695a7..7499d82ec 100644 --- a/domain/src/main/java/com/moez/QKSMS/manager/NotificationManager.kt +++ b/domain/src/main/java/com/moez/QKSMS/manager/NotificationManager.kt @@ -32,4 +32,4 @@ interface NotificationManager { fun getNotificationForBackup(): NotificationCompat.Builder -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt index afb1a339d..217e312a0 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt @@ -32,6 +32,8 @@ interface MessageRepository { fun getMessageForPart(id: Long): Message? + fun getLastIncomingMessage(threadId: Long): RealmResults + fun getUnreadCount(): Long fun getPart(id: Long): MmsPart? diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index 2acfe37f3..4dee89005 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -19,16 +19,22 @@ package com.moez.QKSMS.util import android.content.Context +import android.content.SharedPreferences import android.os.Build import android.provider.Settings import com.f2prateek.rx.preferences2.Preference import com.f2prateek.rx.preferences2.RxSharedPreferences import com.moez.QKSMS.common.util.extensions.versionCode +import io.reactivex.Observable import javax.inject.Inject import javax.inject.Singleton @Singleton -class Preferences @Inject constructor(context: Context, private val rxPrefs: RxSharedPreferences) { +class Preferences @Inject constructor( + context: Context, + private val rxPrefs: RxSharedPreferences, + private val sharedPrefs: SharedPreferences +) { companion object { const val NIGHT_MODE_SYSTEM = 0 @@ -118,12 +124,28 @@ class Preferences @Inject constructor(context: Context, private val rxPrefs: RxS } } - fun theme(threadId: Long = 0): Preference { + /** + * Returns a stream of preference keys for changing preferences + */ + val keyChanges: Observable = Observable.create { emitter -> + // Making this a lambda would cause it to be GCd + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + emitter.onNext(key) + } + + emitter.setCancellable { + sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) + } + + sharedPrefs.registerOnSharedPreferenceChangeListener(listener) + }.share() + + fun theme(recipientId: Long = 0): Preference { val default = rxPrefs.getInteger("theme", 0xFF0097A7.toInt()) - return when (threadId) { + return when (recipientId) { 0L -> default - else -> rxPrefs.getInteger("theme_$threadId", default.get()) + else -> rxPrefs.getInteger("theme_$recipientId", default.get()) } } @@ -171,4 +193,4 @@ class Preferences @Inject constructor(context: Context, private val rxPrefs: RxS else -> rxPrefs.getString("ringtone_$threadId", default.get()) } } -} \ No newline at end of file +} 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 660881e2a..93a39aa33 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 @@ -30,6 +30,11 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeBoolean import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.extensions.asObservable +import com.moez.QKSMS.extensions.mapNotNull +import com.moez.QKSMS.repository.ConversationRepository +import com.moez.QKSMS.repository.MessageRepository +import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.Preferences import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable @@ -51,6 +56,9 @@ import javax.inject.Inject abstract class QkThemedActivity : QkActivity() { @Inject lateinit var colors: Colors + @Inject lateinit var conversationRepo: ConversationRepository + @Inject lateinit var messageRepo: MessageRepository + @Inject lateinit var phoneNumberUtils: PhoneNumberUtils @Inject lateinit var prefs: Preferences /** @@ -61,10 +69,40 @@ abstract class QkThemedActivity : QkActivity() { /** * Switch the theme if the threadId changes + * Set it based on the latest message in the conversation */ - val theme = threadId + val theme: Observable = threadId .distinctUntilChanged() - .switchMap { threadId -> colors.themeObservable(threadId) } + .switchMap { threadId -> + val conversation = conversationRepo.getConversation(threadId) + when { + conversation == null -> Observable.just(0L) + + conversation.recipients.size == 1 -> Observable.just(conversation.recipients.first()?.id ?: 0L) + + else -> messageRepo.getLastIncomingMessage(conversation.id) + .asObservable() + .mapNotNull { messages -> messages.firstOrNull() } + .distinctUntilChanged { message -> message.address } + .mapNotNull { message -> + conversation.recipients.find { recipient -> + phoneNumberUtils.compare(recipient.address, message.address) + } + } + .map { recipient -> recipient.id } + .startWith(conversation.recipients.firstOrNull()?.id ?: 0) + .distinctUntilChanged() + } + } + .switchMap { colors.themeObservable(it) } + + /** + * Emits an event whenever any theme changes, whether it be global or for some recipient. This is useful + * for invalidating recyclerviews + */ + val allThemes by lazy { + prefs.keyChanges.filter { key -> key.contains("theme") } + } @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt index fd0d9f1b4..5c94561d9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt @@ -66,10 +66,10 @@ class Colors @Inject constructor(private val context: Context, private val prefs private val secondaryTextLuminance = measureLuminance(context.getColorCompat(R.color.textSecondaryDark)) private val tertiaryTextLuminance = measureLuminance(context.getColorCompat(R.color.textTertiaryDark)) - fun theme(threadId: Long = 0): Theme = Theme(prefs.theme(threadId).get(), this) + fun theme(recipientId: Long = 0): Theme = Theme(prefs.theme(recipientId).get(), this) - fun themeObservable(threadId: Long = 0): Observable { - return prefs.theme(threadId).asObservable() + fun themeObservable(recipientId: Long = 0): Observable { + return prefs.theme(recipientId).asObservable() .map { color -> Theme(color, this) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index 7bbcb6aeb..3d6625fee 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -113,6 +113,11 @@ class NotificationManagerImpl @Inject constructor( } val conversation = conversationRepo.getConversation(threadId) ?: return + val lastRecipient = conversation.lastMessage?.let { lastMessage -> + conversation.recipients.find { recipient -> + phoneNumberUtils.compare(recipient.address, lastMessage.address) + } + } ?: conversation.recipients.firstOrNull() val contentIntent = Intent(context, ComposeActivity::class.java).putExtra("threadId", threadId) val taskStackBuilder = TaskStackBuilder.create(context) @@ -134,7 +139,7 @@ class NotificationManagerImpl @Inject constructor( val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId)) .setCategory(NotificationCompat.CATEGORY_MESSAGE) - .setColor(colors.theme(threadId).theme) + .setColor(colors.theme(lastRecipient?.id ?: 0).theme) .setPriority(NotificationCompat.PRIORITY_MAX) .setSmallIcon(R.drawable.ic_notification) .setNumber(messages.size) @@ -158,8 +163,9 @@ class NotificationManagerImpl @Inject constructor( val person = Person.Builder() if (!message.isMe()) { - val recipient = conversation.recipients - .firstOrNull { phoneNumberUtils.compare(it.address, message.address) } + val recipient = conversation.recipients.find { recipient -> + phoneNumberUtils.compare(recipient.address, message.address) + } person.setName(recipient?.getDisplayName() ?: message.address) person.setIcon(GlideApp.with(context) @@ -302,6 +308,12 @@ class NotificationManagerImpl @Inject constructor( } val conversation = conversationRepo.getConversation(message.threadId) ?: return + val lastRecipient = conversation.lastMessage?.let { lastMessage -> + conversation.recipients.find { recipient -> + phoneNumberUtils.compare(recipient.address, lastMessage.address) + } + } ?: conversation.recipients.firstOrNull() + val threadId = conversation.id val contentIntent = Intent(context, ComposeActivity::class.java).putExtra("threadId", threadId) @@ -313,7 +325,7 @@ class NotificationManagerImpl @Inject constructor( val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId)) .setContentTitle(context.getString(R.string.notification_message_failed_title)) .setContentText(context.getString(R.string.notification_message_failed_text, conversation.getTitle())) - .setColor(colors.theme(threadId).theme) + .setColor(colors.theme(lastRecipient?.id ?: 0).theme) .setPriority(NotificationManagerCompat.IMPORTANCE_MAX) .setSmallIcon(R.drawable.ic_notification_failed) .setAutoCancel(true) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 70fc0b39d..1a289b118 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -43,7 +43,7 @@ class AvatarView @JvmOverloads constructor( /** * This value can be changes if we should use the theme from a particular conversation */ - var threadId: Long = 0 + var recipientId: Long = 0 set(value) { if (field == value) return field = value @@ -81,13 +81,13 @@ class AvatarView @JvmOverloads constructor( super.onFinishInflate() if (!isInEditMode) { - applyTheme(threadId) + applyTheme(recipientId) updateView() } } - private fun applyTheme(threadId: Long) { - colors.theme(threadId).run { + private fun applyTheme(recipientId: Long) { + colors.theme(recipientId).run { setBackgroundTint(theme) initial.setTextColor(textPrimary) icon.setTint(textPrimary) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt index f00c65619..695abe817 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt @@ -35,7 +35,7 @@ class GroupAvatarView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : ConstraintLayout(context, attrs) { - var contacts: List = ArrayList() + var recipients: List = ArrayList() set(value) { field = value.sortedWith(compareByDescending { contact -> contact.contact?.lookupKey }) updateView() @@ -54,17 +54,25 @@ class GroupAvatarView @JvmOverloads constructor( } private fun updateView() { - avatar1Frame.setBackgroundTint(when (contacts.size > 1) { + avatar1Frame.setBackgroundTint(when (recipients.size > 1) { true -> context.resolveThemeColor(android.R.attr.windowBackground) false -> context.getColorCompat(android.R.color.transparent) }) avatar1Frame.updateLayoutParams { - matchConstraintPercentWidth = if (contacts.size > 1) 0.75f else 1.0f + matchConstraintPercentWidth = if (recipients.size > 1) 0.75f else 1.0f } - avatar2.isVisible = contacts.size > 1 + avatar2.isVisible = recipients.size > 1 - avatar1.setContact(contacts.getOrNull(0)?.contact) - avatar2.setContact(contacts.getOrNull(1)?.contact) + + recipients.getOrNull(0).let { recipient -> + avatar1.recipientId = recipient?.id ?: 0 + avatar1.setContact(recipient?.contact) + } + + recipients.getOrNull(1).let { recipient -> + avatar2.recipientId = recipient?.id ?: 0 + avatar2.setContact(recipient?.contact) + } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt index 3f2ff65fe..aab9fccc5 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt @@ -41,7 +41,7 @@ class PagerTitleView @JvmOverloads constructor(context: Context, attrs: Attribut @Inject lateinit var colors: Colors - private val threadId: Subject = BehaviorSubject.create() + private val recipientId: Subject = BehaviorSubject.create() var pager: ViewPager? = null set(value) { @@ -55,8 +55,8 @@ class PagerTitleView @JvmOverloads constructor(context: Context, attrs: Attribut if (!isInEditMode) appComponent.inject(this) } - fun setThreadId(id: Long) { - threadId.onNext(id) + fun setRecipientId(id: Long) { + recipientId.onNext(id) } private fun recreate() { @@ -90,9 +90,9 @@ class PagerTitleView @JvmOverloads constructor(context: Context, attrs: Attribut intArrayOf(android.R.attr.state_activated), intArrayOf(-android.R.attr.state_activated)) - threadId + recipientId .distinctUntilChanged() - .switchMap { threadId -> colors.themeObservable(threadId) } + .switchMap { recipientId -> colors.themeObservable(recipientId) } .map { theme -> val textSecondary = context.resolveThemeColor(android.R.attr.textColorSecondary) ColorStateList(states, intArrayOf(theme.theme, textSecondary)) 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 222e6d362..af73405ed 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 @@ -73,7 +73,7 @@ class BlockedMessagesAdapter @Inject constructor( view.isActivated = isSelected(conversation.id) - view.avatars.contacts = conversation.recipients + view.avatars.recipients = conversation.recipients view.title.collapseEnabled = conversation.recipients.size > 1 view.title.text = conversation.getTitle() view.date.text = dateFormatter.getConversationTimestamp(conversation.date) 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 ab1b0fa1d..fcc4239eb 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 @@ -150,6 +150,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .doOnNext { attach.setBackgroundTint(it.theme) } .doOnNext { attach.setTint(it.textPrimary) } .doOnNext { messageAdapter.theme = it } + .autoDisposable(scope()) + .subscribe() + + allThemes .autoDisposable(scope()) .subscribe { messageList.scrapViews() } @@ -177,7 +181,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { return } - threadId.onNext(state.selectedConversation) + threadId.onNext(state.threadId) title = when { state.selectedMessages > 0 -> getString(R.string.compose_title_selected, state.selectedMessages) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt index 3d6babaf0..0de28b089 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt @@ -28,7 +28,7 @@ import io.realm.RealmResults data class ComposeState( val hasError: Boolean = false, val editingMode: Boolean = false, - val selectedConversation: Long = 0, + val threadId: Long = 0, val selectedChips: List = ArrayList(), val sendAsGroup: Boolean = true, val conversationtitle: String = "", diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 22b2b79c2..04108060c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -27,6 +27,7 @@ import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.ClipboardUtils +import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.MessageDetailsFormatter import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.compat.SubscriptionManagerCompat @@ -77,6 +78,7 @@ class ComposeViewModel @Inject constructor( @Named("addresses") private val addresses: List, @Named("text") private val sharedText: String, @Named("attachments") private val sharedAttachments: Attachments, + private val colors: Colors, private val contactRepo: ContactRepository, private val context: Context, private val activeConversationManager: ActiveConversationManager, @@ -97,7 +99,7 @@ class ComposeViewModel @Inject constructor( private val subscriptionManager: SubscriptionManagerCompat ) : QkViewModel(ComposeState( editingMode = threadId == 0L && addresses.isEmpty(), - selectedConversation = threadId, + threadId = threadId, query = query) ) { @@ -171,13 +173,13 @@ class ComposeViewModel @Inject constructor( .takeUntil(state.filter { state -> !state.editingMode }) .subscribe(selectedChips::onNext) - // When the conversation changes, mark read, and update the threadId and the messages for the adapter + // When the conversation changes, mark read, and update the recipientId and the messages for the adapter disposables += conversation .distinctUntilChanged { conversation -> conversation.id } .observeOn(AndroidSchedulers.mainThread()) .map { conversation -> val messages = messageRepo.getMessages(conversation.id) - newState { copy(selectedConversation = conversation.id, messages = Pair(conversation, messages)) } + newState { copy(threadId = conversation.id, messages = Pair(conversation, messages)) } messages } .switchMap { messages -> messages.asObservable() } 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 de24eff13..ffd83a4bb 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 @@ -98,9 +98,6 @@ class MessagesAdapter @Inject constructor( field = value contactCache.clear() - // Update the theme - theme = colors.theme(value?.first?.id ?: 0) - updateData(value?.second) } @@ -145,9 +142,6 @@ class MessagesAdapter @Inject constructor( view.findViewById(R.id.cancel).setTint(theme.theme) } else { view = layoutInflater.inflate(R.layout.message_list_item_in, parent, false) - view.avatar.threadId = conversation?.id ?: 0 - view.body.setTextColor(theme.textPrimary) - view.body.setBackgroundTint(theme.theme) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -187,6 +181,11 @@ class MessagesAdapter @Inject constructor( val next = if (position == itemCount - 1) null else getItem(position + 1) val view = viewHolder.containerView + val theme = when (message.isOutgoingMessage()) { + true -> colors.theme() + false -> colors.theme(contactCache[message.address]?.id ?: 0) + } + // Update the selected state view.isActivated = isSelected(message.id) || highlight == message.id @@ -231,11 +230,14 @@ class MessagesAdapter @Inject constructor( val media = message.parts.filter { !it.isSmil() && !it.isText() } view.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context)) - // Bind the avatar + // Bind the avatar and bubble colour if (!message.isMe()) { - view.avatar.threadId = conversation?.id ?: 0 + view.avatar.recipientId = contactCache[message.address]?.id ?: 0 view.avatar.setContact(contactCache[message.address]?.contact) view.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) + + view.body.setTextColor(theme.textPrimary) + view.body.setBackgroundTint(theme.theme) } // Bind the body text 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 1c5cf36c2..101e7ba59 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 @@ -87,7 +87,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = false - view.avatar.contacts = listOf(Recipient(contact = contact)) + view.avatar.recipients = listOf(Recipient(contact = contact)) view.title.text = contact.numbers.joinToString { it.address } @@ -102,7 +102,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = prev !is ComposeItem.Recent view.icon.setImageResource(R.drawable.ic_history_black_24dp) - view.avatar.contacts = conversation.recipients + view.avatar.recipients = conversation.recipients view.title.text = conversation.getTitle() @@ -123,7 +123,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = prev !is ComposeItem.Starred view.icon.setImageResource(R.drawable.ic_star_black_24dp) - view.avatar.contacts = listOf(Recipient(contact = contact)) + view.avatar.recipients = listOf(Recipient(contact = contact)) view.title.text = contact.name @@ -139,7 +139,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = prev !is ComposeItem.Group view.icon.setImageResource(R.drawable.ic_people_black_24dp) - view.avatar.contacts = group.contacts.map { contact -> Recipient(contact = contact) } + view.avatar.recipients = group.contacts.map { contact -> Recipient(contact = contact) } view.title.text = group.title @@ -158,7 +158,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = false - view.avatar.contacts = listOf(Recipient(contact = contact)) + view.avatar.recipients = listOf(Recipient(contact = contact)) view.title.text = contact.name 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 45a010e31..af8a345e8 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 @@ -78,10 +78,9 @@ class ConversationInfoController( media.adapter = mediaAdapter media.addItemDecoration(itemDecoration) - themedActivity - ?.theme + themedActivity?.theme ?.autoDisposable(scope()) - ?.subscribe { recipients?.scrapViews() } + ?.subscribe { recipients.scrapViews() } } override fun onAttach(view: View) { @@ -91,7 +90,9 @@ class ConversationInfoController( showBackButton(true) } - override fun recipientClicks(): Observable = recipientAdapter.clicks + override fun contactClicks(): Observable = recipientAdapter.contactClicks + + override fun themeClicks(): Observable = recipientAdapter.themeClicks override fun nameClicks(): Observable<*> = name.clicks() @@ -99,8 +100,6 @@ class ConversationInfoController( override fun notificationClicks(): Observable<*> = notifications.clicks() - override fun themeClicks(): Observable<*> = themePrefs.clicks() - override fun archiveClicks(): Observable<*> = archive.clicks() override fun blockClicks(): Observable<*> = block.clicks() @@ -115,8 +114,6 @@ class ConversationInfoController( return } - themedActivity?.threadId?.onNext(state.threadId) - recipientAdapter.threadId = state.threadId recipientAdapter.updateData(state.recipients) name.setVisible(state.recipients?.size ?: 0 >= 2) @@ -124,8 +121,6 @@ class ConversationInfoController( notifications.isEnabled = !state.blocked - themePrefs.isEnabled = !state.blocked - archive.isEnabled = !state.blocked archive.title = activity?.getString(when (state.archived) { true -> R.string.info_unarchive @@ -142,8 +137,8 @@ class ConversationInfoController( override fun showNameDialog(name: String) = nameDialog.setText(name).show() - override fun showThemePicker(threadId: Long) { - router.pushController(RouterTransaction.with(ThemePickerController(threadId)) + override fun showThemePicker(recipientId: Long) { + router.pushController(RouterTransaction.with(ThemePickerController(recipientId)) .pushChangeHandler(QkChangeHandler()) .popChangeHandler(QkChangeHandler())) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt index 06c44e0ad..10c7b7814 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt @@ -100,7 +100,7 @@ class ConversationInfoPresenter @Inject constructor( super.bindIntents(view) // Add or display the contact - view.recipientClicks() + view.contactClicks() .mapNotNull(conversationRepo::getRecipient) .doOnNext { recipient -> recipient.contact?.lookupKey?.let(navigator::showContact) @@ -109,6 +109,11 @@ class ConversationInfoPresenter @Inject constructor( .autoDisposable(view.scope(Lifecycle.Event.ON_DESTROY)) // ... this should be the default .subscribe() + // Show the theme settings for the conversation + view.themeClicks() + .autoDisposable(view.scope()) + .subscribe(view::showThemePicker) + // Show the conversation title dialog view.nameClicks() .withLatestFrom(conversation) { _, conversation -> conversation } @@ -130,12 +135,6 @@ class ConversationInfoPresenter @Inject constructor( .autoDisposable(view.scope()) .subscribe { conversation -> navigator.showNotificationSettings(conversation.id) } - // Show the theme settings for the conversation - view.themeClicks() - .withLatestFrom(conversation) { _, conversation -> conversation } - .autoDisposable(view.scope()) - .subscribe { conversation -> view.showThemePicker(conversation.id) } - // Toggle the archived state of the conversation view.archiveClicks() .withLatestFrom(conversation) { _, conversation -> conversation } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt index e2c1d9713..1b71de22b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt @@ -23,18 +23,18 @@ import io.reactivex.Observable interface ConversationInfoView : QkViewContract { - fun recipientClicks(): Observable + fun contactClicks(): Observable + fun themeClicks(): Observable fun nameClicks(): Observable<*> fun nameChanges(): Observable fun notificationClicks(): Observable<*> - fun themeClicks(): Observable<*> fun archiveClicks(): Observable<*> fun blockClicks(): Observable<*> fun deleteClicks(): Observable<*> fun confirmDelete(): Observable<*> fun showNameDialog(name: String) - fun showThemePicker(threadId: Long) + fun showThemePicker(recipientId: Long) fun showBlockingDialog(conversations: List, block: Boolean) fun requestDefaultSms() fun showDeleteDialog() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt index 92f0b2cf6..9a6eb3997 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt @@ -20,24 +20,24 @@ package com.moez.QKSMS.feature.conversationinfo import android.view.LayoutInflater import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.model.Recipient -import io.reactivex.disposables.CompositeDisposable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.conversation_recipient_list_item.view.* import javax.inject.Inject -class ConversationRecipientAdapter @Inject constructor() : QkRealmAdapter() { +class ConversationRecipientAdapter @Inject constructor( + private val colors: Colors +) : QkRealmAdapter() { - var threadId: Long = 0L - val clicks: Subject = PublishSubject.create() - - private val disposables = CompositeDisposable() + val contactClicks: Subject = PublishSubject.create() + val themeClicks: Subject = PublishSubject.create() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val layoutInflater = LayoutInflater.from(parent.context) @@ -45,7 +45,12 @@ class ConversationRecipientAdapter @Inject constructor() : QkRealmAdapter() { init { @@ -60,7 +62,6 @@ class ConversationsAdapter @Inject constructor( view.snippet.maxLines = 5 view.unread.isVisible = true - view.unread.setTint(colors.theme().theme) view.date.setTypeface(view.date.typeface, Typeface.BOLD) view.date.setTextColor(textColorPrimary) @@ -85,11 +86,12 @@ class ConversationsAdapter @Inject constructor( override fun onBindViewHolder(viewHolder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return + val lastMessage = conversation.lastMessage ?: return val view = viewHolder.containerView view.isActivated = isSelected(conversation.id) - view.avatars.contacts = conversation.recipients + view.avatars.recipients = conversation.recipients view.title.collapseEnabled = conversation.recipients.size > 1 view.title.text = conversation.getTitle() view.date.text = dateFormatter.getConversationTimestamp(conversation.date) @@ -99,6 +101,16 @@ class ConversationsAdapter @Inject constructor( else -> conversation.snippet } view.pinned.isVisible = conversation.pinned + + // If the last message wasn't incoming, then the colour doesn't really matter anyway + val recipient = when (conversation.recipients.size) { + 1 -> conversation.recipients.firstOrNull() + else -> conversation.recipients.find { recipient -> + phoneNumberUtils.compare(recipient.address, lastMessage.address) + } + } + + view.unread.setTint(colors.theme(recipient?.id ?: 0).theme) } override fun getItemId(index: Int): Long { @@ -108,4 +120,4 @@ class ConversationsAdapter @Inject constructor( override fun getItemViewType(position: Int): Int { return if (getItem(position)?.unread == false) 0 else 1 } -} \ No newline at end of file +} 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 0d2665baa..e8e5e58ff 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 @@ -155,7 +155,6 @@ class MainActivity : QkThemedActivity(), MainView { // Set the theme color tint to the recyclerView, progressbar, and FAB theme - .doOnNext { recyclerView.scrapViews() } .autoDisposable(scope()) .subscribe { theme -> // Set the color for the drawer icons @@ -183,6 +182,10 @@ class MainActivity : QkThemedActivity(), MainView { compose.setTint(theme.textPrimary) } + allThemes + .autoDisposable(scope()) + .subscribe { recyclerView.scrapViews() } + itemTouchCallback.adapter = conversationsAdapter conversationsAdapter.autoScrollToStart(recyclerView) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt index 6e429aa59..6d03af13f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt @@ -73,7 +73,7 @@ class SearchAdapter @Inject constructor( } view.title.text = title - view.avatars.contacts = result.conversation.recipients + view.avatars.recipients = result.conversation.recipients when (result.messages == 0) { true -> { 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 9612bcce4..392196d41 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 @@ -90,7 +90,7 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { finish() } - threadId.onNext(state.selectedConversation) + threadId.onNext(state.threadId) title = state.title diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyState.kt index 573dde17e..34dae4c97 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyState.kt @@ -25,7 +25,7 @@ import io.realm.RealmResults data class QkReplyState( val hasError: Boolean = false, - val selectedConversation: Long = 0, + val threadId: Long = 0, val title: String = "", val expanded: Boolean = false, val data: Pair>? = null, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyViewModel.kt index d29c2068c..4ef550cd1 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyViewModel.kt @@ -54,7 +54,7 @@ class QkReplyViewModel @Inject constructor( private val navigator: Navigator, private val sendMessage: SendMessage, private val subscriptionManager: SubscriptionManagerCompat -) : QkViewModel(QkReplyState(selectedConversation = threadId)) { +) : QkViewModel(QkReplyState(threadId = threadId)) { private val conversation by lazy { conversationRepo.getConversationAsync(threadId) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt index 43dfcab71..e23c2a687 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt @@ -68,7 +68,7 @@ class ScheduledMessageAdapter @Inject constructor( val view = holder.containerView // GroupAvatarView only accepts recipients, so map the phone numbers to recipients - view.avatars.contacts = message.recipients.map { address -> Recipient(address = address) } + view.avatars.recipients = message.recipients.map { address -> Recipient(address = address) } view.recipients.text = message.recipients.joinToString(",") { address -> contactCache[address]?.name?.takeIf { it.isNotBlank() } ?: address 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 1ef80a3b5..ae4dc56f2 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 @@ -38,7 +38,9 @@ import kotlinx.android.synthetic.main.theme_picker_controller.* import kotlinx.android.synthetic.main.theme_picker_hsv.* import javax.inject.Inject -class ThemePickerController(val threadId: Long = 0L) : QkController(), ThemePickerView { +class ThemePickerController( + val recipientId: Long = 0L +) : QkController(), ThemePickerView { @Inject override lateinit var presenter: ThemePickerPresenter @@ -107,7 +109,7 @@ class ThemePickerController(val threadId: Long = 0L) : QkController = viewQksmsPlusSubject override fun render(state: ThemePickerState) { - tabs.setThreadId(state.threadId) + tabs.setRecipientId(state.recipientId) hex.setText(Integer.toHexString(state.newColor).takeLast(6)) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt index 546380e8d..53168406a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt @@ -34,14 +34,14 @@ import javax.inject.Named class ThemePickerPresenter @Inject constructor( prefs: Preferences, - @Named("threadId") private val threadId: Long, + @Named("recipientId") private val recipientId: Long, private val billingManager: BillingManager, private val colors: Colors, private val navigator: Navigator, private val widgetManager: WidgetManager -) : QkPresenter(ThemePickerState(threadId = threadId)) { +) : QkPresenter(ThemePickerState(recipientId = recipientId)) { - private val theme: Preference = prefs.theme(threadId) + private val theme: Preference = prefs.theme(recipientId) override fun bindIntents(view: ThemePickerView) { super.bindIntents(view) @@ -55,7 +55,7 @@ class ThemePickerPresenter @Inject constructor( .autoDisposable(view.scope()) .subscribe { color -> theme.set(color) - if (threadId == 0L) { + if (recipientId == 0L) { widgetManager.updateTheme() } } @@ -81,7 +81,7 @@ class ThemePickerPresenter @Inject constructor( view.showQksmsPlusSnackbar() } else { theme.set(color) - if (threadId == 0L) { + if (recipientId == 0L) { widgetManager.updateTheme() } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerState.kt index 2aa731a0b..81c44dcf1 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerState.kt @@ -19,7 +19,7 @@ package com.moez.QKSMS.feature.themepicker data class ThemePickerState( - val threadId: Long = 0, + val recipientId: Long = 0, val applyThemeVisible: Boolean = false, val newColor: Int = -1, val newTextColor: Int = -1 diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/injection/ThemePickerModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/injection/ThemePickerModule.kt index c320bdfc2..c5c9b8d15 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/injection/ThemePickerModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/injection/ThemePickerModule.kt @@ -29,7 +29,7 @@ class ThemePickerModule(private val controller: ThemePickerController) { @Provides @ControllerScope - @Named("threadId") - fun provideThreadId(): Long = controller.threadId + @Named("recipientId") + fun provideThreadId(): Long = controller.recipientId } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index c80d6fd19..ee8e75b52 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -21,6 +21,7 @@ package com.moez.QKSMS.injection import android.app.Application import android.content.ContentResolver import android.content.Context +import android.content.SharedPreferences import android.preference.PreferenceManager import androidx.lifecycle.ViewModelProvider import com.f2prateek.rx.preferences2.RxSharedPreferences @@ -101,8 +102,13 @@ class AppModule(private var application: Application) { @Provides @Singleton - fun provideRxPreferences(context: Context): RxSharedPreferences { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) + fun provideSharedPreferences(context: Context): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(context) + } + + @Provides + @Singleton + fun provideRxPreferences(preferences: SharedPreferences): RxSharedPreferences { return RxSharedPreferences.create(preferences) } diff --git a/presentation/src/main/res/layout/conversation_info_controller.xml b/presentation/src/main/res/layout/conversation_info_controller.xml index 40670ba94..0ccb95d7f 100644 --- a/presentation/src/main/res/layout/conversation_info_controller.xml +++ b/presentation/src/main/res/layout/conversation_info_controller.xml @@ -65,13 +65,6 @@ app:icon="@drawable/ic_notifications_black_24dp" app:title="@string/info_notifications" /> - - - \ No newline at end of file + diff --git a/presentation/src/main/res/layout/conversation_recipient_list_item.xml b/presentation/src/main/res/layout/conversation_recipient_list_item.xml index 959413c4e..970d478ec 100644 --- a/presentation/src/main/res/layout/conversation_recipient_list_item.xml +++ b/presentation/src/main/res/layout/conversation_recipient_list_item.xml @@ -42,7 +42,10 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" + android:layout_marginEnd="8dp" app:layout_constraintBottom_toTopOf="@id/address" + app:layout_constraintEnd_toStartOf="@id/add" + app:layout_constraintHorizontal_bias="0" app:layout_constraintStart_toEndOf="@id/avatar" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" @@ -54,6 +57,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@id/name" app:layout_constraintStart_toStartOf="@id/name" app:layout_constraintTop_toBottomOf="@id/name" tools:text="(123) 456-7890" /> @@ -62,11 +66,24 @@ android:id="@+id/add" android:layout_width="40dp" android:layout_height="40dp" + android:layout_marginEnd="16dp" android:padding="8dp" android:src="@drawable/ic_person_add_black_24dp" android:tint="?android:attr/textColorSecondary" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/theme" + app:layout_constraintTop_toTopOf="parent" /> + + - \ No newline at end of file + -- GitLab From 684237425472fe8cee23e8cef8e138c748f60b39 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Thu, 26 Dec 2019 18:19:41 -0500 Subject: [PATCH 050/213] Automatic contact colours Closes #133 --- .../moez/QKSMS/migration/QkRealmMigration.kt | 11 +- .../java/com/moez/QKSMS/util/Preferences.kt | 12 +- .../QKSMS/common/base/QkThemedActivity.kt | 19 +- .../java/com/moez/QKSMS/common/util/Colors.kt | 83 ++++-- .../common/util/NotificationManagerImpl.kt | 4 +- .../moez/QKSMS/common/widget/AvatarView.kt | 41 +-- .../QKSMS/common/widget/GroupAvatarView.kt | 11 +- .../QKSMS/common/widget/PagerTitleView.kt | 6 +- .../QKSMS/feature/compose/ComposeActivity.kt | 8 +- .../moez/QKSMS/feature/compose/ComposeView.kt | 1 + .../QKSMS/feature/compose/ComposeViewModel.kt | 7 + .../QKSMS/feature/compose/MessagesAdapter.kt | 5 +- .../feature/compose/editing/ChipsAdapter.kt | 3 +- .../compose/editing/DetailedChipView.kt | 3 +- .../ConversationRecipientAdapter.kt | 5 +- .../conversations/ConversationsAdapter.kt | 2 +- .../moez/QKSMS/feature/main/MainActivity.kt | 17 +- .../com/moez/QKSMS/feature/main/MainView.kt | 3 +- .../moez/QKSMS/feature/main/MainViewModel.kt | 15 + .../feature/settings/SettingsController.kt | 3 + .../feature/settings/SettingsPresenter.kt | 5 + .../QKSMS/feature/settings/SettingsState.kt | 1 + .../main/res/layout/settings_controller.xml | 8 + presentation/src/main/res/values/colors.xml | 260 ++++++++++++++++++ presentation/src/main/res/values/strings.xml | 1 + 25 files changed, 428 insertions(+), 106 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index be9f96872..6e19486fe 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -19,9 +19,9 @@ package com.moez.QKSMS.migration import android.annotation.SuppressLint -import com.f2prateek.rx.preferences2.RxSharedPreferences import com.moez.QKSMS.extensions.map import com.moez.QKSMS.mapper.CursorToContactImpl +import com.moez.QKSMS.util.Preferences import io.realm.DynamicRealm import io.realm.DynamicRealmObject import io.realm.FieldAttribute @@ -32,7 +32,7 @@ import javax.inject.Inject class QkRealmMigration @Inject constructor( private val cursorToContact: CursorToContactImpl, - private val prefs: RxSharedPreferences + private val prefs: Preferences ) : RealmMigration { companion object { @@ -176,7 +176,7 @@ class QkRealmMigration @Inject constructor( // Migrate conversation themes val recipients = mutableMapOf() // Map of recipientId:theme realm.where("Conversation").findAll().forEach { conversation -> - val pref = prefs.getInteger("theme_${conversation.getLong("id")}") + val pref = prefs.theme(conversation.getLong("id")) if (pref.isSet) { conversation.getList("Recipient").forEach { recipient -> recipients[recipient.getLong("id")] = pref.get() @@ -187,9 +187,12 @@ class QkRealmMigration @Inject constructor( } recipients.forEach { (recipientId, theme) -> - prefs.getInteger("theme_$recipientId").set(theme) + prefs.theme(recipientId).set(theme) } + // This is enabled for new users, but the behaviour shouldn't change automatically for old users + prefs.autoColor.set(false) + version++ } diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index 4dee89005..f21adcc96 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -90,6 +90,7 @@ class Preferences @Inject constructor( val nightStart = rxPrefs.getString("nightStart", "18:00") val nightEnd = rxPrefs.getString("nightEnd", "6:00") val black = rxPrefs.getBoolean("black", false) + val autoColor = rxPrefs.getBoolean("autoColor", true) val systemFont = rxPrefs.getBoolean("systemFont", false) val textSize = rxPrefs.getInteger("textSize", TEXT_SIZE_NORMAL) val blockingManager = rxPrefs.getInteger("blockingManager", BLOCKING_MANAGER_QKSMS) @@ -140,12 +141,13 @@ class Preferences @Inject constructor( sharedPrefs.registerOnSharedPreferenceChangeListener(listener) }.share() - fun theme(recipientId: Long = 0): Preference { - val default = rxPrefs.getInteger("theme", 0xFF0097A7.toInt()) - + fun theme( + recipientId: Long = 0, + default: Int = rxPrefs.getInteger("theme", 0xFF0097A7.toInt()).get() + ): Preference { return when (recipientId) { - 0L -> default - else -> rxPrefs.getInteger("theme_$recipientId", default.get()) + 0L -> rxPrefs.getInteger("theme", 0xFF0097A7.toInt()) + else -> rxPrefs.getInteger("theme_$recipientId", default) } } 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 93a39aa33..14b858b2c 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 @@ -30,6 +30,7 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeBoolean import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.extensions.asObservable import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.repository.ConversationRepository @@ -76,9 +77,9 @@ abstract class QkThemedActivity : QkActivity() { .switchMap { threadId -> val conversation = conversationRepo.getConversation(threadId) when { - conversation == null -> Observable.just(0L) + conversation == null -> Observable.just(Optional(null)) - conversation.recipients.size == 1 -> Observable.just(conversation.recipients.first()?.id ?: 0L) + conversation.recipients.size == 1 -> Observable.just(Optional(conversation.recipients.first())) else -> messageRepo.getLastIncomingMessage(conversation.id) .asObservable() @@ -89,20 +90,12 @@ abstract class QkThemedActivity : QkActivity() { phoneNumberUtils.compare(recipient.address, message.address) } } - .map { recipient -> recipient.id } - .startWith(conversation.recipients.firstOrNull()?.id ?: 0) + .map { recipient -> Optional(recipient) } + .startWith(Optional(conversation.recipients.firstOrNull())) .distinctUntilChanged() } } - .switchMap { colors.themeObservable(it) } - - /** - * Emits an event whenever any theme changes, whether it be global or for some recipient. This is useful - * for invalidating recyclerviews - */ - val allThemes by lazy { - prefs.keyChanges.filter { key -> key.contains("theme") } - } + .switchMap { colors.themeObservable(it.value) } @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt index 5c94561d9..7eed92601 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt @@ -20,15 +20,23 @@ package com.moez.QKSMS.common.util import android.content.Context import android.graphics.Color +import androidx.core.content.res.getColorOrThrow import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.getColorCompat +import com.moez.QKSMS.model.Recipient +import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.Preferences import io.reactivex.Observable import javax.inject.Inject import javax.inject.Singleton +import kotlin.math.absoluteValue @Singleton -class Colors @Inject constructor(private val context: Context, private val prefs: Preferences) { +class Colors @Inject constructor( + private val context: Context, + private val phoneNumberUtils: PhoneNumberUtils, + private val prefs: Preferences +) { data class Theme(val theme: Int, private val colors: Colors) { val highlight by lazy { colors.highlightColorForTheme(theme) } @@ -37,27 +45,31 @@ class Colors @Inject constructor(private val context: Context, private val prefs val textTertiary by lazy { colors.textTertiaryOnThemeForColor(theme) } } - val materialColors = listOf( - listOf(0xffffebee, 0xffffcdd2, 0xffef9a9a, 0xffe57373, 0xffef5350, 0xfff44336, 0xffe53935, 0xffd32f2f, 0xffc62828, 0xffb71c1c), - listOf(0xffFCE4EC, 0xffF8BBD0, 0xffF48FB1, 0xffF06292, 0xffEC407A, 0xffE91E63, 0xffD81B60, 0xffC2185B, 0xffAD1457, 0xff880E4F), - listOf(0xffF3E5F5, 0xffE1BEE7, 0xffCE93D8, 0xffBA68C8, 0xffAB47BC, 0xff9C27B0, 0xff8E24AA, 0xff7B1FA2, 0xff6A1B9A, 0xff4A148C), - listOf(0xffEDE7F6, 0xffD1C4E9, 0xffB39DDB, 0xff9575CD, 0xff7E57C2, 0xff673AB7, 0xff5E35B1, 0xff512DA8, 0xff4527A0, 0xff311B92), - listOf(0xffE8EAF6, 0xffC5CAE9, 0xff9FA8DA, 0xff7986CB, 0xff5C6BC0, 0xff3F51B5, 0xff3949AB, 0xff303F9F, 0xff283593, 0xff1A237E), - listOf(0xffE3F2FD, 0xffBBDEFB, 0xff90CAF9, 0xff64B5F6, 0xff42A5F5, 0xff2196F3, 0xff1E88E5, 0xff1976D2, 0xff1565C0, 0xff0D47A1), - listOf(0xffE1F5FE, 0xffB3E5FC, 0xff81D4FA, 0xff4FC3F7, 0xff29B6F6, 0xff03A9F4, 0xff039BE5, 0xff0288D1, 0xff0277BD, 0xff01579B), - listOf(0xffE0F7FA, 0xffB2EBF2, 0xff80DEEA, 0xff4DD0E1, 0xff26C6DA, 0xff00BCD4, 0xff00ACC1, 0xff0097A7, 0xff00838F, 0xff006064), - listOf(0xffE0F2F1, 0xffB2DFDB, 0xff80CBC4, 0xff4DB6AC, 0xff26A69A, 0xff009688, 0xff00897B, 0xff00796B, 0xff00695C, 0xff004D40), - listOf(0xffE8F5E9, 0xffC8E6C9, 0xffA5D6A7, 0xff81C784, 0xff66BB6A, 0xff4CAF50, 0xff43A047, 0xff388E3C, 0xff2E7D32, 0xff1B5E20), - listOf(0xffF1F8E9, 0xffDCEDC8, 0xffC5E1A5, 0xffAED581, 0xff9CCC65, 0xff8BC34A, 0xff7CB342, 0xff689F38, 0xff558B2F, 0xff33691E), - listOf(0xffF9FBE7, 0xffF0F4C3, 0xffE6EE9C, 0xffDCE775, 0xffD4E157, 0xffCDDC39, 0xffC0CA33, 0xffAFB42B, 0xff9E9D24, 0xff827717), - listOf(0xffFFFDE7, 0xffFFF9C4, 0xffFFF59D, 0xffFFF176, 0xffFFEE58, 0xffFFEB3B, 0xffFDD835, 0xffFBC02D, 0xffF9A825, 0xffF57F17), - listOf(0xffFFF8E1, 0xffFFECB3, 0xffFFE082, 0xffFFD54F, 0xffFFCA28, 0xffFFC107, 0xffFFB300, 0xffFFA000, 0xffFF8F00, 0xffFF6F00), - listOf(0xffFFF3E0, 0xffFFE0B2, 0xffFFCC80, 0xffFFB74D, 0xffFFA726, 0xffFF9800, 0xffFB8C00, 0xffF57C00, 0xffEF6C00, 0xffE65100), - listOf(0xffFBE9E7, 0xffFFCCBC, 0xffFFAB91, 0xffFF8A65, 0xffFF7043, 0xffFF5722, 0xffF4511E, 0xffE64A19, 0xffD84315, 0xffBF360C), - listOf(0xffEFEBE9, 0xffD7CCC8, 0xffBCAAA4, 0xffA1887F, 0xff8D6E63, 0xff795548, 0xff6D4C41, 0xff5D4037, 0xff4E342E, 0xff3E2723), - listOf(0xffFAFAFA, 0xffF5F5F5, 0xffEEEEEE, 0xffE0E0E0, 0xffBDBDBD, 0xff9E9E9E, 0xff757575, 0xff616161, 0xff424242, 0xff212121), - listOf(0xffECEFF1, 0xffCFD8DC, 0xffB0BEC5, 0xff90A4AE, 0xff78909C, 0xff607D8B, 0xff546E7A, 0xff455A64, 0xff37474F, 0xff263238)) - .map { it.map { it.toInt() } } + val materialColors: List> = listOf( + R.array.material_red, + R.array.material_pink, + R.array.material_purple, + R.array.material_deep_purple, + R.array.material_indigo, + R.array.material_blue, + R.array.material_light_blue, + R.array.material_cyan, + R.array.material_teal, + R.array.material_green, + R.array.material_light_green, + R.array.material_lime, + R.array.material_yellow, + R.array.material_amber, + R.array.material_orange, + R.array.material_deep_orange, + R.array.material_brown, + R.array.material_gray, + R.array.material_blue_gray) + .map { res -> context.resources.obtainTypedArray(res) } + .map { typedArray -> (0 until typedArray.length()).map(typedArray::getColorOrThrow) } + + private val randomColors: List = context.resources.obtainTypedArray(R.array.random_colors) + .let { typedArray -> (0 until typedArray.length()).map(typedArray::getColorOrThrow) } private val minimumContrastRatio = 2 @@ -66,10 +78,21 @@ class Colors @Inject constructor(private val context: Context, private val prefs private val secondaryTextLuminance = measureLuminance(context.getColorCompat(R.color.textSecondaryDark)) private val tertiaryTextLuminance = measureLuminance(context.getColorCompat(R.color.textTertiaryDark)) - fun theme(recipientId: Long = 0): Theme = Theme(prefs.theme(recipientId).get(), this) + fun theme(recipient: Recipient? = null): Theme { + val pref = prefs.theme(recipient?.id ?: 0) + val color = when { + recipient == null || !prefs.autoColor.get() || pref.isSet -> pref.get() + else -> generateColor(recipient) + } + return Theme(color, this) + } - fun themeObservable(recipientId: Long = 0): Observable { - return prefs.theme(recipientId).asObservable() + fun themeObservable(recipient: Recipient? = null): Observable { + val pref = when { + recipient == null || !prefs.autoColor.get() -> prefs.theme() + else -> prefs.theme(recipient.id, generateColor(recipient)) + } + return pref.asObservable() .map { color -> Theme(color, this) } } @@ -110,4 +133,12 @@ class Colors @Inject constructor(private val context: Context, private val prefs return 0.2126 * array[0] + 0.7152 * array[1] + 0.0722 * array[2] + 0.05 } -} \ No newline at end of file + private fun generateColor(recipient: Recipient): Int { + val first = recipient.contact?.name?.firstOrNull() + ?: phoneNumberUtils.normalizeNumber(recipient.address).firstOrNull() + ?: '#' + + val index = first.hashCode().absoluteValue % randomColors.size + return randomColors[index] + } +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index 3d6625fee..22b47ff54 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -139,7 +139,7 @@ class NotificationManagerImpl @Inject constructor( val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId)) .setCategory(NotificationCompat.CATEGORY_MESSAGE) - .setColor(colors.theme(lastRecipient?.id ?: 0).theme) + .setColor(colors.theme(lastRecipient).theme) .setPriority(NotificationCompat.PRIORITY_MAX) .setSmallIcon(R.drawable.ic_notification) .setNumber(messages.size) @@ -325,7 +325,7 @@ class NotificationManagerImpl @Inject constructor( val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId)) .setContentTitle(context.getString(R.string.notification_message_failed_title)) .setContentText(context.getString(R.string.notification_message_failed_text, conversation.getTitle())) - .setColor(colors.theme(lastRecipient?.id ?: 0).theme) + .setColor(colors.theme(lastRecipient).theme) .setPriority(NotificationManagerCompat.IMPORTANCE_MAX) .setSmallIcon(R.drawable.ic_notification_failed) .setAutoCancel(true) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 1a289b118..054f20958 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -28,7 +28,7 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.injection.appComponent -import com.moez.QKSMS.model.Contact +import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.util.GlideApp import kotlinx.android.synthetic.main.avatar_view.view.* import javax.inject.Inject @@ -40,28 +40,20 @@ class AvatarView @JvmOverloads constructor( @Inject lateinit var colors: Colors @Inject lateinit var navigator: Navigator - /** - * This value can be changes if we should use the theme from a particular conversation - */ - var recipientId: Long = 0 - set(value) { - if (field == value) return - field = value - applyTheme(value) - } - private var lookupKey: String? = null private var name: String? = null private var photoUri: String? = null private var lastUpdated: Long? = null + private var theme: Colors.Theme init { if (!isInEditMode) { appComponent.inject(this) } - View.inflate(context, R.layout.avatar_view, this) + theme = colors.theme() + View.inflate(context, R.layout.avatar_view, this) setBackgroundResource(R.drawable.circle) clipToOutline = true } @@ -69,11 +61,12 @@ class AvatarView @JvmOverloads constructor( /** * Use the [contact] information to display the avatar. */ - fun setContact(contact: Contact?) { - lookupKey = contact?.lookupKey - name = contact?.name - photoUri = contact?.photoUri - lastUpdated = contact?.lastUpdate + fun setRecipient(recipient: Recipient?) { + lookupKey = recipient?.contact?.lookupKey + name = recipient?.contact?.name + photoUri = recipient?.contact?.photoUri + lastUpdated = recipient?.contact?.lastUpdate + theme = colors.theme(recipient) updateView() } @@ -81,20 +74,16 @@ class AvatarView @JvmOverloads constructor( super.onFinishInflate() if (!isInEditMode) { - applyTheme(recipientId) updateView() } } - private fun applyTheme(recipientId: Long) { - colors.theme(recipientId).run { - setBackgroundTint(theme) - initial.setTextColor(textPrimary) - icon.setTint(textPrimary) - } - } - private fun updateView() { + // Apply theme + setBackgroundTint(theme.theme) + initial.setTextColor(theme.textPrimary) + icon.setTint(theme.textPrimary) + if (name?.isNotEmpty() == true) { val initials = name?.split(" ").orEmpty() .filter { name -> name.isNotEmpty() } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt index 695abe817..85b240a15 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt @@ -64,15 +64,8 @@ class GroupAvatarView @JvmOverloads constructor( avatar2.isVisible = recipients.size > 1 - recipients.getOrNull(0).let { recipient -> - avatar1.recipientId = recipient?.id ?: 0 - avatar1.setContact(recipient?.contact) - } - - recipients.getOrNull(1).let { recipient -> - avatar2.recipientId = recipient?.id ?: 0 - avatar2.setContact(recipient?.contact) - } + recipients.getOrNull(0).run(avatar1::setRecipient) + recipients.getOrNull(1).run(avatar2::setRecipient) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt index aab9fccc5..0d3d5db6b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt @@ -29,7 +29,9 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.forEach import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.injection.appComponent +import com.moez.QKSMS.repository.ConversationRepository import com.uber.autodispose.android.ViewScopeProvider import com.uber.autodispose.autoDisposable import io.reactivex.subjects.BehaviorSubject @@ -40,6 +42,7 @@ import javax.inject.Inject class PagerTitleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { @Inject lateinit var colors: Colors + @Inject lateinit var conversationRepo: ConversationRepository private val recipientId: Subject = BehaviorSubject.create() @@ -92,7 +95,8 @@ class PagerTitleView @JvmOverloads constructor(context: Context, attrs: Attribut recipientId .distinctUntilChanged() - .switchMap { recipientId -> colors.themeObservable(recipientId) } + .map { recipientId -> Optional(conversationRepo.getRecipient(recipientId)) } + .switchMap { recipient -> colors.themeObservable(recipient.value) } .map { theme -> val textSecondary = context.resolveThemeColor(android.R.attr.textColorSecondary) ColorStateList(states, intArrayOf(theme.theme, textSecondary)) 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 fcc4239eb..ad524c85c 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 @@ -153,10 +153,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .autoDisposable(scope()) .subscribe() - allThemes - .autoDisposable(scope()) - .subscribe { messageList.scrapViews() } - window.callback = ComposeWindowCallback(window.callback, this) // These theme attributes don't apply themselves on API 21 @@ -299,6 +295,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { startActivityForResult(intent, SelectContactRequestCode) } + override fun themeChanged() { + messageList.scrapViews() + } + override fun showKeyboard() { message.postDelayed({ message.showKeyboard() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index f4adc28b2..56fe0f22f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -62,6 +62,7 @@ interface ComposeView : QkView { fun requestStoragePermission() fun requestSmsPermission() fun showContacts(sharing: Boolean, chips: List) + fun themeChanged() fun showKeyboard() fun requestCamera() fun requestGallery() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 04108060c..3a24af87b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -394,6 +394,13 @@ class ComposeViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe(view::scrollToMessage) + // Theme changes + prefs.keyChanges + .filter { key -> key.contains("theme") } + .doOnNext { view.themeChanged() } + .autoDisposable(view.scope()) + .subscribe() + // Retry sending view.messageClickIntent .mapNotNull(messageRepo::getMessage) 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 ffd83a4bb..88fbfb0ba 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 @@ -183,7 +183,7 @@ class MessagesAdapter @Inject constructor( val theme = when (message.isOutgoingMessage()) { true -> colors.theme() - false -> colors.theme(contactCache[message.address]?.id ?: 0) + false -> colors.theme(contactCache[message.address]) } // Update the selected state @@ -232,8 +232,7 @@ class MessagesAdapter @Inject constructor( // Bind the avatar and bubble colour if (!message.isMe()) { - view.avatar.recipientId = contactCache[message.address]?.id ?: 0 - view.avatar.setContact(contactCache[message.address]?.contact) + view.avatar.setRecipient(contactCache[message.address]) view.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) view.body.setTextColor(theme.textPrimary) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt index 50cc7eb27..0bd7f0847 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt @@ -30,6 +30,7 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint +import com.moez.QKSMS.model.Recipient import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.contact_chip.* import kotlinx.android.synthetic.main.contact_chip.view.* @@ -60,7 +61,7 @@ class ChipsAdapter @Inject constructor() : QkAdapter() { val chip = getItem(position) val view = holder.containerView - view.avatar.setContact(chip.contact) + view.avatar.setRecipient(Recipient(id = -1, contact = chip.contact)) view.name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address } 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 ce35202aa..370d1a59c 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 @@ -27,6 +27,7 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.injection.appComponent +import com.moez.QKSMS.model.Recipient import kotlinx.android.synthetic.main.contact_chip_detailed.view.* import javax.inject.Inject @@ -54,7 +55,7 @@ class DetailedChipView(context: Context) : RelativeLayout(context) { } fun setChip(chip: Chip) { - avatar.setContact(chip.contact) + avatar.setRecipient(Recipient(id = -1, contact = chip.contact)) name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address info.text = chip.address } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt index 9a6eb3997..35baf7bed 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt @@ -59,8 +59,7 @@ class ConversationRecipientAdapter @Inject constructor( val recipient = getItem(position) ?: return val view = holder.containerView - view.avatar.recipientId = recipient.id - view.avatar.setContact(recipient.contact) + view.avatar.setRecipient(recipient) view.name.text = recipient.contact?.name ?: recipient.address @@ -69,7 +68,7 @@ class ConversationRecipientAdapter @Inject constructor( view.add.setVisible(recipient.contact == null) - val theme = colors.theme(recipient.id) + val theme = colors.theme(recipient) view.theme.setTint(theme.theme) } 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 de51d8b50..0b32f81dd 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 @@ -110,7 +110,7 @@ class ConversationsAdapter @Inject constructor( } } - view.unread.setTint(colors.theme(recipient?.id ?: 0).theme) + view.unread.setTint(colors.theme(recipient).theme) } override fun getItemId(index: Int): Long { 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 e8e5e58ff..c0816b72f 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 @@ -83,7 +83,7 @@ class MainActivity : QkThemedActivity(), MainView { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override val onNewIntentIntent: Subject = PublishSubject.create() - override val activityResumedIntent: 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 drawerOpenIntent: Observable by lazy { @@ -182,10 +182,6 @@ class MainActivity : QkThemedActivity(), MainView { compose.setTint(theme.textPrimary) } - allThemes - .autoDisposable(scope()) - .subscribe { recyclerView.scrapViews() } - itemTouchCallback.adapter = conversationsAdapter conversationsAdapter.autoScrollToStart(recyclerView) @@ -330,7 +326,12 @@ class MainActivity : QkThemedActivity(), MainView { override fun onResume() { super.onResume() - activityResumedIntent.onNext(Unit) + activityResumedIntent.onNext(true) + } + + override fun onPause() { + super.onPause() + activityResumedIntent.onNext(false) } override fun onDestroy() { @@ -366,6 +367,10 @@ class MainActivity : QkThemedActivity(), MainView { conversationsAdapter.clearSelection() } + override fun themeChanged() { + recyclerView.scrapViews() + } + override fun showBlockingDialog(conversations: List, block: Boolean) { blockingDialog.show(this, conversations, block) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt index f774a28cf..47511e09f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt @@ -26,7 +26,7 @@ import io.reactivex.Observable interface MainView : QkView { val onNewIntentIntent: Observable - val activityResumedIntent: Observable<*> + val activityResumedIntent: Observable val queryChangedIntent: Observable val composeIntent: Observable val drawerOpenIntent: Observable @@ -47,6 +47,7 @@ interface MainView : QkView { fun requestPermissions() fun clearSearch() fun clearSelection() + fun themeChanged() fun showBlockingDialog(conversations: List, block: Boolean) fun showDeleteDialog(conversations: List) fun showChangelog(changelog: ChangelogManager.Changelog) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index dd9340845..37f90b9f5 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -131,6 +131,7 @@ class MainViewModel @Inject constructor( } val permissions = view.activityResumedIntent + .filter { resumed -> resumed } .observeOn(Schedulers.io()) .map { Triple(permissionManager.isDefaultSms(), permissionManager.hasReadSms(), permissionManager.hasContacts()) } .distinctUntilChanged() @@ -202,6 +203,20 @@ class MainViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe { data -> newState { copy(page = Searching(loading = false, data = data)) } } + view.activityResumedIntent + .filter { resumed -> !resumed } + .switchMap { + // Take until the activity is resumed + prefs.keyChanges + .filter { key -> key.contains("theme") } + .map { true } + .mergeWith(prefs.autoColor.asObservable()) + .doOnNext { view.themeChanged() } + .takeUntil(view.activityResumedIntent.filter { resumed -> resumed }) + } + .autoDisposable(view.scope()) + .subscribe() + view.composeIntent .autoDisposable(view.scope()) .subscribe { navigator.showCompose() } 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 c29b0e69b..083abd8c6 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 @@ -161,6 +161,9 @@ class SettingsController : QkController newState { copy(autoColor = autoColor) } } + disposables += prefs.systemFont.asObservable() .subscribe { enabled -> newState { copy(systemFontEnabled = enabled) } } @@ -164,6 +167,8 @@ class SettingsPresenter @Inject constructor( R.id.textSize -> view.showTextSizePicker() + R.id.autoColor -> prefs.autoColor.set(!prefs.autoColor.get()) + R.id.systemFont -> prefs.systemFont.set(!prefs.systemFont.get()) R.id.unicode -> prefs.unicode.set(!prefs.unicode.get()) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt index f78a841f5..1119b737a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt @@ -28,6 +28,7 @@ data class SettingsState( val nightStart: String = "", val nightEnd: String = "", val black: Boolean = false, + val autoColor: Boolean = true, val autoEmojiEnabled: Boolean = true, val notificationsEnabled: Boolean = true, val sendDelaySummary: String = "", diff --git a/presentation/src/main/res/layout/settings_controller.xml b/presentation/src/main/res/layout/settings_controller.xml index b96f8b836..8e34c5e93 100644 --- a/presentation/src/main/res/layout/settings_controller.xml +++ b/presentation/src/main/res/layout/settings_controller.xml @@ -84,6 +84,14 @@ app:icon="@drawable/ic_format_size_black_24dp" app:title="@string/settings_text_size_title" /> + + #00838F + + + #e57373 + #7986CB + #00BFA5 + #81C784 + #FFAB40 + #FF8A65 + #8d6e63 + #448AFF + #78909c + + + + #FFEBEE + #FFCDD2 + #EF9A9A + #E57373 + #EF5350 + #F44336 + #E53935 + #D32F2F + #C62828 + #B71C1C + + + + #FCE4EC + #F8BBD0 + #F48FB1 + #F06292 + #EC407A + #E91E63 + #D81B60 + #C2185B + #AD1457 + #880E4F + + + + #F3E5F5 + #E1BEE7 + #CE93D8 + #BA68C8 + #AB47BC + #9C27B0 + #8E24AA + #7B1FA2 + #6A1B9A + #4A148C + + + + #EDE7F6 + #D1C4E9 + #B39DDB + #9575CD + #7E57C2 + #673AB7 + #5E35B1 + #512DA8 + #4527A0 + #311B92 + + + + #E8EAF6 + #C5CAE9 + #9FA8DA + #7986CB + #5C6BC0 + #3F51B5 + #3949AB + #303F9F + #283593 + #1A237E + + + + #E3F2FD + #BBDEFB + #90CAF9 + #64B5F6 + #42A5F5 + #2196F3 + #1E88E5 + #1976D2 + #1565C0 + #0D47A1 + + + + #E1F5FE + #B3E5FC + #81D4FA + #4FC3F7 + #29B6F6 + #03A9F4 + #039BE5 + #0288D1 + #0277BD + #01579B + + + + #E0F7FA + #B2EBF2 + #80DEEA + #4DD0E1 + #26C6DA + #00BCD4 + #00ACC1 + #0097A7 + #00838F + #006064 + + + + #E0F2F1 + #B2DFDB + #80CBC4 + #4DB6AC + #26A69A + #009688 + #00897B + #00796B + #00695C + #004D40 + + + + #E8F5E9 + #C8E6C9 + #A5D6A7 + #81C784 + #66BB6A + #4CAF50 + #43A047 + #388E3C + #2E7D32 + #1B5E20 + + + + #F1F8E9 + #DCEDC8 + #C5E1A5 + #AED581 + #9CCC65 + #8BC34A + #7CB342 + #689F38 + #558B2F + #33691E + + + + #F9FBE7 + #F0F4C3 + #E6EE9C + #DCE775 + #D4E157 + #CDDC39 + #C0CA33 + #AFB42B + #9E9D24 + #827717 + + + + #FFFDE7 + #FFF9C4 + #FFF59D + #FFF176 + #FFEE58 + #FFEB3B + #FDD835 + #FBC02D + #F9A825 + #F57F17 + + + + #FFF8E1 + #FFECB3 + #FFE082 + #FFD54F + #FFCA28 + #FFC107 + #FFB300 + #FFA000 + #FF8F00 + #FF6F00 + + + + #FFF3E0 + #FFE0B2 + #FFCC80 + #FFB74D + #FFA726 + #FF9800 + #FB8C00 + #F57C00 + #EF6C00 + #E65100 + + + + #FBE9E7 + #FFCCBC + #FFAB91 + #FF8A65 + #FF7043 + #FF5722 + #F4511E + #E64A19 + #D84315 + #BF360C + + + + #EFEBE9 + #D7CCC8 + #BCAAA4 + #A1887F + #8D6E63 + #795548 + #6D4C41 + #5D4037 + #4E342E + #3E2723 + + + + #FAFAFA + #F5F5F5 + #EEEEEE + #E0E0E0 + #BDBDBD + #9E9E9E + #757575 + #616161 + #424242 + #212121 + + + + #ECEFF1 + #CFD8DC + #B0BEC5 + #90A4AE + #78909C + #607D8B + #546E7A + #455A64 + #37474F + #263238 + + diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index e1ee899d2..35c6011af 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -213,6 +213,7 @@ Pure black night mode Start time End time + Automatic contact colors Font size Use system font Automatic emoji -- GitLab From d6dcc55a36c8257d6f0f62c1899d5b32db4fdf5e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Thu, 26 Dec 2019 19:34:39 -0500 Subject: [PATCH 051/213] Show custom/random colours in compose screen --- .../QKSMS/extensions/CollectionExtensions.kt | 13 ++++++ .../repository/ConversationRepositoryImpl.kt | 11 +++++ .../repository/ConversationRepository.kt | 2 + .../compose/editing/ComposeItemAdapter.kt | 40 ++++++++++++++++--- 4 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 data/src/main/java/com/moez/QKSMS/extensions/CollectionExtensions.kt diff --git a/data/src/main/java/com/moez/QKSMS/extensions/CollectionExtensions.kt b/data/src/main/java/com/moez/QKSMS/extensions/CollectionExtensions.kt new file mode 100644 index 000000000..e22d70dd8 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/extensions/CollectionExtensions.kt @@ -0,0 +1,13 @@ +package com.moez.QKSMS.extensions + +inline fun Iterable.associateByNotNull(keySelector: (T) -> K?): Map { + val map = hashMapOf() + forEach { value -> + val key = keySelector(value) + if (key != null) { + map[key] = value + } + } + + return map +} diff --git a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt index 24935ee6b..02ac38f7e 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt @@ -206,6 +206,17 @@ class ConversationRepositoryImpl @Inject constructor( .observeOn(Schedulers.io()) } + override fun getUnmanagedRecipients(): Observable> { + val realm = Realm.getDefaultInstance() + return realm.where(Recipient::class.java) + .isNotNull("contact") + .findAllAsync() + .asObservable() + .filter { it.isLoaded && it.isValid } + .map { realm.copyFromRealm(it) } + .subscribeOn(AndroidSchedulers.mainThread()) + } + override fun getRecipient(recipientId: Long): Recipient? { return Realm.getDefaultInstance() .where(Recipient::class.java) diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt index a6f1a0711..0377c6770 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt @@ -54,6 +54,8 @@ interface ConversationRepository { fun getUnmanagedConversations(): Observable> + fun getUnmanagedRecipients(): Observable> + fun getRecipient(recipientId: Long): Recipient? fun getThreadId(recipient: String): Long? 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 101e7ba59..718edcc13 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 @@ -29,21 +29,35 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.forwardTouches import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.extensions.associateByNotNull import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.ContactGroup import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Recipient +import com.moez.QKSMS.repository.ConversationRepository +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.contact_list_item.view.* import javax.inject.Inject -class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAdapter() { +class ComposeItemAdapter @Inject constructor( + private val colors: Colors, + private val conversationRepo: ConversationRepository +) : QkAdapter() { val clicks: Subject = PublishSubject.create() val longClicks: Subject = PublishSubject.create() private val numbersViewPool = RecyclerView.RecycledViewPool() + private val disposables = CompositeDisposable() + + var recipients: Map = mapOf() + set(value) { + field = value + notifyDataSetChanged() + } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val layoutInflater = LayoutInflater.from(parent.context) @@ -87,7 +101,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = false - view.avatar.recipients = listOf(Recipient(contact = contact)) + view.avatar.recipients = listOf(createRecipient(contact)) view.title.text = contact.numbers.joinToString { it.address } @@ -123,7 +137,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = prev !is ComposeItem.Starred view.icon.setImageResource(R.drawable.ic_star_black_24dp) - view.avatar.recipients = listOf(Recipient(contact = contact)) + view.avatar.recipients = listOf(createRecipient(contact)) view.title.text = contact.name @@ -139,7 +153,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = prev !is ComposeItem.Group view.icon.setImageResource(R.drawable.ic_people_black_24dp) - view.avatar.recipients = group.contacts.map { contact -> Recipient(contact = contact) } + view.avatar.recipients = group.contacts.map(::createRecipient) view.title.text = group.title @@ -158,7 +172,7 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda view.icon.isVisible = false - view.avatar.recipients = listOf(Recipient(contact = contact)) + view.avatar.recipients = listOf(createRecipient(contact)) view.title.text = contact.name @@ -168,6 +182,22 @@ class ComposeItemAdapter @Inject constructor(private val colors: Colors) : QkAda (view.numbers.adapter as PhoneNumberAdapter).data = contact.numbers } + private fun createRecipient(contact: Contact): Recipient { + return recipients[contact.lookupKey] ?: Recipient( + address = contact.numbers.firstOrNull()?.address ?: "", + contact = contact) + } + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + disposables += conversationRepo.getUnmanagedRecipients() + .map { recipients -> recipients.associateByNotNull { recipient -> recipient.contact?.lookupKey } } + .subscribe { recipients -> this@ComposeItemAdapter.recipients = recipients } + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + disposables.clear() + } + override fun areItemsTheSame(old: ComposeItem, new: ComposeItem): Boolean { val oldIds = old.getContacts().map { contact -> contact.lookupKey } val newIds = new.getContacts().map { contact -> contact.lookupKey } -- GitLab From c3c7b495c922036b7b94e176ff221411027cc07a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 27 Dec 2019 00:39:00 -0500 Subject: [PATCH 052/213] Long-press to copy phone number Closes #1348 --- .../ConversationInfoController.kt | 4 +++- .../ConversationInfoPresenter.kt | 19 ++++++++++++++++++- .../conversationinfo/ConversationInfoView.kt | 3 ++- .../ConversationRecipientAdapter.kt | 11 +++++++++-- presentation/src/main/res/values/strings.xml | 1 + 5 files changed, 33 insertions(+), 5 deletions(-) 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 af8a345e8..d618b344d 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 @@ -90,7 +90,9 @@ class ConversationInfoController( showBackButton(true) } - override fun contactClicks(): Observable = recipientAdapter.contactClicks + override fun recipientClicks(): Observable = recipientAdapter.recipientClicks + + override fun recipientLongClicks(): Observable = recipientAdapter.recipientLongClicks override fun themeClicks(): Observable = recipientAdapter.themeClicks diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt index 10c7b7814..f595b0623 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt @@ -18,9 +18,13 @@ */ package com.moez.QKSMS.feature.conversationinfo +import android.content.Context import androidx.lifecycle.Lifecycle +import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter +import com.moez.QKSMS.common.util.ClipboardUtils +import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.extensions.asObservable import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.interactor.DeleteConversations @@ -32,6 +36,7 @@ import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable +import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.subjects.BehaviorSubject @@ -42,6 +47,7 @@ import javax.inject.Named class ConversationInfoPresenter @Inject constructor( @Named("threadId") threadId: Long, messageRepo: MessageRepository, + private val context: Context, private val conversationRepo: ConversationRepository, private val deleteConversations: DeleteConversations, private val markArchived: MarkArchived, @@ -100,7 +106,7 @@ class ConversationInfoPresenter @Inject constructor( super.bindIntents(view) // Add or display the contact - view.contactClicks() + view.recipientClicks() .mapNotNull(conversationRepo::getRecipient) .doOnNext { recipient -> recipient.contact?.lookupKey?.let(navigator::showContact) @@ -109,6 +115,17 @@ class ConversationInfoPresenter @Inject constructor( .autoDisposable(view.scope(Lifecycle.Event.ON_DESTROY)) // ... this should be the default .subscribe() + // Copy phone number + view.recipientLongClicks() + .mapNotNull(conversationRepo::getRecipient) + .map { recipient -> recipient.address } + .observeOn(AndroidSchedulers.mainThread()) + .autoDisposable(view.scope()) + .subscribe { address -> + ClipboardUtils.copy(context, address) + context.makeToast(R.string.info_copied_address) + } + // Show the theme settings for the conversation view.themeClicks() .autoDisposable(view.scope()) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt index 1b71de22b..343dd8d03 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt @@ -23,7 +23,8 @@ import io.reactivex.Observable interface ConversationInfoView : QkViewContract { - fun contactClicks(): Observable + fun recipientClicks(): Observable + fun recipientLongClicks(): Observable fun themeClicks(): Observable fun nameClicks(): Observable<*> fun nameChanges(): Observable diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt index 35baf7bed..41b6cffa4 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt @@ -36,7 +36,8 @@ class ConversationRecipientAdapter @Inject constructor( private val colors: Colors ) : QkRealmAdapter() { - val contactClicks: Subject = PublishSubject.create() + val recipientClicks: Subject = PublishSubject.create() + val recipientLongClicks: Subject = PublishSubject.create() val themeClicks: Subject = PublishSubject.create() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { @@ -45,7 +46,13 @@ class ConversationRecipientAdapter @Inject constructor( return QkViewHolder(view).apply { view.setOnClickListener { val recipient = getItem(adapterPosition) ?: return@setOnClickListener - contactClicks.onNext(recipient.id) + recipientClicks.onNext(recipient.id) + } + + view.setOnLongClickListener { + val recipient = getItem(adapterPosition) ?: return@setOnLongClickListener false + recipientLongClicks.onNext(recipient.id) + return@setOnLongClickListener true } view.theme.setOnClickListener { diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 35c6011af..d29e7bb11 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -145,6 +145,7 @@ Failed to send. Tap to try again Details + Address copied Conversation title Notifications Theme -- GitLab From 3f24143c8c7f58e6cb56efb6a854637eb7444684 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 27 Dec 2019 01:20:10 -0500 Subject: [PATCH 053/213] Use install referrer api --- data/build.gradle | 1 + .../moez/QKSMS/manager/ReferralManagerImpl.kt | 57 +++++++++++++++++++ .../com/moez/QKSMS/manager/ReferralManager.kt | 7 +++ .../java/com/moez/QKSMS/util/Preferences.kt | 1 + .../com/moez/QKSMS/common/QKApplication.kt | 9 ++- .../com/moez/QKSMS/injection/AppModule.kt | 5 ++ 6 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 data/src/main/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/manager/ReferralManager.kt diff --git a/data/build.gradle b/data/build.gradle index e023ae7a5..d48030806 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -82,6 +82,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version" + implementation 'com.android.installreferrer:installreferrer:1.1' implementation 'com.callcontrol:datashare:1.2.0' implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version" implementation "com.jakewharton.timber:timber:$timber_version" diff --git a/data/src/main/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt b/data/src/main/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt new file mode 100644 index 000000000..4446d6740 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt @@ -0,0 +1,57 @@ +package com.moez.QKSMS.manager + +import android.content.Context +import com.android.installreferrer.api.InstallReferrerClient +import com.android.installreferrer.api.InstallReferrerStateListener +import com.moez.QKSMS.util.Preferences +import kotlinx.coroutines.suspendCancellableCoroutine +import javax.inject.Inject +import kotlin.coroutines.resume + +class ReferralManagerImpl @Inject constructor( + private val analytics: AnalyticsManager, + private val context: Context, + private val prefs: Preferences +) : ReferralManager { + + override suspend fun trackReferrer() { + if (prefs.didSetReferrer.get()) { + return + } + + context.packageManager.getInstallerPackageName(context.packageName)?.let { installer -> + analytics.setUserProperty("Installer", installer) + } + + val referrerClient = InstallReferrerClient.newBuilder(context).build() + val responseCode = suspendCancellableCoroutine { cont -> + referrerClient.startConnection(object : InstallReferrerStateListener { + override fun onInstallReferrerSetupFinished(responseCode: Int) { + cont.resume(responseCode) + } + + override fun onInstallReferrerServiceDisconnected() { + cont.resume(InstallReferrerClient.InstallReferrerResponse.SERVICE_DISCONNECTED) + } + }) + + cont.invokeOnCancellation { + referrerClient.endConnection() + } + } + + when (responseCode) { + InstallReferrerClient.InstallReferrerResponse.OK -> { + analytics.setUserProperty("Referrer", referrerClient.installReferrer.installReferrer) + prefs.didSetReferrer.set(true) + } + + InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> { + prefs.didSetReferrer.set(true) + } + } + + referrerClient.endConnection() + } + +} diff --git a/domain/src/main/java/com/moez/QKSMS/manager/ReferralManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/ReferralManager.kt new file mode 100644 index 000000000..30bcfff40 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/manager/ReferralManager.kt @@ -0,0 +1,7 @@ +package com.moez.QKSMS.manager + +interface ReferralManager { + + suspend fun trackReferrer() + +} diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index f21adcc96..dbb796123 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -75,6 +75,7 @@ class Preferences @Inject constructor( } // Internal + val didSetReferrer = rxPrefs.getBoolean("didSetReferrer", false) val night = rxPrefs.getBoolean("night", false) val canUseSubId = rxPrefs.getBoolean("canUseSubId", true) val version = rxPrefs.getInteger("version", context.versionCode) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt index 57208769c..723a8b4f0 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt @@ -31,6 +31,7 @@ import com.moez.QKSMS.common.util.FileLoggingTree import com.moez.QKSMS.injection.AppComponentManager import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.manager.ReferralManager import com.moez.QKSMS.migration.QkMigration import com.moez.QKSMS.migration.QkRealmMigration import com.moez.QKSMS.util.NightModeManager @@ -43,6 +44,9 @@ import dagger.android.HasBroadcastReceiverInjector import dagger.android.HasServiceInjector import io.realm.Realm import io.realm.RealmConfiguration +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -62,6 +66,7 @@ class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverIn @Inject lateinit var fileLoggingTree: FileLoggingTree @Inject lateinit var nightModeManager: NightModeManager @Inject lateinit var realmMigration: QkRealmMigration + @Inject lateinit var referralManager: ReferralManager override fun onCreate() { super.onCreate() @@ -76,8 +81,8 @@ class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverIn .schemaVersion(QkRealmMigration.SchemaVersion) .build()) - packageManager.getInstallerPackageName(packageName)?.let { installer -> - analyticsManager.setUserProperty("Installer", installer) + GlobalScope.launch(Dispatchers.IO) { + referralManager.trackReferrer() } nightModeManager.updateCurrentTheme() diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index ee8e75b52..0350da761 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -48,6 +48,8 @@ import com.moez.QKSMS.manager.NotificationManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.manager.PermissionManagerImpl import com.moez.QKSMS.manager.RatingManager +import com.moez.QKSMS.manager.ReferralManager +import com.moez.QKSMS.manager.ReferralManagerImpl import com.moez.QKSMS.manager.ShortcutManager import com.moez.QKSMS.manager.WidgetManager import com.moez.QKSMS.manager.WidgetManagerImpl @@ -160,6 +162,9 @@ class AppModule(private var application: Application) { @Provides fun provideShortcutManager(manager: ShortcutManagerImpl): ShortcutManager = manager + @Provides + fun provideReferralManager(manager: ReferralManagerImpl): ReferralManager = manager + @Provides fun provideWidgetManager(manager: WidgetManagerImpl): WidgetManager = manager -- GitLab From 19195a2c3c7a1f55a57c9edb89b602e1d824468c Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 27 Dec 2019 02:13:33 -0500 Subject: [PATCH 054/213] Include version in output file name --- presentation/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/presentation/build.gradle b/presentation/build.gradle index bf6502ec0..99926569e 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -34,6 +34,8 @@ android { versionCode 2209 versionName "3.7.10" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + setProperty("archivesBaseName", "QKSMS-v${versionName}") } signingConfigs { -- GitLab From 1343a67c554e0b0b8426c67c8b419b0830cb08e8 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 27 Dec 2019 18:58:34 -0500 Subject: [PATCH 055/213] Upgrade to SDK 29, use requestLegacyExternalStorage --- common/build.gradle | 2 +- data/build.gradle | 2 +- domain/build.gradle | 2 +- presentation/build.gradle | 2 +- presentation/src/main/AndroidManifest.xml | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/common/build.gradle b/common/build.gradle index ec0f93d96..a893d1c18 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -24,7 +24,7 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 28 // Don't upgrade to 29 until we support Scoped storage + targetSdkVersion 29 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/data/build.gradle b/data/build.gradle index d48030806..f8c517b1a 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -32,7 +32,7 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 28 // Don't upgrade to 29 until we support Scoped storage + targetSdkVersion 29 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "AMPLITUDE_API_KEY", "\"${System.getenv("AMPLITUDE_API_KEY")}\"" diff --git a/domain/build.gradle b/domain/build.gradle index a018510e7..85a69fd8d 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -32,7 +32,7 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 28 // Don't upgrade to 29 until we support Scoped storage + targetSdkVersion 29 } } diff --git a/presentation/build.gradle b/presentation/build.gradle index 99926569e..96c402aa7 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -30,7 +30,7 @@ android { defaultConfig { applicationId "com.moez.QKSMS" minSdkVersion 21 - targetSdkVersion 28 // Don't upgrade to 29 until we support Scoped storage + targetSdkVersion 29 versionCode 2209 versionName "3.7.10" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index 08e36b7ab..b082c22fd 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:requestLegacyExternalStorage="true" android:supportsRtl="true" android:theme="@style/AppLaunchTheme"> -- GitLab From e37be08ae9d3d8325551ccf5e1e7d3a7c806c4b9 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 27 Dec 2019 19:11:25 -0500 Subject: [PATCH 056/213] Max width for message bubbles --- presentation/src/main/res/layout/message_list_item_in.xml | 3 ++- presentation/src/main/res/layout/message_list_item_out.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/res/layout/message_list_item_in.xml b/presentation/src/main/res/layout/message_list_item_in.xml index 2201260d8..0c5d7f90a 100644 --- a/presentation/src/main/res/layout/message_list_item_in.xml +++ b/presentation/src/main/res/layout/message_list_item_in.xml @@ -103,8 +103,9 @@ app:layout_constraintHorizontal_bias="0" app:layout_constraintStart_toEndOf="@id/avatar" app:layout_constraintTop_toBottomOf="@id/attachments" + app:layout_constraintWidth_max="384dp" tools:backgroundTint="@color/tools_theme" - tools:text="@tools:sample/lorem" /> + tools:text="@tools:sample/lorem/random" /> Date: Sat, 28 Dec 2019 00:41:11 -0500 Subject: [PATCH 057/213] Store stripped message #1149 --- .../com/moez/QKSMS/repository/MessageRepositoryImpl.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index 2b4ba0687..805dda88c 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -302,10 +302,16 @@ class MessageRepositoryImpl @Inject constructor( else -> prefs.signature.get() } + // We only care about stripping SMS + val strippedBody = when (prefs.unicode.get()) { + true -> StripAccents.stripAccents(signedBody) + false -> signedBody + } + if (addresses.size == 1 && attachments.isEmpty()) { // SMS if (delay > 0) { // With delay val sendTime = System.currentTimeMillis() + delay - val message = insertSentSms(subId, threadId, addresses.first(), signedBody, sendTime) + val message = insertSentSms(subId, threadId, addresses.first(), strippedBody, sendTime) val intent = getIntentForDelayedSms(message.id) @@ -316,7 +322,7 @@ class MessageRepositoryImpl @Inject constructor( alarmManager.setExact(AlarmManager.RTC_WAKEUP, sendTime, intent) } } else { // No delay - val message = insertSentSms(subId, threadId, addresses.first(), signedBody, System.currentTimeMillis()) + val message = insertSentSms(subId, threadId, addresses.first(), strippedBody, System.currentTimeMillis()) sendSms(message) } } else { // MMS -- GitLab From d1bbe2c3cdf773a47ecfa7b8b255a030a4c093fc Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 28 Dec 2019 01:00:56 -0500 Subject: [PATCH 058/213] Add option to send long messages as MMS Closes #1104 --- .../com/moez/QKSMS/repository/MessageRepositoryImpl.kt | 9 ++++++++- domain/src/main/java/com/moez/QKSMS/util/Preferences.kt | 1 + .../moez/QKSMS/feature/settings/SettingsController.kt | 1 + .../com/moez/QKSMS/feature/settings/SettingsPresenter.kt | 5 +++++ .../com/moez/QKSMS/feature/settings/SettingsState.kt | 1 + .../src/main/res/drawable/ic_message_black_24dp.xml | 9 +++++++++ presentation/src/main/res/layout/settings_controller.xml | 9 +++++++++ presentation/src/main/res/values/strings.xml | 2 ++ 8 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 presentation/src/main/res/drawable/ic_message_black_24dp.xml diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index 805dda88c..af81e68af 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -302,13 +302,20 @@ class MessageRepositoryImpl @Inject constructor( else -> prefs.signature.get() } + val smsManager = subId.takeIf { it != -1 } + ?.let(SmsManagerFactory::createSmsManager) + ?: SmsManager.getDefault() + // We only care about stripping SMS val strippedBody = when (prefs.unicode.get()) { true -> StripAccents.stripAccents(signedBody) false -> signedBody } - if (addresses.size == 1 && attachments.isEmpty()) { // SMS + val parts = smsManager.divideMessage(strippedBody).orEmpty() + val forceMms = prefs.longAsMms.get() && parts.size > 1 + + if (addresses.size == 1 && attachments.isEmpty() && !forceMms) { // SMS if (delay > 0) { // With delay val sendTime = System.currentTimeMillis() + delay val message = insertSentSms(subId, threadId, addresses.first(), strippedBody, sendTime) diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index dbb796123..d783e0249 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -109,6 +109,7 @@ class Preferences @Inject constructor( val signature = rxPrefs.getString("signature", "") val unicode = rxPrefs.getBoolean("unicode", false) val mobileOnly = rxPrefs.getBoolean("mobileOnly", false) + val longAsMms = rxPrefs.getBoolean("longAsMms", false) val mmsSize = rxPrefs.getInteger("mmsSize", 300) val logging = rxPrefs.getBoolean("logging", false) 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 083abd8c6..d035766ea 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 @@ -168,6 +168,7 @@ class SettingsController : QkController newState { copy(mobileOnly = enabled) } } + disposables += prefs.longAsMms.asObservable() + .subscribe { enabled -> newState { copy(longAsMms = enabled) } } + val mmsSizeLabels = context.resources.getStringArray(R.array.mms_sizes) val mmsSizeIds = context.resources.getIntArray(R.array.mms_sizes_ids) disposables += prefs.mmsSize.asObservable() @@ -175,6 +178,8 @@ class SettingsPresenter @Inject constructor( R.id.mobileOnly -> prefs.mobileOnly.set(!prefs.mobileOnly.get()) + R.id.longAsMms -> prefs.longAsMms.set(!prefs.longAsMms.get()) + R.id.mmsSize -> view.showMmsSizePicker() R.id.sync -> syncMessages.execute(Unit) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt index 1119b737a..f18ccd800 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt @@ -41,6 +41,7 @@ data class SettingsState( val splitSmsEnabled: Boolean = false, val stripUnicodeEnabled: Boolean = false, val mobileOnly: Boolean = false, + val longAsMms: Boolean = false, val maxMmsSizeSummary: String = "100KB", val maxMmsSizeId: Int = 100, val syncProgress: SyncRepository.SyncProgress = SyncRepository.SyncProgress.Idle diff --git a/presentation/src/main/res/drawable/ic_message_black_24dp.xml b/presentation/src/main/res/drawable/ic_message_black_24dp.xml new file mode 100644 index 000000000..d2876bfad --- /dev/null +++ b/presentation/src/main/res/drawable/ic_message_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/presentation/src/main/res/layout/settings_controller.xml b/presentation/src/main/res/layout/settings_controller.xml index 8e34c5e93..0ae7a8cf0 100644 --- a/presentation/src/main/res/layout/settings_controller.xml +++ b/presentation/src/main/res/layout/settings_controller.xml @@ -177,6 +177,15 @@ app:title="@string/settings_mobile_title" app:widget="@layout/settings_switch_widget" /> + + Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database -- GitLab From eb9cd59218b21d9236d053fc148e555c7dc4493f Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 28 Dec 2019 03:06:35 -0500 Subject: [PATCH 059/213] Sort drafts first, save drafts for new conversations, show drafts in widget Closes #1296 --- .../repository/ConversationRepositoryImpl.kt | 22 +++++++++++++++---- .../conversations/ConversationsAdapter.kt | 8 +++---- .../QKSMS/feature/widget/WidgetAdapter.kt | 9 ++++++-- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt index 02ac38f7e..9cb9aad1a 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt @@ -58,11 +58,18 @@ class ConversationRepositoryImpl @Inject constructor( return Realm.getDefaultInstance() .where(Conversation::class.java) .notEqualTo("id", 0L) - .isNotNull("lastMessage") .equalTo("archived", archived) .equalTo("blocked", false) .isNotEmpty("recipients") - .sort("pinned", Sort.DESCENDING, "lastMessage.date", Sort.DESCENDING) + .beginGroup() + .isNotNull("lastMessage") + .or() + .isNotEmpty("draft") + .endGroup() + .sort( + arrayOf("pinned", "draft", "lastMessage.date"), + arrayOf(Sort.DESCENDING, Sort.DESCENDING, Sort.DESCENDING) + ) .findAllAsync() } @@ -71,11 +78,18 @@ class ConversationRepositoryImpl @Inject constructor( realm.refresh() realm.copyFromRealm(realm.where(Conversation::class.java) .notEqualTo("id", 0L) - .isNotNull("lastMessage") .equalTo("archived", false) .equalTo("blocked", false) .isNotEmpty("recipients") - .sort("pinned", Sort.DESCENDING, "lastMessage.date", Sort.DESCENDING) + .beginGroup() + .isNotNull("lastMessage") + .or() + .isNotEmpty("draft") + .endGroup() + .sort( + arrayOf("pinned", "draft", "lastMessage.date"), + arrayOf(Sort.DESCENDING, Sort.DESCENDING, Sort.DESCENDING) + ) .findAll()) } } 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 0b32f81dd..6285346f5 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 @@ -86,7 +86,6 @@ class ConversationsAdapter @Inject constructor( override fun onBindViewHolder(viewHolder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return - val lastMessage = conversation.lastMessage ?: return val view = viewHolder.containerView view.isActivated = isSelected(conversation.id) @@ -94,7 +93,7 @@ class ConversationsAdapter @Inject constructor( view.avatars.recipients = conversation.recipients view.title.collapseEnabled = conversation.recipients.size > 1 view.title.text = conversation.getTitle() - view.date.text = dateFormatter.getConversationTimestamp(conversation.date) + view.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) view.snippet.text = when { conversation.draft.isNotEmpty() -> context.getString(R.string.main_draft, conversation.draft) conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) @@ -103,8 +102,9 @@ class ConversationsAdapter @Inject constructor( view.pinned.isVisible = conversation.pinned // If the last message wasn't incoming, then the colour doesn't really matter anyway - val recipient = when (conversation.recipients.size) { - 1 -> conversation.recipients.firstOrNull() + val lastMessage = conversation.lastMessage + val recipient = when { + conversation.recipients.size == 1 || lastMessage == null -> conversation.recipients.firstOrNull() else -> conversation.recipients.find { recipient -> phoneNumberUtils.compare(recipient.address, lastMessage.address) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt index 410a9fd6d..f8011ef8a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt @@ -142,13 +142,18 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { remoteViews.setTextViewText(R.id.name, boldText(conversation.getTitle(), conversation.unread)) // Date - val timestamp = dateFormatter.getConversationTimestamp(conversation.date) + val timestamp = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) remoteViews.setTextColor(R.id.date, if (conversation.unread) textPrimary else textTertiary) remoteViews.setTextViewText(R.id.date, boldText(timestamp, conversation.unread)) // Snippet + val snippet = when { + conversation.draft.isNotEmpty() -> context.getString(R.string.main_draft, conversation.draft) + conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) + else -> conversation.snippet + } remoteViews.setTextColor(R.id.snippet, if (conversation.unread) textPrimary else textTertiary) - remoteViews.setTextViewText(R.id.snippet, boldText(conversation.snippet, conversation.unread)) + remoteViews.setTextViewText(R.id.snippet, boldText(snippet, conversation.unread)) // Launch conversation on click val clickIntent = Intent().putExtra("threadId", conversation.id) -- GitLab From 1080e540edccf2e88d5d01c8d9186bb09a616c98 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 29 Dec 2019 17:07:37 -0500 Subject: [PATCH 060/213] Correctly use ContainerView with ViewHolder --- .../com/moez/QKSMS/common/MenuItemAdapter.kt | 8 +- .../QKSMS/feature/backup/BackupAdapter.kt | 9 +- .../messages/BlockedMessagesAdapter.kt | 20 ++-- .../feature/compose/AttachmentAdapter.kt | 8 +- .../QKSMS/feature/compose/MessagesAdapter.kt | 57 +++++----- .../feature/compose/editing/ChipsAdapter.kt | 6 +- .../compose/editing/ComposeItemAdapter.kt | 103 +++++++++--------- .../compose/editing/PhoneNumberAdapter.kt | 7 +- .../QKSMS/feature/compose/part/FileBinder.kt | 36 +++--- .../QKSMS/feature/compose/part/MediaBinder.kt | 14 +-- .../QKSMS/feature/compose/part/PartBinder.kt | 4 +- .../feature/compose/part/PartsAdapter.kt | 15 ++- .../QKSMS/feature/compose/part/VCardBinder.kt | 34 +++--- .../ConversationMediaAdapter.kt | 6 +- .../ConversationRecipientAdapter.kt | 14 +-- .../conversations/ConversationsAdapter.kt | 20 ++-- .../feature/gallery/GalleryPagerAdapter.kt | 10 +- .../moez/QKSMS/feature/main/SearchAdapter.kt | 20 ++-- .../scheduled/ScheduledMessageAdapter.kt | 20 ++-- .../ScheduledMessageAttachmentAdapter.kt | 11 +- .../QKSMS/feature/themepicker/ThemeAdapter.kt | 10 +- 21 files changed, 218 insertions(+), 214 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt index bc506cd80..656305482 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt @@ -33,6 +33,7 @@ import com.moez.QKSMS.common.util.extensions.setVisible import io.reactivex.disposables.CompositeDisposable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.menu_list_item.* import kotlinx.android.synthetic.main.menu_list_item.view.* import javax.inject.Inject @@ -83,11 +84,10 @@ class MenuItemAdapter @Inject constructor(private val context: Context, private override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val menuItem = getItem(position) - val view = holder.containerView - view.title.text = menuItem.title - view.check.isActivated = (menuItem.actionId == selectedItem) - view.check.setVisible(selectedItem != null) + holder.title.text = menuItem.title + holder.check.isActivated = (menuItem.actionId == selectedItem) + holder.check.setVisible(selectedItem != null) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt index ff9270e8f..038295dc6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt @@ -29,7 +29,7 @@ import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.model.BackupFile import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.backup_list_item.view.* +import kotlinx.android.synthetic.main.backup_list_item.* import javax.inject.Inject class BackupAdapter @Inject constructor( @@ -50,13 +50,12 @@ class BackupAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val backup = getItem(position) - val view = holder.containerView val count = backup.messages - view.title.text = dateFormatter.getDetailedTimestamp(backup.date) - view.messages.text = context.resources.getQuantityString(R.plurals.backup_message_count, count, count) - view.size.text = Formatter.formatFileSize(context, backup.size) + holder.title.text = dateFormatter.getDetailedTimestamp(backup.date) + holder.messages.text = context.resources.getQuantityString(R.plurals.backup_message_count, count, count) + holder.size.text = Formatter.formatFileSize(context, backup.size) } } \ No newline at end of file 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 af73405ed..9a4c37e38 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 @@ -31,6 +31,7 @@ import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.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 @@ -69,24 +70,23 @@ class BlockedMessagesAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return - val view = holder.containerView - view.isActivated = isSelected(conversation.id) + holder.containerView.isActivated = isSelected(conversation.id) - view.avatars.recipients = conversation.recipients - view.title.collapseEnabled = conversation.recipients.size > 1 - view.title.text = conversation.getTitle() - view.date.text = dateFormatter.getConversationTimestamp(conversation.date) + holder.avatars.recipients = conversation.recipients + holder.title.collapseEnabled = conversation.recipients.size > 1 + holder.title.text = conversation.getTitle() + holder.date.text = dateFormatter.getConversationTimestamp(conversation.date) - view.blocker.text = when (conversation.blockingClient) { + holder.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 } - view.reason.text = conversation.blockReason - view.blocker.isVisible = view.blocker.text.isNotEmpty() - view.reason.isVisible = view.blocker.text.isNotEmpty() + holder.reason.text = conversation.blockReason + holder.blocker.isVisible = holder.blocker.text.isNotEmpty() + holder.reason.isVisible = holder.blocker.text.isNotEmpty() } override fun getItemViewType(position: Int): Int { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt index a158a3951..3563798bc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt @@ -33,7 +33,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.attachment_contact_list_item.view.* +import kotlinx.android.synthetic.main.attachment_contact_list_item.* +import kotlinx.android.synthetic.main.attachment_image_list_item.* import kotlinx.android.synthetic.main.attachment_image_list_item.view.* import javax.inject.Inject @@ -69,18 +70,17 @@ class AttachmentAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val attachment = getItem(position) - val view = holder.containerView when (attachment) { is Attachment.Image -> Glide.with(context) .load(attachment.getUri()) - .into(view.thumbnail) + .into(holder.thumbnail) is Attachment.Contact -> Observable.just(attachment.vCard) .mapNotNull { vCard -> Ezvcard.parse(vCard).first() } .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { vcard -> view.name?.text = vcard.formattedName.value } + .subscribe { vcard -> holder.name?.text = vcard.formattedName.value } } } 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 88fbfb0ba..f11b33983 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 @@ -59,7 +59,15 @@ import com.moez.QKSMS.util.Preferences 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.attachments +import kotlinx.android.synthetic.main.message_list_item_in.body +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 java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -175,11 +183,10 @@ class MessagesAdapter @Inject constructor( } } - override fun onBindViewHolder(viewHolder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val message = getItem(position) ?: return val previous = if (position == 0) null else getItem(position - 1) val next = if (position == itemCount - 1) null else getItem(position + 1) - val view = viewHolder.containerView val theme = when (message.isOutgoingMessage()) { true -> colors.theme() @@ -187,10 +194,10 @@ class MessagesAdapter @Inject constructor( } // Update the selected state - view.isActivated = isSelected(message.id) || highlight == message.id + holder.containerView.isActivated = isSelected(message.id) || highlight == message.id // Bind the cancel view - view.findViewById(R.id.cancel)?.let { cancel -> + holder.cancel?.let { cancel -> val isCancellable = message.isSending() && message.date > System.currentTimeMillis() cancel.setVisible(isCancellable) cancel.clicks().subscribe { cancelSending.onNext(message.id) } @@ -212,31 +219,31 @@ class MessagesAdapter @Inject constructor( } // Bind the message status - bindStatus(viewHolder, message, next) + bindStatus(holder, message, next) // Bind the timestamp val timeSincePrevious = TimeUnit.MILLISECONDS.toMinutes(message.date - (previous?.date ?: 0)) val simIndex = subs.takeIf { it.size > 1 }?.indexOfFirst { it.subscriptionId == message.subId } ?: -1 - view.timestamp.text = dateFormatter.getMessageTimestamp(message.date) - view.simIndex.text = "${simIndex + 1}" + holder.timestamp.text = dateFormatter.getMessageTimestamp(message.date) + holder.simIndex.text = "${simIndex + 1}" - view.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD + holder.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD || message.subId != previous?.subId && simIndex != -1) - view.sim.setVisible(message.subId != previous?.subId && simIndex != -1) - view.simIndex.setVisible(message.subId != previous?.subId && simIndex != -1) + holder.sim.setVisible(message.subId != previous?.subId && simIndex != -1) + holder.simIndex.setVisible(message.subId != previous?.subId && simIndex != -1) // Bind the grouping val media = message.parts.filter { !it.isSmil() && !it.isText() } - view.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context)) + holder.containerView.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context)) // Bind the avatar and bubble colour if (!message.isMe()) { - view.avatar.setRecipient(contactCache[message.address]) - view.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) + holder.avatar.setRecipient(contactCache[message.address]) + holder.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) - view.body.setTextColor(theme.textPrimary) - view.body.setBackgroundTint(theme.theme) + holder.body.setTextColor(theme.textPrimary) + holder.body.setBackgroundTint(theme.theme) } // Bind the body text @@ -262,31 +269,29 @@ class MessagesAdapter @Inject constructor( } } val emojiOnly = messageText.isNotBlank() && messageText.matches(EMOJI_REGEX) - textViewStyler.setTextSize(view.body, when (emojiOnly) { + textViewStyler.setTextSize(holder.body, when (emojiOnly) { true -> TextViewStyler.SIZE_EMOJI false -> TextViewStyler.SIZE_PRIMARY }) - view.body.text = messageText - view.body.setVisible(message.isSms() || messageText.isNotBlank()) - view.body.setBackgroundResource(getBubble( + holder.body.text = messageText + holder.body.setVisible(message.isSms() || messageText.isNotBlank()) + holder.body.setBackgroundResource(getBubble( emojiOnly = emojiOnly, canGroupWithPrevious = canGroup(message, previous) || media.isNotEmpty(), canGroupWithNext = canGroup(message, next), isMe = message.isMe())) // Bind the attachments - val partsAdapter = view.attachments.adapter as PartsAdapter + val partsAdapter = holder.attachments.adapter as PartsAdapter partsAdapter.theme = theme - partsAdapter.setData(message, previous, next, view) + partsAdapter.setData(message, previous, next, holder) } - private fun bindStatus(viewHolder: QkViewHolder, message: Message, next: Message?) { - val view = viewHolder.containerView - + private fun bindStatus(holder: QkViewHolder, message: Message, next: Message?) { val age = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - message.date) - view.status.text = when { + holder.status.text = when { message.isSending() -> context.getString(R.string.message_status_sending) message.isDelivered() -> context.getString(R.string.message_status_delivered, dateFormatter.getTimestamp(message.dateSent)) @@ -300,7 +305,7 @@ class MessagesAdapter @Inject constructor( else -> dateFormatter.getTimestamp(message.date) } - view.status.setVisible(when { + holder.status.setVisible(when { expanded[message.id] == true -> true message.isSending() -> true message.isFailedMessage() -> true diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt index 0bd7f0847..04f5e7c78 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt @@ -33,7 +33,6 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.model.Recipient import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.contact_chip.* -import kotlinx.android.synthetic.main.contact_chip.view.* import javax.inject.Inject class ChipsAdapter @Inject constructor() : QkAdapter() { @@ -59,10 +58,9 @@ class ChipsAdapter @Inject constructor() : QkAdapter() { override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val chip = getItem(position) - val view = holder.containerView - view.avatar.setRecipient(Recipient(id = -1, contact = chip.contact)) - view.name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address + holder.avatar.setRecipient(Recipient(id = -1, contact = chip.contact)) + holder.name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address } /** 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 718edcc13..3acb379eb 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 @@ -19,7 +19,6 @@ package com.moez.QKSMS.feature.compose.editing import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView @@ -39,6 +38,7 @@ import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign 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 @@ -85,101 +85,100 @@ class ComposeItemAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val prevItem = if (position > 0) getItem(position - 1) else null val item = getItem(position) - val view = holder.containerView when (item) { - is ComposeItem.New -> bindNew(view, item.value) - is ComposeItem.Recent -> bindRecent(view, item.value, prevItem) - is ComposeItem.Starred -> bindStarred(view, item.value, prevItem) - is ComposeItem.Person -> bindPerson(view, item.value, prevItem) - is ComposeItem.Group -> bindGroup(view, item.value, prevItem) + 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) } } - private fun bindNew(view: View, contact: Contact) { - view.index.isVisible = false + private fun bindNew(holder: QkViewHolder, contact: Contact) { + holder.index.isVisible = false - view.icon.isVisible = false + holder.icon.isVisible = false - view.avatar.recipients = listOf(createRecipient(contact)) + holder.avatar.recipients = listOf(createRecipient(contact)) - view.title.text = contact.numbers.joinToString { it.address } + holder.title.text = contact.numbers.joinToString { it.address } - view.subtitle.isVisible = false + holder.subtitle.isVisible = false - view.numbers.isVisible = false + holder.numbers.isVisible = false } - private fun bindRecent(view: View, conversation: Conversation, prev: ComposeItem?) { - view.index.isVisible = false + private fun bindRecent(holder: QkViewHolder, conversation: Conversation, prev: ComposeItem?) { + holder.index.isVisible = false - view.icon.isVisible = prev !is ComposeItem.Recent - view.icon.setImageResource(R.drawable.ic_history_black_24dp) + holder.icon.isVisible = prev !is ComposeItem.Recent + holder.icon.setImageResource(R.drawable.ic_history_black_24dp) - view.avatar.recipients = conversation.recipients + holder.avatar.recipients = conversation.recipients - view.title.text = conversation.getTitle() + holder.title.text = conversation.getTitle() - view.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() - view.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> + holder.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() + holder.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> recipient.contact?.name ?: recipient.address } - view.numbers.isVisible = conversation.recipients.size == 1 - (view.numbers.adapter as PhoneNumberAdapter).data = conversation.recipients + holder.numbers.isVisible = conversation.recipients.size == 1 + (holder.numbers.adapter as PhoneNumberAdapter).data = conversation.recipients .mapNotNull { recipient -> recipient.contact } .flatMap { contact -> contact.numbers } } - private fun bindStarred(view: View, contact: Contact, prev: ComposeItem?) { - view.index.isVisible = false + private fun bindStarred(holder: QkViewHolder, contact: Contact, prev: ComposeItem?) { + holder.index.isVisible = false - view.icon.isVisible = prev !is ComposeItem.Starred - view.icon.setImageResource(R.drawable.ic_star_black_24dp) + holder.icon.isVisible = prev !is ComposeItem.Starred + holder.icon.setImageResource(R.drawable.ic_star_black_24dp) - view.avatar.recipients = listOf(createRecipient(contact)) + holder.avatar.recipients = listOf(createRecipient(contact)) - view.title.text = contact.name + holder.title.text = contact.name - view.subtitle.isVisible = false + holder.subtitle.isVisible = false - view.numbers.isVisible = true - (view.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + holder.numbers.isVisible = true + (holder.numbers.adapter as PhoneNumberAdapter).data = contact.numbers } - private fun bindGroup(view: View, group: ContactGroup, prev: ComposeItem?) { - view.index.isVisible = false + private fun bindGroup(holder: QkViewHolder, group: ContactGroup, prev: ComposeItem?) { + holder.index.isVisible = false - view.icon.isVisible = prev !is ComposeItem.Group - view.icon.setImageResource(R.drawable.ic_people_black_24dp) + holder.icon.isVisible = prev !is ComposeItem.Group + holder.icon.setImageResource(R.drawable.ic_people_black_24dp) - view.avatar.recipients = group.contacts.map(::createRecipient) + holder.avatar.recipients = group.contacts.map(::createRecipient) - view.title.text = group.title + holder.title.text = group.title - view.subtitle.isVisible = true - view.subtitle.text = group.contacts.joinToString(", ") { it.name } + holder.subtitle.isVisible = true + holder.subtitle.text = group.contacts.joinToString(", ") { it.name } - view.numbers.isVisible = false + holder.numbers.isVisible = false } - private fun bindPerson(view: View, contact: Contact, prev: ComposeItem?) { - view.index.isVisible = true - view.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" - view.index.isVisible = prev !is ComposeItem.Person || + 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 || (contact.name[0].isLetter() && !contact.name[0].equals(prev.value.name[0], ignoreCase = true)) || (!contact.name[0].isLetter() && prev.value.name[0].isLetter()) - view.icon.isVisible = false + holder.icon.isVisible = false - view.avatar.recipients = listOf(createRecipient(contact)) + holder.avatar.recipients = listOf(createRecipient(contact)) - view.title.text = contact.name + holder.title.text = contact.name - view.subtitle.isVisible = false + holder.subtitle.isVisible = false - view.numbers.isVisible = true - (view.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + holder.numbers.isVisible = true + (holder.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/PhoneNumberAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt index 565bd1b9f..534f013e9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt @@ -24,7 +24,7 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.model.PhoneNumber -import kotlinx.android.synthetic.main.contact_number_list_item.view.* +import kotlinx.android.synthetic.main.contact_number_list_item.* class PhoneNumberAdapter : QkAdapter() { @@ -36,10 +36,9 @@ class PhoneNumberAdapter : QkAdapter() { override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val number = getItem(position) - val view = holder.containerView - view.address.text = number.address - view.type.text = number.type + holder.address.text = number.address + holder.type.text = number.type } override fun areItemsTheSame(old: PhoneNumber, new: PhoneNumber): Boolean { 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 73d9ab836..ff12a1bdd 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 @@ -21,9 +21,9 @@ package com.moez.QKSMS.feature.compose.part import android.annotation.SuppressLint import android.content.Context import android.view.Gravity -import android.view.View import android.widget.FrameLayout import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint @@ -34,7 +34,7 @@ import com.moez.QKSMS.model.MmsPart import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers -import kotlinx.android.synthetic.main.mms_file_list_item.view.* +import kotlinx.android.synthetic.main.mms_file_list_item.* import javax.inject.Inject class FileBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { @@ -47,16 +47,16 @@ class FileBinder @Inject constructor(colors: Colors, private val context: Contex @SuppressLint("CheckResult") override fun bindPart( - view: View, + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) - .let(view.fileBackground::setBackgroundResource) + .let(holder.fileBackground::setBackgroundResource) - view.setOnClickListener { clicks.onNext(part.id) } + holder.containerView.setOnClickListener { clicks.onNext(part.id) } Observable.just(part.getUri()) .map(context.contentResolver::openInputStream) @@ -71,23 +71,23 @@ class FileBinder @Inject constructor(colors: Colors, private val context: Contex } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { size -> view.size.text = size } + .subscribe { size -> holder.size.text = size } - view.filename.text = part.name + holder.filename.text = part.name - val params = view.fileBackground.layoutParams as FrameLayout.LayoutParams + val params = holder.fileBackground.layoutParams as FrameLayout.LayoutParams if (!message.isMe()) { - view.fileBackground.layoutParams = params.apply { gravity = Gravity.START } - view.fileBackground.setBackgroundTint(theme.theme) - view.icon.setTint(theme.textPrimary) - view.filename.setTextColor(theme.textPrimary) - view.size.setTextColor(theme.textTertiary) + holder.fileBackground.layoutParams = params.apply { gravity = Gravity.START } + holder.fileBackground.setBackgroundTint(theme.theme) + holder.icon.setTint(theme.textPrimary) + holder.filename.setTextColor(theme.textPrimary) + holder.size.setTextColor(theme.textTertiary) } else { - view.fileBackground.layoutParams = params.apply { gravity = Gravity.END } - view.fileBackground.setBackgroundTint(view.context.resolveThemeColor(R.attr.bubbleColor)) - view.icon.setTint(view.context.resolveThemeColor(android.R.attr.textColorSecondary)) - view.filename.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorPrimary)) - view.size.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorTertiary)) + holder.fileBackground.layoutParams = params.apply { gravity = Gravity.END } + 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)) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt index dafece1c6..1ac437a5c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt @@ -19,8 +19,8 @@ package com.moez.QKSMS.feature.compose.part import android.content.Context -import android.view.View import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.BubbleImageView @@ -29,7 +29,7 @@ import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.util.GlideApp -import kotlinx.android.synthetic.main.mms_preview_list_item.view.* +import kotlinx.android.synthetic.main.mms_preview_list_item.* import javax.inject.Inject class MediaBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { @@ -40,23 +40,23 @@ class MediaBinder @Inject constructor(colors: Colors, private val context: Conte override fun canBindPart(part: MmsPart) = part.isImage() || part.isVideo() override fun bindPart( - view: View, + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { - view.video.setVisible(part.isVideo()) - view.setOnClickListener { clicks.onNext(part.id) } + holder.video.setVisible(part.isVideo()) + holder.containerView.setOnClickListener { clicks.onNext(part.id) } - view.thumbnail.bubbleStyle = when { + holder.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 else -> BubbleImageView.Style.ONLY } - GlideApp.with(context).load(part.getUri()).fitCenter().into(view.thumbnail) + GlideApp.with(context).load(part.getUri()).fitCenter().into(holder.thumbnail) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt index fc3fb7fc4..d56f8547d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt @@ -18,7 +18,7 @@ */ package com.moez.QKSMS.feature.compose.part -import android.view.View +import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart @@ -36,7 +36,7 @@ abstract class PartBinder { abstract fun canBindPart(part: MmsPart): Boolean abstract fun bindPart( - view: View, + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, 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 31c67aee5..567f49268 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 @@ -31,7 +31,7 @@ import com.moez.QKSMS.feature.compose.BubbleUtils.canGroup import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import io.reactivex.Observable -import kotlinx.android.synthetic.main.message_list_item_in.view.* +import kotlinx.android.synthetic.main.message_list_item_in.* import javax.inject.Inject class PartsAdapter @Inject constructor( @@ -54,35 +54,34 @@ class PartsAdapter @Inject constructor( private lateinit var message: Message private var previous: Message? = null private var next: Message? = null - private var messageView: View? = null + private var holder: QkViewHolder? = null private var bodyVisible: Boolean = true - fun setData(message: Message, previous: Message?, next: Message?, messageView: View) { + fun setData(message: Message, previous: Message?, next: Message?, holder: QkViewHolder) { this.message = message this.previous = previous this.next = next - this.messageView = messageView - this.bodyVisible = messageView.body.visibility == View.VISIBLE + this.holder = holder + this.bodyVisible = holder.body.visibility == View.VISIBLE this.data = message.parts.filter { !it.isSmil() && !it.isText() } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val layout = partBinders.getOrNull(viewType)?.partLayout ?: 0 val view = LayoutInflater.from(parent.context).inflate(layout, parent, false) - messageView?.let(view::forwardTouches) + holder?.containerView?.let(view::forwardTouches) return QkViewHolder(view) } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val part = data[position] - val view = holder.containerView val canGroupWithPrevious = canGroup(message, previous) || position > 0 val canGroupWithNext = canGroup(message, next) || position < itemCount - 1 || bodyVisible partBinders .firstOrNull { it.canBindPart(part) } - ?.bindPart(view, part, message, canGroupWithPrevious, canGroupWithNext) + ?.bindPart(holder, part, message, canGroupWithPrevious, canGroupWithNext) } override fun getItemViewType(position: Int): Int { 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 205437d4e..9e068137d 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 @@ -20,9 +20,9 @@ package com.moez.QKSMS.feature.compose.part import android.content.Context import android.view.Gravity -import android.view.View import android.widget.FrameLayout import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint @@ -36,7 +36,7 @@ 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.view.* +import kotlinx.android.synthetic.main.mms_vcard_list_item.* import javax.inject.Inject class VCardBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { @@ -47,37 +47,37 @@ class VCardBinder @Inject constructor(colors: Colors, private val context: Conte override fun canBindPart(part: MmsPart) = part.isVCard() override fun bindPart( - view: View, + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) - .let(view.vCardBackground::setBackgroundResource) + .let(holder.vCardBackground::setBackgroundResource) - view.setOnClickListener { clicks.onNext(part.id) } + holder.containerView.setOnClickListener { clicks.onNext(part.id) } Observable.just(part.getUri()) .map(context.contentResolver::openInputStream) .mapNotNull { inputStream -> inputStream.use { Ezvcard.parse(it).first() } } .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { vcard -> view.name?.text = vcard.formattedName.value } + .subscribe { vcard -> holder.name?.text = vcard.formattedName.value } - val params = view.vCardBackground.layoutParams as FrameLayout.LayoutParams + val params = holder.vCardBackground.layoutParams as FrameLayout.LayoutParams if (!message.isMe()) { - view.vCardBackground.layoutParams = params.apply { gravity = Gravity.START } - view.vCardBackground.setBackgroundTint(theme.theme) - view.vCardAvatar.setTint(theme.textPrimary) - view.name.setTextColor(theme.textPrimary) - view.label.setTextColor(theme.textTertiary) + holder.vCardBackground.layoutParams = params.apply { gravity = Gravity.START } + holder.vCardBackground.setBackgroundTint(theme.theme) + holder.vCardAvatar.setTint(theme.textPrimary) + holder.name.setTextColor(theme.textPrimary) + holder.label.setTextColor(theme.textTertiary) } else { - view.vCardBackground.layoutParams = params.apply { gravity = Gravity.END } - view.vCardBackground.setBackgroundTint(view.context.resolveThemeColor(R.attr.bubbleColor)) - view.vCardAvatar.setTint(view.context.resolveThemeColor(android.R.attr.textColorSecondary)) - view.name.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorPrimary)) - view.label.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorTertiary)) + holder.vCardBackground.layoutParams = params.apply { gravity = Gravity.END } + 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)) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationMediaAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationMediaAdapter.kt index e34063c0f..16565c6d1 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationMediaAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationMediaAdapter.kt @@ -29,6 +29,7 @@ import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.util.GlideApp +import kotlinx.android.synthetic.main.conversation_media_list_item.* import kotlinx.android.synthetic.main.conversation_media_list_item.view.* import javax.inject.Inject @@ -50,14 +51,13 @@ class ConversationMediaAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val part = getItem(position) ?: return - val view = holder.containerView GlideApp.with(context) .load(part.getUri()) .fitCenter() - .into(view.thumbnail) + .into(holder.thumbnail) - view.video.setVisible(part.isVideo()) + holder.video.setVisible(part.isVideo()) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt index 41b6cffa4..f165e5429 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt @@ -29,6 +29,7 @@ import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.model.Recipient import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.conversation_recipient_list_item.* import kotlinx.android.synthetic.main.conversation_recipient_list_item.view.* import javax.inject.Inject @@ -64,19 +65,18 @@ class ConversationRecipientAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val recipient = getItem(position) ?: return - val view = holder.containerView - view.avatar.setRecipient(recipient) + holder.avatar.setRecipient(recipient) - view.name.text = recipient.contact?.name ?: recipient.address + holder.name.text = recipient.contact?.name ?: recipient.address - view.address.text = recipient.address - view.address.setVisible(recipient.contact != null) + holder.address.text = recipient.address + holder.address.setVisible(recipient.contact != null) - view.add.setVisible(recipient.contact == null) + holder.add.setVisible(recipient.contact == null) val theme = colors.theme(recipient) - view.theme.setTint(theme.theme) + holder.theme.setTint(theme.theme) } } 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 6285346f5..56b5f51e6 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,6 +33,7 @@ import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.util.PhoneNumberUtils +import kotlinx.android.synthetic.main.conversation_list_item.* import kotlinx.android.synthetic.main.conversation_list_item.view.* import javax.inject.Inject @@ -84,22 +85,21 @@ class ConversationsAdapter @Inject constructor( } } - override fun onBindViewHolder(viewHolder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return - val view = viewHolder.containerView - view.isActivated = isSelected(conversation.id) + holder.containerView.isActivated = isSelected(conversation.id) - view.avatars.recipients = conversation.recipients - view.title.collapseEnabled = conversation.recipients.size > 1 - view.title.text = conversation.getTitle() - view.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) - view.snippet.text = when { + holder.avatars.recipients = conversation.recipients + holder.title.collapseEnabled = conversation.recipients.size > 1 + holder.title.text = conversation.getTitle() + holder.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) + holder.snippet.text = when { conversation.draft.isNotEmpty() -> context.getString(R.string.main_draft, conversation.draft) conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) else -> conversation.snippet } - view.pinned.isVisible = conversation.pinned + holder.pinned.isVisible = conversation.pinned // If the last message wasn't incoming, then the colour doesn't really matter anyway val lastMessage = conversation.lastMessage @@ -110,7 +110,7 @@ class ConversationsAdapter @Inject constructor( } } - view.unread.setTint(colors.theme(recipient).theme) + holder.unread.setTint(colors.theme(recipient).theme) } override fun getItemId(index: Int): Long { 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 2569a6c07..f3a4405bc 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 @@ -39,8 +39,9 @@ import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.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.view.* +import kotlinx.android.synthetic.main.gallery_video_page.* import java.util.* import javax.inject.Inject @@ -90,7 +91,6 @@ class GalleryPagerAdapter @Inject constructor(private val context: Context) : Qk override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val part = getItem(position) ?: return - val view = holder.containerView when (getItemViewType(position)) { VIEW_TYPE_IMAGE -> { // We need to explicitly request a gif from glide for animations to work @@ -98,12 +98,12 @@ class GalleryPagerAdapter @Inject constructor(private val context: Context) : Qk ContentType.IMAGE_GIF -> GlideApp.with(context) .asGif() .load(part.getUri()) - .into(view.image) + .into(holder.image) else -> GlideApp.with(context) .asBitmap() .load(part.getUri()) - .into(view.image) + .into(holder.image) } } @@ -111,7 +111,7 @@ class GalleryPagerAdapter @Inject constructor(private val context: Context) : Qk val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(null) val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory) val exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector) - view.video.player = exoPlayer + holder.video.player = exoPlayer exoPlayers.add(exoPlayer) val dataSourceFactory = DefaultDataSourceFactory(context, Util.getUserAgent(context, "QKSMS")) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt index 6d03af13f..9a964a208 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt @@ -33,6 +33,7 @@ import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.model.SearchResult +import kotlinx.android.synthetic.main.search_list_item.* import kotlinx.android.synthetic.main.search_list_item.view.* import javax.inject.Inject @@ -56,12 +57,11 @@ class SearchAdapter @Inject constructor( } } - override fun onBindViewHolder(viewHolder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val previous = data.getOrNull(position - 1) val result = getItem(position) - val view = viewHolder.containerView - view.resultsHeader.setVisible(result.messages > 0 && previous?.messages == 0) + holder.resultsHeader.setVisible(result.messages > 0 && previous?.messages == 0) val query = result.query val title = SpannableString(result.conversation.getTitle()) @@ -71,23 +71,23 @@ class SearchAdapter @Inject constructor( title.setSpan(BackgroundColorSpan(highlightColor), index, index + query.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) index = title.indexOf(query, index + query.length, true) } - view.title.text = title + holder.title.text = title - view.avatars.recipients = result.conversation.recipients + holder.avatars.recipients = result.conversation.recipients when (result.messages == 0) { true -> { - view.date.setVisible(true) - view.date.text = dateFormatter.getConversationTimestamp(result.conversation.date) - view.snippet.text = when (result.conversation.me) { + holder.date.setVisible(true) + holder.date.text = dateFormatter.getConversationTimestamp(result.conversation.date) + holder.snippet.text = when (result.conversation.me) { true -> context.getString(R.string.main_sender_you, result.conversation.snippet) false -> result.conversation.snippet } } false -> { - view.date.setVisible(false) - view.snippet.text = context.getString(R.string.main_message_results, result.messages) + holder.date.setVisible(false) + holder.snippet.text = context.getString(R.string.main_message_results, result.messages) } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt index e23c2a687..23c09227c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt @@ -18,6 +18,7 @@ */ package com.moez.QKSMS.feature.scheduled +import android.content.Context import android.net.Uri import android.view.LayoutInflater import android.view.ViewGroup @@ -34,10 +35,12 @@ import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.util.PhoneNumberUtils import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.scheduled_message_list_item.* import kotlinx.android.synthetic.main.scheduled_message_list_item.view.* import javax.inject.Inject class ScheduledMessageAdapter @Inject constructor( + private val context: Context, private val contactRepo: ContactRepository, private val dateFormatter: DateFormatter, private val phoneNumberUtils: PhoneNumberUtils @@ -52,7 +55,7 @@ class ScheduledMessageAdapter @Inject constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.scheduled_message_list_item, parent, false) - view.attachments.adapter = ScheduledMessageAttachmentAdapter() + view.attachments.adapter = ScheduledMessageAttachmentAdapter(context) view.attachments.setRecycledViewPool(imagesViewPool) return QkViewHolder(view).apply { @@ -65,21 +68,20 @@ class ScheduledMessageAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val message = getItem(position) ?: return - val view = holder.containerView // GroupAvatarView only accepts recipients, so map the phone numbers to recipients - view.avatars.recipients = message.recipients.map { address -> Recipient(address = address) } + holder.avatars.recipients = message.recipients.map { address -> Recipient(address = address) } - view.recipients.text = message.recipients.joinToString(",") { address -> + holder.recipients.text = message.recipients.joinToString(",") { address -> contactCache[address]?.name?.takeIf { it.isNotBlank() } ?: address } - view.date.text = dateFormatter.getScheduledTimestamp(message.date) - view.body.text = message.body + holder.date.text = dateFormatter.getScheduledTimestamp(message.date) + holder.body.text = message.body - val adapter = view.attachments.adapter as ScheduledMessageAttachmentAdapter + val adapter = holder.attachments.adapter as ScheduledMessageAttachmentAdapter adapter.data = message.attachments.map(Uri::parse) - view.attachments.isVisible = message.attachments.isNotEmpty() + holder.attachments.isVisible = message.attachments.isNotEmpty() } /** @@ -102,4 +104,4 @@ class ScheduledMessageAdapter @Inject constructor( } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt index 8b4e8838d..558c8af7a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt @@ -18,6 +18,7 @@ */ package com.moez.QKSMS.feature.scheduled +import android.content.Context import android.net.Uri import android.view.LayoutInflater import android.view.ViewGroup @@ -26,9 +27,12 @@ import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.util.GlideApp import kotlinx.android.synthetic.main.attachment_image_list_item.view.* +import kotlinx.android.synthetic.main.scheduled_message_image_list_item.* import javax.inject.Inject -class ScheduledMessageAttachmentAdapter @Inject constructor() : QkAdapter() { +class ScheduledMessageAttachmentAdapter @Inject constructor( + private val context: Context +) : QkAdapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.scheduled_message_image_list_item, parent, false) @@ -39,9 +43,8 @@ class ScheduledMessageAttachmentAdapter @Inject constructor() : QkAdapter() override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val attachment = getItem(position) - val view = holder.containerView - GlideApp.with(view).load(attachment).into(view.thumbnail) + GlideApp.with(context).load(attachment).into(holder.thumbnail) } -} \ 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 7489a34ad..cf99614e7 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 @@ -36,6 +36,7 @@ import com.moez.QKSMS.common.util.extensions.setVisible 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 @@ -70,7 +71,6 @@ class ThemeAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val palette = getItem(position) - val view = holder.containerView val screenWidth = Resources.getSystem().displayMetrics.widthPixels val minPadding = (16 * 6).dpToPx(context) @@ -81,12 +81,12 @@ class ThemeAdapter @Inject constructor( } val swatchPadding = (screenWidth - size * 5) / 12 - view.palette.removeAllViews() - view.palette.setPadding(swatchPadding, swatchPadding, swatchPadding, swatchPadding) + holder.palette.removeAllViews() + holder.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, view.palette, false).apply { + LayoutInflater.from(context).inflate(R.layout.theme_list_item, holder.palette, false).apply { // Send clicks to the selected subject setOnClickListener { colorSelected.onNext(color) } @@ -107,7 +107,7 @@ class ThemeAdapter @Inject constructor( } } } - .forEach { theme -> view.palette.addView(theme) } + .forEach { theme -> holder.palette.addView(theme) } } } \ No newline at end of file -- GitLab From 873dfd322a306a9b7e5fdf442a7867287fb08a41 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 00:31:38 -0500 Subject: [PATCH 061/213] Upgrade gradle, force jdk 8 --- build.gradle | 18 ++++++++++++++++-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index ba1820abc..802a92280 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { ext.exoplayer_version = "2.8.1" ext.glide_version = "4.8.0" ext.junit_version = '4.12' - ext.kotlin_version = '1.3.31' + ext.kotlin_version = '1.3.60' ext.lifecycle_version = '2.1.0' ext.material_version = '1.0.0' ext.mockito_version = '2.18.3' @@ -42,7 +42,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'io.fabric.tools:gradle:1.29.0' @@ -68,3 +68,17 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir } + +subprojects { + afterEvaluate { + if (project.hasProperty('kapt')) { + kapt { + // we expect this closure to run over a org.jetbrains.kotlin.gradle.plugin.KaptExtension + javacOptions { + option("-source", "8") + option("-target", "8") + } + } + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6914a81f7..4b9cec46b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jun 06 19:27:58 EDT 2019 +#Tue Dec 03 23:30:52 EST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip -- GitLab From 021952250888e1d8898616f29080e7271c922a65 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 00:32:44 -0500 Subject: [PATCH 062/213] Add config.yml --- .circleci/config.yml | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..a5ed0ee26 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,80 @@ +version: 2 + +jobs: + build: + working_directory: ~/code + docker: + - image: circleci/android:api-29 + steps: + - checkout + - restore_cache: + key: jars-{{ checksum "build.gradle" }}-{{ checksum "presentation/build.gradle" }}-{{ checksum "data/build.gradle" }}-{{ checksum "domain/build.gradle" }} + - run: + name: Download dependencies + command: ./gradlew androidDependencies + - save_cache: + paths: + - ~/.gradle + key: jars-{{ checksum "build.gradle" }}-{{ checksum "presentation/build.gradle" }}-{{ checksum "data/build.gradle" }}-{{ checksum "domain/build.gradle" }} + - run: + name: Gradle build + command: ./gradlew :presentation:assembleWithAnalyticsRelease :presentation:bundleWithAnalyticsRelease assembleAndroidTest -PtestCoverageEnabled='true' + - store_artifacts: + path: presentation/build/outputs + destination: builds + - persist_to_workspace: + root: presentation/build/outputs + paths: . + + test: + working_directory: ~/code + docker: + - image: circleci/android:api-29 + steps: + - checkout + - restore_cache: + key: jars-{{ checksum "build.gradle" }}-{{ checksum "presentation/build.gradle" }}-{{ checksum "data/build.gradle" }}-{{ checksum "domain/build.gradle" }} + - run: + name: Download dependencies + command: ./gradlew androidDependencies + - save_cache: + paths: + - ~/.gradle + key: jars-{{ checksum "build.gradle" }}-{{ checksum "presentation/build.gradle" }}-{{ checksum "data/build.gradle" }}-{{ checksum "domain/build.gradle" }} + - store_test_results: + path: presentation/build/test-results + + publish-github-release: + docker: + - image: cibuilds/github:0.10 + steps: + - attach_workspace: + at: presentation/build/outputs + - run: + name: "Publish Release on GitHub" + command: | + VERSION=$(my-binary --version) + ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION} presentation/build/outputs/ + +workflows: + version: 2 + main: + jobs: + - build: + filters: + tags: + only: /^v.*/ + - test: + requires: + - build + filters: + tags: + only: /^v.*/ + - deploy: + requires: + - test + filters: + branches: + ignore: /.*/ + tags: + only: /^v.*/ -- GitLab From a5a03f67598fbbca72c2bfa4d33c9a7e12833ebc Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 01:26:40 -0500 Subject: [PATCH 063/213] Add secret files --- .circleci/config.yml | 5 +++++ secrets.tar.enc | Bin 8208 -> 11296 bytes 2 files changed, 5 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index a5ed0ee26..1d6130b67 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,11 @@ jobs: paths: - ~/.gradle key: jars-{{ checksum "build.gradle" }}-{{ checksum "presentation/build.gradle" }}-{{ checksum "data/build.gradle" }}-{{ checksum "domain/build.gradle" }} + - run: + name: Decrypt and unzip secrets + command: | + openssl aes-256-cbc -d -in secrets.tar.enc -k ${SECRETS_KEY} -iv ${SECRETS_IV} >> secrets.tar + tar xvf secrets.tar - run: name: Gradle build command: ./gradlew :presentation:assembleWithAnalyticsRelease :presentation:bundleWithAnalyticsRelease assembleAndroidTest -PtestCoverageEnabled='true' diff --git a/secrets.tar.enc b/secrets.tar.enc index 35ec7e2a41fd55d371889a7165714576c7fb4506..a96512c7e039c7a011ba5a1823cedb9a235a7a9a 100644 GIT binary patch literal 11296 zcmWGe%qdAtiH|@1_XguWu4Apek=y4vJ}XgJR>tHi+j7p3k#? z5}&wd^iqYtmWzC6emeE{p>v+1g;qIVho?z)1%v#@JEi(Ngm-BvmsS=$f8=~!>H99N zTf7ece^r&8VB^f2+O$`_@6e*j=8i&L(jVt-3Oo|uQK43L^h(Rg)^)d{i+bD(!pf$v zDvB+iDjaj^KU+t2^V6f!=NnYMHcd3%6aRk32^VPx?~0`vcSVbNvli!CEw-9lx8|W8 z55vK$0j{>IjMDd{#Ld0b-t?0x?%r|R<0m#19e#55<>?D*+pMEr2B^IV-kVtZ>sy$! zz^`?}na?x4|K9WXBYpmB$;4H`HzlszjCr_NNZdW>&dMa;#ciRU2e&ZmX6?CgV}jG3 zo|xm#?#Fw5*4$FOvG2^U+R&u(G>vz`*Y8(7&;Ppk*Llz1hlS=Jd$j9LtcMJ4fz8Au`Hh5#*`a}GRX)$m3Cw;l$#J~KV*lAm@ z4b77~?+5&DKXd=A*Fo`9;%oI+zwg@4+H5cIb*A^zWfuYjO0OJv6n=j4I_t?l0_T0I zDDQgvEC0b>og;ypE7`2KSZ+G$aLn(ll2qWmiq5@KR$QsG*bkPvHAR`^t=^HzTVi?P z&XwzTqLY;pBW%(I(r^5}eEsLNBd;~@*qTg_j8)5D@@&)DG=Dy?D>DC6JJod3-s}=v zdP5>eU$IPQx6DF452mCOj7*J|N>9R6?%AzZR=B%!-ljz}o=#7U3cOnQbn3@#b3J7@ zL>KcNeJ3yZz9Qh>*3aJma+pOVwKqIgV=J1a-!-MJRx!=3)ZL~3o`&p<#~&{@);B%9 z-?G!WetG-!-uYRw-Bl^tYW_cpmpx^C`D~ivwcZ0&Kdq;^q&~cFt!4DU^#9Kk|MxAy z_A7S^wkuThPHbsy+E(7`p+4{4@kz(~`Qsk*`5WKQ^#9-Da4w@wL}s@1tG|VIeM=A9 zu54L+`j+IC1s%d)U0*Ls&Chn7x-wcXAoRdpj-0ufZE2jhk9!;cWhj$gb>#KcGu)|# zlFe+TrT4ZLpXP5?K7MJPt>~Ng-`&}7>PMZ3@#B2Kct5=V3){S7d;9+gC?r3v<6RqH zD^vE6^GEVjg}rkh{Q1d!ooVvU3D5Y?*34lqk4;*%m@9ln*uFJ?B8oOi8?Z6d{)l>a z>1yKaHocFl|2}&0HBbImvhEU{z7wzFbVHW@Rsq164~|7{Bu>rSdabG zeDzC6uJuJhoSI5=e!tIZ#luzSzxR1rl`3C9-|gZjsBowIn@{NvA7j(X_J3!h=F7c* z%<}*GH%C*a3v-XT zjXQUey7|3IscAoj-4&jyA9}OqiPP^T><5;8i9cGl|BC1Vxj(PhDmF}sHoSRTW$r@n zi@W(3OD#wK;kW&%bJF_v!+e#W z>4GHS|KIi-(!4vq@~^U<(Js$$p)o&zQ>aLjVbvCiKUJreiu}3wap$(< zU!5`{7A@=yc=LHjUo6{XKdw*PPo9*X(YdF_?>@)NAFXROKfb$s@?{r~t?NyXb5{-C z<(}PHBHowge=2dmK*%es>?Ur@s3R&4Uu7csX6ZP;Z7jc(zgOVw-w!%zN*jaSp3RHpnze29 zMzyUXN4T!>tXTOqfFXvtEv%#LlW_TrfBAEn#HQXa+Gen&ylHR1#T(I*!gG%mw+eY% z{4DF#nE6RTmPsWx;!4vM=@(9+D;VO}IUZPN)Mi+nk~pjG)$F)14bEfcH?H0O^5uln z#7XZTF13Vqcj4`IZ$6qhCDTB3;SEP?)u(!j%8xq=%;-^!53g$ArXq zPM>$rX7_#b=II%oCs!-jA3MC5lZ`Ebz5ZnS&VQk|-NU^;GyRFbWxpkif9FnLF+-h$ zKPO&gR#`h)VZ+7Htf`VaQ*FWy*lanX>QHp|^m@msxkd4D?l=CpaV`Xxx9zva4eCoaF7og%hUFm5=Ay-S&h1nqJBG z$ar=cu2jw=`rFS=Jo@PWhkswa>K(#gFPo~cO_X(`$?vpBI=NXZH*%$XlTiPbdQ;$f z;LUk@?&WWuYT4&GSkJ7|xoggUF8S3-?{l^66|U;{dnUQ`v^`XMeKsaMw_PhC>Sa?> z`sdHBM+CpIu71AgZnt0kg2QIJ!^FSXX^FT3!57M-dyV}7_iV|`L{=kvNN+3?2>2Q4Bh!-{-md6 zz5Oxq3f;%`mJA`5>S!NG@1FU*ZQox#;3S!S8K^tu8AqH zC<}eF#cO&>%68EPuYhISWiCWr+%O~5U*y;J>gqYGAKWbT-ljAEP_okXGfaQd4(^VP zk$$asH(WGCyl-Z!p6tR|T5DTcWUfDD{}8>$K<(5TE033Mv)A0&H|38{*@R>BUY|L? zJ2yY;hDx55ufKcu1J{4?ky5SCdMkW|%H0kg_~n~$LH^ef`T6S;^ec82F&N4#PWslx zs&(n)1l0{;X_LZ_24);Ou;#U4tM-mxHv+G3xxCPQ>WMQukI#;(K9#+qGM-DkZ}v5Y z1zkTpWl!N;>))HMOuMb{?Q($YJ*7CA zxhY3g8`pO#O>;2&Ki~QV!?LVPJ||h19SqE>xO??Ua&R5Xx?QY4XZnPjR%hQ?8goi^ zq3fLw6Q%eMiFHKfDXqNuw7TnHYxOqSy^+&4&)8hIba!{gB<73h56@H)Or}zK8 z%HJbi$t4#0N=|Y5&F~*t89P|}q`8!{76d0OTV&xsOCa&m<#UQoS1b~@JPV)keBZ3> zXXWjygQSkz-%_2yciptx^Wmmyi>nyGv^!>d^_uJ5-$V0bXvu| ze9dU1(AU#t5B=%ssD1walV8?!_Fuba_g{+APhYE0Q4zjv;`=%tFOhj2qD`7L)2xAq0fq)9u~guH#KFS9GHTe+5D zrTJ`+ZH6@g?H+N*+BZw6?%K@oVHuyH^Q`&LW^B_DZARaMs-XM?W4M|N6e;bMT7m)6I1!9ToZFuX$AE5`)5~ zf6dkr*}+qp7RnkgiQA*KXtlhaa@jfmX$eB-g_{2foqn1BU{b{LWoa|a@{e-pPndo4 zh}yo;4BvMi?^mkWeP~IX)BN(>*$uM)@>Gu=SJ&AYnJ+o*$lI+-%iiD2xwEKIG@GIN zxv4nk(>b0$TMoX8+kScy|o^~z?I4?pG|v|D)dw_NQG$J0Uj zU1cw?$W%t(`T5#YLaoYNZkJ@qL)KrCuTD(}Z%KUceAnH#llt~gN>W=L9%**^ax=4t zh`2`PsioP6)n;7|dAjp=g$&o~f9peIG!nc6h zyX%wBc9;4WRX zg|qDwPfuaIbJBQ0*RRqQh6|5yR80R>?JH%xRb`>Ur`&h50v!$A@*=8}*D)(px9O^L z*I3BBFk3I`T3FiBHPeL4*J|_evu|rA@)x~(@6BRZ!oM-@ zaERsk>9ZGpOlH}=uGKcyLdJEk!x_JS!W-Rgi9b2aSWu>D>-|+aGEuVBfrlH7BdQ z>)fH23WiH;R2E3T{w2(K***>2HWEF%9t)wK&>_%&lO zyPfgd)vLdJN)dfqV<9E#d{WS1X~~W^H>MoE@@f5_{PQMdM+@HlcrLvujdR%>^T2<8 zd2e?!>v3P=DSYB|Yaw5BW5+GV2{y7@QYEw$PTna$&Hw1$`!K!YWTvyC$+M!aCZC*J zS=;)NS9-rl&_z3m1jM@vi6%0doHVT?DY?C*M*Dc%B(e9|Af87sAti+*O%tm zH!alN8{YIn{&&69rLPrh)yyjjZ4tMlQpZM%Q&|Deo& z(dNV7-WjqYpLJ*NW373(Xa1hYCK8jL?7381dW!uK|Jg=1&tE)^n^oQ@>KvZ5hV|&~ zFMj8GbUZg)`qlGb`?3?ZFJ8s$7kWS0Jj+t7{P`ij-yA!tnl;M=jRgE>uVI`w|FR6r z)jgq>S6#$DJ$^j(!JQ46Uju)fy>vZ2jyrG7-kjv^2P3*%St=y`G<$r?A_R)wblWqg zPSDyC$-3E5YSH8~X+3YAy?TG4e`D-vVuu&1yf8bW!YG`Y zY$|m#`~IEVS$~%7XHgB8Qd&Ngmn+MmS8E0<(^#>TV#S@FyL{I6bDL=~Ru<>u!-8CiPW zb^pO?^SAp_Z*|>Uvw~HqC2fU=flo$8P0+BU{lU35^VHfR4!7_5CRel9Nfe&>D?g+B#oc4k%&QOEx4m1V z;-03mgJb`Z$p6mW{K-5H`;rc@IOlw=wXvIQA?p3MdSdF_UXG8mZ|FT!Ugxg&_4Ld+ zCm*denWFh^&3fyY+l3}`Hs2SSUvln(YRZ$Bmh2*|*J@wBp&iIE${hXfd|gt()<+gO6yu}H*-!V!C#av;`r%pGp8Q3N~UnY9nV2Av@wku)t?s^y9EJ*fWXvB8- z+82hmw_CP&p1D*~$A04396^hXtUB8g1hu3tuA`O2D3HGuIdKaR*IHy z2=1RGq4e(NLq*zyKsCYT$ZUtL)`5Hkr&-QyBb2m?X;IgmHMs1eBjzv4&*mj;%itWsG z_^z>r(f+~pzf5u3LhMXFQH=TCuQF%wF>m|4&UDT1Fx_1&r(Zq1w$?J~j@UB0t5Yhy z#m%^%WSm#ap1k_t9Y2@L6I*iM#4O#xxa>g?d+_@;Z=<4q&-$@ZDSy&|DhA12qDvl5 z{#3ATfk5bvt1LZjN?(L}O88Ip4|rA2v*y z*OZ&W=P!JllN1wv{eAa>vsDvzsxG=?D#%yhX8ki=!9!ZQyx>a0FMZ~`eREp$Tw_-{ zXReajo_5^o&w@GCpA*$HPAy>gt-OzuO|kyy6tn8pJ+EpHKYbm(`braz{<@u!n)emz zl)~4mxFn^q&3_iK^_bW+!;1}>`yYz#-Ti-h_&TlBhkKtKOAtT(V$%BTBc~G&OGYt8 zztzbw`#pupGu2A>>P)wHde46taH}sItA@>|xLu4TGEzeaHcx}8Y+VZv?__0;`-+Ksp$ z)v@MzTitb%uHWIQ*}R-nL{Qx&d81#@tu>EAoaVi4VCaz6j?4&o+Sgcpdrw_S;+=;R zKYy7qgEMHqMq0^fUB|0jvQ{VFs;2B&d!B>sT!H)M>Xc{yFLIWC*l2dHXdPzuDIw(hxjNz(qdb- z^lti#qd&49Tu@y9E%;!RfzgJQ=YAL%3P+r8UpBFP%dxwRHP^qjHEdCy71n(7joudC zVn@v{5jU+K?%JaH=H(4<-Pwum4!8f>vNng$e?RM44u^QzkI>dn4l+k1(u=e&HMj15 z|6X`f!mWpY#5^YKoz%!QWl6E4hz@_1S@y@MNnA^t*IiS}duCI(f1je{wfKV{eYG-f zx0RP&%TnkUey(gh@v@%&3ugA7-P^ZZ3|4q3$#+bA&E@OTdTO;Vzv{W4SQ1xO|1|9% zi^W#k+L}+tG;ilk-ng&et9TDrS@guDPix*BYUbDWOZB+6rs>nC`PJL%cC{`vv3~cX zZ~G)s>Dkg7Sg)^sn`dBlaAN+!^?uo&go-~J@ayQly|*2SlIBnyK&y35;3pE-VW<;ir$@7pI%&-L?9zI?U6 z?EdLyxz}3XC;pZ0lelqu!ruR)WwFbpxBoQCQdQDEGYs4Q-bGDUwYGiujuDpwHdz;7d2gvNoD+Zuqm&>7PiCOWQpPcI%^P+Hrn zTCmF@HFKux?)i&Y_g%W;Q8iP2O|!`HWekhG(zaYW>cbzbQ~qd@G((Aw{kmH_ieIg~ zKIh=S>YJKv6CXcr_buL8`25{urH_eq%e61~YHB{VC7%WQWQFACt|)GA zJJYGy=vMl6`>uG270=CD555g_Gfj#Lv^ZKClyYs_yCqWY-vo|L-s_Ur%HNl`!)TuF zwbE5DPBw9$ezfoSHK*AUQtOU89&kPDc4yBLt%{T{c85x~KYMY9cSp+(ot=MO3%_Z) z?}$HtaM878`eKut=}hg%k|E>$}17qXhuUgE9nvBg}j92x89xy4&* zytKP?zF~&s{uNK7mA6@~UXZi;*dDR3T_!RPKTIMwy%)H3h%^kv8LHWi)m_%!9z zw%bQydVOqj#Sf>qZ4)&TdM>1q*DUV7?0cB-+dbP|*B_ne!~g$8Q%Uso`*EE)Kj$cQ za2NR;8`e6^*z z`MJR-R7{$F`07dUnwqEVJv=**LDyX2T9&6UQ+s|}Qu6#a(+j=dmA$R6N;&u8fSl`| z)qFiyIF`-l>3YBF@_Sp>`b1XIYh`S^{z?X4-_*QRH{dhlCdOmuS1uF3-*xGu_Amaa zb24JZ=kAiLQn#A&!+QEYzIwkpoin!@%I_SvJS}ggncH$Z>WuTQTx~5^t+t-@CKdB7Nbz+QM_}23qE$EM4{WtN?r`GN}88%1dRx z|6<%PyXWSIZ;QNIPtV&QA89nHN%yNliRZh|XFUDw^G)acyrbTgIsbe+V?gKXr7yDY z>ZvfTw(VACW&e{GC$y~KQeD<0$MP$CH*dappPAcg-K)uo((8LU=bPH*J0%Hxv`P%S zaaZSg%~UnPz$;!l&KmQtJZNZ(|F-8)!uI6#Z{j>RoSi2VkY6Bg8-3Y*1$*kBCaV@$FLtiFDd%|op~#6hvy|sq6hsDAX!7P8 z=`Wdnz@hdapPcx1S6_p}v$wtMy}8k0Le1>mdnf<8meIX5cSowf$Hh;(i(~dXFKg@Z z`I9!e=2q|yjrDhIlX&d6nkXOI#^ac6{9Hx8R+a+~k@sN*fO!71PU(YS%up zs_H~^!@cdB^A6qfJ8-1&z~hCnlke9>PO_BuzmQ@j()C2h=?0SsXXqR!X(=axEju>u zNZGw8I(1L;*Z<}PB?~`A)RmUM|Ij|IBHa9N!c&tEE3U7&bYtNMv-4lSCe@!!$_r|} za$hWD()~Sm7hd=N|7VT!Eykx8?xb@VmWfTc8goYPlAKh#{pOCe-`qJ}_mm&&@XQLl zd~HYmbL$rh^N$PnJEunYan&!2d~%^J*f`<*?u^ul4_0c{zFF$@&cVYiTcup*+@|ln zt*6>W4@ON*-p3uK<|twj{8%yM#u`n=D+$N+UUhS*B~3YK_b{tdA-c(X#*v#o?_Yg? zJblhmCeCR-N@;4Be3ymZTF1Oblh10Q3IHi*<)=Ud;+#iXc}weWw{8!^iXdTV~LhJR&f@e?um_5IEb z%g5Izvf5=la8?UB#$kE+?~+NJd4Iyr?kQIMtaGhj{Z#sg8;<7=E-~01{!w7&g$V%( z;Z6&V70mkPYQlKk;&O`Gj@*yeQlE2K?ceol%L69mV<*ouzG;{{S24%nu-C=dqzcx; z4$V9*W{y))(;5XYyW6MoTjrcz7Ib1|x66Vh5mSFid+01lF6f_GXMFEaVEjwg@C`jZ zl1lT}`OADN?wfr1fZ4VbQ?uzJyu7VBYr``)H_LC0*tEoP>cbaY^GddHyDa3XI^g`E zD&_cv|7QPL_X&w`2ugF$=H4##^}wXhEpr6kC47nddEj|KQLq1u0#2K#$NmNuciXO= z3{CYaS4r8HmtG=wPUbb|8) zYEp4S?%=*-Pq)voo3v!F#ot#4O17DusM;Wtf5N}LTHEKyo}$*PuVj+6en|Xy{V|$> z&n-AyI4I4g`)%{LHAeN~wFX*?9jDrByOdQJ21-5b={&UJiX;n%QB(EWSpr*ozn1^$ zTe0u+9K}}Qo_=4&W3I2_j{n|K^DDD;_Uyi#PgiqSdH3FNe>8Qot>5!E+j8r_`OkM| zN&d;Zmrv43 z|4Xa(ukSbXKh?)=&lF~yo21HQ`#7ud{@+QgORrw`yZ7U(Z%y6$L-r}xM4u`spO2q1 z-Dl$DHusp-Z@WEwlv{FqyrU1;F}_(L-M9D9=~UAPsm@al&YEU;K5E9Nf3>Pj`;3EF z4*!Z{^O*G}+JE=6BWGJ*)z{~Ve^hN!xV?X}h{=aZcaFtQ+OGWgoQ0^OH?OenE+ba6 zH}Bk4IlSj{N6K7u<=U$H>HOa|6Be_Zf2S%+=IxkrRq2h%e zs&~_|p9}A`wcB;AXei+Co#gJV(z-Q8H81w5K}c)Xw>uo6v(}YO`OB5j)D~bht8>m0 z)ufj*EZ6hhT0F({irelk8RLwTKbSus%s%*g@zLfKxAydI=6uV8>(9jX`kkB>A8%3j z=U@Ef_g4Cbb5mlKbg%FEyt-$~TszKu8<$I8+e1YPOl$W%QDicYI<{Tl*^f^XY?xQX z2OeV0dnCT6;7Ci>u_I0f<(`oZdt zsp{*#bdwIP>$%5Y6-U00SnhM`C}V$gQWW297Vl7&s<5s4o>SNVUhgTGq@5Vw+{imO zQ*`>H2}YM=3fnIDojiEz=z*4FU(P7^_IWV=+O=t;tfOFodDU+5sv$Z|2_rbpQ0Br4_#z<{KS(n{<0Z9+aGyys8v^beebN7)aO0`HZTIICJl*wv)5+dh{^eYER=t^cYxb58kCuu2IybMK zC0yWdOU#8PTX+78a*sH-O>2A}@$vMT*A|6Gcs{zV?wXY*Fv*8Qf>mji=Y)0MCK^d^ z=4|@Z?wxXn(P61cch}~s;#K>?rtQ?7obzp;iN$`$sCeNw92M)NRrDV1dU)slT34p3 zx)8R;Sy?_E{N|op=PzuMt-8UK_j=luii5M3+HZ2&9dEFAe}ekr5^+bFhp$f0({P`2 z=g&oHW5x$>!uXk!m z2(!CdANHL1&D7yc_51%;?)ogVa?iNUmsZ_=zwuC@+1Gl%*QwH z{zFkZCBI`O|9n~^uD!(dh@)(x%}Hs-HJ&%GACNe|^UuEnrz+P>ms-2yTm;{Y3!aiQ z`+$SvRZYNgn zV_#a%mA{zf{N?09>;Ae&EAK4VfB10Hs>l9m^H|^d73E%i5qm(R*?WmptzaeY;}u zmCMH2yX?bXEmEr7YiPi|VarO4ZExFrdI$r!z`zxEg!asTb+T*$QQtMW%tN72fSMrl|-hTT7+v{0`6$Ss? zzqDG?Zok;Rzk!wZ%oVG_zVGeX{XAEdw8xTW{GCJWg?cd+R0n{rax=B5v2t z&Ye2XjBS2AZ(8Z)CxyX!98YVnaK!xbd}FcL>hy<9{)D-I!>&wOayFvH`T3S}%<&Z_ zo7VFOEPj8wuQY9^v!Z3vy zr@(*P@w`p(s^U`~JX!v|{#;aMX~?Q;lV@C?-?^sQwzMqm@!s0(e-qVmXLi+In|R~Z z{Kd}qS`9a4Y3w$y*FCm=^@5l2ufNY=S+F5~=B7OHwr~49i%kMeJ~IXgTB&~(u-|dQ zG4Jqp0Y^?om7OWil^YIOe{D>EEW_Hy$d?e!da39OtKkez^WFcOr%RsApEf=5)s$Jj a^HVNYDhZWbf3Qz)gJp!Li^IJ-{Z|0`>?nBv literal 8208 zcmaDX=n~s(RyMEaF4t;(_GRg(zH)h3`J7ez%&U^$b?opeFJ3F&))(6YwYCIvT7NNP zR{q?S+q81>&gNAPk^+|}?>c_NH`qhBV2QQtwTH67Kl11NO5FTcEG+P%u(0k<_td$C z(nfzboR}dLYn0?|6|0(jK6%TDS?lk$EB0Kry?npHYtr4GgYELKkDcFn^4HN|x#N}c zYc>Z?HjQ`NYop_L>&>|nWkRtNSF?P$7t$*7&T3AR`|%5`>*YR#IGlaW_?dTqT%W|W zfKOYsgPd;`PI0RVy0|ofGgkJ$sN}I}6Ax8Qo@npf+3|fxX83I#)otD8->=uzT#VRz zbES3Q0a@|-%gk{)ZyLT`yd3jM*dp3)gH4izcZDV6exJ}xi3?5a%Qafmg>={QY3GZ@ z&$v*pe#TnPJ%Yt*ho#bj4;!9NW0Q=yH{Y?DZO6f`HS5m>oLn+-;f#Yio#FfP{um2x zE>{xbDtWw(@7C}Bz^+s=e~J=lBhWsZqz)N zw?n@q*3|Kyht|)fvAUhKZbbNv0@t)j0l zHB8&Cv{hSYnT3z(KK;KoXG~+23%Gu~V4hj*(v_& zs#F6{=r*<`QtLKmW;iC~^6q+^y7Xhk@=MoP-gi7H8$o-Ii80&e*XzpSc z=G@AS|MZqV^G(_t$?7tld9&f8bxT5@dh(w->&k6Z|Kzp%ZBzUE%KT3SuY8)E=P^b8 z%=AYrXaDD4QJuqJ&-dWfqHXyaZ#TT3uGX?8yrv?t-S)`qEz+D%X6dYKc6*TOdoTVn z+l~$ghn|1eBHrG6Cwj`WwaIJa#A~q}p6B;PURr3s`iko!{X`i_0C$PSh*@o!!gCpf@`1YrO$`2MqPgLVEg$B zrx|A~*Zoy|$yXcIvXSS;>hvPJxQEAIm3}?!+0knrFHmt%eR9^>M7PTmX74vUQ1bF{M^<*3}Arw|DxpCRXs!^UY_Mu9cs;@!v70MrGDVObkDFG4{Xu zyWPX2PIt-6bZZ-VHUG$SHnEi&y#Dd~CT=Rw`M)NW&-25bBY!oEPaA)+T>e4i*H_km z26Z!^`dn*Vup{3l^TLq_Pp>uke7jpPVPSYf!Mc-E=B2%yxmwYG3#2N9YzU&Wu;m`BFnMID@p(#4%`aGq*m%0j` zY&yGT;eV|^-`Dh11+JH{X_4QSXt?gid7tpDf1ETc0zzLzf9J|MYP~?WdgG_By}yI` zX6-+{`61Kyc=_~y_qjq==5ul#EL2Z9)~}JV(lBqj;CcaH$2RjnR;g<1?@Q#TtUqFQ zWaHl{2N>OhcKEUvzw#83_@TZ;l6yK!{%*ck*|G8#E4+^WQtW-bce!kY)E6Vhw=8d> z4NH!vtjzuuP&DVk7Qp}m$(bg0o&POtR)7C_Gy1I7Cm&rqomG4+H@v1UXszYCwQP&W zPVTQMrA4Qgv45C8>3+%v&RIP&61$|8ybIT^T_-*v?yTN12FK9pI}Wc~ec9*S9ut1K z%LkG)_eZY~nQq$LH>>V&_x+Aub*qFHt81aW*1kW*5NQaPzl~`Sw44UiN>@6zZ#wFL=wWraO6> z@JEBtyD`7*MfX}Ic;A}IpS!^4XR9NNiJ^#6$9AnX^7kf}E)^`|eid>t(8N2N`^2e~ zgOi)e%#!vu37!4ca%!dcHa4e}w~v+{tJ9ZWb$9b((KO{}O2^)_D_3}$ZlCPr`?xrH zDx3NFGh1ip^-q3$Xv*}v7EXc8N}Cvp-4D-My)1%>+bM0a(XqtJ)n~)rT?}wsqS|~f zzWL^t_#?J$e|$O*yel~0bnAMxvUK#j_$Yp!{DjaS2`TN5pYMylyj=Eo>SYhfHov2n z7O!e8Y=~?5CS9w-EBkoj^PJk;(2O&WkCZGK@8H(i9m`cw_lu3?_=S@#JIW4rxtzR~6S3^#$z3z=tuJ5s zD&3HM-=D`n_Zk(xdg;G0?mDBYc>mgktC<*$%&#xG_J2~H!^yV!8<$L|U}s{P9By6y zSYoC_Qf=0ICcT))ywRuHFK?gUnm*xCvSqiA2J1riU&TB63&S^e&%9`VZ+&aFO=^1e z@8`*m((5MPeZj79Mx;&S_%uOJGzix|aAN zMa^K-&;6M;yK;-yZobIIve8Ij>WoB_rnqR!A`S7tFP9g*NHJ&qXWPTEs*i_B^t5Nq ziH@}=cPDe_Khijq8P!x-CN23-C-da$4<)PG@+y6jQzCR5=Nw(su>Ma*+bqsUvtPAq z=x%6!`F4%}g*%%&gBPrQBYMQ|n6p&A?w;ZBz4YbOg1ZKmkq>Kwv;U?~37TCi!#l-7P|Mlg z^B05Lrp-IoF3IW)pZMncQf_iy-Z`2K$Pe08bE zS6RAVEff6`lX#`p*X{F@PKMMOTYjYacU(Wm_S|yqLInk%Nj&mU#ocJMi;^|N;@zPyYt=X`w0@#9~H!{KSgH}axxOIw)-`R7fL%~lZ`uXv92YKGuM3lrgR&|7mGlhEi zN={S!c1`<Jtt&n+I#3bw0j>rR-U0t7XTzUZ>yl4?b)% z`)j{kap9uGrX<0ZrlU>2&cBs=8shS>F`bQdrPEs7NdEh(h4U92@eXQ>)t#_J;@&!o zTbcdux9zS!am0r6-b?9OwE}Bboih9<$LGNW4$?Zf$;2TS0Of!o!*~bx8Hm3_bI*Xe7gJQ zx$kSg8>KFEI#3|D&iUOG0lsRV8#X1AzrJ!hoE@8TV$a;QBD`0PbETPub?+%WUvoXX z;p-Qn2J?FD^hWXhWs-BA+}A(E{P2gy%cfi3Z$;$=vg(`FFXHgtyy5tlNzeNB-nFb% z=JS_dC-6$E-brVw?4{QWrX8H$Q*CxO##k-M;zQ->88b@ys*0K?O63b)HZ3@D|Ld0b zvC|ILxU1XBHP!VP{$D@$_*tp%dm0K?yi01msam!xvV|ExHGm#4pST^I@S=kKji_zz`_qa_>m}$SFrlv&UV19Imlls-Hn@5f; zc*bdK9QJ>DcC+)uFPct?H>Hnf9=W~1#A8+4$u(lVXZxSV&dv9_`fX8F0`>bclM-&ed5d8T35k$$tXb+OMZHi>f+7J990e!Kl5i|?bbx>AA@9@WXqaEK~eutMusP_h+-e#&T$suXz@<{JrtQ8YjUg z_3Q_eL+_Z#9@~1;g z*2yXbBfo=ok9RR}o!hk}_grX()Qg(-TFdrddnJTz9o7h4dt0C8@j$u8(yP>R_iEnS z$FmE++)$5Wezk}3+O56D{HrzAaIKv6>hhKAVq#_cuG|z@^~>%=b9=JZ?a5DnF5Ph_ zJGVMvYsDS*_|@xGrFZJe$KGG;mb3G7eTCcPgLnQ$m(^c%kCFZ_H$Op4ZgM;8_K3oX zWe%Z#inSD0PP?GLvMDTU2EW9rqZxmUTV@@9`fy8Rz~xUl{Z_w@NUpIcKm7B^mq{yx z?gZRhw_fLsQX7l2%gUeP8uEf+AAhC2TEAL6f0nj6!~PGBPo-DzugnQ*3S7?d&}_xj z*$u0e;kA+eEAv;iEEf699eTJsyZ=FeEn%>0*c(t$vkv}W~eb(1-~$L=INN@3P)DcKQxw0_Dv zo2_$i)$s<-ubI#@SsBTB`~ux+;`Wkj{2!K>?=00&pA7LPR-JmkDtF) zWz^aHKYn@Vy~i~UyqotY7{6S~ez3Oa>;8>-B|9VS9NC|(*PRxyrm0o__=M@*r_Lu= zM{j)jcwv$#|HcD4Wt;e1pLxwxS#!SXe2eL=z__Ze&@erblhR)V`r)BU9e&L+|?TH@i z)jM1KC!OrqHqe@OUHta-N=>zCJ7-!}=-iF-%l3H*UR^po&uYnmb0=a? z+FhFYx!wM(eR*wfn#oDmofgd&Jq^pI%B%`q+g2{aB>7>(vtE7HOZ$JfxDzg?~yNu}&CZB53=5;=&o_SW*`slf)|6aijrwTRxzC5kt8h!2Ou6e#o`X?D&_k8xa zSU6y@x8seK{#P#U%2v-TC@!{?oV4fBjY~BgJxa<7A1(;YO|!qj9(PasOi!2G&FAH7 z_}Y}OG5jtz3;TIl>+eOEDF>NT)28T2@qP>Uouauq-G_H!p_lmSXUp802T%Dw{U zQ)>-gh%UNn|MZgbkBwIwFUn{xc@`mR^~~;^bS=NcDJ|a#%u-uc8eEn*cQw3O{^zEQ z>@R;UEuXVW-hcSN((c`ARgc}9F7=66-7&hi(AMg@bh>k>+NW24=Ef{MP-yhl@y)OI=Tjbk`<**+r*m4H z^cS&4hAm3Lt4?lq?0EZ=*YoSSQysRyR&#ISd7Qc|{&L*Kupjc%U9GLdPux2^ZI$x| zp^kUbzpBeQVppqQIdvu2c2+f`_a?~;8LQ)irvCrysw8$^U!-jEJr;}pV7Vo}4Vl~J z*58iLbS~^nz2mm#+}sHgg}weU{?U@{+S6~B8dmm+y-c_?uhsMX_K5;d+~-A3O?Kig zGY~tke$#T%naNrgpPV{o+$(#(fZ^+r1?`Ksut~&Ki9ODGpYd?dGnvPN3D^Gpd}}|~ zbfcM+|2ywQrOrnzAx{O)ADb$o9{PXcI<6L(sVDYNJaziTKDGN#4Q_vtahJXH?|Yr? zTEqGCjd!r5O)>myu`ccT3jfGQ>|!45cb0TqtKO6mb6sz-?6=;h>iyd}d8KCiz7GEP zetKokBgqMyW8JeQb*5jKbIr|S@sH3C2gErWr?W^NifUz;GCyJN*Y2KwS92zBGA^xK zS0VFl&zlJf){+ZaKHOgKnBd0TeU0hU+qYI#_P-A}gzVM3&VNk2-#gbed9rYrrpME- z8f&~O{AVnds)=Vi_V}w{QZ`rf!3QlD=DVLUPu;z0uW!Lk$Gq6=l~Lu53!52=B-$U% z+_6WDcbR?@%f`j87(bp!GL>j+*!@n$rS6O1`o}6CJ_X;b(~`Ko@4)6*u>i^Y8J)*g zF#F8=+ZV%8xxPa8O9i_Tm!11V;WJ9{7n<*iJ(DRpwqQ=X}pcFDmYbG8326i;J=={x1&QoY*Qat+DyOiQ3 zS$VF3W5d?QN%t)mZS+*WVX$u76i%tYZFjFm2-d8o}o=t{th{`$Da2)uBkSZsE2?U5_{X-Ewwy(}mgZ zdde0tv!tG5UU4>Y%2m~aBGMlh%zAj`&oSRRi-r14g4YV?-wwTY@`#K$ckf4L)62KT zZqI5H?lqdWK%4)1zJiOs>2c|G5oe}8i222`)*@MAp@{6Q4YtcYc24^o{L((2`~LQa z-xen5Zn7-;*&X-sL*4=N&5KOcL)MvH)~@h7xpCG7)ny!y%)^=%Xf8f9MfIzV&@@{) zZm+~CD-ZE-H%w!Yu9~#e@P=)^>M_T2E@uXyT0)YiZ+6Kq5u#Yta3ts0rm>HoYy z`NyRjrmrgZ$F6FYWff}tQE%|>>;84UcPhlHFCVa*sHmlKd6kon+U4!D0FCxn#@aIUs5@gT>QZC-LEX0 zfa6=Ip4z{PB_gP;#3sMJrO@QoLbIg~x1EeC*L;?p6)GUqmfp)*YMXoh&MN&K!K}%j zFZcz?K3*R*rOEff!JYY(u#H4$;$YJ+VLMqUf`GjyaoPr8fVDo+yKqPcC!s zo$583_{m>W^@3mML)~lc`vpXDD{h8fo@fzt79&dwu@;eT_#z%(}Xr zNd~z#57;@%Jp3*t%``FiD#4yL{nd$2e|4-rt828bIR2>qe`?S#539$Sa_?>@Gjh0T z+~;KZUv^5C>By}gj9o2v^E}2pJ?Hlx2 z+uhfv^t5~9GQ5S8f zzRI+8_bpAS+kNc9tlgWKxr+1?c*=HoMn_1k&bhHZGu=vI+N0-^qH?n@e`hX!czkYW ze#Bkd`uTR}z3v{eI{PoBdj71qSq#Tkxf;#z-L#fpM&niN-pC!V;`yfRP#`b@G+Q|D?VEt4#=7Y*Pyj-p6CSPI>#Izlin(QRBEcnBmf+5BS{ zMV-HyQ9e(Z`^djffnQ|Hg|2na*yk${o%Lj9p$(8uM8-gbg{?x(yfay>Nw|P;%C#!>4tGhb5pM_Re88{h6P{8Qi1Om z@}^JeX*=|*PV04I#I+rIpYBK2KR^HC&66^9>6dGbi~nRwwSE2JDOmj1;K{?@tyf#u zS?MjvnD^uf+x~Xbm3LY4?H9kAyXumj`*DxSf0CG@-Gsg#>;JNNcHXkM2(`|O)_iTw ztKVs5nAAp`5sG3Dz2Cjs`tc)y%R1T@f(3lpTegae{w-URGhMU%kzwuZd%r6 zZ1d)yStIi^&VO5G`JcXO$+F#OL4VJc*)@ zIkj6dM9yl~YZdPP`!A*~+4XhWxBa}%4`PLHmA}8({vkZKx%*E`{)$@HU+EX#Ojssq zv`c+y?*GMHYW@5{IX|Y#PhBb~=Kq4T*j@eMC##-Sa}2&W{(Q#N^kdy?p0n;o{bx?I zORu>R7`nR2Yfp5FSfz61jNL9K-xOV+FPtG8sW9uqfec17zNZ=-XHRRh^b6i{zGmY5 z=Y4z1#y*i#+b`@;vu>RA<=i`$XXk42N>9W(CD<+7aqXw1g4;9CSrz*pp1#=a|E~Q@ z_~$t~`y#KcRm!Z2C}whxi=D6exAd41f4!^Mq$Luj0ZcdNZTmK9X2>JQ*0Vfw#dl4e zJiFgMMmz65Z+6lAGbQXDUI{Pr_)oP@pPgTra{S)3tY3a@+cyWLz7)NAMKPLXW9F89 ziI*jt;u%jxhgDqP^iadGXO~;V9tH-T;Ds@ZE)}gc4}F_{eV1~j^D>!V>XtvltK))& zm-ZFD{%!X`)OUWAq~I&N8)8Rgm43<1``fWb$Nur78!|>Gn@SHeKhrz6@bZM3`#HL` zg*l0SodMIO&91mGu|K`8@bC8SEoP?=9DiEt>w54Jg9dl+_vhC0g}#PaANHMCQt!F2 ztKK9nr)lk@)?V@B|6H?YI{7K@emo`nSXWf>=9C?$B;1#VS_$p1U6Qa$ftmH(kIi4( z)^E@_)UqJ?{jQEvv(2^stucw5r1r&UW`y=}r!^Cfn6Hnkn^3SlpzlrUqlGhGd;Hep zV{PsKDqbcQ@O8HS#)4(%dKSycJeZy__4NwR6W*(ox#!NzT(Q^wMc2oLI&){%-K-V2 j&o0xE+;%|4X0eB0P#2S$^V=I!#4oAu4LVu8VgV}v0+RZX -- GitLab From c2fd5d8321b565ef4ee199a3f023930d154effb1 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 01:40:44 -0500 Subject: [PATCH 064/213] Explicitly use sha1 --- .circleci/config.yml | 2 +- secrets.tar.enc | Bin 11296 -> 11296 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1d6130b67..0709bbc92 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,7 @@ jobs: - run: name: Decrypt and unzip secrets command: | - openssl aes-256-cbc -d -in secrets.tar.enc -k ${SECRETS_KEY} -iv ${SECRETS_IV} >> secrets.tar + openssl aes-256-cbc -d -in secrets.tar.enc -md sha1 -k ${SECRETS_KEY} -iv ${SECRETS_IV} >> secrets.tar tar xvf secrets.tar - run: name: Gradle build diff --git a/secrets.tar.enc b/secrets.tar.enc index a96512c7e039c7a011ba5a1823cedb9a235a7a9a..4291a913328f7cbd5af4f09fa13a338e917eb168 100644 GIT binary patch literal 11296 zcmWGe%qdAtiH|?NfA#~{RRYgEZaX#qnwb46++;Dw#t$Dks%)07WUH9)$H3X{lcRs} zdFh30&hnW~W=YHV<^^9<7UfwM&FP?+b)x$QYufb=+sfCgrBxd z;fsd5`|ec) z`1Pbhf8C915!p#!&o`{PA8ho^LRfyk-K2!W?hmcDS-HIg_Q+&9l;)Arw* z42-h3mwQ??MU}?Clw`SkhSz6b9m8LPy#*;3S4}zMQ(@BL!?-0rtR(spd)EPTQD2Z+HIQ=Gm zx&8vTIc%~sI7=Cn&HgAx@@c>D)LwWzYRch@|7JwHEZ;2lo=HSsdBMKSiM?9)yp!!& zlehAoM~~e1zWXWV!?KCK2UgW*?Y`;DHsxe&hjahNutSR$*u9^XwkCYqE$Nm=JdgW! zXFNW$J*7SFVD@^pUlW{+&xrc}>@VT*z0+%+a(1U*hD5St*8GCRJ4+WWpZ4{*ai(tL z%Oee}<|$s2179VvM>L8X&r@0UQ__foeXgmR(GkUnCp_QIDm?$N^-Ta{f`j;lrM@M; z{J&;j^l%QD=X51wM)1_k#F)CZ3M&p<2HD1m$Q3f*m+7v%I!nT-==SFwFWcBxtv$bTGgm2{Xv^=Pyan+a4ZQDyxquP5VY}4lO@yuM8eQ2Fk zm5TAHTgSeq9oT91H+N#N_$|NuKVNGfN&!bBtNQQ9(=PswE;74Z?*8>A>utlpv~Zm#@1rhAi+)_Sy!GQG&QzUQ{DFP! z^Lg&Rx9u{M)_>k=CYI3XV|KQ0jf~UvpuBv0)xXZl<@-7JZ&JAVQ~29`H_lr>Vyoup ziqGO-o_s)g^5#8-#)kv9?>y+h{l@rwyhmZ<$-J)_0NsSY;}2F;2oUVZ-b@@4F{hh7#d-Mn{u`St>{ zB5&=>54G=n>zynbW4Y6LHV;$#;$t70!hctYu0G*)YJIZUnO#zk>Vz6pUVIOJAfEA9 zNp6M8;yq7)J`ytdiH)EjaPgH*wF|l|8TBPFdTpmYOnc`&OPJj?+fR?6)~+{xDs3de?=- z#jjW*=l(0Zdq!&A>F&J!>8aC|4(-jlcx~0Zh_t}K3fbn1SKYZ*wTH^^nI|N!z4O+} z=vDCAZnco8fS-I03quxj_ZCmFPk$PF`-?$@l?(I53l9(9|C@hG-@mdw#l*ZU^Wy3A zcl#M$J=;Cc?ZDS%%Y2@mI?*`gdcN~4)?TjJ@iTSu?(#~$jSl+uzP(VN#b?gtin)Ex z!6z(r(w((VH5Z=H3OU1J&((8V>-nj@tM%^t>BklvlHwNeP!Bx1{K?4`NrKvwyM@*2 zw4Tjod$rr9JM0jf!L?q+w(LKfJ=fK_C>Z6u$bX_K5IWJ%y}B|#c~(33mUF+Fj{g#H z;3&E~!9!|q{^8eZmlO(YcAjnvi??;M+5UI0!`sc4+_Mx{9nq5JNWOZo)O?oc$BNU( z4!``g?!4LI=o^39WWG#4TNm8-nP-u=cjeqXo7EDM?EVjLr^IYOD=QTd@=JY&c=QDS zhy@3&ceG3Ilb42URrq3%Ltn#wmH9hoOqoI4&9HuT?+xHozd-nb?_7H#m z>PoAB^B?vFmLH-^48R%K0F)8(!Nt=gEO;#(f5a*fyXGJ4$jl7rh0Z?Tk`GeW!IBi+>f?0*BueQVZ)Jn zeab4+d-YqUO=-U9tHLd2&3k`o5in)ep%DucO~`@@C4Oy2&yu-A8e*FA-+8~r=>R?K-Z!Sl}#|1eF}r$Q~; zTXe;rgnGV{mHv8R@tgl8kGU(2bW1Gl7AU^{TAjuw)94?{WYShBBR%DIVd3xVFMkHk zW;yl7(`M;Mb@AZe3sbK0hLB!DS_b?ykh0fgV+7(+*y= z>g%1Lu{G{eqD#2ja)Yz~EvJ6?@{8w#K7 z*1W9jifFa`mKW<{_^(V|X~T1XT`~21wZ>l4r_(NTIah~0bg>Qp|J&;K16$#!0=C;S z7mE`1Zd~@{Qt=A4mTOAg*ZXcx7kH4;ueJJ;4RCLBq{jCGx`h*U|wxY%x)i4;s?aw>CK$^epJTJHLIy zp_1P1XO5~Yi+EaKrYO6jSNy^4wN|OYPolEfeR3=A-TI`Gl$f$2?D)B=zJnI-=e+hC zU2a|WSpWU&CGVD5)qhC;_Vq$x|BLK>iTa5ao%O+<|1Z7}vk5r8XL{zXzb(N{OKm1* ze7vvrU-Rb4d3NVBv_A$o9+^CGR>!&vPHf`;=I%)Gdf7O?O8xV_U3R_8Ts6HPcwek* zWu5%>miD@{UL40fc22b0#`bvPHer#rT37D-lg{2T<;saEH)8bqGTGqyM$Iqf*=to~ zc@-`#+%9@~MySK2tEtsz)4!~<$+!O1`|n@iC!fc?^Vc?Rmwd)1v_4pDU*+q7$!j$K zyjvZg7ZqpD{iLAxd`~#*&$CVbGCDbHc$R$DT0ecE^J>MubfM~h8w%~1r#gu!CM$}n zZZg~7v@Tm!V0x08XQJBfSyEfR?|18SY>7;+5BeInB-$=FUVm<1io&1$bFXkFK9jMo z(%78FlQSXLr)P+Krji}9Z)%Rb9>zyHp!-yjsuU6iuq*r~-EHh3~UtP5U0zv9|w z1r6@UHEkPz>j;?5We z(Re6W#^J-dfJ6FGD^FdF)K?UHcZ#RTE-*NJvgM|8^OtVvym#rw6NjO&S+Rzd3-~1%A6Mmf5`50ifrr5x@OH#m32-ho;`QHyWi&p-CXyJ79A{L z6*#e__}`P(FIw^zGRnCdHadL~>i*4h=f>CPqVjXkEe-fQJK)I5X~yTA^^SkyS1?~? zxaR7`W0jltNZ9*)KEp2lde4d@*N$qeT0Z|}sEpx)%c^c=-;&#&dM;H~*4QV$+CTro zq5NuN?ax)+-9L?wXs&+r^oZ%w`XckJMLItf6EDk8>UZDp?(!;2@t<1$x*t1M9ryN!yr?we%Z`DSgtCdFk`@>Yud%59q| zOgI0eWNxfc=6usOn`d9(-<=CfZf-H2^5DfH&B$nz4+lhCA2c^>J@Ea&`Dvn5tw-Ho zU)7ck%dVC_d?=rCTHR1foin9EDHOw*FpEl!zYXH+wsIW@&8LQR}Gta zPhcLG9`8rCX=f!i2Zz~Ctq4lmKRY#F`-yGdL5~8Tp1vqewq5>-&;KRAE>P;RzW?mi z;?l+W1-~@|Z(e*m`*XjlNoqD@+}z_LFGEjwv=;vV>9gQQxUR)yx8sLPs&lG&Ouu(5 zow@n&^7WUUz6fb9Xo!-@O=5S@->Nd*c#8l_vEHAXMQlpWA$G1u`_H%u-i@j@zk0oA z(W$2k>&{$l%WS)7#>b;*5?9VKeMeXHlOH0xPG#AzkJ`Iw+Kgg_GSB%FmhCsJzH`Io ziSo0!^H=VkzUrwaR95_I_wflvx>4cBw;p!>`gqSf%Rg3ftFxp_igTyS6=?8T&XqM} zf5PSz@;}?WV7-mrvuq_@-k|CAvr{^DZcAfdm}GRrEc~@|R&4Cd#2Hb?MW1!azABhA zGw+|w<^3z{CVV-aYUSc2de=k3Mf>}4pQG!%<8>BC{<(QM4d$~$u zy8RZ>(zz41%HNM(oHfmjaZX^wkqv*Uc5}R6e(c)Z;0f>g*K2PrsS=yzx~gi`dYQP# z6D(FLetG5M;*&U2@*tOEzVXJdoUiAtuhjT`nJfC@E-Bqxd-rt-hOYen>ZGl$`tCeo z^Zm8+cwbvxIugtLBE|ZD!JkJJeq1WcZ}TQKuIt)6ZH@56-TPm+?|7fL`Q!?n{b70= zCa)gWr)(hGEJ*&zo?JC^X^4fU1%gvCToHJa0{qy{~)`RoO zCB;DhZ`LQa9&4-)v({jAz2g3)HU8HY#ZwZQ+}~VprLFZ#Pnr5rn5lQm*7-B``QES0 zF%z14_I2?w<)$fDD!Ts5p6Ht6>H55kTVVG*#%E4fCVsQ1zkT;{`B(Me!`i3YjQqlv z2Js&|I#c$1v4DQBRry8M2@#jS%{Z@Pmm9J}Ni%KV0%muE86Wx6)Xo1MT(#oW7vFgv zQ|7!|(lWmwckb-RzqeeUR<`B8jsD!p8|Uwq*gCWL`;;@=*VxKi&UI=DyV3w()e^G>7|* zUHgydNSNF%3Ev|BF23`hj8&k7-;t8ExSkJ-KgqpLU1^eCuqO3jVYb|EL$2mlRqY{K zHs{)k<*&u!R-JU8Y|c2nF|8}l=eLTKg8kZ!Qjw{*@>fW#_nXPG ze%TB8mMxzX*8ftv;lP+aBlA$nO5UBNA1=@P;Qip!t1TVpC+F#;PZ!>DcFD)sl|N1f zue+3feqY^$TA_8Dn0NEnU+9m%wfm0$2mN==UnNv>S2D^(%Wbe$D8R9@H2#e>m-g>nS#@{bUo#6`E{+V z{7Bdl5uWK~$GTHyb2+q2pS-Lw_gCrGBl)NKL?1n~KI9U4waAY}ciM$3H~VJgE&9gv zCu6fz&`s6#QXiF891uKfF1CM#BBM!~%KG5Eq}3taf&B6R=Uph_i;FJMc)i!~A0LYt z1A}!?z$!cCFEJB!f3BYrpXb@~bc-n08>616|EKQ!c=4one(&PrOi#W^?F{Ij>ABM6 zY0vrNmbuwB(NSwYC~UagxBSseVS)Ufbk`c`?*{G0+Aal^dp~h+zrkFx_3{~&Ux^p* z)o-yoWU@H#bI3mK=IzJbkFV7Y*wef5!6fzlpZ4W!et+>6KU2hlH8?)Q{o>E^mD*h{ z%=x>GSH|G(6=!ktXNfn3b7pHv6fD|nlUlg(shiitzmtBvJHdK|KSa2+u{+DAWXb#q zfn7yYOq}s?)!wPn~7Kb*AtROhY@E+;;m*e>^EMs@lh+uIkOJ*>MiOY~y>jQq1MWt*8~ z8g?!GZFcTk%tMy>Qd^(3OxstN{^iQVa@*;b*zyA2?)cHWZ9>3|Et^j+QTM9${+OLS z$@EC|*>?56m-a_pO7S(%Qn>!N`&W6)$rpYXL~Uzrt*2f!zjS@gT4D9_qyHLrpLv-w z?c=vOK7Dd)POnZ$>h1A!=eeM&@pZX%aPmoOW!c%+&!;?C!t0+_e35I42=~hsXI>rL z*0pf;rOC4^*X(oE3w`}~n&zq3d5il_rhdq?IOOa1jrI4ZpoeWD`I6SFV?WhC@!$VC zeoCXJf60%p%WtYLIUN$v6l2e9ZChJx^iVCbc!OW?w#yIt)PI=ok=^FL!TPOqKft;Qh#e5mP~z({SA$UZfLk3m6#mx^0|cOmRaZbD4bX>zPXDd zMPMtd>(^~-JuZkF$Q?`j;bHYxA|>1?AiL^F#?ROcemxeeirN$RuJjyU-C>*Sa$`Z{ z7iFGioAvz5UEWVwexs-{fBNqP*&j@yyCvjPiuSgt^(<}t@iemb*w*8`*CQAYWxQJ; z72jjFFaNGZ@w=N^K37-0kv%fuc3qWGc3y8)$lqs`^Wzs?Z?k7QuJl*lZ|9ZY{b$0K z)-8V+-}v@G!b7>PKNgE*XLd|;^<$W$vo-9(m(6w^t5+6ooiS;Z%6((LOZ~=|1TE@r zc-vnsPr204@bBQKLw6RRlI!{KFGbugu8<5<4_e8Dz(#}tl}(^4c^ zot%01FrR;PBkgI}ez(H4Et#7GxLXeCwbK$+d41^%I5HtS7}e_yic@u@Ij=g#kQ&dquAIw&^&o$IFTwE=od z3~d?&b~1as*X$3xoZR_?$F=%^)T~WEUf8je`|_Qv_4C!cblxw?eOf2WPuJWAfz3RR z(vsgZ?q9j*dT8L**hR0)kDWfgap%5Uhn~N?eX)S$GUo)z4YObDKmV?;yZDRmgcYnp zbIu1yOw4!?dnndAZSIe*>91e>7Fcz~Hu6+1&)a*_%UkTqEL7ec2b0I_1+- z#_e-&I$Zsh=9MV2=bk6?sm%MQWb6L7G%N@?`nh$(?8}8xU9Ft@oqGfJ3VvDp`umHR zHpxj`2j9sYj@=)loe}0eak=B<(%u+5o~PTx_XwR4kPJ{yT3&5)SCD1z;zl`-$*&*Z z(yY*1SoI`iiB{YTqk@jt6SmE$`2V`uaJtng{jkRz{3*8_V>_p{)^N@eSt_howd4Pa z&2i0M+%H$Wy_+NasCn7;C0S3M_lH(?2EVFxntAwfz`5g-H-1>R>-&$#> z{kM0r<=d8ZKArvxR2d3w69gS5Brk6Cw=EA^n|A!{Guv%SHdBPISs(dq+Q!`49(>^M z0(q9}U+y|3S~$!JRP1Zt@FXw%6Yrne1q<(W?9DJr;4^=(SjTW*X8nqlEasVOBCMAw z#c6GSbM4O*iEF&d=S#S^%)W7W#(&FSC4M$#DpJ3?QhMF_n5*?(HutlC*mmUShQ}M+uN=H4GAS}~G5h?E_c`wGx+34*%Mf#VUOB00 ztC5L%&Fr%oDJja!T=LdcrkD-sMp$onVHC@osO zC(O`5Z2iAqZ_9VIeegY?f5%MD)>T7sm6KI9Lqx8yec27IGoC#&uj*?~`j~mm!Mfw| zs;ND>v#rBeR`#`*H^=6?mu=jbyn%BYQ6e0!>Kci)6U;CmN(Hd+`jQqpX1a+r%a`Cg)X@rZkag$(#nb9$(OZS zo=ot#?;jX3WpeCFPnWepTY3I>wp%TVIdti<$Du5)?d5rMOMT`Wa(;~y{C8=49cZGQXhmD+u558KYds%gg^TWyuEeerTsy^+3q7u!9pV?^73Z7u=gJzbY4*MV$#X{$#4@)?2z9Zek#y$HzN62*jQ@0u!dCvdR;GER2 zxGhC;ReD$Wlk5)JMvmowIrMlPjlRV@-zxTLxm}t&NkHRgR~fG@Pc+-%hq4~Vn{B2g zI%KHDS4Qki_-gves`S9}K$mMBy&Y#>&zip>cay$+YuL>8*$XD~d2#K!|LES94ISz= zdoCp_+}(UViX&~VRmrt}2?Nf@0{VA1Wpwg3Z`^Vtq2phc(2=8$Vw)2urrcuctXJF3 zbGOp9W8W1MPA?IS6{#AZ82E1->G#^~&|)vyuES?^!Y-}_<-*YxjH#OaK^y#C5xWIjrmf0_SA++h$mCqspjD_6Avrer(=YRW9S)}~Q z zO+J=p24MsL)2o9ObZq}_a{BNsJ2Kzf?a-@tEG5Sz)lbwsyJv0mLTYZ^d8OQPgX1zH zV#~Bsm&w^pby|Lg&sL_XbTad|%HRLhbI%-CDb;3)QkfALIrY179mi+;`+WBNmS@wZ zJzb^ObNGYO^xuD*i@xsS-TdcA@vXxm53BhyIF`*5|EAd_dCSO_^R#$=)9gLbTm9bk z9C>s)!9c2ev)^U+Uxtg-CNBE+y)CiK`-$Y_4V$y1azmNxg}%*rZZ9v}%sWfW+%({@ z`@H$BqRT4co$uyd4KUs(rFZZ0zuz|B=B4X=eOd6XtpD@D1x2=1vI&=iFJ0JWBR)B% zmupgz#G#6bEB{;+<~p(H{)QLH7tdzue$`D+5e_y#+FrxWH2wdqOV~kM+&y=9 z^`i3Qa+WL)j%8OiIu{6qsQOtIE}pgKu>JRc45j9gYU|d%o6qszXV3JvN1s;in*H~c z_lh~|_ovLW7x`?k>BFnYy!56g1$I%2-OLWTlTTaz{4bWc$LsDtgX(nicmM3aTvL5y z>ypyl$fEfC=KC)mY#uAhLsumqxhA+sd`guPW9CDHne0tLM?MN)=RbI5<`TP8E^~L5 zO`Ba)@T+gv^oFQE9%im~MN?tC^y6XuW8ZdL~> zob)`mqyE|{rL%vH{Bq}8tokmp{oum*>gMVB{gtxwHE(K%&tt#y=xNLBj+qNnjw?K< zuc--q-uy_=Jo|p2qUrCg5098Un3Q=l=H<2^1G`01dH-gq&VRR|xjSe1Ih$sUm!~sa zEc_d44@4~d_WjK4U;A5((!3Z>hw-noJ2tVhP_p*%1g=M&fpR`>(H^tf%@;p<`9|sq zgIKoa;lQ+4AF8kH+*thYUwUULKl_B+Jce`jiG0vlcbqBkPXBd*k7rDBvcIraZ@zmV zAwydKz%OTsUsYuEgQudVWF-rYMLm-E7_Gq=vS z`l!&K-eT3^ImhwgyHE2{O=R;*oew>FWz+ntcv*Iw6z}XRaqhUI^%bFM4rSZoZ|r`U zI(u{E)N?^bbwU%lq$hm1d6n~%+@z|Vv-2LMY8o6_!l}2=WY$Nu8>T82S2Ol*`_27Q z@5-cUo=1cu57i&g*~y-DXK6ul(u&YMpQ8=;$^T-hnCWHZJt=X*k+{e1DM_ajqXJ-i<|GfCX-xqK5r>_3Bsefqio_OXQxVX|-K7krWS4A+eh znsuNgb1K7xEt{sdtWgzO@Y29Y$0CsX$NQ@x@hAU3YY;E0TlVlc(^hkpeh8}1XN_^1_{rc{ zl85&qwKVMfpO! zDvJuYJm$hv2xN%c?hTNNmxM}6B zX$SA-vczuPwB|$5#QO`>mj0BU-*oYpaND2tbK)b4uW;OQI*}u=;-OKunulW7t(^JO zSHIxzuCDJZac7Wez4a*X-lO0!iP=w@Sqy1Y`aMlm6?kV|)8E6)KN=}8C7K2NRwydy?!%7y8F z!cD#e|9{oMF@dxHkLjtz`s*Hk7fvu_mK|d-4d<pbPpas5-bRQrw8GljUGS!Owloe#Ob!O}jrGbBIy_F84W6E6-W z_NXuXb-MJ{B7@q?_J3?XUez_q6))VAy=5swIoEgZV^z=Oy?Z2An=K1wV)CEJ!r7Vh z=xlhzqhFFYH-}$zebybs%ek_1P3vi{cUu?^O@A9TNo}`)o!~mBa-VpyMKeyd{aEKT zGxY?|j#5?5e=K=kEGMmwDZB4pGUxxMzH+?%YC5K{ zST5I?-kquwTJ71P!nk&_#yTs9yPu=~Gqf}?{!{%_u;p9UvH6pQPJU7C>Z;uK_@$g^ z9shjmPHTmpozH%!-JCeXCV0=&gVrW}#TWcqIsY(ySt_&sS?L0woMku4-$(^%uh>(1 z_G7%crk%ETznr18YDu>8ME~9c+v2~!7B5W?eX=>~;`Tk}k2w-&>@oMq&#$%LrSU)S z@w63Hdwh?66`H*xuKR@cywIIbS8jf_Xs=P;%k8$BM+}asb*-M(n=Es#G@`Vi;mG!z zU--2Db#JMecZTr?gS}|V-w)|O7?XDt?s?cQA+ScOT!ZCLY|*E*1APsLzdTcw3JlkK zm%Kk$q};0J(n~gdr*$$*=Pvv3{PCk-W}UhDY#eK6JyN-#cYI5vrL7^arYpy#l-O9U zJF6qu*R^fxjX$}bOJq;npY9_%lO*|S9mN#(Z(RDNrS-f56QfFrhs@dhHlIIQ!l}#B z_Mdjq{xhB7*xyN?#e9G4IXdy|)i0lY3hwc{{4?J&KYhMTec$RWJZZISFR+C>@YV24 z)GV2>L8e``^7i?q(vznja#v)3{z~rVef=P9-P^nBtq-{c?0RYP5^}+xE literal 11296 zcmWGe%qdAtiH|@1_XguWu4Apek=y4vJ}XgJR>tHi+j7p3k#? z5}&wd^iqYtmWzC6emeE{p>v+1g;qIVho?z)1%v#@JEi(Ngm-BvmsS=$f8=~!>H99N zTf7ece^r&8VB^f2+O$`_@6e*j=8i&L(jVt-3Oo|uQK43L^h(Rg)^)d{i+bD(!pf$v zDvB+iDjaj^KU+t2^V6f!=NnYMHcd3%6aRk32^VPx?~0`vcSVbNvli!CEw-9lx8|W8 z55vK$0j{>IjMDd{#Ld0b-t?0x?%r|R<0m#19e#55<>?D*+pMEr2B^IV-kVtZ>sy$! zz^`?}na?x4|K9WXBYpmB$;4H`HzlszjCr_NNZdW>&dMa;#ciRU2e&ZmX6?CgV}jG3 zo|xm#?#Fw5*4$FOvG2^U+R&u(G>vz`*Y8(7&;Ppk*Llz1hlS=Jd$j9LtcMJ4fz8Au`Hh5#*`a}GRX)$m3Cw;l$#J~KV*lAm@ z4b77~?+5&DKXd=A*Fo`9;%oI+zwg@4+H5cIb*A^zWfuYjO0OJv6n=j4I_t?l0_T0I zDDQgvEC0b>og;ypE7`2KSZ+G$aLn(ll2qWmiq5@KR$QsG*bkPvHAR`^t=^HzTVi?P z&XwzTqLY;pBW%(I(r^5}eEsLNBd;~@*qTg_j8)5D@@&)DG=Dy?D>DC6JJod3-s}=v zdP5>eU$IPQx6DF452mCOj7*J|N>9R6?%AzZR=B%!-ljz}o=#7U3cOnQbn3@#b3J7@ zL>KcNeJ3yZz9Qh>*3aJma+pOVwKqIgV=J1a-!-MJRx!=3)ZL~3o`&p<#~&{@);B%9 z-?G!WetG-!-uYRw-Bl^tYW_cpmpx^C`D~ivwcZ0&Kdq;^q&~cFt!4DU^#9Kk|MxAy z_A7S^wkuThPHbsy+E(7`p+4{4@kz(~`Qsk*`5WKQ^#9-Da4w@wL}s@1tG|VIeM=A9 zu54L+`j+IC1s%d)U0*Ls&Chn7x-wcXAoRdpj-0ufZE2jhk9!;cWhj$gb>#KcGu)|# zlFe+TrT4ZLpXP5?K7MJPt>~Ng-`&}7>PMZ3@#B2Kct5=V3){S7d;9+gC?r3v<6RqH zD^vE6^GEVjg}rkh{Q1d!ooVvU3D5Y?*34lqk4;*%m@9ln*uFJ?B8oOi8?Z6d{)l>a z>1yKaHocFl|2}&0HBbImvhEU{z7wzFbVHW@Rsq164~|7{Bu>rSdabG zeDzC6uJuJhoSI5=e!tIZ#luzSzxR1rl`3C9-|gZjsBowIn@{NvA7j(X_J3!h=F7c* z%<}*GH%C*a3v-XT zjXQUey7|3IscAoj-4&jyA9}OqiPP^T><5;8i9cGl|BC1Vxj(PhDmF}sHoSRTW$r@n zi@W(3OD#wK;kW&%bJF_v!+e#W z>4GHS|KIi-(!4vq@~^U<(Js$$p)o&zQ>aLjVbvCiKUJreiu}3wap$(< zU!5`{7A@=yc=LHjUo6{XKdw*PPo9*X(YdF_?>@)NAFXROKfb$s@?{r~t?NyXb5{-C z<(}PHBHowge=2dmK*%es>?Ur@s3R&4Uu7csX6ZP;Z7jc(zgOVw-w!%zN*jaSp3RHpnze29 zMzyUXN4T!>tXTOqfFXvtEv%#LlW_TrfBAEn#HQXa+Gen&ylHR1#T(I*!gG%mw+eY% z{4DF#nE6RTmPsWx;!4vM=@(9+D;VO}IUZPN)Mi+nk~pjG)$F)14bEfcH?H0O^5uln z#7XZTF13Vqcj4`IZ$6qhCDTB3;SEP?)u(!j%8xq=%;-^!53g$ArXq zPM>$rX7_#b=II%oCs!-jA3MC5lZ`Ebz5ZnS&VQk|-NU^;GyRFbWxpkif9FnLF+-h$ zKPO&gR#`h)VZ+7Htf`VaQ*FWy*lanX>QHp|^m@msxkd4D?l=CpaV`Xxx9zva4eCoaF7og%hUFm5=Ay-S&h1nqJBG z$ar=cu2jw=`rFS=Jo@PWhkswa>K(#gFPo~cO_X(`$?vpBI=NXZH*%$XlTiPbdQ;$f z;LUk@?&WWuYT4&GSkJ7|xoggUF8S3-?{l^66|U;{dnUQ`v^`XMeKsaMw_PhC>Sa?> z`sdHBM+CpIu71AgZnt0kg2QIJ!^FSXX^FT3!57M-dyV}7_iV|`L{=kvNN+3?2>2Q4Bh!-{-md6 zz5Oxq3f;%`mJA`5>S!NG@1FU*ZQox#;3S!S8K^tu8AqH zC<}eF#cO&>%68EPuYhISWiCWr+%O~5U*y;J>gqYGAKWbT-ljAEP_okXGfaQd4(^VP zk$$asH(WGCyl-Z!p6tR|T5DTcWUfDD{}8>$K<(5TE033Mv)A0&H|38{*@R>BUY|L? zJ2yY;hDx55ufKcu1J{4?ky5SCdMkW|%H0kg_~n~$LH^ef`T6S;^ec82F&N4#PWslx zs&(n)1l0{;X_LZ_24);Ou;#U4tM-mxHv+G3xxCPQ>WMQukI#;(K9#+qGM-DkZ}v5Y z1zkTpWl!N;>))HMOuMb{?Q($YJ*7CA zxhY3g8`pO#O>;2&Ki~QV!?LVPJ||h19SqE>xO??Ua&R5Xx?QY4XZnPjR%hQ?8goi^ zq3fLw6Q%eMiFHKfDXqNuw7TnHYxOqSy^+&4&)8hIba!{gB<73h56@H)Or}zK8 z%HJbi$t4#0N=|Y5&F~*t89P|}q`8!{76d0OTV&xsOCa&m<#UQoS1b~@JPV)keBZ3> zXXWjygQSkz-%_2yciptx^Wmmyi>nyGv^!>d^_uJ5-$V0bXvu| ze9dU1(AU#t5B=%ssD1walV8?!_Fuba_g{+APhYE0Q4zjv;`=%tFOhj2qD`7L)2xAq0fq)9u~guH#KFS9GHTe+5D zrTJ`+ZH6@g?H+N*+BZw6?%K@oVHuyH^Q`&LW^B_DZARaMs-XM?W4M|N6e;bMT7m)6I1!9ToZFuX$AE5`)5~ zf6dkr*}+qp7RnkgiQA*KXtlhaa@jfmX$eB-g_{2foqn1BU{b{LWoa|a@{e-pPndo4 zh}yo;4BvMi?^mkWeP~IX)BN(>*$uM)@>Gu=SJ&AYnJ+o*$lI+-%iiD2xwEKIG@GIN zxv4nk(>b0$TMoX8+kScy|o^~z?I4?pG|v|D)dw_NQG$J0Uj zU1cw?$W%t(`T5#YLaoYNZkJ@qL)KrCuTD(}Z%KUceAnH#llt~gN>W=L9%**^ax=4t zh`2`PsioP6)n;7|dAjp=g$&o~f9peIG!nc6h zyX%wBc9;4WRX zg|qDwPfuaIbJBQ0*RRqQh6|5yR80R>?JH%xRb`>Ur`&h50v!$A@*=8}*D)(px9O^L z*I3BBFk3I`T3FiBHPeL4*J|_evu|rA@)x~(@6BRZ!oM-@ zaERsk>9ZGpOlH}=uGKcyLdJEk!x_JS!W-Rgi9b2aSWu>D>-|+aGEuVBfrlH7BdQ z>)fH23WiH;R2E3T{w2(K***>2HWEF%9t)wK&>_%&lO zyPfgd)vLdJN)dfqV<9E#d{WS1X~~W^H>MoE@@f5_{PQMdM+@HlcrLvujdR%>^T2<8 zd2e?!>v3P=DSYB|Yaw5BW5+GV2{y7@QYEw$PTna$&Hw1$`!K!YWTvyC$+M!aCZC*J zS=;)NS9-rl&_z3m1jM@vi6%0doHVT?DY?C*M*Dc%B(e9|Af87sAti+*O%tm zH!alN8{YIn{&&69rLPrh)yyjjZ4tMlQpZM%Q&|Deo& z(dNV7-WjqYpLJ*NW373(Xa1hYCK8jL?7381dW!uK|Jg=1&tE)^n^oQ@>KvZ5hV|&~ zFMj8GbUZg)`qlGb`?3?ZFJ8s$7kWS0Jj+t7{P`ij-yA!tnl;M=jRgE>uVI`w|FR6r z)jgq>S6#$DJ$^j(!JQ46Uju)fy>vZ2jyrG7-kjv^2P3*%St=y`G<$r?A_R)wblWqg zPSDyC$-3E5YSH8~X+3YAy?TG4e`D-vVuu&1yf8bW!YG`Y zY$|m#`~IEVS$~%7XHgB8Qd&Ngmn+MmS8E0<(^#>TV#S@FyL{I6bDL=~Ru<>u!-8CiPW zb^pO?^SAp_Z*|>Uvw~HqC2fU=flo$8P0+BU{lU35^VHfR4!7_5CRel9Nfe&>D?g+B#oc4k%&QOEx4m1V z;-03mgJb`Z$p6mW{K-5H`;rc@IOlw=wXvIQA?p3MdSdF_UXG8mZ|FT!Ugxg&_4Ld+ zCm*denWFh^&3fyY+l3}`Hs2SSUvln(YRZ$Bmh2*|*J@wBp&iIE${hXfd|gt()<+gO6yu}H*-!V!C#av;`r%pGp8Q3N~UnY9nV2Av@wku)t?s^y9EJ*fWXvB8- z+82hmw_CP&p1D*~$A04396^hXtUB8g1hu3tuA`O2D3HGuIdKaR*IHy z2=1RGq4e(NLq*zyKsCYT$ZUtL)`5Hkr&-QyBb2m?X;IgmHMs1eBjzv4&*mj;%itWsG z_^z>r(f+~pzf5u3LhMXFQH=TCuQF%wF>m|4&UDT1Fx_1&r(Zq1w$?J~j@UB0t5Yhy z#m%^%WSm#ap1k_t9Y2@L6I*iM#4O#xxa>g?d+_@;Z=<4q&-$@ZDSy&|DhA12qDvl5 z{#3ATfk5bvt1LZjN?(L}O88Ip4|rA2v*y z*OZ&W=P!JllN1wv{eAa>vsDvzsxG=?D#%yhX8ki=!9!ZQyx>a0FMZ~`eREp$Tw_-{ zXReajo_5^o&w@GCpA*$HPAy>gt-OzuO|kyy6tn8pJ+EpHKYbm(`braz{<@u!n)emz zl)~4mxFn^q&3_iK^_bW+!;1}>`yYz#-Ti-h_&TlBhkKtKOAtT(V$%BTBc~G&OGYt8 zztzbw`#pupGu2A>>P)wHde46taH}sItA@>|xLu4TGEzeaHcx}8Y+VZv?__0;`-+Ksp$ z)v@MzTitb%uHWIQ*}R-nL{Qx&d81#@tu>EAoaVi4VCaz6j?4&o+Sgcpdrw_S;+=;R zKYy7qgEMHqMq0^fUB|0jvQ{VFs;2B&d!B>sT!H)M>Xc{yFLIWC*l2dHXdPzuDIw(hxjNz(qdb- z^lti#qd&49Tu@y9E%;!RfzgJQ=YAL%3P+r8UpBFP%dxwRHP^qjHEdCy71n(7joudC zVn@v{5jU+K?%JaH=H(4<-Pwum4!8f>vNng$e?RM44u^QzkI>dn4l+k1(u=e&HMj15 z|6X`f!mWpY#5^YKoz%!QWl6E4hz@_1S@y@MNnA^t*IiS}duCI(f1je{wfKV{eYG-f zx0RP&%TnkUey(gh@v@%&3ugA7-P^ZZ3|4q3$#+bA&E@OTdTO;Vzv{W4SQ1xO|1|9% zi^W#k+L}+tG;ilk-ng&et9TDrS@guDPix*BYUbDWOZB+6rs>nC`PJL%cC{`vv3~cX zZ~G)s>Dkg7Sg)^sn`dBlaAN+!^?uo&go-~J@ayQly|*2SlIBnyK&y35;3pE-VW<;ir$@7pI%&-L?9zI?U6 z?EdLyxz}3XC;pZ0lelqu!ruR)WwFbpxBoQCQdQDEGYs4Q-bGDUwYGiujuDpwHdz;7d2gvNoD+Zuqm&>7PiCOWQpPcI%^P+Hrn zTCmF@HFKux?)i&Y_g%W;Q8iP2O|!`HWekhG(zaYW>cbzbQ~qd@G((Aw{kmH_ieIg~ zKIh=S>YJKv6CXcr_buL8`25{urH_eq%e61~YHB{VC7%WQWQFACt|)GA zJJYGy=vMl6`>uG270=CD555g_Gfj#Lv^ZKClyYs_yCqWY-vo|L-s_Ur%HNl`!)TuF zwbE5DPBw9$ezfoSHK*AUQtOU89&kPDc4yBLt%{T{c85x~KYMY9cSp+(ot=MO3%_Z) z?}$HtaM878`eKut=}hg%k|E>$}17qXhuUgE9nvBg}j92x89xy4&* zytKP?zF~&s{uNK7mA6@~UXZi;*dDR3T_!RPKTIMwy%)H3h%^kv8LHWi)m_%!9z zw%bQydVOqj#Sf>qZ4)&TdM>1q*DUV7?0cB-+dbP|*B_ne!~g$8Q%Uso`*EE)Kj$cQ za2NR;8`e6^*z z`MJR-R7{$F`07dUnwqEVJv=**LDyX2T9&6UQ+s|}Qu6#a(+j=dmA$R6N;&u8fSl`| z)qFiyIF`-l>3YBF@_Sp>`b1XIYh`S^{z?X4-_*QRH{dhlCdOmuS1uF3-*xGu_Amaa zb24JZ=kAiLQn#A&!+QEYzIwkpoin!@%I_SvJS}ggncH$Z>WuTQTx~5^t+t-@CKdB7Nbz+QM_}23qE$EM4{WtN?r`GN}88%1dRx z|6<%PyXWSIZ;QNIPtV&QA89nHN%yNliRZh|XFUDw^G)acyrbTgIsbe+V?gKXr7yDY z>ZvfTw(VACW&e{GC$y~KQeD<0$MP$CH*dappPAcg-K)uo((8LU=bPH*J0%Hxv`P%S zaaZSg%~UnPz$;!l&KmQtJZNZ(|F-8)!uI6#Z{j>RoSi2VkY6Bg8-3Y*1$*kBCaV@$FLtiFDd%|op~#6hvy|sq6hsDAX!7P8 z=`Wdnz@hdapPcx1S6_p}v$wtMy}8k0Le1>mdnf<8meIX5cSowf$Hh;(i(~dXFKg@Z z`I9!e=2q|yjrDhIlX&d6nkXOI#^ac6{9Hx8R+a+~k@sN*fO!71PU(YS%up zs_H~^!@cdB^A6qfJ8-1&z~hCnlke9>PO_BuzmQ@j()C2h=?0SsXXqR!X(=axEju>u zNZGw8I(1L;*Z<}PB?~`A)RmUM|Ij|IBHa9N!c&tEE3U7&bYtNMv-4lSCe@!!$_r|} za$hWD()~Sm7hd=N|7VT!Eykx8?xb@VmWfTc8goYPlAKh#{pOCe-`qJ}_mm&&@XQLl zd~HYmbL$rh^N$PnJEunYan&!2d~%^J*f`<*?u^ul4_0c{zFF$@&cVYiTcup*+@|ln zt*6>W4@ON*-p3uK<|twj{8%yM#u`n=D+$N+UUhS*B~3YK_b{tdA-c(X#*v#o?_Yg? zJblhmCeCR-N@;4Be3ymZTF1Oblh10Q3IHi*<)=Ud;+#iXc}weWw{8!^iXdTV~LhJR&f@e?um_5IEb z%g5Izvf5=la8?UB#$kE+?~+NJd4Iyr?kQIMtaGhj{Z#sg8;<7=E-~01{!w7&g$V%( z;Z6&V70mkPYQlKk;&O`Gj@*yeQlE2K?ceol%L69mV<*ouzG;{{S24%nu-C=dqzcx; z4$V9*W{y))(;5XYyW6MoTjrcz7Ib1|x66Vh5mSFid+01lF6f_GXMFEaVEjwg@C`jZ zl1lT}`OADN?wfr1fZ4VbQ?uzJyu7VBYr``)H_LC0*tEoP>cbaY^GddHyDa3XI^g`E zD&_cv|7QPL_X&w`2ugF$=H4##^}wXhEpr6kC47nddEj|KQLq1u0#2K#$NmNuciXO= z3{CYaS4r8HmtG=wPUbb|8) zYEp4S?%=*-Pq)voo3v!F#ot#4O17DusM;Wtf5N}LTHEKyo}$*PuVj+6en|Xy{V|$> z&n-AyI4I4g`)%{LHAeN~wFX*?9jDrByOdQJ21-5b={&UJiX;n%QB(EWSpr*ozn1^$ zTe0u+9K}}Qo_=4&W3I2_j{n|K^DDD;_Uyi#PgiqSdH3FNe>8Qot>5!E+j8r_`OkM| zN&d;Zmrv43 z|4Xa(ukSbXKh?)=&lF~yo21HQ`#7ud{@+QgORrw`yZ7U(Z%y6$L-r}xM4u`spO2q1 z-Dl$DHusp-Z@WEwlv{FqyrU1;F}_(L-M9D9=~UAPsm@al&YEU;K5E9Nf3>Pj`;3EF z4*!Z{^O*G}+JE=6BWGJ*)z{~Ve^hN!xV?X}h{=aZcaFtQ+OGWgoQ0^OH?OenE+ba6 zH}Bk4IlSj{N6K7u<=U$H>HOa|6Be_Zf2S%+=IxkrRq2h%e zs&~_|p9}A`wcB;AXei+Co#gJV(z-Q8H81w5K}c)Xw>uo6v(}YO`OB5j)D~bht8>m0 z)ufj*EZ6hhT0F({irelk8RLwTKbSus%s%*g@zLfKxAydI=6uV8>(9jX`kkB>A8%3j z=U@Ef_g4Cbb5mlKbg%FEyt-$~TszKu8<$I8+e1YPOl$W%QDicYI<{Tl*^f^XY?xQX z2OeV0dnCT6;7Ci>u_I0f<(`oZdt zsp{*#bdwIP>$%5Y6-U00SnhM`C}V$gQWW297Vl7&s<5s4o>SNVUhgTGq@5Vw+{imO zQ*`>H2}YM=3fnIDojiEz=z*4FU(P7^_IWV=+O=t;tfOFodDU+5sv$Z|2_rbpQ0Br4_#z<{KS(n{<0Z9+aGyys8v^beebN7)aO0`HZTIICJl*wv)5+dh{^eYER=t^cYxb58kCuu2IybMK zC0yWdOU#8PTX+78a*sH-O>2A}@$vMT*A|6Gcs{zV?wXY*Fv*8Qf>mji=Y)0MCK^d^ z=4|@Z?wxXn(P61cch}~s;#K>?rtQ?7obzp;iN$`$sCeNw92M)NRrDV1dU)slT34p3 zx)8R;Sy?_E{N|op=PzuMt-8UK_j=luii5M3+HZ2&9dEFAe}ekr5^+bFhp$f0({P`2 z=g&oHW5x$>!uXk!m z2(!CdANHL1&D7yc_51%;?)ogVa?iNUmsZ_=zwuC@+1Gl%*QwH z{zFkZCBI`O|9n~^uD!(dh@)(x%}Hs-HJ&%GACNe|^UuEnrz+P>ms-2yTm;{Y3!aiQ z`+$SvRZYNgn zV_#a%mA{zf{N?09>;Ae&EAK4VfB10Hs>l9m^H|^d73E%i5qm(R*?WmptzaeY;}u zmCMH2yX?bXEmEr7YiPi|VarO4ZExFrdI$r!z`zxEg!asTb+T*$QQtMW%tN72fSMrl|-hTT7+v{0`6$Ss? zzqDG?Zok;Rzk!wZ%oVG_zVGeX{XAEdw8xTW{GCJWg?cd+R0n{rax=B5v2t z&Ye2XjBS2AZ(8Z)CxyX!98YVnaK!xbd}FcL>hy<9{)D-I!>&wOayFvH`T3S}%<&Z_ zo7VFOEPj8wuQY9^v!Z3vy zr@(*P@w`p(s^U`~JX!v|{#;aMX~?Q;lV@C?-?^sQwzMqm@!s0(e-qVmXLi+In|R~Z z{Kd}qS`9a4Y3w$y*FCm=^@5l2ufNY=S+F5~=B7OHwr~49i%kMeJ~IXgTB&~(u-|dQ zG4Jqp0Y^?om7OWil^YIOe{D>EEW_Hy$d?e!da39OtKkez^WFcOr%RsApEf=5)s$Jj a^HVNYDhZWbf3Qz)gJp!Li^IJ-{Z|0`>?nBv -- GitLab From 61ffd8a61b62501e949c6e918e5ffa6e1d164b67 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 01:49:43 -0500 Subject: [PATCH 065/213] Remove tests from build stage --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0709bbc92..84a4fd99d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,7 +23,7 @@ jobs: tar xvf secrets.tar - run: name: Gradle build - command: ./gradlew :presentation:assembleWithAnalyticsRelease :presentation:bundleWithAnalyticsRelease assembleAndroidTest -PtestCoverageEnabled='true' + command: ./gradlew :presentation:assembleWithAnalyticsRelease :presentation:bundleWithAnalyticsRelease - store_artifacts: path: presentation/build/outputs destination: builds -- GitLab From df5fc30af794be8a76f25c41a010994f28419688 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 01:55:25 -0500 Subject: [PATCH 066/213] Deploy test --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 84a4fd99d..e24bb4fc1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,7 +49,7 @@ jobs: - store_test_results: path: presentation/build/test-results - publish-github-release: + deploy: docker: - image: cibuilds/github:0.10 steps: -- GitLab From b107dfec0dd20ae6a011be79d6a1fbad388e4fd0 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 02:00:09 -0500 Subject: [PATCH 067/213] Store apk and bundle --- .circleci/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e24bb4fc1..e9de94a8c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,8 +25,11 @@ jobs: name: Gradle build command: ./gradlew :presentation:assembleWithAnalyticsRelease :presentation:bundleWithAnalyticsRelease - store_artifacts: - path: presentation/build/outputs + path: presentation/build/outputs/apk destination: builds + - store_artifacts: + path: presentation/build/outputs/bundle + destination: builds - persist_to_workspace: root: presentation/build/outputs paths: . -- GitLab From cbbda567914b090ca7fe61139eab1f56ed8921f5 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 02:01:55 -0500 Subject: [PATCH 068/213] Spaces --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e9de94a8c..5a7fc68b7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,8 +28,8 @@ jobs: path: presentation/build/outputs/apk destination: builds - store_artifacts: - path: presentation/build/outputs/bundle - destination: builds + path: presentation/build/outputs/bundle + destination: builds - persist_to_workspace: root: presentation/build/outputs paths: . -- GitLab From 3ebd8449e1fdeeff11344cc6ac7d7da95e3d9c84 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 02:11:31 -0500 Subject: [PATCH 069/213] Include version in file name --- presentation/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/presentation/build.gradle b/presentation/build.gradle index 96c402aa7..c393a19c4 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -33,6 +33,7 @@ android { targetSdkVersion 29 versionCode 2209 versionName "3.7.10" + setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" setProperty("archivesBaseName", "QKSMS-v${versionName}") -- GitLab From 523f94b43296c57f7b1fafd35c3310e373adf706 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 02:21:30 -0500 Subject: [PATCH 070/213] =?UTF-8?q?Don=E2=80=99t=20worry=20about=20deletio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .circleci/config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5a7fc68b7..c17427667 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,9 +60,7 @@ jobs: at: presentation/build/outputs - run: name: "Publish Release on GitHub" - command: | - VERSION=$(my-binary --version) - ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION} presentation/build/outputs/ + command: ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} presentation/build/outputs/ workflows: version: 2 -- GitLab From 10ec6d65ec8864ec1c059b1eaab3d9e153575485 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 4 Dec 2019 02:28:58 -0500 Subject: [PATCH 071/213] Correct deletion --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c17427667..c2e5ca073 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,7 +60,7 @@ jobs: at: presentation/build/outputs - run: name: "Publish Release on GitHub" - command: ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} presentation/build/outputs/ + command: ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} presentation/build/outputs/ workflows: version: 2 -- GitLab From 11f032e423a975f2615f522520a647a4787fb357 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 29 Dec 2019 22:47:52 -0500 Subject: [PATCH 072/213] Another try --- .circleci/config.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c2e5ca073..d524fe69b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,12 +24,11 @@ jobs: - run: name: Gradle build command: ./gradlew :presentation:assembleWithAnalyticsRelease :presentation:bundleWithAnalyticsRelease + - run: + name: Flatten outputs + command: find presentation/build/outputs -mindepth 2 -type f -exec mv -i '{}' presentation/build/outputs/ ';' - store_artifacts: - path: presentation/build/outputs/apk - destination: builds - - store_artifacts: - path: presentation/build/outputs/bundle - destination: builds + path: presentation/build/outputs - persist_to_workspace: root: presentation/build/outputs paths: . @@ -60,7 +59,7 @@ jobs: at: presentation/build/outputs - run: name: "Publish Release on GitHub" - command: ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} presentation/build/outputs/ + command: ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${CIRCLE_TAG} presentation/build/outputs/ workflows: version: 2 -- GitLab From 2d1de6068f1642b604464b8771caa93ab891703f Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 1 Jan 2020 02:02:22 -0500 Subject: [PATCH 073/213] Use broader contacts intent --- .../src/main/java/com/moez/QKSMS/common/Navigator.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt index 4cd9e69b7..adcabe442 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt @@ -237,14 +237,9 @@ class Navigator @Inject constructor( } fun addContact(address: String) { - val uri = Uri.parse("tel: $address") - var intent = Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, uri) - - if (intent.resolveActivity(context.packageManager) == null) { - intent = Intent(Intent.ACTION_INSERT) - .setType(ContactsContract.Contacts.CONTENT_TYPE) - .putExtra(ContactsContract.Intents.Insert.PHONE, address) - } + val intent = Intent(Intent.ACTION_INSERT) + .setType(ContactsContract.Contacts.CONTENT_TYPE) + .putExtra(ContactsContract.Intents.Insert.PHONE, address) startActivityExternal(intent) } -- GitLab From 3b50aff58cac27aa7a53e39cc684981b6d9c881e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 1 Jan 2020 02:11:15 -0500 Subject: [PATCH 074/213] Migrate badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75b5928f7..a59f27141 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # QKSMS -[![Build Status](https://travis-ci.org/moezbhatti/qksms.svg?branch=master)](https://travis-ci.org/moezbhatti/qksms) +[![Build Status](https://circleci.com/gh/moezbhatti/qksms/tree/master.svg?style=svg)](https://circleci.com/gh/moezbhatti/qksms/tree/master) [![Liberapay donation](https://img.shields.io/badge/donate-liberapay-yellow.svg)](https://liberapay.com/moezbhatti/) [![Bitcoin donation](https://img.shields.io/badge/donate-bitcoin-yellow.svg)](https://qklabs.com/donate-btc/) [![PayPal donation](https://img.shields.io/badge/donate-paypal-yellow.svg)](https://qklabs.com/donate) -- GitLab From c3186fa2f7959f908b929883f4dfdc8221b0656a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 1 Jan 2020 15:49:47 -0500 Subject: [PATCH 075/213] Make sure we have contacts permission before trying to sync --- .../java/com/moez/QKSMS/feature/main/MainViewModel.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index 37f90b9f5..bdb6f9992 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -113,10 +113,12 @@ class MainViewModel @Inject constructor( } // Sync contacts when we detect a change - disposables += contactAddedListener.listen() - .debounce(1, TimeUnit.SECONDS) - .subscribeOn(Schedulers.io()) - .subscribe { syncContacts.execute(Unit) } + if (permissionManager.hasContacts()) { + disposables += contactAddedListener.listen() + .debounce(1, TimeUnit.SECONDS) + .subscribeOn(Schedulers.io()) + .subscribe { syncContacts.execute(Unit) } + } ratingManager.addSession() markAllSeen.execute(Unit) -- GitLab From df898994d48cdfda9800d4275e862c7451875aa9 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 1 Jan 2020 19:30:48 -0500 Subject: [PATCH 076/213] =?UTF-8?q?Fix=20default=20sms=20=E2=80=9CCHANGE?= =?UTF-8?q?=E2=80=9D=20button=20not=20working?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1548 --- .../main/java/com/moez/QKSMS/feature/main/MainActivity.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 c0816b72f..114ac9645 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 @@ -34,6 +34,7 @@ import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.app.ActivityCompat import androidx.core.view.GravityCompat import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.ItemTouchHelper @@ -133,7 +134,9 @@ class MainActivity : QkThemedActivity(), MainView { onNewIntentIntent.onNext(intent) (snackbar as? ViewStub)?.setOnInflateListener { _, _ -> - snackbarButton.clicks().autoDisposable(scope()).subscribe(snackbarButtonIntent) + snackbarButton.clicks() + .autoDisposable(scope(Lifecycle.Event.ON_DESTROY)) + .subscribe(snackbarButtonIntent) } (syncing as? ViewStub)?.setOnInflateListener { _, _ -> -- GitLab From 11e733bf593853a924fe3ecbc944e57da5a5fbe7 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 1 Jan 2020 20:03:46 -0500 Subject: [PATCH 077/213] Increase max signature length Closes #1539 --- presentation/src/main/res/layout/field_dialog.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/res/layout/field_dialog.xml b/presentation/src/main/res/layout/field_dialog.xml index 5170aeb3f..b0fec2b93 100644 --- a/presentation/src/main/res/layout/field_dialog.xml +++ b/presentation/src/main/res/layout/field_dialog.xml @@ -25,7 +25,7 @@ android:layout_height="wrap_content" android:background="@null" android:lines="1" - android:maxLength="30" + android:maxLength="64" android:padding="24dp" android:singleLine="true" android:textStyle="bold" -- GitLab From 0e77d99fc6f219a82943ec84e8b3ffdeacaddb03 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 1 Jan 2020 20:48:29 -0500 Subject: [PATCH 078/213] Fix 24h timestamps when language is Japanese Closes #1547 --- .../main/java/com/moez/QKSMS/common/util/DateFormatter.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/DateFormatter.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/DateFormatter.kt index 00974bbc3..d4628c58f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/DateFormatter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/DateFormatter.kt @@ -39,7 +39,10 @@ class DateFormatter @Inject constructor(val context: Context) { var formattedPattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), pattern) if (DateFormat.is24HourFormat(context)) { - formattedPattern = formattedPattern.replace("h", "HH").replace(" a".toRegex(), "") + formattedPattern = formattedPattern + .replace("h", "HH") + .replace("K", "HH") + .replace(" a".toRegex(), "") } return SimpleDateFormat(formattedPattern, Locale.getDefault()) -- GitLab From 72a61f5b44a5aa9ddb4c5e8285924abe4cca73c0 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 7 Jan 2020 22:53:44 -0500 Subject: [PATCH 079/213] =?UTF-8?q?Don=E2=80=99t=20use=20config=20override?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1526 --- .../main/java/com/android/mms/transaction/DownloadManager.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/DownloadManager.java b/android-smsmms/src/main/java/com/android/mms/transaction/DownloadManager.java index 5ac48647f..698817ca6 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/DownloadManager.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/DownloadManager.java @@ -80,8 +80,6 @@ public class DownloadManager { String httpParams = MmsConfig.getHttpParams(); if (!TextUtils.isEmpty(httpParams)) { configOverrides.putString(SmsManager.MMS_CONFIG_HTTP_PARAMS, httpParams); - } else { - configOverrides = smsManager.getCarrierConfigValues(); } grantUriPermission(context, contentUri); -- GitLab From 0f21fc80c22c4ac22f575018b78512a0bd21f42c Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 8 Jan 2020 18:59:46 -0500 Subject: [PATCH 080/213] Improve MMS attachment quality Fixes #1551 --- .../util/extensions/GlobalExtensions.kt | 5 + data/build.gradle | 1 + .../QKSMS/repository/ImageRepositoryImpl.kt | 86 ------------- .../QKSMS/repository/MessageRepositoryImpl.kt | 115 +++++++++++++----- .../com/moez/QKSMS/util/GlideAppModule.kt | 1 + .../java/com/moez/QKSMS/util/ImageUtils.kt | 80 +++--------- .../moez/QKSMS/repository/ImageRepository.kt | 28 ----- .../java/com/moez/QKSMS/util/Preferences.kt | 2 +- .../com/moez/QKSMS/injection/AppModule.kt | 5 - presentation/src/main/res/values/strings.xml | 4 +- 10 files changed, 113 insertions(+), 214 deletions(-) create mode 100644 common/src/main/java/com/moez/QKSMS/common/util/extensions/GlobalExtensions.kt delete mode 100644 data/src/main/java/com/moez/QKSMS/repository/ImageRepositoryImpl.kt delete mode 100644 domain/src/main/java/com/moez/QKSMS/repository/ImageRepository.kt diff --git a/common/src/main/java/com/moez/QKSMS/common/util/extensions/GlobalExtensions.kt b/common/src/main/java/com/moez/QKSMS/common/util/extensions/GlobalExtensions.kt new file mode 100644 index 000000000..69b16be1d --- /dev/null +++ b/common/src/main/java/com/moez/QKSMS/common/util/extensions/GlobalExtensions.kt @@ -0,0 +1,5 @@ +package com.moez.QKSMS.common.util.extensions + +fun now(): Long { + return System.currentTimeMillis() +} diff --git a/data/build.gradle b/data/build.gradle index f8c517b1a..099338873 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation "androidx.exifinterface:exifinterface:$androidx_exifinterface_version" // glide + implementation "com.github.bumptech.glide:gifencoder-integration:$glide_version" implementation "com.github.bumptech.glide:glide:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version" diff --git a/data/src/main/java/com/moez/QKSMS/repository/ImageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ImageRepositoryImpl.kt deleted file mode 100644 index a4aee3642..000000000 --- a/data/src/main/java/com/moez/QKSMS/repository/ImageRepositoryImpl.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.repository - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Matrix -import android.net.Uri -import androidx.exifinterface.media.ExifInterface -import javax.inject.Inject - -class ImageRepositoryImpl @Inject constructor(private val context: Context) : ImageRepository { - - override fun loadImage(uri: Uri, width: Int, height: Int): Bitmap? { - val orientation = context.contentResolver.openInputStream(uri)?.use(::ExifInterface) - ?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) - val rotated = orientation == ExifInterface.ORIENTATION_ROTATE_90 - || orientation == ExifInterface.ORIENTATION_ROTATE_270 - - // Determine the dimensions - val dimensionsOptions = BitmapFactory.Options().apply { inJustDecodeBounds = true } - BitmapFactory.decodeStream(context.contentResolver.openInputStream(uri), null, dimensionsOptions) - val srcWidth = if (rotated) dimensionsOptions.outHeight else dimensionsOptions.outWidth - val srcHeight = if (rotated) dimensionsOptions.outWidth else dimensionsOptions.outHeight - - // If we get the dimensions and they don't exceed the max size, we don't need to scale - val inputStream = context.contentResolver.openInputStream(uri) - val bitmap = if ((width == 0 || srcWidth < width) && (height == 0 || srcHeight < height)) { - BitmapFactory.decodeStream(inputStream) - } else { - val widthScaleFactor = width.toDouble() / srcWidth - val heightScaleFactor = height.toDouble() / srcHeight - val options = when { - widthScaleFactor > heightScaleFactor -> BitmapFactory.Options().apply { - inScaled = true - inSampleSize = 4 - inDensity = srcHeight - inTargetDensity = height * inSampleSize - } - - else -> BitmapFactory.Options().apply { - inScaled = true - inSampleSize = 4 - inDensity = srcWidth - inTargetDensity = width * inSampleSize - } - } - BitmapFactory.decodeStream(inputStream, null, options) ?: return null - } - - return when (orientation) { - ExifInterface.ORIENTATION_ROTATE_90 -> rotateBitmap(bitmap, 90f) - ExifInterface.ORIENTATION_ROTATE_180 -> rotateBitmap(bitmap, 180f) - ExifInterface.ORIENTATION_ROTATE_270 -> rotateBitmap(bitmap, 270f) - else -> bitmap - } - } - - private fun rotateBitmap(bitmap: Bitmap, degree: Float): Bitmap { - val w = bitmap.width - val h = bitmap.height - - val mtx = Matrix() - mtx.postRotate(degree) - - return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true) - } - -} diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index af81e68af..ca45deae9 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -24,6 +24,7 @@ import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.Intent +import android.graphics.BitmapFactory import android.media.MediaScannerConnection import android.os.Build import android.os.Environment @@ -40,6 +41,7 @@ import com.google.android.mms.pdu_alt.PduPersister import com.klinker.android.send_message.SmsManagerFactory import com.klinker.android.send_message.StripAccents import com.klinker.android.send_message.Transaction +import com.moez.QKSMS.common.util.extensions.now import com.moez.QKSMS.compat.TelephonyCompat import com.moez.QKSMS.extensions.anyOf import com.moez.QKSMS.manager.ActiveConversationManager @@ -66,12 +68,12 @@ import java.io.FileOutputStream import java.io.IOException import javax.inject.Inject import javax.inject.Singleton +import kotlin.math.sqrt @Singleton class MessageRepositoryImpl @Inject constructor( private val activeConversationManager: ActiveConversationManager, private val context: Context, - private val imageRepository: ImageRepository, private val messageIds: KeyManager, private val phoneNumberUtils: PhoneNumberUtils, private val prefs: Preferences, @@ -329,49 +331,98 @@ class MessageRepositoryImpl @Inject constructor( alarmManager.setExact(AlarmManager.RTC_WAKEUP, sendTime, intent) } } else { // No delay - val message = insertSentSms(subId, threadId, addresses.first(), strippedBody, System.currentTimeMillis()) + val message = insertSentSms(subId, threadId, addresses.first(), strippedBody, now()) sendSms(message) } } else { // MMS val parts = arrayListOf() - if (signedBody.isNotBlank()) { - parts += MMSPart("text", ContentType.TEXT_PLAIN, signedBody.toByteArray()) - } + val maxWidth = smsManager.carrierConfigValues.getInt(SmsManager.MMS_CONFIG_MAX_IMAGE_WIDTH) + .takeIf { prefs.mmsSize.get() == -1 } ?: Int.MAX_VALUE - val smsManager = subId.takeIf { it != -1 } - ?.let(SmsManagerFactory::createSmsManager) - ?: SmsManager.getDefault() - val width = smsManager.carrierConfigValues.getInt(SmsManager.MMS_CONFIG_MAX_IMAGE_WIDTH) - val height = smsManager.carrierConfigValues.getInt(SmsManager.MMS_CONFIG_MAX_IMAGE_HEIGHT) + val maxHeight = smsManager.carrierConfigValues.getInt(SmsManager.MMS_CONFIG_MAX_IMAGE_HEIGHT) + .takeIf { prefs.mmsSize.get() == -1 } ?: Int.MAX_VALUE - // Add the GIFs as attachments - parts += attachments - .mapNotNull { attachment -> attachment as? Attachment.Image } - .filter { attachment -> attachment.isGif(context) } - .mapNotNull { attachment -> attachment.getUri() } - .map { uri -> ImageUtils.compressGif(context, uri, prefs.mmsSize.get() * 1024) } - .map { bitmap -> MMSPart("image", ContentType.IMAGE_GIF, bitmap) } + var remainingBytes = when (prefs.mmsSize.get()) { + -1 -> smsManager.carrierConfigValues.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE) + 0 -> Int.MAX_VALUE + else -> prefs.mmsSize.get() * 1024 + } * 0.9 // Ugly, but buys us a bit of wiggle room - // Compress the images and add them as attachments - var totalImageBytes = 0 - parts += attachments - .mapNotNull { attachment -> attachment as? Attachment.Image } - .filter { attachment -> !attachment.isGif(context) } - .mapNotNull { attachment -> attachment.getUri() } - .mapNotNull { uri -> tryOrNull { imageRepository.loadImage(uri, width, height) } } - .also { totalImageBytes = it.sumBy { it.allocationByteCount } } - .map { bitmap -> - val byteRatio = bitmap.allocationByteCount / totalImageBytes.toFloat() - ImageUtils.compressBitmap(bitmap, (prefs.mmsSize.get() * 1024 * byteRatio).toInt()) - } - .map { bitmap -> MMSPart("image", ContentType.IMAGE_JPEG, bitmap) } + signedBody.takeIf { it.isNotEmpty() }?.toByteArray()?.let { bytes -> + remainingBytes -= bytes.size + parts += MMSPart("text", ContentType.TEXT_PLAIN, bytes) + } - // Send contacts + // Attach contacts parts += attachments .mapNotNull { attachment -> attachment as? Attachment.Contact } .map { attachment -> attachment.vCard.toByteArray() } - .map { vCard -> MMSPart("contact", ContentType.TEXT_VCARD, vCard) } + .map { vCard -> + remainingBytes -= vCard.size + MMSPart("contact", ContentType.TEXT_VCARD, vCard) + } + + val imageBytesByAttachment = attachments + .mapNotNull { attachment -> attachment as? Attachment.Image } + .associateWith { attachment -> + val uri = attachment.getUri() ?: return@associateWith byteArrayOf() + when (attachment.isGif(context)) { + true -> ImageUtils.getScaledGif(context, uri, maxWidth, maxHeight) + false -> ImageUtils.getScaledImage(context, uri, maxWidth, maxHeight) + } + } + .toMutableMap() + + val imageByteCount = imageBytesByAttachment.values.sumBy { byteArray -> byteArray.size } + if (imageByteCount > remainingBytes) { + imageBytesByAttachment.forEach { (attachment, originalBytes) -> + val uri = attachment.getUri() ?: return@forEach + val maxBytes = originalBytes.size / imageByteCount.toFloat() * remainingBytes + + // Get the image dimensions + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeStream(context.contentResolver.openInputStream(uri), null, options) + val width = options.outWidth + val height = options.outHeight + val aspectRatio = width.toFloat() / height.toFloat() + + var attempts = 0 + var scaledBytes = originalBytes + + while (scaledBytes.size > maxBytes) { + // Estimate how much we need to scale the image down by. If it's still too big, we'll need to + // try smaller and smaller values + val scale = maxBytes / originalBytes.size * (0.9 - attempts * 0.2) + if (scale <= 0) { + Timber.w("Failed to compress ${originalBytes.size / 1024}Kb to ${maxBytes.toInt() / 1024}Kb") + return@forEach + } + + val newArea = scale * width * height + val newWidth = sqrt(newArea * aspectRatio).toInt() + val newHeight = (newWidth / aspectRatio).toInt() + + attempts++ + scaledBytes = when (attachment.isGif(context)) { + true -> ImageUtils.getScaledGif(context, uri, newWidth, newHeight, 80) + false -> ImageUtils.getScaledImage(context, uri, newWidth, newHeight, 80) + } + + Timber.d("Compression attempt $attempts: ${scaledBytes.size / 1024}/${maxBytes.toInt() / 1024}Kb ($width*$height -> $newWidth*$newHeight)") + } + + Timber.v("Compressed ${originalBytes.size / 1024}Kb to ${scaledBytes.size / 1024}Kb with a target size of ${maxBytes.toInt() / 1024}Kb in $attempts attempts") + imageBytesByAttachment[attachment] = scaledBytes + } + } + + imageBytesByAttachment.forEach { (attachment, bytes) -> + parts += when (attachment.isGif(context)) { + true -> MMSPart("image", ContentType.IMAGE_GIF, bytes) + false -> MMSPart("image", ContentType.IMAGE_JPEG, bytes) + } + } // We need to strip the separators from outgoing MMS, or else they'll appear to have sent and not go through val transaction = Transaction(context) diff --git a/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt b/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt index cddc39f2a..294a2c647 100644 --- a/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt +++ b/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt @@ -34,6 +34,7 @@ class GlideAppModule : AppGlideModule() { } override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + // registry.prepend(GifDrawable::class.java, ReEncodingGifResourceEncoder(context, glide.bitmapPool)) } } diff --git a/data/src/main/java/com/moez/QKSMS/util/ImageUtils.kt b/data/src/main/java/com/moez/QKSMS/util/ImageUtils.kt index c9d613d83..ff74b7fd5 100644 --- a/data/src/main/java/com/moez/QKSMS/util/ImageUtils.kt +++ b/data/src/main/java/com/moez/QKSMS/util/ImageUtils.kt @@ -19,77 +19,35 @@ package com.moez.QKSMS.util import android.content.Context -import android.graphics.Bitmap import android.net.Uri -import com.bumptech.glide.load.resource.bitmap.CenterCrop import java.io.ByteArrayOutputStream object ImageUtils { - fun compressGif(context: Context, uri: Uri, maxBytes: Int): ByteArray { - val request = GlideApp + fun getScaledGif(context: Context, uri: Uri, maxWidth: Int, maxHeight: Int, quality: Int = 90): ByteArray { + val gif = GlideApp .with(context) .asGif() .load(uri) - .transform(CenterCrop()) - - val gif = request.submit().get() - - val width = gif.firstFrame.width - val height = gif.firstFrame.height - - val stream = ByteArrayOutputStream() - GifEncoder(context, GlideApp.get(context).bitmapPool) - .encodeTransformedToStream(gif, stream) - - val unscaledBytes = stream.size().toDouble() - - var attempts = 0 - var bytes = unscaledBytes - while (maxBytes > 0 && bytes > maxBytes) { - val scale = Math.sqrt(maxBytes / unscaledBytes) * (1 - attempts * 0.1) - - val scaledGif = request.submit((width * scale).toInt(), (height * scale).toInt()).get() - - stream.reset() - GifEncoder(context, GlideApp.get(context).bitmapPool) - .encodeTransformedToStream(scaledGif, stream) - - attempts++ - bytes = stream.size().toDouble() - } - - return stream.toByteArray() + .centerInside() + .encodeQuality(quality) + .submit(maxWidth, maxHeight) + .get() + + val outputStream = ByteArrayOutputStream() + GifEncoder(context, GlideApp.get(context).bitmapPool).encodeTransformedToStream(gif, outputStream) + return outputStream.toByteArray() } - fun compressBitmap(src: Bitmap, maxBytes: Int): ByteArray { - val quality = 90 - - val height = src.height - val width = src.width - - val stream = ByteArrayOutputStream() - src.compress(Bitmap.CompressFormat.JPEG, quality, stream) - - val unscaledBytes = stream.size().toDouble() - - // Based on the byte size of the bitmap, we'll try to reduce the image's dimensions such - // that it will fit within the max byte size set. If we don't get it right the first time, - // use a slightly heavier compression until we fit within the max size - var attempts = 0 - var bytes = unscaledBytes - while (maxBytes > 0 && bytes > maxBytes) { - val scale = Math.sqrt(maxBytes / unscaledBytes) * (1 - attempts * 0.1) - - stream.reset() - Bitmap.createScaledBitmap(src, (width * scale).toInt(), (height * scale).toInt(), true) - .compress(Bitmap.CompressFormat.JPEG, quality, stream) - - attempts++ - bytes = stream.size().toDouble() - } - - return stream.toByteArray().also { src.recycle() } + fun getScaledImage(context: Context, uri: Uri, maxWidth: Int, maxHeight: Int, quality: Int = 90): ByteArray { + return GlideApp + .with(context) + .`as`(ByteArray::class.java) + .load(uri) + .centerInside() + .encodeQuality(quality) + .submit(maxWidth, maxHeight) + .get() } } \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ImageRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ImageRepository.kt deleted file mode 100644 index da02a1f8b..000000000 --- a/domain/src/main/java/com/moez/QKSMS/repository/ImageRepository.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.repository - -import android.graphics.Bitmap -import android.net.Uri - -interface ImageRepository { - - fun loadImage(uri: Uri, width: Int, height: Int): Bitmap? - -} diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index d783e0249..e994247df 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -110,7 +110,7 @@ class Preferences @Inject constructor( val unicode = rxPrefs.getBoolean("unicode", false) val mobileOnly = rxPrefs.getBoolean("mobileOnly", false) val longAsMms = rxPrefs.getBoolean("longAsMms", false) - val mmsSize = rxPrefs.getInteger("mmsSize", 300) + val mmsSize = rxPrefs.getInteger("mmsSize", -1) val logging = rxPrefs.getBoolean("logging", false) init { diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index 0350da761..dc9adb0f8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -76,8 +76,6 @@ import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ContactRepositoryImpl import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.ConversationRepositoryImpl -import com.moez.QKSMS.repository.ImageRepository -import com.moez.QKSMS.repository.ImageRepositoryImpl import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.repository.MessageRepositoryImpl import com.moez.QKSMS.repository.ScheduledMessageRepository @@ -205,9 +203,6 @@ class AppModule(private var application: Application) { @Provides fun provideConversationRepository(repository: ConversationRepositoryImpl): ConversationRepository = repository - @Provides - fun provideImageRepository(repository: ImageRepositoryImpl): ImageRepository = repository - @Provides fun provideMessageRepository(repository: MessageRepositoryImpl): MessageRepository = repository diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index bfecb0438..13dfecddb 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -434,9 +434,10 @@ + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB @@ -444,6 +445,7 @@ + -1 100 200 300 -- GitLab From f8a00d73dafdf54ae2a5779bfdb0b8469c21cac4 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 8 Jan 2020 23:33:36 -0500 Subject: [PATCH 081/213] Fix build --- data/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/data/build.gradle b/data/build.gradle index 099338873..f8c517b1a 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -53,7 +53,6 @@ dependencies { implementation "androidx.exifinterface:exifinterface:$androidx_exifinterface_version" // glide - implementation "com.github.bumptech.glide:gifencoder-integration:$glide_version" implementation "com.github.bumptech.glide:glide:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version" -- GitLab From ea2edbb8fecc381100d1ed88adb7790bb5dfcc19 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 11 Jan 2020 14:03:17 -0500 Subject: [PATCH 082/213] Rewrite ConversationInfo screen Fixes #1413 --- .../ConversationInfoAdapter.kt | 153 ++++++++++++++++++ .../ConversationInfoController.kt | 75 +++------ .../conversationinfo/ConversationInfoItem.kt | 20 +++ .../ConversationInfoPresenter.kt | 50 +++--- .../conversationinfo/ConversationInfoState.kt | 13 +- .../conversationinfo/ConversationInfoView.kt | 1 + .../ConversationMediaAdapter.kt | 63 -------- .../ConversationRecipientAdapter.kt | 82 ---------- .../GridSpacingItemDecoration.kt | 22 ++- .../layout/conversation_info_controller.xml | 92 +---------- .../res/layout/conversation_info_settings.xml | 74 +++++++++ .../layout/conversation_media_list_item.xml | 5 +- 12 files changed, 324 insertions(+), 326 deletions(-) create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoAdapter.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoItem.kt delete mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationMediaAdapter.kt delete mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt create mode 100644 presentation/src/main/res/layout/conversation_info_settings.xml 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 new file mode 100644 index 000000000..f75034171 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoAdapter.kt @@ -0,0 +1,153 @@ +package com.moez.QKSMS.feature.conversationinfo + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.jakewharton.rxbinding2.view.clicks +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkAdapter +import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.extensions.isVideo +import com.moez.QKSMS.feature.conversationinfo.ConversationInfoItem.* +import com.moez.QKSMS.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( + private val context: Context, + private val colors: Colors +) : QkAdapter() { + + val recipientClicks: Subject = PublishSubject.create() + val recipientLongClicks: Subject = PublishSubject.create() + val themeClicks: Subject = PublishSubject.create() + val nameClicks: Subject = PublishSubject.create() + val notificationClicks: Subject = PublishSubject.create() + val archiveClicks: Subject = PublishSubject.create() + val blockClicks: Subject = PublishSubject.create() + val deleteClicks: Subject = PublishSubject.create() + val mediaClicks: Subject = PublishSubject.create() + + 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) + } + + 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) + } + } + + 1 -> QkViewHolder(inflater.inflate(R.layout.conversation_info_settings, parent, false)).apply { + groupName.clicks().subscribe(nameClicks) + notifications.clicks().subscribe(notificationClicks) + archive.clicks().subscribe(archiveClicks) + block.clicks().subscribe(blockClicks) + 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) + } + } + + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + when (val item = getItem(position)) { + is ConversationInfoRecipient -> { + val recipient = item.value + holder.avatar.setRecipient(recipient) + + holder.name.text = recipient.contact?.name ?: recipient.address + + holder.address.text = recipient.address + holder.address.setVisible(recipient.contact != null) + + holder.add.setVisible(recipient.contact == null) + + val theme = colors.theme(recipient) + holder.theme.setTint(theme.theme) + } + + is ConversationInfoSettings -> { + holder.groupName.isVisible = item.recipients.size > 1 + holder.groupName.summary = item.name + + holder.notifications.isEnabled = !item.blocked + + holder.archive.isEnabled = !item.blocked + holder.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) { + true -> R.string.info_unblock + false -> R.string.info_block + }) + } + + is ConversationInfoMedia -> { + val part = item.value + + GlideApp.with(context) + .load(part.getUri()) + .fitCenter() + .into(holder.thumbnail) + + holder.video.isVisible = part.isVideo() + } + } + } + + override fun getItemViewType(position: Int): Int { + return when (data[position]) { + is ConversationInfoRecipient -> 0 + is ConversationInfoSettings -> 1 + is ConversationInfoMedia -> 2 + } + } + + override fun areItemsTheSame(old: ConversationInfoItem, new: ConversationInfoItem): Boolean { + return when { + old is ConversationInfoRecipient && new is ConversationInfoRecipient -> { + old.value.id == new.value.id + } + + old is ConversationInfoSettings && new is ConversationInfoSettings -> { + true + } + + old is ConversationInfoMedia && new is ConversationInfoMedia -> { + old.value.id == new.value.id + } + + else -> false + } + } + +} 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 d618b344d..95a1f241b 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 @@ -20,15 +20,13 @@ package com.moez.QKSMS.feature.conversationinfo import android.view.View import androidx.appcompat.app.AlertDialog +import androidx.recyclerview.widget.GridLayoutManager import com.bluelinelabs.conductor.RouterTransaction -import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController -import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.scrapViews -import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.FieldDialog import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoModule @@ -49,9 +47,7 @@ class ConversationInfoController( @Inject override lateinit var presenter: ConversationInfoPresenter @Inject lateinit var blockingDialog: BlockingDialog @Inject lateinit var navigator: Navigator - @Inject lateinit var recipientAdapter: ConversationRecipientAdapter - @Inject lateinit var mediaAdapter: ConversationMediaAdapter - @Inject lateinit var itemDecoration: GridSpacingItemDecoration + @Inject lateinit var adapter: ConversationInfoAdapter private val nameDialog: FieldDialog by lazy { FieldDialog(activity!!, activity!!.getString(R.string.info_name), nameChangeSubject::onNext) @@ -71,16 +67,17 @@ class ConversationInfoController( } override fun onViewCreated() { - items.postDelayed({ items?.animateLayoutChanges = true }, 100) - - recipients.adapter = recipientAdapter - - media.adapter = mediaAdapter - media.addItemDecoration(itemDecoration) + recyclerView.adapter = adapter + recyclerView.addItemDecoration(GridSpacingItemDecoration(adapter, activity!!)) + recyclerView.layoutManager = GridLayoutManager(activity, 3).apply { + spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int = if (adapter.getItemViewType(position) == 2) 1 else 3 + } + } themedActivity?.theme ?.autoDisposable(scope()) - ?.subscribe { recipients.scrapViews() } + ?.subscribe { recyclerView.scrapViews() } } override fun onAttach(view: View) { @@ -90,53 +87,27 @@ class ConversationInfoController( showBackButton(true) } - override fun recipientClicks(): Observable = recipientAdapter.recipientClicks - - override fun recipientLongClicks(): Observable = recipientAdapter.recipientLongClicks - - override fun themeClicks(): Observable = recipientAdapter.themeClicks - - override fun nameClicks(): Observable<*> = name.clicks() - - override fun nameChanges(): Observable = nameChangeSubject - - override fun notificationClicks(): Observable<*> = notifications.clicks() - - override fun archiveClicks(): Observable<*> = archive.clicks() - - override fun blockClicks(): Observable<*> = block.clicks() - - override fun deleteClicks(): Observable<*> = delete.clicks() - - override fun confirmDelete(): Observable<*> = confirmDeleteSubject - override fun render(state: ConversationInfoState) { if (state.hasError) { activity?.finish() return } - recipientAdapter.updateData(state.recipients) - - name.setVisible(state.recipients?.size ?: 0 >= 2) - name.summary = state.name - - notifications.isEnabled = !state.blocked - - archive.isEnabled = !state.blocked - archive.title = activity?.getString(when (state.archived) { - true -> R.string.info_unarchive - false -> R.string.info_archive - }) - - block.title = activity?.getString(when (state.blocked) { - true -> R.string.info_unblock - false -> R.string.info_block - }) - - mediaAdapter.updateData(state.media) + adapter.data = state.data } + override fun recipientClicks(): Observable = adapter.recipientClicks + override fun recipientLongClicks(): Observable = adapter.recipientLongClicks + override fun themeClicks(): Observable = adapter.themeClicks + override fun nameClicks(): Observable<*> = adapter.nameClicks + override fun nameChanges(): Observable = nameChangeSubject + override fun notificationClicks(): Observable<*> = adapter.notificationClicks + override fun archiveClicks(): Observable<*> = adapter.archiveClicks + override fun blockClicks(): Observable<*> = adapter.blockClicks + override fun deleteClicks(): Observable<*> = adapter.deleteClicks + override fun confirmDelete(): Observable<*> = confirmDeleteSubject + override fun mediaClicks(): Observable = adapter.mediaClicks + override fun showNameDialog(name: String) = nameDialog.setText(name).show() override fun showThemePicker(recipientId: Long) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoItem.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoItem.kt new file mode 100644 index 000000000..810aa21c8 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoItem.kt @@ -0,0 +1,20 @@ +package com.moez.QKSMS.feature.conversationinfo + +import com.moez.QKSMS.model.MmsPart +import com.moez.QKSMS.model.Recipient +import io.realm.RealmList + +sealed class ConversationInfoItem { + + data class ConversationInfoRecipient(val value: Recipient) : ConversationInfoItem() + + data class ConversationInfoSettings( + val name: String, + val recipients: RealmList, + val archived: Boolean, + val blocked: Boolean + ) : ConversationInfoItem() + + data class ConversationInfoMedia(val value: MmsPart) : ConversationInfoItem() + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt index f595b0623..2ad141bd7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt @@ -27,6 +27,8 @@ import com.moez.QKSMS.common.util.ClipboardUtils import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.extensions.asObservable import com.moez.QKSMS.extensions.mapNotNull +import com.moez.QKSMS.feature.conversationinfo.ConversationInfoItem.ConversationInfoMedia +import com.moez.QKSMS.feature.conversationinfo.ConversationInfoItem.ConversationInfoRecipient import com.moez.QKSMS.interactor.DeleteConversations import com.moez.QKSMS.interactor.MarkArchived import com.moez.QKSMS.interactor.MarkUnarchived @@ -37,6 +39,7 @@ import com.moez.QKSMS.repository.MessageRepository import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.subjects.BehaviorSubject @@ -55,7 +58,7 @@ class ConversationInfoPresenter @Inject constructor( private val navigator: Navigator, private val permissionManager: PermissionManager ) : QkPresenter( - ConversationInfoState(threadId = threadId, media = messageRepo.getPartsForConversation(threadId)) + ConversationInfoState(threadId = threadId) ) { private val conversation: Subject = BehaviorSubject.create() @@ -77,29 +80,25 @@ class ConversationInfoPresenter @Inject constructor( disposables += markUnarchived disposables += deleteConversations - // Update the recipients whenever they change - disposables += conversation - .map { conversation -> conversation.recipients } - .distinctUntilChanged() - .subscribe { recipients -> newState { copy(recipients = recipients) } } + val partsObservable = messageRepo.getPartsForConversation(threadId) + .asObservable() + .filter { parts -> parts.isLoaded && parts.isValid} - // Update conversation title whenever it changes - disposables += conversation - .map { conversation -> conversation.name } - .distinctUntilChanged() - .subscribe { name -> newState { copy(name = name) } } - - // Update the view's archived state whenever it changes - disposables += conversation - .map { conversation -> conversation.archived } - .distinctUntilChanged() - .subscribe { archived -> newState { copy(archived = archived) } } - - // Update the view's blocked state whenever it changes - disposables += conversation - .map { conversation -> conversation.blocked } - .distinctUntilChanged() - .subscribe { blocked -> newState { copy(blocked = blocked) } } + disposables += Observables + .combineLatest(conversation, partsObservable) { conversation, parts -> + val data = mutableListOf() + + data += conversation.recipients.map(::ConversationInfoRecipient) + data += ConversationInfoItem.ConversationInfoSettings( + name = conversation.name, + recipients = conversation.recipients, + archived = conversation.archived, + blocked = conversation.blocked) + data += parts.map(::ConversationInfoMedia) + + newState { copy(data = data) } + } + .subscribe() } override fun bindIntents(view: ConversationInfoView) { @@ -180,6 +179,11 @@ class ConversationInfoPresenter @Inject constructor( .withLatestFrom(conversation) { _, conversation -> conversation } .autoDisposable(view.scope()) .subscribe { conversation -> deleteConversations.execute(listOf(conversation.id)) } + + // Media + view.mediaClicks() + .autoDisposable(view.scope()) + .subscribe(navigator::showMedia) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoState.kt index cbda9f47e..693a862ee 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoState.kt @@ -18,17 +18,8 @@ */ package com.moez.QKSMS.feature.conversationinfo -import com.moez.QKSMS.model.MmsPart -import com.moez.QKSMS.model.Recipient -import io.realm.RealmList -import io.realm.RealmResults - data class ConversationInfoState( - val name: String = "", - val recipients: RealmList? = null, val threadId: Long = 0, - val archived: Boolean = false, - val blocked: Boolean = false, - val media: RealmResults? = null, + val data: List = listOf(), val hasError: Boolean = false -) \ No newline at end of file +) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt index 343dd8d03..5a86ffffe 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt @@ -33,6 +33,7 @@ interface ConversationInfoView : QkViewContract { fun blockClicks(): Observable<*> fun deleteClicks(): Observable<*> fun confirmDelete(): Observable<*> + fun mediaClicks(): Observable fun showNameDialog(name: String) fun showThemePicker(recipientId: Long) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationMediaAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationMediaAdapter.kt deleted file mode 100644 index 16565c6d1..000000000 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationMediaAdapter.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.feature.conversationinfo - -import android.content.Context -import android.view.LayoutInflater -import android.view.ViewGroup -import com.moez.QKSMS.R -import com.moez.QKSMS.common.Navigator -import com.moez.QKSMS.common.base.QkRealmAdapter -import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.extensions.isVideo -import com.moez.QKSMS.model.MmsPart -import com.moez.QKSMS.util.GlideApp -import kotlinx.android.synthetic.main.conversation_media_list_item.* -import kotlinx.android.synthetic.main.conversation_media_list_item.view.* -import javax.inject.Inject - -class ConversationMediaAdapter @Inject constructor( - private val context: Context, - private val navigator: Navigator -) : QkRealmAdapter() { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val inflater = LayoutInflater.from(parent.context) - val view = inflater.inflate(R.layout.conversation_media_list_item, parent, false) - return QkViewHolder(view).apply { - view.thumbnail.setOnClickListener { - val part = getItem(adapterPosition) ?: return@setOnClickListener - navigator.showMedia(part.id) - } - } - } - - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val part = getItem(position) ?: return - - GlideApp.with(context) - .load(part.getUri()) - .fitCenter() - .into(holder.thumbnail) - - holder.video.setVisible(part.isVideo()) - } - -} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt deleted file mode 100644 index f165e5429..000000000 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.feature.conversationinfo - -import android.view.LayoutInflater -import android.view.ViewGroup -import com.moez.QKSMS.R -import com.moez.QKSMS.common.base.QkRealmAdapter -import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.common.util.Colors -import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.model.Recipient -import io.reactivex.subjects.PublishSubject -import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.conversation_recipient_list_item.* -import kotlinx.android.synthetic.main.conversation_recipient_list_item.view.* -import javax.inject.Inject - -class ConversationRecipientAdapter @Inject constructor( - private val colors: Colors -) : QkRealmAdapter() { - - val recipientClicks: Subject = PublishSubject.create() - val recipientLongClicks: Subject = PublishSubject.create() - val themeClicks: Subject = PublishSubject.create() - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val layoutInflater = LayoutInflater.from(parent.context) - val view = layoutInflater.inflate(R.layout.conversation_recipient_list_item, parent, false) - return QkViewHolder(view).apply { - view.setOnClickListener { - val recipient = getItem(adapterPosition) ?: return@setOnClickListener - recipientClicks.onNext(recipient.id) - } - - view.setOnLongClickListener { - val recipient = getItem(adapterPosition) ?: return@setOnLongClickListener false - recipientLongClicks.onNext(recipient.id) - return@setOnLongClickListener true - } - - view.theme.setOnClickListener { - val recipient = getItem(adapterPosition) ?: return@setOnClickListener - themeClicks.onNext(recipient.id) - } - } - } - - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val recipient = getItem(position) ?: return - - holder.avatar.setRecipient(recipient) - - holder.name.text = recipient.contact?.name ?: recipient.address - - holder.address.text = recipient.address - holder.address.setVisible(recipient.contact != null) - - holder.add.setVisible(recipient.contact == null) - - val theme = colors.theme(recipient) - holder.theme.setTint(theme.theme) - } - -} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/GridSpacingItemDecoration.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/GridSpacingItemDecoration.kt index 6c4b4e71c..41bfdf906 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/GridSpacingItemDecoration.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/GridSpacingItemDecoration.kt @@ -23,9 +23,13 @@ import android.graphics.Rect import android.view.View import androidx.recyclerview.widget.RecyclerView import com.moez.QKSMS.common.util.extensions.dpToPx -import javax.inject.Inject +import com.moez.QKSMS.feature.conversationinfo.ConversationInfoItem.ConversationInfoMedia +import com.moez.QKSMS.feature.conversationinfo.ConversationInfoItem.ConversationInfoRecipient -class GridSpacingItemDecoration @Inject constructor(context: Context) : RecyclerView.ItemDecoration() { +class GridSpacingItemDecoration( + private val adapter: ConversationInfoAdapter, + private val context: Context +) : RecyclerView.ItemDecoration() { private val spanCount = 3 private val spacing = 2.dpToPx(context) @@ -34,13 +38,19 @@ class GridSpacingItemDecoration @Inject constructor(context: Context) : Recycler super.getItemOffsets(outRect, view, parent, state) val position = parent.getChildAdapterPosition(view) - val column = position % spanCount + val item = adapter.getItem(position) - outRect.left = column * spacing / spanCount - outRect.right = spacing - (column + 1) * spacing / spanCount + if (item is ConversationInfoRecipient && adapter.getItem(position + 1) !is ConversationInfoRecipient) { + outRect.bottom = 8.dpToPx(context) + } else if (item is ConversationInfoMedia) { + val firstPartIndex = adapter.data.indexOfFirst { it is ConversationInfoMedia } + val localPartIndex = position - firstPartIndex + + val column = localPartIndex % spanCount - if (position >= spanCount) { outRect.top = spacing + outRect.left = column * spacing / spanCount + outRect.right = spacing - (column + 1) * spacing / spanCount } } diff --git a/presentation/src/main/res/layout/conversation_info_controller.xml b/presentation/src/main/res/layout/conversation_info_controller.xml index 0ccb95d7f..65558d5d7 100644 --- a/presentation/src/main/res/layout/conversation_info_controller.xml +++ b/presentation/src/main/res/layout/conversation_info_controller.xml @@ -17,91 +17,11 @@ ~ You should have received a copy of the GNU General Public License ~ along with QKSMS. If not, see . --> - - - - - - - - - - - - - - - - - - - - - - - - - + android:clipChildren="false" + android:clipToPadding="false" + android:paddingTop="8dp" + android:paddingBottom="8dp" /> diff --git a/presentation/src/main/res/layout/conversation_info_settings.xml b/presentation/src/main/res/layout/conversation_info_settings.xml new file mode 100644 index 000000000..50d5903af --- /dev/null +++ b/presentation/src/main/res/layout/conversation_info_settings.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/conversation_media_list_item.xml b/presentation/src/main/res/layout/conversation_media_list_item.xml index 9838824b4..aeee85976 100644 --- a/presentation/src/main/res/layout/conversation_media_list_item.xml +++ b/presentation/src/main/res/layout/conversation_media_list_item.xml @@ -22,11 +22,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - -- GitLab From 5c23a8acaaed934f9788fc2c211061cba9030dfb Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 11 Jan 2020 14:38:54 -0500 Subject: [PATCH 083/213] Improve randomness of contact colours --- .../src/main/java/com/moez/QKSMS/common/util/Colors.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt index 7eed92601..ed74dace7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt @@ -134,11 +134,7 @@ class Colors @Inject constructor( } private fun generateColor(recipient: Recipient): Int { - val first = recipient.contact?.name?.firstOrNull() - ?: phoneNumberUtils.normalizeNumber(recipient.address).firstOrNull() - ?: '#' - - val index = first.hashCode().absoluteValue % randomColors.size + val index = recipient.address.hashCode().absoluteValue % randomColors.size return randomColors[index] } } -- GitLab From 7ccdecb3cc993fda3d5c283a2e2bc23d30f14a36 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 11 Jan 2020 21:02:48 -0500 Subject: [PATCH 084/213] Improve randomized colours --- presentation/src/main/res/values/colors.xml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/presentation/src/main/res/values/colors.xml b/presentation/src/main/res/values/colors.xml index d9533b5e2..503891293 100644 --- a/presentation/src/main/res/values/colors.xml +++ b/presentation/src/main/res/values/colors.xml @@ -66,15 +66,13 @@ #00838F - #e57373 - #7986CB - #00BFA5 - #81C784 - #FFAB40 - #FF8A65 - #8d6e63 - #448AFF - #78909c + #06C9AF + #6DC966 + #F1AF28 + #FF8963 + #FF6969 + #5D99FF + #8899EC -- GitLab From 25904059c46a21f544e707b94a03fd46f7918cde Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 11 Jan 2020 21:45:35 -0500 Subject: [PATCH 085/213] Fix clipToOutline issues --- presentation/src/main/res/drawable/circle.xml | 5 +++-- presentation/src/main/res/layout/avatar_view.xml | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/presentation/src/main/res/drawable/circle.xml b/presentation/src/main/res/drawable/circle.xml index eeac2e096..a073500be 100644 --- a/presentation/src/main/res/drawable/circle.xml +++ b/presentation/src/main/res/drawable/circle.xml @@ -18,8 +18,9 @@ ~ along with QKSMS. If not, see . --> + android:shape="rectangle"> + - \ No newline at end of file + diff --git a/presentation/src/main/res/layout/avatar_view.xml b/presentation/src/main/res/layout/avatar_view.xml index 8f8e2aa9f..420fb7960 100644 --- a/presentation/src/main/res/layout/avatar_view.xml +++ b/presentation/src/main/res/layout/avatar_view.xml @@ -23,7 +23,6 @@ android:id="@+id/avatar" android:layout_width="match_parent" android:layout_height="match_parent" - tools:background="@color/tools_theme" tools:parentTag="com.moez.QKSMS.common.widget.AvatarView"> - \ No newline at end of file + -- GitLab From 7d9cd35c7c1ca1c6168d0afaa66164e9e2c23556 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 01:21:10 -0500 Subject: [PATCH 086/213] Use correct default val for theme observable --- .../src/main/java/com/moez/QKSMS/common/util/Colors.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt index ed74dace7..aee7f9be2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt @@ -89,7 +89,8 @@ class Colors @Inject constructor( fun themeObservable(recipient: Recipient? = null): Observable { val pref = when { - recipient == null || !prefs.autoColor.get() -> prefs.theme() + recipient == null -> prefs.theme() + prefs.autoColor.get() -> prefs.theme(recipient.id, prefs.theme().get()) else -> prefs.theme(recipient.id, generateColor(recipient)) } return pref.asObservable() -- GitLab From abd198db815c4ce8c322e0690d65c7de9c388a2f Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 02:02:39 -0500 Subject: [PATCH 087/213] Use Recipient instead of Chip --- .../QKSMS/mapper/CursorToRecipientImpl.kt | 9 +++---- .../repository/ConversationRepositoryImpl.kt | 6 +++++ .../repository/ConversationRepository.kt | 2 ++ .../QKSMS/feature/compose/ComposeActivity.kt | 6 ++--- .../QKSMS/feature/compose/ComposeState.kt | 4 +-- .../moez/QKSMS/feature/compose/ComposeView.kt | 6 ++--- .../QKSMS/feature/compose/ComposeViewModel.kt | 20 +++++++++----- .../QKSMS/feature/compose/editing/Chip.kt | 26 ------------------- .../feature/compose/editing/ChipsAdapter.kt | 16 ++++++------ .../compose/editing/DetailedChipView.kt | 14 +++++----- .../feature/contacts/ContactsViewModel.kt | 17 +++++------- 11 files changed, 55 insertions(+), 71 deletions(-) delete mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/Chip.kt diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToRecipientImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToRecipientImpl.kt index 074828909..08bf3974c 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToRecipientImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToRecipientImpl.kt @@ -37,11 +37,10 @@ class CursorToRecipientImpl @Inject constructor( const val COLUMN_ADDRESS = 1 } - override fun map(from: Cursor) = Recipient().apply { - id = from.getLong(COLUMN_ID) - address = from.getString(COLUMN_ADDRESS) - lastUpdate = System.currentTimeMillis() - } + override fun map(from: Cursor) = Recipient( + id = from.getLong(COLUMN_ID), + address = from.getString(COLUMN_ADDRESS), + lastUpdate = System.currentTimeMillis()) override fun getRecipientCursor(): Cursor? { return when (permissionManager.hasReadSms()) { diff --git a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt index 9cb9aad1a..7706c904a 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt @@ -220,6 +220,12 @@ class ConversationRepositoryImpl @Inject constructor( .observeOn(Schedulers.io()) } + override fun getRecipients(): RealmResults { + val realm = Realm.getDefaultInstance() + return realm.where(Recipient::class.java) + .findAll() + } + override fun getUnmanagedRecipients(): Observable> { val realm = Realm.getDefaultInstance() return realm.where(Recipient::class.java) diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt index 0377c6770..7ccf33ff5 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt @@ -54,6 +54,8 @@ interface ConversationRepository { fun getUnmanagedConversations(): Observable> + fun getRecipients(): RealmResults + fun getUnmanagedRecipients(): Observable> fun getRecipient(recipientId: Long): Recipient? 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 ad524c85c..a2d6cd0ab 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 @@ -54,10 +54,10 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.util.extensions.showKeyboard -import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ChipsAdapter import com.moez.QKSMS.feature.contacts.ContactsActivity import com.moez.QKSMS.model.Attachment +import com.moez.QKSMS.model.Recipient import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable import dagger.android.AndroidInjection @@ -90,7 +90,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val activityVisibleIntent: Subject = PublishSubject.create() override val chipsSelectedIntent: Subject> = PublishSubject.create() - override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } + override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } override val menuReadyIntent: Observable = menu.map { Unit } override val optionsItemIntent: Subject = PublishSubject.create() override val sendAsGroupIntent by lazy { sendAsGroupBackground.clicks() } @@ -286,7 +286,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { startActivityForResult(Intent.createChooser(intent, null), AttachContactRequestCode) } - override fun showContacts(sharing: Boolean, chips: List) { + override fun showContacts(sharing: Boolean, chips: List) { message.hideKeyboard() val serialized = HashMap(chips.associate { chip -> chip.address to chip.contact?.lookupKey }) val intent = Intent(this, ContactsActivity::class.java) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt index 0de28b089..1524b2018 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeState.kt @@ -19,17 +19,17 @@ package com.moez.QKSMS.feature.compose import com.moez.QKSMS.compat.SubscriptionInfoCompat -import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message +import com.moez.QKSMS.model.Recipient import io.realm.RealmResults data class ComposeState( val hasError: Boolean = false, val editingMode: Boolean = false, val threadId: Long = 0, - val selectedChips: List = ArrayList(), + val selectedChips: List = ArrayList(), val sendAsGroup: Boolean = true, val conversationtitle: String = "", val loading: Boolean = false, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index 56fe0f22f..d8a78eec2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -22,8 +22,8 @@ import android.net.Uri import androidx.annotation.StringRes import androidx.core.view.inputmethod.InputContentInfoCompat import com.moez.QKSMS.common.base.QkView -import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.model.Attachment +import com.moez.QKSMS.model.Recipient import io.reactivex.Observable import io.reactivex.subjects.Subject @@ -31,7 +31,7 @@ interface ComposeView : QkView { val activityVisibleIntent: Observable val chipsSelectedIntent: Subject> - val chipDeletedIntent: Subject + val chipDeletedIntent: Subject val menuReadyIntent: Observable val optionsItemIntent: Observable val sendAsGroupIntent: Observable<*> @@ -61,7 +61,7 @@ interface ComposeView : QkView { fun requestDefaultSms() fun requestStoragePermission() fun requestSmsPermission() - fun showContacts(sharing: Boolean, chips: List) + fun showContacts(sharing: Boolean, chips: List) fun themeChanged() fun showKeyboard() fun requestCamera() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 3a24af87b..5fa8815e3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -36,7 +36,6 @@ import com.moez.QKSMS.extensions.asObservable import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.extensions.mapNotNull -import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.interactor.AddScheduledMessage import com.moez.QKSMS.interactor.CancelDelayedMessage import com.moez.QKSMS.interactor.DeleteMessages @@ -49,6 +48,7 @@ import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Attachments import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message +import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository @@ -104,10 +104,10 @@ class ComposeViewModel @Inject constructor( ) { private val attachments: Subject> = BehaviorSubject.createDefault(sharedAttachments) - private val chipsReducer: Subject<(List) -> List> = PublishSubject.create() + private val chipsReducer: Subject<(List) -> List> = PublishSubject.create() private val conversation: Subject = BehaviorSubject.create() private val messages: Subject> = BehaviorSubject.create() - private val selectedChips: Subject> = BehaviorSubject.createDefault(listOf()) + private val selectedChips: Subject> = BehaviorSubject.createDefault(listOf()) private val searchResults: Subject> = BehaviorSubject.create() private val searchSelection: Subject = BehaviorSubject.createDefault(-1) @@ -163,11 +163,11 @@ class ComposeViewModel @Inject constructor( .subscribe(conversation::onNext) if (addresses.isNotEmpty()) { - selectedChips.onNext(addresses.map { address -> Chip(address) }) + selectedChips.onNext(addresses.map { address -> Recipient(address = address) }) } disposables += chipsReducer - .scan(listOf()) { previousState, reducer -> reducer(previousState) } + .scan(listOf()) { previousState, reducer -> reducer(previousState) } .doOnNext { chips -> newState { copy(selectedChips = chips) } } .skipUntil(state.filter { state -> state.editingMode }) .takeUntil(state.filter { state -> !state.editingMode }) @@ -241,13 +241,19 @@ class ComposeViewModel @Inject constructor( } // Filter out any numbers that are already selected hashmap.filter { (address) -> - chips.none { chip -> phoneNumberUtils.compare(address, chip.address) } + chips.none { recipient -> phoneNumberUtils.compare(address, recipient.address) } } } .filter { hashmap -> hashmap.isNotEmpty() } .map { hashmap -> hashmap.map { (address, lookupKey) -> - Chip(address, lookupKey?.let(contactRepo::getUnmanagedContact)) + conversationRepo.getRecipients() + .asSequence() + .filter { recipient -> recipient.contact?.lookupKey == lookupKey } + .first { recipient -> phoneNumberUtils.compare(recipient.address, address) } + ?: Recipient( + address = address, + contact = lookupKey?.let(contactRepo::getUnmanagedContact)) } } .autoDisposable(view.scope()) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/Chip.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/Chip.kt deleted file mode 100644 index 31846fbdb..000000000 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/Chip.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2019 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.feature.compose.editing - -import com.moez.QKSMS.model.Contact - -data class Chip( - val address: String, - val contact: Contact? = null -) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt index 04f5e7c78..b415d32f3 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt @@ -35,10 +35,10 @@ import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.contact_chip.* import javax.inject.Inject -class ChipsAdapter @Inject constructor() : QkAdapter() { +class ChipsAdapter @Inject constructor() : QkAdapter() { var view: RecyclerView? = null - val chipDeleted: PublishSubject = PublishSubject.create() + val chipDeleted: PublishSubject = PublishSubject.create() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val inflater = LayoutInflater.from(parent.context) @@ -57,18 +57,18 @@ class ChipsAdapter @Inject constructor() : QkAdapter() { } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val chip = getItem(position) + val recipient = getItem(position) - holder.avatar.setRecipient(Recipient(id = -1, contact = chip.contact)) - holder.name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address + holder.avatar.setRecipient(recipient) + holder.name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address } /** * The [context] has to come from a view, because we're inflating a view that used themed attrs */ - private fun showDetailedChip(context: Context, chip: Chip) { + private fun showDetailedChip(context: Context, recipient: Recipient) { val detailedChipView = DetailedChipView(context) - detailedChipView.setChip(chip) + detailedChipView.setRecipient(recipient) val rootView = view?.rootView as ViewGroup @@ -83,7 +83,7 @@ class ChipsAdapter @Inject constructor() : QkAdapter() { detailedChipView.show() detailedChipView.setOnDeleteListener { - chipDeleted.onNext(chip) + chipDeleted.onNext(recipient) detailedChipView.hide() } } 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 370d1a59c..aa90cd994 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 @@ -45,8 +45,14 @@ class DetailedChipView(context: Context) : RelativeLayout(context) { isFocusable = true isFocusableInTouchMode = true + } + + fun setRecipient(recipient: Recipient) { + avatar.setRecipient(recipient) + name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address + info.text = recipient.address - colors.theme().let { theme -> + colors.theme(recipient).let { theme -> card.setBackgroundTint(theme.theme) name.setTextColor(theme.textPrimary) info.setTextColor(theme.textTertiary) @@ -54,12 +60,6 @@ class DetailedChipView(context: Context) : RelativeLayout(context) { } } - fun setChip(chip: Chip) { - avatar.setRecipient(Recipient(id = -1, contact = chip.contact)) - name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address - info.text = chip.address - } - fun show() { startAnimation(AlphaAnimation(0f, 1f).apply { duration = 200 }) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt index cb9386f29..c101d218e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt @@ -22,7 +22,6 @@ import android.view.inputmethod.EditorInfo import com.moez.QKSMS.common.base.QkViewModel import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.extensions.removeAccents -import com.moez.QKSMS.feature.compose.editing.Chip import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.PhoneNumberAction import com.moez.QKSMS.filter.ContactFilter @@ -32,6 +31,7 @@ import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.ContactGroup import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.PhoneNumber +import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.util.PhoneNumberUtils @@ -68,7 +68,7 @@ class ContactsViewModel @Inject constructor( .observeOn(Schedulers.io()) .map { hashmap -> hashmap.map { (address, lookupKey) -> - Chip(address, lookupKey?.let(contactsRepo::getUnmanagedContact)) + Recipient(address = address, contact = lookupKey?.let(contactsRepo::getUnmanagedContact)) } } @@ -181,11 +181,10 @@ class ContactsViewModel @Inject constructor( .observeOn(Schedulers.io()) .autoDisposable(view.scope()) .subscribe { (composeItem, force) -> - val contacts = composeItem.getContacts() - val newChips = contacts.map { contact -> + view.finish(HashMap(composeItem.getContacts().associate { contact -> if (contact.numbers.size == 1 || contact.getDefaultNumber() != null && !force) { - val number = contact.getDefaultNumber() ?: contact.numbers[0]!! - Chip(number.address, contact) + val address = contact.getDefaultNumber()?.address ?: contact.numbers[0]!!.address + address to contact.lookupKey } else { runBlocking { newState { copy(selectedContact = contact) } @@ -203,12 +202,10 @@ class ContactsViewModel @Inject constructor( setDefaultPhoneNumber.execute(params) } - Chip(number.address, contact) + number.address to contact.lookupKey } ?: return@subscribe } - } - - view.finish(HashMap(newChips.associate { chip -> chip.address to chip.contact?.lookupKey })) + })) } } -- GitLab From 57ce854f18a8655ab8acff5ddf2ad4933002bdf3 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 02:05:04 -0500 Subject: [PATCH 088/213] Fix 7d9cd35c7c1ca1c6168d0afaa66164e9e2c23556 --- .../src/main/java/com/moez/QKSMS/common/util/Colors.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt index aee7f9be2..35dfe09c0 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt @@ -90,8 +90,8 @@ class Colors @Inject constructor( fun themeObservable(recipient: Recipient? = null): Observable { val pref = when { recipient == null -> prefs.theme() - prefs.autoColor.get() -> prefs.theme(recipient.id, prefs.theme().get()) - else -> prefs.theme(recipient.id, generateColor(recipient)) + prefs.autoColor.get() -> prefs.theme(recipient.id, generateColor(recipient)) + else -> prefs.theme(recipient.id, prefs.theme().get()) } return pref.asObservable() .map { color -> Theme(color, this) } -- GitLab From be741c01446bbe5d816cd92a3611b9db229368ff Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 18:32:45 -0500 Subject: [PATCH 089/213] Formatting --- .../manager/ActiveConversationManagerImpl.kt | 2 +- .../manager/ActiveConversationManager.kt | 2 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 31 +++++++++---------- .../conversations/ConversationsAdapter.kt | 8 ----- .../moez/QKSMS/feature/main/MainActivity.kt | 21 ++++++------- .../src/main/res/layout/main_activity.xml | 1 + 6 files changed, 28 insertions(+), 37 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/manager/ActiveConversationManagerImpl.kt b/data/src/main/java/com/moez/QKSMS/manager/ActiveConversationManagerImpl.kt index 65892a0de..4ef827650 100644 --- a/data/src/main/java/com/moez/QKSMS/manager/ActiveConversationManagerImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/manager/ActiveConversationManagerImpl.kt @@ -34,4 +34,4 @@ class ActiveConversationManagerImpl @Inject constructor() : ActiveConversationMa return threadId } -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/manager/ActiveConversationManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/ActiveConversationManager.kt index 35d125346..4ed432d4c 100644 --- a/domain/src/main/java/com/moez/QKSMS/manager/ActiveConversationManager.kt +++ b/domain/src/main/java/com/moez/QKSMS/manager/ActiveConversationManager.kt @@ -29,4 +29,4 @@ interface ActiveConversationManager { fun getActiveConversation(): Long? -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 5fa8815e3..201d7fa0b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -27,7 +27,6 @@ import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.ClipboardUtils -import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.MessageDetailsFormatter import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.compat.SubscriptionManagerCompat @@ -78,7 +77,6 @@ class ComposeViewModel @Inject constructor( @Named("addresses") private val addresses: List, @Named("text") private val sharedText: String, @Named("attachments") private val sharedAttachments: Attachments, - private val colors: Colors, private val contactRepo: ContactRepository, private val context: Context, private val activeConversationManager: ActiveConversationManager, @@ -449,21 +447,22 @@ class ComposeViewModel @Inject constructor( .subscribe { message -> cancelMessage.execute(message.id) } // Set the current conversation - Observables.combineLatest( - view.activityVisibleIntent.distinctUntilChanged(), - conversation.mapNotNull { conversation -> - conversation.takeIf { it.isValid }?.id - }.distinctUntilChanged()) - { visible, threadId -> - when (visible) { - true -> { - activeConversationManager.setActiveConversation(threadId) - markRead.execute(listOf(threadId)) - } + Observables + .combineLatest( + view.activityVisibleIntent.distinctUntilChanged(), + conversation.mapNotNull { conversation -> + conversation.takeIf { it.isValid }?.id + }.distinctUntilChanged()) + { visible, threadId -> + when (visible) { + true -> { + activeConversationManager.setActiveConversation(threadId) + markRead.execute(listOf(threadId)) + } - false -> activeConversationManager.setActiveConversation(null) - } - } + false -> activeConversationManager.setActiveConversation(null) + } + } .autoDisposable(view.scope()) .subscribe() 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 56b5f51e6..cf741f840 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 @@ -45,10 +45,6 @@ class ConversationsAdapter @Inject constructor( private val phoneNumberUtils: PhoneNumberUtils ) : QkRealmAdapter() { - init { - setHasStableIds(true) - } - 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) @@ -113,10 +109,6 @@ class ConversationsAdapter @Inject constructor( holder.unread.setTint(colors.theme(recipient).theme) } - override fun getItemId(index: Int): Long { - return getItem(index)!!.id - } - override fun getItemViewType(position: Int): Int { return if (getItem(position)?.unread == false) 0 else 1 } 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 114ac9645..ca2106afc 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 @@ -38,7 +38,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import com.jakewharton.rxbinding2.view.clicks import com.jakewharton.rxbinding2.widget.textChanges @@ -150,8 +149,8 @@ class MainActivity : QkThemedActivity(), MainView { homeIntent.onNext(Unit) } - recyclerView.setHasFixedSize(true) - recyclerView.layoutManager = LinearLayoutManager(this) + itemTouchCallback.adapter = conversationsAdapter + conversationsAdapter.autoScrollToStart(recyclerView) // Don't allow clicks to pass through the drawer layout drawer.clicks().autoDisposable(scope()).subscribe() @@ -161,8 +160,10 @@ class MainActivity : QkThemedActivity(), MainView { .autoDisposable(scope()) .subscribe { theme -> // Set the color for the drawer icons - val states = arrayOf(intArrayOf(android.R.attr.state_activated), + val states = arrayOf( + intArrayOf(android.R.attr.state_activated), intArrayOf(-android.R.attr.state_activated)) + resolveThemeColor(android.R.attr.textColorSecondary) .let { textSecondary -> ColorStateList(states, intArrayOf(theme.theme, textSecondary)) } .let { tintList -> @@ -185,9 +186,6 @@ class MainActivity : QkThemedActivity(), MainView { compose.setTint(theme.textPrimary) } - itemTouchCallback.adapter = conversationsAdapter - conversationsAdapter.autoScrollToStart(recyclerView) - // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { toolbarSearch.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) @@ -286,10 +284,11 @@ class MainActivity : QkThemedActivity(), MainView { inbox.isActivated = state.page is Inbox 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 (drawerLayout.isDrawerOpen(GravityCompat.START) && !state.drawerOpen) { + drawerLayout.closeDrawer(GravityCompat.START) + } else if (!drawerLayout.isDrawerVisible(GravityCompat.START) && state.drawerOpen) { + drawerLayout.openDrawer(GravityCompat.START) + } when (state.syncing) { is SyncRepository.SyncProgress.Idle -> { diff --git a/presentation/src/main/res/layout/main_activity.xml b/presentation/src/main/res/layout/main_activity.xml index ab58cee80..f19d171f7 100644 --- a/presentation/src/main/res/layout/main_activity.xml +++ b/presentation/src/main/res/layout/main_activity.xml @@ -84,6 +84,7 @@ android:clipToPadding="false" android:paddingTop="8dp" android:paddingBottom="8dp" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toTopOf="@id/bottom" app:layout_constraintTop_toBottomOf="@id/toolbar" tools:listitem="@layout/conversation_list_item" /> -- GitLab From d0251d22950ea4a33424b79e43510c5252de1f10 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 18:39:12 -0500 Subject: [PATCH 090/213] Fix weird auto scrolling issue --- .../QKSMS/common/util/extensions/ViewExtensions.kt | 10 +--------- .../java/com/moez/QKSMS/feature/main/MainViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt index ab33108fd..f2bf92d17 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt @@ -120,14 +120,6 @@ fun ViewPager.addOnPageChangeListener(listener: (Int) -> Unit) { } fun RecyclerView.scrapViews() { - val adapter = adapter - val layoutManager = layoutManager - - this.adapter = null - this.layoutManager = null - - this.adapter = adapter - this.layoutManager = layoutManager - + recycledViewPool.clear() adapter?.notifyDataSetChanged() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index bdb6f9992..a1e27f939 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -212,7 +212,7 @@ class MainViewModel @Inject constructor( prefs.keyChanges .filter { key -> key.contains("theme") } .map { true } - .mergeWith(prefs.autoColor.asObservable()) + .mergeWith(prefs.autoColor.asObservable().skip(1)) .doOnNext { view.themeChanged() } .takeUntil(view.activityResumedIntent.filter { resumed -> resumed }) } -- GitLab From f2e754e47fa2e4e2f4ac279f7487ce408e82a5bc Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 20:50:55 -0500 Subject: [PATCH 091/213] Fix QKSMS+ theme preview --- .../java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt | 4 +++- presentation/src/main/res/drawable/color_picker_gradient.xml | 3 ++- presentation/src/main/res/layout/hsv_picker_view.xml | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt index aec322ca9..155224433 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt @@ -33,7 +33,9 @@ import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.hsv_picker_view.view.* -class HSVPickerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { +class HSVPickerView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : ConstraintLayout(context, attrs) { val selectedColor: Subject = BehaviorSubject.create() diff --git a/presentation/src/main/res/drawable/color_picker_gradient.xml b/presentation/src/main/res/drawable/color_picker_gradient.xml index 74e5855c8..50f0d5cef 100644 --- a/presentation/src/main/res/drawable/color_picker_gradient.xml +++ b/presentation/src/main/res/drawable/color_picker_gradient.xml @@ -23,7 +23,8 @@ - \ No newline at end of file + diff --git a/presentation/src/main/res/layout/hsv_picker_view.xml b/presentation/src/main/res/layout/hsv_picker_view.xml index 1ae8c543d..bdc1685a7 100644 --- a/presentation/src/main/res/layout/hsv_picker_view.xml +++ b/presentation/src/main/res/layout/hsv_picker_view.xml @@ -29,7 +29,6 @@ android:layout_height="0dp" android:background="@drawable/rounded_rectangle_4dp" android:backgroundTint="@color/white" - android:rotation="90" app:layout_constraintBottom_toBottomOf="@id/saturation" app:layout_constraintEnd_toEndOf="@id/saturation" app:layout_constraintStart_toStartOf="@id/saturation" -- GitLab From 41d2e9b15d2809e23dfb016f783f7339d8f2c8fa Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 22:17:57 -0500 Subject: [PATCH 092/213] =?UTF-8?q?Don=E2=80=99t=20recreate=20existing=20n?= =?UTF-8?q?otification=20channels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moez/QKSMS/manager/NotificationManager.kt | 2 +- .../java/com/moez/QKSMS/common/Navigator.kt | 4 +- .../common/util/NotificationManagerImpl.kt | 101 +++++++++--------- 3 files changed, 56 insertions(+), 51 deletions(-) diff --git a/domain/src/main/java/com/moez/QKSMS/manager/NotificationManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/NotificationManager.kt index 7499d82ec..2af74aa71 100644 --- a/domain/src/main/java/com/moez/QKSMS/manager/NotificationManager.kt +++ b/domain/src/main/java/com/moez/QKSMS/manager/NotificationManager.kt @@ -26,7 +26,7 @@ interface NotificationManager { fun notifyFailed(threadId: Long) - fun createNotificationChannel(threadId: Long) + fun createNotificationChannel(threadId: Long = 0L) fun buildNotificationChannelId(threadId: Long): String diff --git a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt index adcabe442..8ade172da 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt @@ -286,8 +286,8 @@ class Navigator @Inject constructor( val channelId = notificationManager.buildNotificationChannelId(threadId) val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) - intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId) - intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + .putExtra(Settings.EXTRA_CHANNEL_ID, channelId) + .putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) startActivity(intent) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index 22b47ff54..1ba84dac3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -18,7 +18,6 @@ */ package com.moez.QKSMS.common.util -import android.annotation.SuppressLint import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager @@ -80,19 +79,8 @@ class NotificationManagerImpl @Inject constructor( private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager init { - @SuppressLint("NewApi") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val name = "Default" - val importance = NotificationManager.IMPORTANCE_HIGH - val channel = NotificationChannel(DEFAULT_CHANNEL_ID, name, importance).apply { - enableLights(true) - lightColor = Color.WHITE - enableVibration(true) - vibrationPattern = VIBRATE_PATTERN - } - - notificationManager.createNotificationChannel(channel) - } + // Make sure the default channel has been initialized + createNotificationChannel() } /** @@ -126,7 +114,8 @@ class NotificationManagerImpl @Inject constructor( val contentPI = taskStackBuilder.getPendingIntent(threadId.toInt() + 10000, PendingIntent.FLAG_UPDATE_CURRENT) val seenIntent = Intent(context, MarkSeenReceiver::class.java).putExtra("threadId", threadId) - val seenPI = PendingIntent.getBroadcast(context, threadId.toInt() + 20000, seenIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val seenPI = PendingIntent.getBroadcast(context, threadId.toInt() + 20000, seenIntent, + PendingIntent.FLAG_UPDATE_CURRENT) // We can't store a null preference, so map it to a null Uri if the pref string is empty val ringtone = prefs.ringtone(threadId).get() @@ -213,13 +202,15 @@ class NotificationManagerImpl @Inject constructor( notification .setLargeIcon(avatar) .setContentTitle(conversation.getTitle()) - .setContentText(context.resources.getQuantityString(R.plurals.notification_new_messages, messages.size, messages.size)) + .setContentText(context.resources.getQuantityString( + R.plurals.notification_new_messages, messages.size, messages.size)) } Preferences.NOTIFICATION_PREVIEWS_NONE -> { notification .setContentTitle(context.getString(R.string.app_name)) - .setContentText(context.resources.getQuantityString(R.plurals.notification_new_messages, messages.size, messages.size)) + .setContentText(context.resources.getQuantityString( + R.plurals.notification_new_messages, messages.size, messages.size)) } } @@ -238,7 +229,8 @@ class NotificationManagerImpl @Inject constructor( when (action) { Preferences.NOTIFICATION_ACTION_READ -> { val intent = Intent(context, MarkReadReceiver::class.java).putExtra("threadId", threadId) - val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 30000, intent, PendingIntent.FLAG_UPDATE_CURRENT) + val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 30000, intent, + PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_check_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ).build() } @@ -248,8 +240,10 @@ class NotificationManagerImpl @Inject constructor( getReplyAction(threadId) } else { val intent = Intent(context, QkReplyActivity::class.java).putExtra("threadId", threadId) - val pi = PendingIntent.getActivity(context, threadId.toInt() + 40000, intent, PendingIntent.FLAG_UPDATE_CURRENT) - NotificationCompat.Action.Builder(R.drawable.ic_reply_white_24dp, actionLabels[action], pi) + val pi = PendingIntent.getActivity(context, threadId.toInt() + 40000, intent, + PendingIntent.FLAG_UPDATE_CURRENT) + NotificationCompat.Action + .Builder(R.drawable.ic_reply_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY).build() } } @@ -258,15 +252,19 @@ class NotificationManagerImpl @Inject constructor( val address = conversation.recipients[0]?.address val intentAction = if (permissions.hasCalling()) Intent.ACTION_CALL else Intent.ACTION_DIAL val intent = Intent(intentAction, Uri.parse("tel:$address")) - val pi = PendingIntent.getActivity(context, threadId.toInt() + 50000, intent, PendingIntent.FLAG_UPDATE_CURRENT) + val pi = PendingIntent.getActivity(context, threadId.toInt() + 50000, intent, + PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_call_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_CALL).build() } Preferences.NOTIFICATION_ACTION_DELETE -> { val messageIds = messages.map { it.id }.toLongArray() - val intent = Intent(context, DeleteMessagesReceiver::class.java).putExtra("threadId", threadId).putExtra("messageIds", messageIds) - val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 60000, intent, PendingIntent.FLAG_UPDATE_CURRENT) + val intent = Intent(context, DeleteMessagesReceiver::class.java) + .putExtra("threadId", threadId) + .putExtra("messageIds", messageIds) + val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 60000, intent, + PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_delete_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_DELETE).build() } @@ -339,9 +337,11 @@ class NotificationManagerImpl @Inject constructor( private fun getReplyAction(threadId: Long): NotificationCompat.Action { val replyIntent = Intent(context, RemoteMessagingReceiver::class.java).putExtra("threadId", threadId) - val replyPI = PendingIntent.getBroadcast(context, threadId.toInt() + 40000, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val replyPI = PendingIntent.getBroadcast(context, threadId.toInt() + 40000, replyIntent, + PendingIntent.FLAG_UPDATE_CURRENT) - val title = context.resources.getStringArray(R.array.notification_actions)[Preferences.NOTIFICATION_ACTION_REPLY] + val title = context.resources.getStringArray(R.array.notification_actions)[ + Preferences.NOTIFICATION_ACTION_REPLY] val responseSet = context.resources.getStringArray(R.array.qk_responses) val remoteInput = RemoteInput.Builder("body") .setLabel(title) @@ -361,27 +361,39 @@ class NotificationManagerImpl @Inject constructor( */ override fun createNotificationChannel(threadId: Long) { - // Only proceed if the android version supports notification channels - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + // Only proceed if the android version supports notification channels, and the channel hasn't + // already been created + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || getNotificationChannel(threadId) != null) { + return + } - conversationRepo.getConversation(threadId)?.let { conversation -> - val channelId = buildNotificationChannelId(threadId) - val name = conversation.getTitle() - val importance = NotificationManager.IMPORTANCE_HIGH - val channel = NotificationChannel(channelId, name, importance).apply { + val channel = when (threadId) { + 0L -> NotificationChannel(DEFAULT_CHANNEL_ID, "Default", NotificationManager.IMPORTANCE_HIGH).apply { enableLights(true) lightColor = Color.WHITE enableVibration(true) vibrationPattern = VIBRATE_PATTERN - lockscreenVisibility = Notification.VISIBILITY_PUBLIC - setSound(prefs.ringtone().get().let(Uri::parse), AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_NOTIFICATION) - .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) - .build()) } - notificationManager.createNotificationChannel(channel) + else -> { + val conversation = conversationRepo.getConversation(threadId) ?: return + val channelId = buildNotificationChannelId(threadId) + val title = conversation.getTitle() + NotificationChannel(channelId, title, NotificationManager.IMPORTANCE_HIGH).apply { + enableLights(true) + lightColor = Color.WHITE + enableVibration(true) + vibrationPattern = VIBRATE_PATTERN + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + setSound(prefs.ringtone().get().let(Uri::parse), AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .build()) + } + } } + + notificationManager.createNotificationChannel(channel) } /** @@ -391,9 +403,8 @@ class NotificationManagerImpl @Inject constructor( val channelId = buildNotificationChannelId(threadId) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - return notificationManager - .notificationChannels - .firstOrNull { channel -> channel.id == channelId } + return notificationManager.notificationChannels + .find { channel -> channel.id == channelId } } return null @@ -407,13 +418,7 @@ class NotificationManagerImpl @Inject constructor( */ private fun getChannelIdForNotification(threadId: Long): String { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channelId = buildNotificationChannelId(threadId) - - return notificationManager - .notificationChannels - .map { channel -> channel.id } - .firstOrNull { id -> id == channelId } - ?: DEFAULT_CHANNEL_ID + return getNotificationChannel(threadId)?.id ?: DEFAULT_CHANNEL_ID } return DEFAULT_CHANNEL_ID -- GitLab From c9de1a61d93e33b3c851e337c1f409a39aa8f813 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 23:12:27 -0500 Subject: [PATCH 093/213] Leave autoColor on for everyone --- .../src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index 6e19486fe..f9f99e8b7 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -190,9 +190,6 @@ class QkRealmMigration @Inject constructor( prefs.theme(recipientId).set(theme) } - // This is enabled for new users, but the behaviour shouldn't change automatically for old users - prefs.autoColor.set(false) - version++ } -- GitLab From 287dd495f8d2af4734baeefde76c666bec530e0a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 23:30:59 -0500 Subject: [PATCH 094/213] Fix 3.8 migration --- data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index f9f99e8b7..74f3ad66d 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -178,7 +178,7 @@ class QkRealmMigration @Inject constructor( realm.where("Conversation").findAll().forEach { conversation -> val pref = prefs.theme(conversation.getLong("id")) if (pref.isSet) { - conversation.getList("Recipient").forEach { recipient -> + conversation.getList("recipients").forEach { recipient -> recipients[recipient.getLong("id")] = pref.get() } -- GitLab From e81e188a79079ca5fba107f163252be6d1204f1d Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 23:37:17 -0500 Subject: [PATCH 095/213] Set user property for autoColor pref --- .../com/moez/QKSMS/feature/settings/SettingsPresenter.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt index 5740bc321..7b2df50d4 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt @@ -27,6 +27,7 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.interactor.SyncMessages +import com.moez.QKSMS.manager.AnalyticsManager import com.moez.QKSMS.repository.SyncRepository import com.moez.QKSMS.util.NightModeManager import com.moez.QKSMS.util.Preferences @@ -42,6 +43,7 @@ import javax.inject.Inject class SettingsPresenter @Inject constructor( colors: Colors, syncRepo: SyncRepository, + private val analytics: AnalyticsManager, private val context: Context, private val billingManager: BillingManager, private val dateFormatter: DateFormatter, @@ -170,7 +172,10 @@ class SettingsPresenter @Inject constructor( R.id.textSize -> view.showTextSizePicker() - R.id.autoColor -> prefs.autoColor.set(!prefs.autoColor.get()) + R.id.autoColor -> { + analytics.setUserProperty("Preference: Auto Color", !prefs.autoColor.get()) + prefs.autoColor.set(!prefs.autoColor.get()) + } R.id.systemFont -> prefs.systemFont.set(!prefs.systemFont.get()) -- GitLab From 3f4693ee0768e98d4ec1761334c735ee127f47ed Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 23:39:20 -0500 Subject: [PATCH 096/213] Update translations --- .../src/main/res/values-ar/strings.xml | 26 +- .../src/main/res/values-bn/strings.xml | 26 +- .../src/main/res/values-cs/strings.xml | 26 +- .../src/main/res/values-da/strings.xml | 26 +- .../src/main/res/values-de/strings.xml | 14 +- .../src/main/res/values-el/strings.xml | 14 +- .../src/main/res/values-es/strings.xml | 30 +- .../src/main/res/values-fa/strings.xml | 26 +- .../src/main/res/values-fi/strings.xml | 16 +- .../src/main/res/values-fr/strings.xml | 26 +- .../src/main/res/values-hi/strings.xml | 24 +- .../src/main/res/values-hr/strings.xml | 14 +- .../src/main/res/values-hu/strings.xml | 36 +- .../src/main/res/values-in/strings.xml | 18 +- .../src/main/res/values-it/strings.xml | 18 +- .../src/main/res/values-iw/strings.xml | 14 +- .../src/main/res/values-ja/strings.xml | 16 +- .../src/main/res/values-ko/strings.xml | 108 ++--- .../src/main/res/values-lt/strings.xml | 14 +- .../src/main/res/values-nb/strings.xml | 26 +- .../src/main/res/values-ne/strings.xml | 14 +- .../src/main/res/values-nl/strings.xml | 16 +- .../src/main/res/values-pl/strings.xml | 20 +- .../src/main/res/values-pt-rBR/strings.xml | 16 +- .../src/main/res/values-pt/strings.xml | 16 +- .../src/main/res/values-ro/strings.xml | 14 +- .../src/main/res/values-ru/strings.xml | 24 +- .../src/main/res/values-sk/strings.xml | 16 +- .../src/main/res/values-sl/strings.xml | 407 ++++++++++++++++++ .../src/main/res/values-sr/strings.xml | 14 +- .../src/main/res/values-sv/strings.xml | 62 +-- .../src/main/res/values-th/strings.xml | 20 +- .../src/main/res/values-tl/strings.xml | 14 +- .../src/main/res/values-tr/strings.xml | 24 +- .../src/main/res/values-uk/strings.xml | 22 +- .../src/main/res/values-ur/strings.xml | 14 +- .../src/main/res/values-vi/strings.xml | 18 +- .../src/main/res/values-zh-rCN/strings.xml | 80 ++-- .../src/main/res/values-zh/strings.xml | 20 +- 39 files changed, 1105 insertions(+), 244 deletions(-) create mode 100644 presentation/src/main/res/values-sl/strings.xml diff --git a/presentation/src/main/res/values-ar/strings.xml b/presentation/src/main/res/values-ar/strings.xml index dc7880852..d558c7076 100644 --- a/presentation/src/main/res/values-ar/strings.xml +++ b/presentation/src/main/res/values-ar/strings.xml @@ -30,9 +30,11 @@ أكتب اسماً أو رقماً تخطي متابعة + Add person اتصال تفاصيل حفظ إلى المعرض + Share فتح درج التنقل %d محددة مسح @@ -88,6 +90,10 @@ إعادة توجيه حذف + Choose a phone number + %s ∙ Default + Just once + Always %d مختارة %1$d من %2$d النتائج إرسال رسالة جماعية @@ -128,6 +134,7 @@ تم إرسال %s فشل الإرسال. إلمس لإعادة المحاولة التفاصيل + Address copied عنوان المحادثة الإشعارات السمة @@ -193,6 +200,7 @@ وضع ليلي بسوادٍ حالك وقت البدء وقت الانتهاء + Automatic contact colors حجم الخط استخدم خط النظام إيموجي تلقائية @@ -203,6 +211,7 @@ الزر 2 الزر 3 معاينات الإشعارات + Wake screen الاهتزاز الصوت بدون نغمة @@ -232,6 +241,8 @@ حذف حركات التشكيل من المحارف في الرسائل النصية الصادرة أرقام الهواتف النقالة فقط عند إنشاء رسالة، أظهر فقط أرقام الهواتف النقالة + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply الضغط التلقائي لمرفقات رسائل الوسائط مزامنة الرسائل إعادة مزامنة رسائلك مع قاعدة البيانات الأصلية للرسائل في النظام @@ -381,13 +392,14 @@ طويل - ١٠٠ ك.ب. - ٢٠٠ ك.ب. - ٣٠٠ ك.ب. (مستحسن) - ٦٠٠ ك.ب. - ١٠٠٠ ك.ب. - ٢٠٠٠ ك.ب. - دون ضغط + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB + No compression حسنًا diff --git a/presentation/src/main/res/values-bn/strings.xml b/presentation/src/main/res/values-bn/strings.xml index fe725cba5..f45596674 100644 --- a/presentation/src/main/res/values-bn/strings.xml +++ b/presentation/src/main/res/values-bn/strings.xml @@ -30,9 +30,11 @@ নাম বা নম্বর টাইপ করুন এড়িয়ে যান চালিয়ে যান + Add person কল করুন বিস্তারিত গ্যালারীতে সেভ করুন + Share নেভিগেশন ড্রয়ার খুলুন নির্বাচিত %d ক্লিয়ার @@ -84,6 +86,10 @@ সামনে পাঠান মুছে ফেলুন + Choose a phone number + %s ∙ Default + Just once + Always নির্বাচিত %d %2$d এর %1$d ফলাফল গ্রুপ বার্তা হিসেবে প্রেরণ করুন @@ -124,6 +130,7 @@ প্রেরিত হয়েছে: %s পাঠাতে ব্যর্থ. আবার চেষ্টা করতে ট্যাপ করুন বিস্তারিত + Address copied কথোপকথনের শিরোনাম নোটিফিকেশন থিম @@ -185,6 +192,7 @@ সম্পূর্ণ কালো রাত্রি মোড শুরুর সময় শেষের সময় + Automatic contact colors ফন্টের আকার সিস্টেম ফন্ট ব্যবহার করুন স্বয়ংক্রিয় ইমোজি @@ -195,6 +203,7 @@ বাটন ২ বাটন ৩ নোটিফিকেশন প্রিভিউ + Wake screen কম্পন শব্দ কোনোটা না @@ -224,6 +233,8 @@ বহির্গামী এসএমএস বার্তার অক্ষর থেকে কথার টান সরিয়ে দিন শুধু মোবাইল নম্বর বার্তা লিখার করার সময়, শুধুমাত্র মোবাইল নম্বর দেখান + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply এমএমএসের সংযুক্তি সক্রিয়ভাবে সংকোচন করুন বার্তাগুলি সিঙ্ক করুন আপনার বার্তাগুলি অ্যান্ড্রয়েডের নিজস্ব এসএমএস এর ডাটাবেস সাতে পুনরায় সিঙ্ক করুন @@ -360,13 +371,14 @@ দীর্ঘ - ১০০KB - ২০০KB - ৩০০KB (সুপারিশ করা) - ৬০০KB - ১০০০KB - ২০০০KB - সংকোচনহীন + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB + No compression ঠিক আছে diff --git a/presentation/src/main/res/values-cs/strings.xml b/presentation/src/main/res/values-cs/strings.xml index 05c6d161f..4c4f3c35e 100644 --- a/presentation/src/main/res/values-cs/strings.xml +++ b/presentation/src/main/res/values-cs/strings.xml @@ -30,9 +30,11 @@ Nová zpráva Přeskočit Pokračovat + Přidat osobu Volat Podrobnosti Uložit do galerie + Sdílet Otevřít menu Vybráno: %d Vymazat @@ -86,6 +88,10 @@ Přeposlat Smazat + Vybrat telefonní číslo + %s ∙ Výchozí + Pouze jednou + Vždy Vybráno: %d %1$d z %2$d výsledků Odeslat jako skupinovou zprávu @@ -126,6 +132,7 @@ Doručeno %s Odeslání se nezdařilo. Klepnutím zkusíte znovu odeslat Podrobnosti + Adresa zkopírována Název konverzace Oznámení Motiv vzhledu @@ -189,6 +196,7 @@ Čistá černá, noční režim Čas zahájení Čas ukončení + Automatické barvy kontaktu Velikost písma Použít systémové písmo Automatické emoji @@ -199,6 +207,7 @@ Tlačítko 2 Tlačítko 3 Nastavení oznámení + Rozsvítit displej Vibrace Vyzvánění Žádný @@ -228,6 +237,8 @@ Odstranit diakritiku v odesílaných SMS zprávách Pouze mobilní čísla Při psaní zprávy zobrazit pouze mobilní telefonní čísla + Odesílat dlouhé zprávy jako MMS + Nedaří-li se odeslat vaše dlouhé zprávy, nebo jsou-li poslány v nesprávném pořadí, lze je odeslat jako MMS. Mohou být účtovány další poplatky. Automaticky komprimovat MMS přílohy Synchronizovat zprávy Opětovná synchronizace zpráv s nativní Android SMS databází @@ -370,13 +381,14 @@ Dlouhá - 100 kB - 200 kB - 300 kB (doporučeno) - 600 kB - 1 000 kB - 2 000 kB - Bez komprese + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB + No compression OK diff --git a/presentation/src/main/res/values-da/strings.xml b/presentation/src/main/res/values-da/strings.xml index 690beae18..e2d01f787 100644 --- a/presentation/src/main/res/values-da/strings.xml +++ b/presentation/src/main/res/values-da/strings.xml @@ -30,9 +30,11 @@ Angiv et navn eller nummer Overspring Fortsæt + Tilføj person Opkald Oplysninger Gem i Galleri + Del Åbn navigeringsskuffe %d valgt Ryd @@ -84,6 +86,10 @@ Videresend Slet + Vælg et telefonnr. + %s ∙ Standard + Kun én gang + Altid %d valgt %1$d af %2$d resultater Send som gruppe besked @@ -124,6 +130,7 @@ Leveret %s Mislykkedes at afsende. Tryk for at forsøge igen Oplysninger + Adresse kopieret Samtaletitel Notifikationer Tema @@ -185,6 +192,7 @@ Ren sort nattilstand Starttidspunkt Sluttidspunkt + Automatiske kontaktfarver Skriftstørrelse Brug systemets skrifttype Automatisk emoji @@ -195,6 +203,7 @@ Knap 2 Knap 3 Notifikationseksempler + Væk skærm Vibration Lyd Ingen @@ -224,6 +233,8 @@ Fjern betoninger fra tegn i udgående SMS\'er Kun mobilnumre Vis kun mobilnumre ved SMS-skrivning, + Send lang SMS som MMS + Hvis dine længere SMS\'er ikke afsendes eller afsendes i forkert rækkefølge, kan du i stedet sende dem som MMS\'er. Ekstragebyrer kan påløbe Auto-komprimér MMS-vedhæftninger Beskedsynkronisering Gen-synkroniéer beskeder med den indbyggede Android SMS-database @@ -360,13 +371,14 @@ Lang - 100 KB - 200 KB - 300 KB (anbefales) - 600 KB - 1.000 KB - 2.000 KB - Ingen komprimering + Automatisk + 100KB + 200KB + 300KB + 600KB + 1MB + 2MB + Ingen kompression OK diff --git a/presentation/src/main/res/values-de/strings.xml b/presentation/src/main/res/values-de/strings.xml index c8eea9e36..4ddd2bd70 100644 --- a/presentation/src/main/res/values-de/strings.xml +++ b/presentation/src/main/res/values-de/strings.xml @@ -30,9 +30,11 @@ Verfassen Überspringen Weiter + Person hinzufügen Anrufen Details In Galerie speichern + Teilen Öffne den Navigation Drawer %d ausgewählt Zurücksetzen @@ -84,6 +86,10 @@ Weiterleiten Löschen + Wählen Sie eine Telefonnummer + %s - Standard + Nur einmal + Immer %d ausgewählt %1$d von %2$d Ergebnissen Als Gruppennachricht senden @@ -124,6 +130,7 @@ Zugestellt %s Fehler beim Senden. Tippen, um es erneut zu versuchen Details + Adresse kopiert Unterhaltungstitel Benachrichtigungen Erscheinungsbild @@ -185,6 +192,7 @@ Rein schwarzer Nachtmodus Anfang Ende + Automatische Kontaktfarben Schriftgröße Systemschriftart verwenden Automatische Emoji @@ -195,6 +203,7 @@ Taste 2 Taste 3 Benachrichtigungsvorschau + Bildschirm aufwecken Vibration Ton Keine @@ -224,6 +233,8 @@ Akzente aus Zeichen in ausgehenden SMS-Nachrichten entfernen Nur Mobilfunknummern Beim Verfassen einer Nachricht nur Mobilfunknummern anzeigen + Lange Nachrichten als MMS senden + Wenn Ihre längeren Textnachrichten nicht gesendet oder in der falsche Reihenfolge gesendet werden, können Sie sie stattdessen als MMS-Nachrichten senden. Dafür können zusätzliche Gebühren anfallen. MMS-Anhänge automatisch komprimieren Nachrichten synchronisieren Nachrichten mit der Android SMS-Datenbank abgleichen @@ -360,9 +371,10 @@ Lang + Automatisch 100KB 200KB - 300KB (empfohlen) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-el/strings.xml b/presentation/src/main/res/values-el/strings.xml index 284b1d7a1..0ea315e82 100644 --- a/presentation/src/main/res/values-el/strings.xml +++ b/presentation/src/main/res/values-el/strings.xml @@ -30,9 +30,11 @@ Πληκτρολογήστε ένα όνομα ή αριθμό Παράλειψη Συνέχεια + Add person Κλήση Λεπτομέρειες Αποθήκευση στη συλλογή + Share Άνοιγμα πλαισίου πλοήγησης Επιλεγμένα: %d Εκκαθάριση @@ -84,6 +86,10 @@ Προώθηση Διαγραφή + Choose a phone number + %s ∙ Default + Just once + Always Επιλέχθηκαν: %d %1$d από %2$d αποτελέσματα Αποστολή ως ομαδικό μήνυμα @@ -124,6 +130,7 @@ Παραδόθηκε: %s Αποτυχία αποστολής. Πατήστε για νέα προσπάθεια Λεπτομέρειες + Address copied Τίτλος συζήτησης Ειδοποιήσεις Θέμα εμφάνισης @@ -185,6 +192,7 @@ Νυχτερινή λειτουργία καθαρού μαύρου Ώρα έναρξης Ώρα λήξης + Automatic contact colors Μέγεθος γραμματοσειράς Χρήση γραμματοσειράς συστήματος Αυτόματα emoji @@ -195,6 +203,7 @@ Κουμπί 2 Κουμπί 3 Προεπισκοπήσεις ειδοποιήσεων + Wake screen Δόνηση Ήχος Κανένα @@ -224,6 +233,8 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database @@ -360,9 +371,10 @@ Long + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-es/strings.xml b/presentation/src/main/res/values-es/strings.xml index ed3fb9a5f..b41dea7e4 100644 --- a/presentation/src/main/res/values-es/strings.xml +++ b/presentation/src/main/res/values-es/strings.xml @@ -30,9 +30,11 @@ Escriba un nombre o número Omitir Continuar + Add person Llamar Detalles Guardar en la galería + Share Abrir cajón de navegación %d Seleccionados Limpiar @@ -84,6 +86,10 @@ Reenviar Borrar + Choose a phone number + %s ∙ Default + Just once + Siempre %d seleccionados %1$d de %2$d resultados Enviar como mensaje de grupo @@ -124,6 +130,7 @@ Entregado %s No se envió. Pulse para volver a intentarlo Detalles + Address copied Título de la conversación Notificaciones Tema @@ -175,7 +182,7 @@ Enviar ahora Copy text - Delete + Borrar Apariencia General @@ -185,6 +192,7 @@ Modo noche negro puro Hora de inicio Hora de finalización + Automatic contact colors Tamaño de la fuente Usar fuente del sistema Emojis automáticos @@ -195,6 +203,7 @@ Botón 2 Botón 3 Previsualización de notificaciones + Wake screen Vibración Sonido Ninguno @@ -224,6 +233,8 @@ Quitar acentos de caracteres en los mensajes SMS salientes Sólo números móviles Al componer un mensaje, sólo mostrar números móviles + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Autocomprimir archivos adjuntos MMS Sincronizar mensajes Vuelve a sincronizar tus mensajes con la base de datos nativa de Android SMS @@ -318,8 +329,8 @@ Llamar Eliminar - Yes - Continue + + Continuar Cancelar Eliminar Guardar @@ -337,10 +348,10 @@ Mensaje no enviado El mensaje a %s no se pudo enviar - System - Disabled - Always on - Automatic + Sistema + Desactivado + Siempre activado + Automático Mostrar nombre y el mensaje @@ -360,13 +371,14 @@ Largo + Automatic 100KB 200KB - 300KB (recomendado) + 300KB 600KB 1000KB 2000KB - Sin compresión + No compression Ok diff --git a/presentation/src/main/res/values-fa/strings.xml b/presentation/src/main/res/values-fa/strings.xml index d379b4688..fbfe97022 100644 --- a/presentation/src/main/res/values-fa/strings.xml +++ b/presentation/src/main/res/values-fa/strings.xml @@ -30,9 +30,11 @@ یک نام یا شماره را وارد کنید بیخیال ادامه + Add person تماس جزئیات ذخیره در گالری + Share بازکردن کشو ناوبری %d انتخاب شده پاکسازی @@ -84,6 +86,10 @@ فوروارد حذف + Choose a phone number + %s ∙ Default + Just once + Always %d انتخاب شده %1$d of %2$d results ارسال پیام به گروه @@ -124,6 +130,7 @@ رسیده %s در ارسال خطایی رخ داد. برای ارسال دوباره، ضربه بزنید جزئیات + Address copied عنوان گفتگو اعلانها پوسته @@ -185,6 +192,7 @@ حالت شب کامل زمان شروع زمان پایان + Automatic contact colors اندازه قلم از فونت سیستم استفاده کن ایموجی خودکار @@ -195,6 +203,7 @@ Button 2 Button 3 بازبینی اعلان + Wake screen لرزش صدای هیچی @@ -224,6 +233,8 @@ حذف کاراکترهای اضافه پیام ارسالی فقط شماره های تلفن وقتی در حال ارسال پیام هستید فقط می توانید شماره تلفن را ببنید + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply فشرده سازی خودکار پیوست ها همگام سازی پیام ها همگام سازی پیام ها با پایگاه داده @@ -360,13 +371,14 @@ طولانی - ۱۰۰KB - ۲۰۰KB - 300 کیلوبایت(توصیه شده) - ۶۰۰KB - ۱۰۰۰KB - ۲۰۰۰KB - بدون فشردگی + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB + No compression باشه diff --git a/presentation/src/main/res/values-fi/strings.xml b/presentation/src/main/res/values-fi/strings.xml index fbe5fe47f..9446d4b9b 100644 --- a/presentation/src/main/res/values-fi/strings.xml +++ b/presentation/src/main/res/values-fi/strings.xml @@ -30,9 +30,11 @@ Kirjoita nimi tai numero Ohita Jatka + Add person Soita Lisätiedot Tallenna galleriaan + Share Avaa navigointilaatikko %d valittu Tyhjennä @@ -84,6 +86,10 @@ Edelleenlähetä Poista + Choose a phone number + %s ∙ Default + Just once + Always %d valittu %1$d / %2$d tuloksesta Lähetä ryhmäviestinä @@ -124,6 +130,7 @@ Toimitettu: %s Lähettäminen epäonnistui. Yritä uudelleen napauttamalla Lisätiedot + Address copied Keskustelun otsikko Ilmoitukset Teema @@ -185,6 +192,7 @@ Musta yötilä Aloitusaika Lopetusaika + Automatic contact colors Fonttikoko Käytä järjestelmäfonttia Automaattinen emoji @@ -195,6 +203,7 @@ Nappi 2 Nappi 3 Esikatselut ilmoituksessa + Wake screen Värinä Ääni Ei mitään @@ -224,6 +233,8 @@ Käytä yksinkertaisia merkkejä lähtevissä SMS-viesteissä Vain matkapuhelinnumerot When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Pakkaa MMS-liitteet automaattisesti Synkronoi viestit Re-sync your messages with the native Android SMS database @@ -360,13 +371,14 @@ Pitkä + Automatic 100KB 200KB - 300KB (Suositeltu) + 300KB 600KB 1000KB 2000KB - Ei pakkausta + No compression Kyllä diff --git a/presentation/src/main/res/values-fr/strings.xml b/presentation/src/main/res/values-fr/strings.xml index f892d8606..96fbf8de6 100644 --- a/presentation/src/main/res/values-fr/strings.xml +++ b/presentation/src/main/res/values-fr/strings.xml @@ -30,9 +30,11 @@ Entrer un nom ou un numéro Ignorer Continuer + Ajouter une personne Appeler Détails Enregistrer dans la galerie + Partager Ouvrir le tiroir de navigation %d sélectionnée Effacer @@ -84,6 +86,10 @@ Transférer Supprimer + Choisir un numéro de téléphone + %s • Défaut + Une seule fois + Toujours %d sélectionnée %1$d sur %2$d résultats Envoyer en tant que message groupé @@ -124,6 +130,7 @@ Délivré %s Échec de l\'envoi. Appuyer pour réessayer Détails + Adresse copiée Titre de la conversation Notifications Thème @@ -185,6 +192,7 @@ Mode noir pur Heure de début Heure de fin + Couleurs de contact automatiques Taille de la police Utiliser la police du système Émoticône automatique @@ -195,6 +203,7 @@ Bouton 2 Bouton 3 Aperçus de notification + Réveiller l\'écran Vibration Son Aucun @@ -224,6 +233,8 @@ Supprimer les accents des caractères lors de l\'envoi de messages SMS Numéros de mobile Lorsque vous composez un message, afficher uniquement les numéros de mobile + Envoyer les messages longs en MMS + Si vos messages texte plus longs ne parviennent pas à être envoyés, ou s\'ils sont envoyés dans le désordre, vous pouvez les envoyer sous forme de messages MMS à la place. Des frais supplémentaires peuvent s\'appliquer Compresser automatiquement les pièces jointes des MMS Synchroniser les messages Resynchroniser les messages avec la base de données Android native @@ -360,13 +371,14 @@ Long - 100 Ko - 200 Ko - 300 Ko (recommandé) - 600 Ko - 1 000 Ko - 2 000 Ko - Aucune compression + Automatique + 100KB + 200KB + 300KB + 1000KB + 1000KB + 2000KB + Pas de compression OK diff --git a/presentation/src/main/res/values-hi/strings.xml b/presentation/src/main/res/values-hi/strings.xml index e90bcfad1..7d0dcbf39 100644 --- a/presentation/src/main/res/values-hi/strings.xml +++ b/presentation/src/main/res/values-hi/strings.xml @@ -30,9 +30,11 @@ रचना छोड़ जारी रखें + Add person कॉल विवरण गैलरी में सहेजें + Share खुला नेविगेशन दराज %d चयनित साफ करें @@ -84,6 +86,10 @@ अग्रेषित करें मिटाएँ + Choose a phone number + %s ∙ Default + Just once + Always %d चयनित %1$d के %2$d परिणाम समूह संदेश के रूप में भेजें @@ -124,6 +130,7 @@ %s वितरित भेजने में असफल। पुनः प्रयास करें विवरण + Address copied Conversation title अधिसूचनाएँ थीम @@ -185,6 +192,7 @@ शुद्ध काली रात अंदाज़ शुरुआत समय अंत समय + Automatic contact colors लिखाई का आकर यंत्र ध्वनियां उपयोग करें Automatic emoji @@ -195,6 +203,7 @@ बटन दो बटन तीन अधिसूचना सेटिंग्स + Wake screen कंपन ध्वनियाँ कुछ नहीं @@ -224,6 +233,8 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database @@ -360,12 +371,13 @@ बड़ा - 100 किलो बाइट - 200 किलो बाइट - 300 किलो बाइट(सुझाव) - 600 किलो बाइट - 1000 किलो बाइट - 2000 किलो बाइट + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB No compression diff --git a/presentation/src/main/res/values-hr/strings.xml b/presentation/src/main/res/values-hr/strings.xml index 1b9b41447..7ba312454 100644 --- a/presentation/src/main/res/values-hr/strings.xml +++ b/presentation/src/main/res/values-hr/strings.xml @@ -30,9 +30,11 @@ Unesite ime ili broj Preskoči Nastavi + Add person Nazovi Detalji Save to gallery + Share Open navigation drawer %d odabrano Očisti @@ -85,6 +87,10 @@ Proslijedi Izbriši + Choose a phone number + %s ∙ Default + Just once + Always %d odabrano %1$d of %2$d results Send as group message @@ -125,6 +131,7 @@ Isporučeno %s Slanje nije uspjelo. Dodirnite da biste pokušali ponovno Detalji + Address copied Conversation title Obavijesti Tema @@ -187,6 +194,7 @@ Potpuno crni noćni način Vrijeme početka Vrijeme završetka + Automatic contact colors Veličina fonta Koristi sustavski font Automatsk emoji @@ -197,6 +205,7 @@ Button 2 Button 3 Pregledi obavijesti + Wake screen Vibracija Zvuk None @@ -226,6 +235,8 @@ Uklonite naglaske sa znakova u odlaznim SMS porukama Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Uskladi poruke Ponovno uskladite svoje poruke sa nativnom Android SMS bazom podataka @@ -365,9 +376,10 @@ Long + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-hu/strings.xml b/presentation/src/main/res/values-hu/strings.xml index 758ed1107..78f78b584 100644 --- a/presentation/src/main/res/values-hu/strings.xml +++ b/presentation/src/main/res/values-hu/strings.xml @@ -30,16 +30,18 @@ Adj meg egy nevet vagy telefonszámot Kihagy Tovább + Személy hozzáadása Hívás Részletek Mentés a galériába + Megosztás Navigációs kiválasztása %d kijelölve Törlés Archiválás Archiválás visszavonása Törlés - Add to contacts + Hozzáadás az ismerősökhöz Rögzítés a tetejére Rögzítés törlése Megjelölés olvasottként @@ -47,7 +49,7 @@ Tiltás Üzenetek szinkronizálása… Te: %s - Draft: %s + Vázlat: %s Eredmények az üzenetek szövegében %d üzenet A beszélgetéseid itt jelennek meg @@ -84,6 +86,10 @@ Továbbítás Törlés + Válassz egy telefonszámot + %s · Alapértelmezett + Csak egyszer + Mindig %d kijelölve %1$d. a %2$d találatból Küldés csoportos üzenetként @@ -124,6 +130,7 @@ Kézbesítve %s Sikertelen küldés. Érintsd meg az újraküldéshez Részletek + Cím átmásolva A beszélgetés témája Értesítések Téma @@ -174,8 +181,8 @@ Időzített üzenet Küldés most - Copy text - Delete + Szöveg másolása + Törlés Megjelenés Általános @@ -185,6 +192,7 @@ Éjfekete mód Kezdés ideje Befejezés ideje + Ismerősök automatikus színezése Betűméret Rendszer betűtípus visszaállítása Automatikus emoji @@ -195,6 +203,7 @@ Gomb 2 Gomb 3 Értesítés előnézet + Képernyő felébresztése Rezgés Hang Nincs @@ -219,11 +228,13 @@ Elküldés megerősítése Megerősíti, hogy az üzenetek sikeresen el lettek-e küldve Aláírás - Add a signature to the end of your messages + Aláírás az üzenet végére Ékezetek törlése A kimenő SMS-üzenetek ékezetes karaktereinek átalakítása Csak mobilszámok Üzenet írásánál csak a mobilszámokat mutassa + A hosszú üzenetek MMS-ként küldése + Ha a hosszabb üzenetek küldése sikertelen, vagy rossz sorrendben érkeznek akkor lehet őket MMS-ként küldeni, de ez díjköteles lehet MMS-mellékletek automatikus tömörítése Üzenetek szinkronizálása Üzenetek újra-szinkronizálása a natív Android SMS-adatbázissal @@ -232,7 +243,7 @@ Hibanaplózás engedélyezve Hibanaplózás tiltva Nyomva tartás ideje (másodpercben) - Blocking + Blokkolás Üzenetek eldobása Üzenetek eldobása a blokkolt küldőktől az elrejtésük helyett Tiltott társalgások @@ -248,11 +259,11 @@ Új szám tiltása Üzenetek tiltása: Telefonszám - Block + Blokkolás Tiltott üzenetek A tiltott üzenetek itt fognak megjelenni - Block - Unblock + Blokkolás + Visszaengedélyezés %s és ennek a számnak a blokkolása %s és ezeknek a számoknak a tiltása @@ -319,7 +330,7 @@ Törlés Igen - Continue + Folytatás Mégsem Törlés Mentés @@ -360,13 +371,14 @@ Hosszú + Automatic 100KB 200KB - 300KB (Javsolt) + 300KB 600KB 1000KB 2000KB - Nincs tömörítés + No compression Oké diff --git a/presentation/src/main/res/values-in/strings.xml b/presentation/src/main/res/values-in/strings.xml index ef98df361..9f3ec415d 100644 --- a/presentation/src/main/res/values-in/strings.xml +++ b/presentation/src/main/res/values-in/strings.xml @@ -30,9 +30,11 @@ Ketik nama atau nomor Lewati Lanjut + Add person Panggil Detail Simpan ke galeri + Berbagi Buka laci navigasi %d dipilih Bersihkan @@ -83,6 +85,10 @@ Teruskan Hapus + Pilih nomor telepon + %s ∙ Default + Hanya sekali + Selalu %d dipilih %1$d dari %2$d hasil Kirim sebagai pesan grup @@ -123,6 +129,7 @@ %s terkirim Gagal mengirim. Ketuk untuk mencoba lagi Detail + Alamat disalin Judul percakapan Notifikasi Tema @@ -183,6 +190,7 @@ Mode malam hitam pekat Waktu mulai Waktu berakhir + Warna kontak otomatis Ukuran fon Gunakan fon sistem Emoji otomatis @@ -193,6 +201,7 @@ Tombol 2 Tombol 3 Pratinjau notifikasi + Layar ambien Getar Suara Tidak ada @@ -217,11 +226,13 @@ Konfirmasi pengiriman Mengonfirmasi bahwa pesan telah berhasil dikirim Tanda tangan - Add a signature to the end of your messages + Tambahkan tanda tangan di akhir pesan anda Hapus aksen Membuang aksen dari karakter di dalam pesan SMS keluar Hanya nomor seluler Saat menulis pesan, hanya tampilkan nomor seluler + Kirim pesan panjang sebagai MMS + Jika pesan teks panjang anda gagal dikirim, atau dikirim dalam urutan yang salah, anda bisa mengirimkannya sebagai pesan MMS. Mungkin akan dikenai tagihan tambahan Otomatis kompres lampiran MMS Sinkronisasi pesan Sinkronisasi ulang pesan anda dengan basis data aplikasi SMS Android bawaan @@ -355,13 +366,14 @@ Lama + Automatic 100KB 200KB - 300KB (Direkomendasikan) + 300KB 600KB 1000KB 2000KB - Tanpa kompresi + No compression Oke diff --git a/presentation/src/main/res/values-it/strings.xml b/presentation/src/main/res/values-it/strings.xml index e6f812b5b..74ce06252 100644 --- a/presentation/src/main/res/values-it/strings.xml +++ b/presentation/src/main/res/values-it/strings.xml @@ -30,9 +30,11 @@ Digita un nome o un numero Salta Continua + Aggiungi persona Chiama Dettagli Salva nella galleria + Condividi Apri il pannello di navigazione %d selezionato Azzera @@ -84,6 +86,10 @@ Inoltra Elimina + Scegli un numero di telefono + %s ∙ Predefinito + Solo una volta + Sempre %d selezionato %1$d di %2$d risultati Invia come messaggio di gruppo @@ -121,9 +127,10 @@ %s selezionato, cambiare SIM Invia messaggio Invio in corso… - %s consegnati + Consegnato %s Impossibile inviare. Tocca per riprovare Dettagli + Indirizzo copiato Titolo della conversazione Notifiche Tema @@ -185,6 +192,7 @@ Modalità notte nero puro Ora di inizio Ora di fine + Colori dei contatti automatici Dimensione carattere Usa il carattere di sistema Emoji automatico @@ -195,6 +203,7 @@ Pulsante 2 Pulsante 3 Anteprima delle notifiche + Schermata iniziale Vibrazione Suono Nessuno @@ -224,6 +233,8 @@ Rimuovi gli accenti dai caratteri negli SMS in uscita Solo numeri mobili Durante la composizione di un messaggio, visualizza solo i numeri di cellulare + Invia messaggi lunghi come MMS + Se i tuoi messaggi di testo più lunghi non vengono inviati o vengono inviati nell\'ordine sbagliato, puoi invece inviarli come messaggi MMS. Potrebbero essere applicati costi aggiuntivi Compressione automatica degli allegati MMS Sincronizza i messaggi Risincronizza i tuoi messaggi con il database SMS Android nativo @@ -360,13 +371,14 @@ Lungo + Automatic 100KB 200KB - 300KB (consigliato) + 300KB 600KB 1000KB 2000KB - Nessuna compressione + No compression Okay diff --git a/presentation/src/main/res/values-iw/strings.xml b/presentation/src/main/res/values-iw/strings.xml index 5117ae65d..2f0ef49e0 100644 --- a/presentation/src/main/res/values-iw/strings.xml +++ b/presentation/src/main/res/values-iw/strings.xml @@ -30,9 +30,11 @@ כתוב הודעה דילוג המשך + להוסיף מישהו התקשר פרטים שמירה לגלריה + שיתוף פתיחת מגירת ניווט %d נבחרו פינוי @@ -86,6 +88,10 @@ העברה מחיקה + נא לבחור מספר טלפון + %s ∙ בררת מחדל + פעם אחד בלבד + תמיד %d נבחרו %1$d מתוך %2$d תוצאות שליחה כהודעה קבוצתית @@ -126,6 +132,7 @@ %s נמסרה השליחה נכשלה. נא לגעת כדי לנסות שוב פרטים + הכתובת הועתקה כותרת התכתבות התראות ערכת עיצוב @@ -189,6 +196,7 @@ מצב לילה שחור טהור זמן התחלה זמן סיום + צבעים אוטומטיים לאנשי קשר גודל גופן שימוש בגופן המערכת אימוג׳י אוטומטי @@ -199,6 +207,7 @@ כפתור 2 כפתור 3 תצוגות מקדימות של התראות + הפעלת המסך רטט צליל ללא @@ -228,6 +237,8 @@ הסרת סימנים דיאקריטיים מתווים במסרונים יוצאים מספרי ניידים בלבד בעת כתיבת הודעה, להציג רק מספרי טלפון של ניידים + לשלוח הודעות ארוכות כ־MMS + אם יש לך בעיה בשליחת הודעות טקסט ארוכות יותר או בעיה ברצף שליחת ההודעה, ניתן לשלוח כהודעת MMS במקום. עלול לגרור עלויות נוספות דחיסה אוטומטית של קבצים מצורפים להודעות MMS סנכרון הודעות סנכרון ההודעות שלך מחדש עם מסד הנתונים המובנה של Android @@ -370,9 +381,10 @@ ארוכה + אוטומטי 100 ק״ב 200 ק״ב - 300 ק״ב (מומלץ) + 300 ק״ב 600 ק״ב 1000 ק״ב 2000 ק״ב diff --git a/presentation/src/main/res/values-ja/strings.xml b/presentation/src/main/res/values-ja/strings.xml index 2fa2d2cf9..ba9e1f4ac 100644 --- a/presentation/src/main/res/values-ja/strings.xml +++ b/presentation/src/main/res/values-ja/strings.xml @@ -30,9 +30,11 @@ 作成 スキップ 続行 + 人を追加 通話 詳細 ギャラリーに保存 + 共有 ナビゲーション引き出しを開く %d 選択された 消去しました @@ -83,6 +85,10 @@ 転送 削除 + 電話番号を選択 + %s ∙ デフォルト + 一回だけ + 常に %d件を選択中 %2$d 結果の %1$d グループ メッセージとして送信します。 @@ -123,6 +129,7 @@ %s を配信しました 送信に失敗しました。タップすると、もう一度やり直します 詳細 + Address copied 会話のタイトル 通知 テーマ @@ -183,6 +190,7 @@ ピュアブラック夜間モード 開始時刻 終了時刻 + Automatic contact colors フォントサイズ システムフォントを使用する 自動絵文字 @@ -193,6 +201,7 @@ ボタン 2 ボタン 3 通知プレビュー + Wake screen 振動 サウンド 無し @@ -222,6 +231,8 @@ 送信する SMS メッセージの文字からアクセントを削除します 携帯電話番号のみ メッセージを作成するときは、携帯電話番号のみを表示します + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply MMS添付ファイルの自動圧縮 メッセージを同期 メッセージをネイティブの Android SMS データベースと再同期します @@ -355,13 +366,14 @@ 長い + Automatic 100KB 200KB - 300KB 【推奨】 + 300KB 600KB 1000KB 2000KB - 圧縮なし + No compression OK diff --git a/presentation/src/main/res/values-ko/strings.xml b/presentation/src/main/res/values-ko/strings.xml index 1afdab8d0..af4128d37 100644 --- a/presentation/src/main/res/values-ko/strings.xml +++ b/presentation/src/main/res/values-ko/strings.xml @@ -30,16 +30,18 @@ 이름 또는 전화번호 입력 건너뛰기 계속 + 대화 상대 추가하기 전화 자세히 갤러리에 저장하기 + 공유 메뉴 열기 %d 개가 선택됨 삭제하기 보관하기 - 보관하지 않기 + 보관 해제 지우기 - Add to contacts + 연락처에 추가 상단에 고정하기 고정 해제하기 읽음으로 표시 @@ -47,14 +49,14 @@ 차단 메시지 동기화 하기 나: %s - Draft: %s + 임시 메시지: %s 메시지 검색 결과 %d 개의 새로운 메시지 대화가 여기에 나타납니다 결과없음 보관된 메시지가 여기 나타납니다 새로운 대화 시작하기 - 다시 문자 보내는게 좋네요 + 다시 문자 보내는게 좋네요! QKSMS 앱을 기본 메시지 앱으로 설정하기 변경하기 권한을 부여해 주세요 @@ -69,27 +71,29 @@ 설정 도움말 & 피드백 친구 초대하기 - 놀랍고 새로운 기능을 사용하고 개발 지원을 할 수 있습니다! + 새롭고 놀라운 기능을 사용할 수 있고, 개발자에게 지원도 할 수 있어요! QKSMS 앱을 즐기고 있으세요? Google Play 스토어에 리뷰를 남겨주세요! 좋아요! 안할래요 삭제 - 정말 이 대화를 삭제할까요? - -%d 개의 대화를 삭제할까요? + 정말 %d 개의 대화를 삭제할까요? 문자 복사하기 전달 삭제 + 전화번호 선택하기 + %s ∙ 기본 + 한 번만 + 항상 %d 개 선택됨 %2$d 중 %1$d 결과 그룹 메시지 보내기 - 받는 사람 및 답장이 모든 사람에게 표시 됩니다. - 새로운 대화가 시작되었어요! 인사말을 건네는 건 어떨까요? + 받는 사람 및 답장이 모든 사람에게 표시됩니다. + 새로운 대화가 시작되었어요! 재미있는 대화를 해보세요! 연락처 예정 선택한 시간은 반드시 현재 이후의 시간으로 설정해야 합니다 @@ -104,8 +108,8 @@ 삭제 세부 정보 종류: %s - %s가 - %s에게 + %s 가 + %s 에게 제목: %s 우선 순위: %s 크기: %s @@ -125,6 +129,7 @@ %s 전송됨 전송 실패, 눌러서 다시 시도하세요 자세히 + Address copied 대화 제목 알림 테마 @@ -134,7 +139,7 @@ 차단 해제 대화 삭제 미디어를 불러올 수 없습니다 - 갤러리에 저장 + 갤러리에 저장하기 백업과 복원 메시지 백업하기 복원하기 @@ -165,19 +170,19 @@ 백업과 복원 예약됨 원하는 그 시간에 자동으로 메시지를 보냅니다. - 잠깐만요! 생일이 언제였나요..? - 12 월 23 일 입니다 - 생일 축하해요! 당신의 생일을 기억하는 멋진 친구죠..? + 잠깐만! 생일이 언제였더라..? + 12월 23일이야 + 생일 축하해! 너의 생일까지 기억해주는 이렇게 좋은 친구가 어디있겠어ㅋㅋ - 12 월 23 일에 보냅니다. + 12월 23일에 보냅니다. 메시지 예약 전송하기 예약된 메시지 지금 보내기 - Copy text - Delete + 텍스트 복사하기 + 삭제 - 외관 + 디자인 일반 설정 빠른 응답 테마 @@ -185,6 +190,7 @@ 완전한 블랙 테마 시작 시간 끝나는 시간 + Automatic contact colors 글꼴 크기 시스템 글꼴 사용 자동 이모티콘 @@ -195,6 +201,7 @@ 버튼 2 버튼 3 알림 미리 보기 + Wake screen 진동 소리 없음 @@ -218,41 +225,43 @@ 전송 확인 전송 성공을 확인합니다 - Signature - Add a signature to the end of your messages + 서명 + 문자 메시지 마지막에 서명을 추가합니다 스트립 악센트 SMS를 주고받을 때, 대문자 없애기 전화번호만 보이기 메시지를 쓸 때, 전화번호만 보이게 하기 + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply 자동으로 MMS 첨부 파일 압축하기 - 메시지 동기화 하기 + 메시지 동기화하기 기본 안드로이드 SMS 데이터베이스에 다시 동기화하기 QKSMS에 대해... 버전 정보 %s 디버그 로깅 설정됨 디버그 로깅 해제됨 기간 (초)을 입력 - Blocking - Drop messages - Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + 차단 + 메시지 자동 삭제 + 차단된 사람으로부터 오는 메시지를 가리는 대신 삭제합니다 + 차단된 대화 + 차단 메니저 QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + 빌트인 차단기능이 있는 기본 필터. + 자동으오 전화와 메시지를 한 곳에서 필터링하세요! Community IQ™는 커뮤니티 기반으로 원하지 않는 스팸 메시지를 차단해줍니다. Should I Answer 앱을 이용해 필요없는 번호에서 보낸 메시지를 자동으로 필터링하기 - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here - Block - Unblock + 차단된 번호 복사하기 + %s로 계속하고 이미 차단된 번호를 복사합니다. + 차단된 번호 + 차단한 번호가 여기에 나타납니다 + 새로운 번호 차단하기 + 차단할 번호 입력 + 전화번호 + 차단 + 차단된 메시지 + 차단된 메시지가 여기에 나옵니다. + 차단 + 차단해제 Continue to %s and block these numbers @@ -359,13 +368,14 @@ 길게 - 100 KB - 200 KB - 300 KB (권장) - 600 KB - 1000 KB - 2000 KB - 압축 안 함 + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB + No compression 확인 diff --git a/presentation/src/main/res/values-lt/strings.xml b/presentation/src/main/res/values-lt/strings.xml index bd484c594..15332f97a 100644 --- a/presentation/src/main/res/values-lt/strings.xml +++ b/presentation/src/main/res/values-lt/strings.xml @@ -30,9 +30,11 @@ Rašyti Praleisti Tęsti + Add person Skambinti Dėtalės Save to gallery + Share Open navigation drawer %d pasirinkta(s) Ištrinti @@ -86,6 +88,10 @@ Persiųsti Ištrinti + Choose a phone number + %s ∙ Default + Just once + Always %d parinktas %1$d of %2$d results Send as group message @@ -126,6 +132,7 @@ Nusiųsta %s Nepavyko nusiųsti. Spausk dar kartą norint persiųsti Dėtalės + Address copied Conversation title Pranešimai Tema @@ -189,6 +196,7 @@ Visiškai juoda nakties tema Pradžios laikas Pabaigos laikas + Automatic contact colors Šrifto dydis Naudoti sistemos šriftą Automatinis emoji @@ -199,6 +207,7 @@ Button 2 Button 3 Pranešimų peržiūra + Wake screen Vibracija Garas None @@ -228,6 +237,8 @@ Ištrinti jūsų SMS žinučių simbolių akcentus Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sinchronizuoti pranešimus Iš naujo sinchronizuoti Android pranešimų duomenų bazę @@ -370,9 +381,10 @@ Ilgas + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-nb/strings.xml b/presentation/src/main/res/values-nb/strings.xml index b4fc604dd..54dafb565 100644 --- a/presentation/src/main/res/values-nb/strings.xml +++ b/presentation/src/main/res/values-nb/strings.xml @@ -30,9 +30,11 @@ Skriv navn eller nummer Hopp over Fortsett + Add person Ring Detaljer Lagre til galleri + Share Åpne navigasjonsskuff %d valgt Tøm @@ -84,6 +86,10 @@ Videresend Slett + Choose a phone number + %s ∙ Default + Just once + Always %d valgt %1$d av %2$d funn Send som gruppemelding @@ -124,6 +130,7 @@ Levert %s Feil ved sending. Trykk for å prøve igjen Detaljer + Address copied Tittel på samtalen Varsler Drakt @@ -185,6 +192,7 @@ Ren svart nattmodus Starttid Sluttidspunkt + Automatic contact colors Skriftstørrelse Bruk systemskrift Automatisk emoji @@ -195,6 +203,7 @@ Knapp 2 Knapp 3 Forhåndsvisninger av varsler + Wake screen Vibrasjon Lyd Ingen @@ -224,6 +233,8 @@ Ta bort akutt-tegn fra utgående meldinger Kun mobilnummre Vis kun mobilnummre når du skriver ny melding + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Komprimer MMS-vedlegg Synkroniser meldinger Resynkroniser meldinger med Androids interne meldingslager @@ -360,13 +371,14 @@ Lang - 100 KB - 200 KB - 300 KB (anbefalt) - 600 KB - 1000 KB - 2000 KB - Ingen komprimering + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB + No compression Ok diff --git a/presentation/src/main/res/values-ne/strings.xml b/presentation/src/main/res/values-ne/strings.xml index ad6492c83..7c86ccfc2 100644 --- a/presentation/src/main/res/values-ne/strings.xml +++ b/presentation/src/main/res/values-ne/strings.xml @@ -30,9 +30,11 @@ Compose Skip Continue + Add person सम्पर्क Details Save to gallery + Share Open navigation drawer %d selected Clear @@ -84,6 +86,10 @@ Forward Delete + Choose a phone number + %s ∙ Default + Just once + Always %d selected %1$d of %2$d results Send as group message @@ -124,6 +130,7 @@ Delivered %s Failed to send. Tap to try again Details + Address copied Conversation title Notifications Theme @@ -185,6 +192,7 @@ Pure black night mode Start time End time + Automatic contact colors Font size Use system font Automatic emoji @@ -195,6 +203,7 @@ Button 2 Button 3 Notification previews + Wake screen Vibration Sound None @@ -224,6 +233,8 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database @@ -360,9 +371,10 @@ Long + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-nl/strings.xml b/presentation/src/main/res/values-nl/strings.xml index c54a0d728..8465731fd 100644 --- a/presentation/src/main/res/values-nl/strings.xml +++ b/presentation/src/main/res/values-nl/strings.xml @@ -30,9 +30,11 @@ Type een naam of nummer Overslaan Verder + Add person Bel Details Opslaan in galerij + Share Open navigatie lade %d geselecteerd Wissen @@ -84,6 +86,10 @@ Doorsturen Verwijderen + Choose a phone number + %s ∙ Default + Just once + Always %d geselecteerd %1$d van de %2$d resultaten Versturen als groepsbericht @@ -124,6 +130,7 @@ Afgeleverd %s Fout bij verzenden. Tik om opnieuw te proberen Details + Address copied Titel van conversatie Notificaties Thema @@ -185,6 +192,7 @@ Pure zwarte nachtmodus Starttijd Eindtijd + Automatic contact colors Tekst grootte Gebruik lettertype van systeem Automatische emoji @@ -195,6 +203,7 @@ Button 2 Button 3 Notificatievoorbeeld + Wake screen Trillen Geluid Geen @@ -224,6 +233,8 @@ Verwijder accenten van karakters voor uitgaande berichten Alleen GSM-nummers Enkel mobiele nummers tonen wanneer je een bericht opstelt + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS bijlagen Synchroniseer berichten Hersynchroniseer jouw berichten vanuit de SMS database op je telefoon @@ -360,13 +371,14 @@ Lang + Automatic 100KB 200KB - 300KB (aanbevolen) + 300KB 600KB 1000KB 2000KB - Geen compressie + No compression Oké diff --git a/presentation/src/main/res/values-pl/strings.xml b/presentation/src/main/res/values-pl/strings.xml index ffe54802a..765231102 100644 --- a/presentation/src/main/res/values-pl/strings.xml +++ b/presentation/src/main/res/values-pl/strings.xml @@ -30,9 +30,11 @@ Nowa wiadomość Pomiń Kontynuuj + Dodaj osobę Zadzwoń Szczegóły Zapisz w galerii + Udostepnij Otwórz panel nawigacyjny %d wybrano Wyczyść @@ -86,6 +88,10 @@ Przekaż dalej Usuń + Wybierz numer telefonu + %s ∙ Domyślny + Tylko raz + Zawsze Wybrano %d Wyniki: %1$d z %2$d Wyślij jako wiadomość grupową @@ -126,6 +132,7 @@ Dostarczono %s Nie udało się wysłać wiadomości. Dotknij, aby spróbować ponownie Szczegóły + Address copied Tytuł rozmowy Powiadomienia Motyw @@ -189,6 +196,7 @@ Czarne tło w trybie nocnym Czas rozpoczęcia Czas zakończenia + Automatic contact colors Rozmiar czcionki Używaj czcionki systemowej Automatyczne emoji @@ -199,6 +207,7 @@ Przycisk 2 Przycisk 3 Zawartość powiadomień + Wake screen Wibracje Dźwięki Brak @@ -223,11 +232,13 @@ Potwierdzenie dostarczenia Potwierdzaj dostarczenie każdej wysłanej wiadomości Podpis - Add a signature to the end of your messages + Dodawaj podpis na końcu wiadomości Usuwaj ogonki Usuwaj ogonki ze znaków w wiadomościach Tylko numery komórkowe Pokazuj kontakty tylko z numerami komórkowymi + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Automatyczna kompresja załączników MMSów Synchronizuj wiadomości Zsynchronizuj wiadomości z systemową bazą danych @@ -246,7 +257,7 @@ Automatycznie filtruj twoje rozmowy i wiadomości tekstowe w jednym wygodnym miejscu! Community IQ™ pozwala zapobiegać niechcianym wiadomościom od znanych spamerów Automatycznie filtruj wiadomości od niechcianych numerów za pomocą aplikacji \"Should I Answer?\" Skopiuj zablokowane numery - Kontynuuj do %s kopiowanie twoich blokowanych numerów + Kontynuuj do %s i skopiuj twoje obecnie blokowane numery Zablokowane numery Twoje blokowane numery się tu pojawią Zablokuj nowy numer @@ -370,13 +381,14 @@ Długi + Automatic 100KB 200KB - 300KB (zalecane) + 300KB 600KB 1000KB 2000KB - Bez kompresji + No compression OK diff --git a/presentation/src/main/res/values-pt-rBR/strings.xml b/presentation/src/main/res/values-pt-rBR/strings.xml index 48f914399..1d0a47836 100644 --- a/presentation/src/main/res/values-pt-rBR/strings.xml +++ b/presentation/src/main/res/values-pt-rBR/strings.xml @@ -30,9 +30,11 @@ Nova Mensagem Pular Continuar + Adicionar pessoa Ligar Detalhes Salvar na galeria + Partilhar Abrir gaveta de navegação %d selecionada(s) Limpar @@ -84,6 +86,10 @@ Encaminhar Excluir + Escolha um número de telefone + %s ∙ Padrão + Uma vez + Sempre %d selecionada(s) %1$d de %2$d resultados Enviar como mensagem de grupo @@ -124,6 +130,7 @@ Entregue %s Falha ao enviar. Toque para tentar novamente Detalhes + Endereço copiado Título da conversa Notificações Tema @@ -185,6 +192,7 @@ Modo noturno preto puro Hora de início Hora de término + Cores automáticas para os contactos Tamanho da fonte Usar fonte do sistema Emoji automático @@ -195,6 +203,7 @@ Botão 2 Botão 3 Pré-visualização de notificações + Ligar ecrã Vibração Som Nenhum @@ -224,6 +233,8 @@ Remover acentos de caracteres em mensagens SMS enviadas Somente números de celular Ao escrever uma mensagem, mostrar apenas os números de celular + Enviar mensagens longas como MMS + Se as mensagens longas não estiverem a ser enviadas ou estão a ser enviadas pela ordem errada, pode tentar enviar como MMS. Podem ser aplicados custos adicionais. Comprimir anexos MMS automaticamente Sincronizar mensagens Re-sincronizar suas mensagens com o banco de dados de SMS nativo do Android @@ -360,13 +371,14 @@ Longo + Automatic 100KB 200KB - 300KB (Recomendado) + 300KB 600KB 1000KB 2000KB - Não comprimir + No compression Ok diff --git a/presentation/src/main/res/values-pt/strings.xml b/presentation/src/main/res/values-pt/strings.xml index 92ce42593..eeb81d35a 100644 --- a/presentation/src/main/res/values-pt/strings.xml +++ b/presentation/src/main/res/values-pt/strings.xml @@ -30,9 +30,11 @@ Digite um nome ou um número Ignorar Continuar + Adicionar pessoa Ligar Detalhes Guardar na galeria + Partilhar Abrir menu de navegação %d selecionada(s) Limpar @@ -84,6 +86,10 @@ Reencaminhar Apagar + Escolha um número de telefone + %s ∙ Padrão + Uma vez + Sempre %d selecionada(s) %1$d de %2$d resultados Enviar como mensagem de grupo @@ -124,6 +130,7 @@ %s entregue Falha ao enviar. Toque para tentar novamente Detalhes + Endereço copiado Título da conversa Notificações Tema @@ -185,6 +192,7 @@ Modo escuro puro Hora de início Hora de fim + Cores automáticas para os contactos Tamanho do texto Usar tipo de letra do sistema Emoji automático @@ -195,6 +203,7 @@ Botão 2 Botão 3 Pré-visualização de notificações + Ligar ecrã Vibração Som Nenhum @@ -224,6 +233,8 @@ Remover acentos dos caracteres nas mensagens enviadas Apenas números de telemóvel Ao escrever uma mensagem, mostrar apenas os números móveis + Enviar mensagens longas como MMS + Se as mensagens longas não estiverem a ser enviadas ou estão a ser enviadas pela ordem errada, pode tentar enviar como MMS. Podem ser aplicados custos adicionais. Comprimir anexos MMS automaticamente Sincronizar mensagens Sincronizar as mensagens com a base de dados nativa do Android @@ -360,13 +371,14 @@ Longo + Automatic 100KB 200KB - 300KB (recomendado) + 300KB 600KB 1000KB 2000KB - Não comprimir + No compression Ok diff --git a/presentation/src/main/res/values-ro/strings.xml b/presentation/src/main/res/values-ro/strings.xml index 50e6edce8..8c6e6ae86 100644 --- a/presentation/src/main/res/values-ro/strings.xml +++ b/presentation/src/main/res/values-ro/strings.xml @@ -30,9 +30,11 @@ Compune Sari Continue + Add person Apel Details Save to gallery + Share Open navigation drawer %d selected Clear @@ -85,6 +87,10 @@ Forward Delete + Choose a phone number + %s ∙ Default + Just once + Always %d selected %1$d of %2$d results Send as group message @@ -125,6 +131,7 @@ Delivered %s Failed to send. Tap to try again Details + Address copied Conversation title Notifications Theme @@ -187,6 +194,7 @@ Pure black night mode Start time End time + Automatic contact colors Font size Use system font Automatic emoji @@ -197,6 +205,7 @@ Button 2 Button 3 Notification previews + Wake screen Vibration Sound None @@ -226,6 +235,8 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database @@ -365,9 +376,10 @@ Long + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-ru/strings.xml b/presentation/src/main/res/values-ru/strings.xml index 090cfe9b6..45c5717b3 100644 --- a/presentation/src/main/res/values-ru/strings.xml +++ b/presentation/src/main/res/values-ru/strings.xml @@ -30,9 +30,11 @@ Написать Пропустить Продолжить + Добавить контакт Позвонить Подробности Сохранить в галерее + Поделиться Открыть боковое меню %d выбрано Очистить @@ -86,6 +88,10 @@ Переслать Удалить + Выбор номера телефона + %s ∙ по умолчанию + Один раз + Всегда %d выбран(о) %1$d из %2$d результатов Отправить как групповое сообщение @@ -126,6 +132,7 @@ Доставлено %s Не удалось отправить. Нажмите, чтобы попробовать ещё раз. Подробности + Адрес скопирован Название разговора Уведомления Тема @@ -189,6 +196,7 @@ Полностью чёрный ночной режим Время начала Время окончания + Автоматические цвета контактов Размер шрифта Использовать системный шрифт Автоматические emoji @@ -199,6 +207,7 @@ Кнопка 2 Кнопка 3 Предпросмотр уведомлений + Экран блокировки Вибросигнал Звук Нет @@ -228,6 +237,8 @@ Удаление акцентов с символов в исходящих SMS-сообщениях Только мобильные номера При составлении сообщения показывать только мобильные номера + Отправлять длинные сообщения как MMS + Если не удаётся отправить длинные текстовые сообщения или они отправляются в неправильном порядке, вы можете отправить их в виде MMS. Может взиматься дополнительная плата. Автоматически сжимать вложения MMS Синхронизация сообщений Принудительная синхронизация сообщений с собственной базой данных SMS Android @@ -370,12 +381,13 @@ Длинная - 100 КБ - 200 КБ - 300 КБ (рекомендуется) - 600 КБ - 1000 КБ - 2000 КБ + Автоматически + 100 Кб + 200 Кб + 300 Кб + 600 Кб + 1000 Кб + 2000 Кб Без сжатия diff --git a/presentation/src/main/res/values-sk/strings.xml b/presentation/src/main/res/values-sk/strings.xml index a1352cca7..ff0d89323 100644 --- a/presentation/src/main/res/values-sk/strings.xml +++ b/presentation/src/main/res/values-sk/strings.xml @@ -30,9 +30,11 @@ Nová správa Preskočiť Pokračovať + Add person Volať Podrobnosti Uložiť do galérie + Share Otvoriť navigačné menu %d vybraných Vymazať @@ -86,6 +88,10 @@ Preposlať Odstrániť + Choose a phone number + %s ∙ Default + Just once + Always %d selected %1$d of %2$d results Poslať ako skupinovú správu @@ -126,6 +132,7 @@ Doručené %s Failed to send. Tap to try again Detaily + Address copied Conversation title Upozornenia Motív @@ -189,6 +196,7 @@ Pure black night mode Čas spustenia Čas ukončenia + Automatic contact colors Veľkosť písma Použiť systémové písmo Automatické emoji @@ -199,6 +207,7 @@ Tlačidlo 2 Tlačidlo 3 Obsah upozornení + Wake screen Vibration Sound None @@ -228,6 +237,8 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database @@ -370,13 +381,14 @@ Long + Automatic 100KB 200KB - 300KB (odporúča sa) + 300KB 600KB 1000KB 2000KB - Bez kompresie + No compression V poriadku diff --git a/presentation/src/main/res/values-sl/strings.xml b/presentation/src/main/res/values-sl/strings.xml new file mode 100644 index 000000000..eb4a56a04 --- /dev/null +++ b/presentation/src/main/res/values-sl/strings.xml @@ -0,0 +1,407 @@ + + + + + Nov pogovor + Sestavi + Bližnjica onemogočena + Arhivirano + Nastavitve + Obvestila + Tema + Išči po prejetih sporočilih… + Vnesi ime ali številko + Preskoči + Nadaljuj + Dodaj osebo + Klic + Podrobnosti + Shrani v galerijo + Deli + Odpri nacigacijski meni + %d izbranih + Počisti + Arhiv + Odstrani iz arhiva + Izbriši + Shrani v imenik + Pripni na vrh + Odpni + Označi kot prebrano + Označi kot neprebrano + Blokiraj + Sinhroniziranje sporočil… + Vi: %s + Osnutek: %s + Rezultati v sporočilih + %d sporočil + Vaši pogovori se bodo pojavili tukaj + Ni rezultatov + Vaši arhivirani pogovori se bodo pojavili tukaj + Začni nov pogovor + Znova vzljubite sporočanje + Napravi QKSMS privzet program za sporočanje + Spremeni + Potrebno dovoljenje + QKSMS potrebuje dovoljenje za pošiljanje in branje SMS sporočil + QKSMS potrebuje dovoljenje za dostop do stikov + Dovoli + Prejeto + Arhiv + Načrtovano + Blokiranje + Več + Nastavitve + Pomagaj & povrate informacije + Povabi prijatelje + Odkleni neverjetne nove funkcije in podpri razvoj + Uživate v QKSMS? + Deli nekaj ljubezni in nas oceni na Google Play! + V REDU! + OPUSTI + Izbriši + + Ali ste prepričani, da želite izbrisati %d pogovor? + Ali ste prepričani, da želite izbrisati %d pogovora? + Ali ste prepričani, da želite izbrisati %d pogovorov? + Ali ste prepričani, da želite izbrisati %d pogovorov? + + + Kopiraj besedilo + Naprej + Izbriši + + Izberi telefonsko številko + %s ∙ Privzeto + Samo enkrat + Vedno + %d izbran + %1$d od %2$d rezultatov + Pošlji kot skupinsko sporočilo + Prejemniki in odgovori bodo vidni vsem + To je začetek vašega pogovora. Povej nekaj lepega! + Kontaktna kartica + Načrtovano za + Izbran čas mora biti v prihodnosti! + Da lahko uporabiš načrtovano sporočanje, moraš odkleniti QKSMS+ + Dodano k načrtovanim sporočilom + Napiši sporočilo… + Kopiraj besedilo + Naprej + Izbriši + Prejšnje + Naslednje + Počisti + Podrobnosti sporočila + Vnesi: %s + Od: %s + Za: %s + Zadeva %s + Prioriteta: %s + Velikost: %s + Poslano: %s + Prejeto: %s + Dostavljeno: %s + Koda napake: %d + Vstavi priponko + Vstavi sliko + Zajemi sliko + Načrtuj sporočilo + Vstavi stik + Napaka pri branju stika + %s izbran, zamenjaj kartico SIM + Pošlji sporočilo + Pošiljanje… + Dostavljeno %s + Napaka pri pošiljanju. Pritisni za ponovni poskus + Podrobnosti + Naslov kopiran + Naslov pogovora + Obvestila + Teme + Arhiv + Ostrani iz arhiva + Blokiraj + Odblokiraj + Izbriši pogovor + Napaka pri nalaganju večpredstavnosti + Shranjeno v galerijo + Varnostna kopija in obnovitev + Ustvarjanje varnostne kopije sporočil + Obnovitev varnostne kopije + Zadnja varnostna kopija + Nalaganje… + Nikoli + Obnovi + Izberi varnostno kopijo + Prosimo odklenite QKSMS+ da omogočite uporabo varnostnih kopij in njihove obnove + Varnostna kopija se izvaja… + Obnavljanje varnostne kopije se izvaja… + Obnovi iz varnotsne kopije + Ali ste prepričani, da želite obnoviti sporočila iz te varnostne kopije? + Prekliči obnovitev varnostne kopije + Sporočila, ki so že bila obnovljena, bodo ostala na vaši napravi + Varnostne kopije + Ni najdenih varnostnih kopij + + %d sporočilo + %d sporočila + %d sporočil + %d sporočila + + Trenutno so za ustvarjanje in obnovo varnostnih kopij podprta le sporočila SMS. MMS podpora in varnostne kopije pridejo kmalu! + Ustvari varnostno kopijo sedaj + Razčlenjevanje varnostne kopije… + %d/%d sporočil + Shranjevanje varnostne kopije… + Sinhroniziranje sporočil… + Končano! + Varnostna kopija in obnovitev + Načrtovano + Samodejno pošlji sporočilo ob nastavljenem točno določenem trenutku + Hej! Kdaj je že tvoj rojstni dan? + Je 23. Decembra + Vse najboljše! Poglej kako dober prijatelj sem, da se spomnim tvojega rojstnega dneva + + Pošiljanje na 23. December + Načrtuj sporočilo + Načrtovano sporočilo + + Pošlji sedaj + Kopiraj besedilo + Izbriši + + Izgled + Glavno + QK Odgovor + Teme + Nočni način + Čisto črni nočni način + Začetni čas + Čas končanja + Samodejne barve stikov + Velikost pisave + Uporabi sistemsko pisavo + Samodejni čustvenček + Obvestila + Tapni za prilagoditev + Dejanja + Gumb 1 + Gumb 2 + Gumb 3 + Predogledi obvestil + Zbudi zaslon + Vibriranje + Zvok + Brez + QK Odgovor + Pojavno obvestilo za nova sporočila + Tapni da opustiš + Tapni izven obvestila, da ga zapreš + Zakasnjeno sporočanje + Dejanja drsenja + Nastavi dejanja drsenja za pogovore + Podrsaj desno + Podrsaj levo + SPREMENI + + Brez + Arhiviraj + Izbriši + Pokliči + Označi kot prebrano + Označi kot neprebrano + + Potrditev dostavitve sporočila + Potrdi, da so bila sporočila dostavljena uspešno + Podpis + Dodaj podpis na konec vaših sporočil + Odstrani narečja + Odstrani narečja iz besedila odhodnih SMS sporočil + Samo telefonske številke + Ko sestavljaš sporočilo, prikaži samo telefonske številke + Pošlji dolga sporočila kot MMS + Če se vaša daljša sporočila ne pošljejo, ali se pošiljajo v napačnem vrstnem redu, jih lahko pošljete kot MMS sporočilo. To lahko povzroči dodatne stroške + Samodejno stisni MMS priponke + Sinhroniziraj sporočila + Znova sinhroniziraj sporočila s sistemsko SMS bazo + Več o QKSMS + Različica %s + Beleženje dnevnika za odpravljanje napak omogočeno + Beleženje dnevnika za odpravljanje napak onemogočeno + Vnesi trajanje (v sekundah) + Blokiranje + Opusti sporočila + Opusti prihajajoča sporočila blokiranih pošiljateljev, namesto da jih skriješ + Blokirani pogovori + Upravljalnik Blokiranja + QKSMS + Vgrajena sposobnost blokiranja v QKSMS + Samodejno razvrstite vaše klice in sporočila v eno priročno mesto! Skupnostni IQ™ omogoča, da preprečite prejemanje sporočil pošiljateljev neželenih sporočil, kateri so skupnosti poznani + Samodejno razvrstite sporočila neželenih številk tako, da uporabiš \"Should I Answer\" aplikacijo + Kopiraj blokirane številke + Nadaljuj z %s in kopiraj obstoječe blokirane številke + Blokirane številke + Vaše blokirane številke se bodo pojavile tukaj + Blokiraj novo številko + Blokiraj besedila od + Telefonska številka + Blokirano + Blokirana sporočila + Blokirana sporočila bodo prikazana tukaj + Blokiraj + Odblokiraj + + Nadaljuj z %s in blokiraj to številko + Nadaljuj z %s in blokiraj ti številki + Nadaljuj z %s in blokiraj te številke + Nadaljuj z %s in blokiraj te številke + + + Nadaljuj z %s in dovoli to številko + Nadaljuj z %s in dovoli ti številki + Nadaljuj z %s in dovoli te številke + Nadaljuj z %s in dovoli te številke + + O aplikaciji + Različica + Razvijalec + Izvirna koda + Dnevnik sprememb + Stik + Licenca + Avtorske pravice + Podpri razvoj, odkleni vse + Že s %s lahko rešiš stradajočega razvijalca + + Doživljenjska nadgradnja za %1$s %2$s + + Odkleni + doniraj za %1$s %2$s + Hvala da podpirate QKSMS! + Zdaj imate dostop do vseh QKSMS+ funkcij + QKSMS+ je brezplačen za F-Droid uporabnike! Če želite podpirati razvoj, so donacije zelo dobrodošle. + Doniraj preko PayPal + Prihaja kmalu + Premium teme + Odkleni čudovite barve tem, ki niso vidne na paleti materialnega dizajna + Auto-čustvenček po meri + Ustvari avto-čustvenček bližnjico po meri + Varnostna kopija sporočila + Samodejno varnostno kopiraj sporočila. Nikoli več ne skrbi za izgubljeno zgodovino, če zamenjaš ali izgubiš telefon + Načrtovana sporočila + Načrtuj, da bodo sporočila poslana ob določenem datumu in času + Zakasnjeno pošiljanje + Počakaj nekaj sekund, preden pošlješ svoje sporočilo + Samodejni temni način + Omogoči temni način, glede na čas dneva + Napredno blokiranje + Blokiraj sporočila, ki vsebujejo ključne besede ali se ujemajo z vzorci + Samodejno-posreduj + Samodejno posreduj sporočila določenih pošiljateljev + Samodejen odgovor + Samodejno odgovori na prihajajoča sporočila s prednastavljenim sporočilom + Več + QKSMS je pod aktivnim razvojem in vaš nakup QKSMS+ bo vseboval vse prihodnje funkcije! + Nalaganje… + Poglej več pogovorov + Označi kot prebrano + Pokliči + Izbriši + Pokaži več + Pokaži manj + Odpri pogovor + Material + HEX + Potrdi + + Brez + Označi kot prebrano + Odgovori + Klic + Izbriši + + Da + Nadaljuj + Prekliči + Izbriši + Shrani + Ustavi + Več + Nastavi + Razveljavi + Kopirano + Arhivirani pogovori + Da uporabiš to, moraš odkleniti QKSMS+ + + Novo sporočilo + %s novi sporočili + %s novih sporočil + %s novih sporočil + + Sporočilo ni poslano + Sporočilo za %s ni bilo poslano + + Sistem + Onemogočeno + Vedno vključen + Samodejno + + + Prikaži ime in sporočilo + Prikaži ime + Skrij vsebino + + + Majhno + Običajno + Veliko + Večje + + + Brez zakasnitve + Kratko + Srednje + Dolgo + + + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB + No compression + + + V redu + Počakaj trenutek + Na poti + Hvala + Zveni dobro + Kako si? + Se strinjam + Ne + Ljubim te + Oprosti + LOL + To je v redu + + diff --git a/presentation/src/main/res/values-sr/strings.xml b/presentation/src/main/res/values-sr/strings.xml index 0f09f6741..c69447caa 100644 --- a/presentation/src/main/res/values-sr/strings.xml +++ b/presentation/src/main/res/values-sr/strings.xml @@ -30,9 +30,11 @@ Састављање Skip Continue + Add person Позови Details Save to gallery + Share Open navigation drawer %d selected Clear @@ -85,6 +87,10 @@ Forward Delete + Choose a phone number + %s ∙ Default + Just once + Always %d selected %1$d of %2$d results Send as group message @@ -125,6 +131,7 @@ Delivered %s Failed to send. Tap to try again Details + Address copied Conversation title Notifications Theme @@ -187,6 +194,7 @@ Pure black night mode Start time End time + Automatic contact colors Font size Use system font Automatic emoji @@ -197,6 +205,7 @@ Button 2 Button 3 Notification previews + Wake screen Vibration Sound None @@ -226,6 +235,8 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database @@ -365,9 +376,10 @@ Long + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-sv/strings.xml b/presentation/src/main/res/values-sv/strings.xml index 396936234..95d430f5b 100644 --- a/presentation/src/main/res/values-sv/strings.xml +++ b/presentation/src/main/res/values-sv/strings.xml @@ -30,16 +30,18 @@ Ange ett namn eller nummer Hoppa över Fortsätt + Lägg till person Ring Detaljer Spara till galleri + Dela Öppna navigeringspanelen %d vald Rensa Arkiv Avarkivera Radera - Add to contacts + Lägg till i kontakter Fäst på toppen Frigör Markera som läst @@ -84,6 +86,10 @@ Vidarebefordra Radera + Välj ett telefonnummer + %s ∙ Default + Bara en gång + Alltid %d vald %1$d av %2$d resultat Skicka som ett grupp-meddelande @@ -124,6 +130,7 @@ Levererade %s Misslyckades med att skicka. Knacka för att försöka igen Detaljer + Address copied Titel för konversation Aviseringar Tema @@ -175,7 +182,7 @@ Skicka nu Copy text - Delete + Radera Utseende Generellt @@ -185,6 +192,7 @@ Svart nattläge Starttid Sluttid + Automatic contact colors Storlek på typsnitt Använd systemteckensnitt Automatisk uttryckssymbol @@ -195,6 +203,7 @@ Knapp 2 Knapp 3 Notifikations förhandsvisningar + Wake screen Vibration Ljud Ingen @@ -214,16 +223,18 @@ Ta bort Ring Markera som läst - Mark as unread + Markera som oläst Leveransrapporter Bekräfta att meddelanden har skickats - Signature - Add a signature to the end of your messages + Signatur + Lägg till en signatur i slutet av dina meddelanden Ta bort accenter Ta bort accenter från tecken i utåtgående SMS-meddelanden Endast mobilnummer Visa endast mobilnummer när ett meddelanden skrivs + Skicka långa meddelanden som MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Komprimera MMS-bilagor automatiskt Synkronisera meddelanden Synkronisera dina meddelanden med telefonens SMS-databas @@ -232,27 +243,27 @@ Felsökningsloggning aktiverades Felsökningsloggning inaktiverad Fyll i varaktighet (sekunder) - Blocking + Blockerar Drop messages Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + Blockerade konversationer + Blockerings-hanterare QKSMS Built-in blocking functionality in QKSMS Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Filtrera automatiskt meddelanden från oönskade nummer genom att använda appen \"Borde jag svara\" - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here + Kopiera blockerade nummer + Fortsätt till %s och kopiera över dina befintliga blockerade nummer + Blockerade nummer + Dina blockerade nummer kommer att visas här + Blockera ett nytt nummer + Blockera SMS från + Telefonnummer + Blockera + Blockerade meddelanden + Dina blockerade meddelanden visas här Block - Unblock + Avblockera Continue to %s and block this number Continue to %s and block these numbers @@ -318,8 +329,8 @@ Ring Ta bort - Yes - Continue + Ja + Fortsätt Avbryt Radera Spara @@ -338,8 +349,8 @@ Meddelandet till %s misslyckades att skicka System - Disabled - Always on + Inaktiverad + Alltid på Automatic @@ -360,13 +371,14 @@ Lång + Automatic 100KB 200KB - 300KB (Rekommenderas) + 300KB 600KB 1000KB 2000KB - Ingen kompression + No compression Okej diff --git a/presentation/src/main/res/values-th/strings.xml b/presentation/src/main/res/values-th/strings.xml index a268215ca..cb366da8d 100644 --- a/presentation/src/main/res/values-th/strings.xml +++ b/presentation/src/main/res/values-th/strings.xml @@ -30,9 +30,11 @@ พิมพ์ชื่อหรือหมายเลข ข้าม ทำต่อ + Add person โทร รายละเอียด บันทึกรูปภาพ + Share เปิดเมนูลัด %d ถูกเลือก ลบออก @@ -83,6 +85,10 @@ ส่งต่อ ลบ + Choose a phone number + %s ∙ Default + Just once + Always %d ถูกเลือก %1$d ใน %2$d ของผลลัพท์ ส่งเป็นข้อความกลุ่ม @@ -123,6 +129,7 @@ %s ถูกส่งแล้ว การส่งล้มเหลว ลองแตะเพื่อส่งอีกครั้ง รายละเอียด + Address copied หัวข้อสนทนา การแจ้งเตือน รูปแบบการแสดงผล @@ -183,6 +190,7 @@ โหมดกลางคืนแบบมืดสนิท เวลาเริ่มต้น เวลาสิ้นสุด + Automatic contact colors ขนาดแบบอักษร ใช้แบบอักษรของระบบ อีโมจิอัตโนมัติ @@ -193,6 +201,7 @@ Button 2 Button 3 ตัวอย่างการแจ้งเตือน + Wake screen การสั่น เสียง ไม่มี @@ -222,6 +231,8 @@ ลบอักษรพิเศษ accents ออกจากข้อความ SMS ที่กำลังจะส่งออก Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply บีบอัดสิ่งที่แนบมาใน MMS อัตโนมัติ ซิงค์ข้อความ ซิงค์ข้อความของคุณกับฐานข้อมูลของ Android SMS @@ -355,13 +366,14 @@ ยาว + Automatic 100KB 200KB - 300KB (แนะนำ) + 300KB 600KB - 1000 กิโลไบต์ - 2000 กิโลไบต์ - ไม่มีการบีบอัด + 1000KB + 2000KB + No compression ตกลง diff --git a/presentation/src/main/res/values-tl/strings.xml b/presentation/src/main/res/values-tl/strings.xml index 72d704646..187332d49 100644 --- a/presentation/src/main/res/values-tl/strings.xml +++ b/presentation/src/main/res/values-tl/strings.xml @@ -30,9 +30,11 @@ Sumulat Laktawan Magpatuloy + Add person Tawagan Mga detalye Save to gallery + Share Open navigation drawer %d selected Clear @@ -84,6 +86,10 @@ Forward Delete + Choose a phone number + %s ∙ Default + Just once + Always %d selected %1$d of %2$d results Send as group message @@ -124,6 +130,7 @@ Delivered %s Failed to send. Tap to try again Details + Address copied Conversation title Notifications Theme @@ -185,6 +192,7 @@ Pure black night mode Start time End time + Automatic contact colors Font size Use system font Automatic emoji @@ -195,6 +203,7 @@ Button 2 Button 3 Notification previews + Wake screen Vibration Sound None @@ -224,6 +233,8 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database @@ -360,9 +371,10 @@ Long + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-tr/strings.xml b/presentation/src/main/res/values-tr/strings.xml index d6eec25fc..891dafb73 100644 --- a/presentation/src/main/res/values-tr/strings.xml +++ b/presentation/src/main/res/values-tr/strings.xml @@ -30,9 +30,11 @@ Bir isme veya numaraya yazın Atla Devam + Kişi ekle Ara Ayrıntılar Galeriye Kaydet + Paylaş Gezinti çubuğunu aç %d Seçili Temizle @@ -84,6 +86,10 @@ Yönlendir Sil + Bir telefon numarası seçin + %s ∙ Varsayılan + Sadece bir kez + Her zaman %d Seçili %1$d / %2$d sonuçlar Grup mesajı gönder @@ -124,6 +130,7 @@ Teslim Edildi %s Gönderilemedi. Yeniden denemek için dokunun Ayrıntılar + Adres kopyalandı Konuşma başlığı Bildirimler Tema @@ -185,6 +192,7 @@ Saf siyah gece modu Başlama zamanı Bitiş Zamanı + Otomatik kişi renkleri Yazı tipi boyutu Sistem yazı tipini kullan Otomatik emoji @@ -195,6 +203,7 @@ Buton 2 Buton 3 Bildirim ön izleme + Uyandırma ekranı Titreşim Ses Hiçbiri @@ -224,6 +233,8 @@ Giden SMS mesajlarından aksanlı karakterleri kaldırın Sadece cep telefonu numaraları Bir mesaj oluştururken, yalnızca cep telefonu numaralarını göster + Uzun mesajları MMS olarak gönder + Uzun metin mesajlarınız gönderilemiyorsa veya yanlış sırada gönderiliyorsa, onları MMS mesajları olarak gönderebilirsiniz. Ek ücretler uygulanabilir Otomatik-MMS ekleri sıkıştır İletileri eşitle İletilerinizi Android SMS veritabanı ile yeniden senkronize edin @@ -362,12 +373,13 @@ BAĞLAM İSTEĞİ Uzun - 100 kilobayt - 200 kilobayt - 300KB (önerilen) - 600 kilobayt - 1000 kilobayt - 2000 kilobayt + Otomatik + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB Sıkıştırma yok diff --git a/presentation/src/main/res/values-uk/strings.xml b/presentation/src/main/res/values-uk/strings.xml index 026e3bed1..d586fc98e 100644 --- a/presentation/src/main/res/values-uk/strings.xml +++ b/presentation/src/main/res/values-uk/strings.xml @@ -30,9 +30,11 @@ Кому: ім\'я або номер Пропустити Продовжити + Додати контакт Зателефонувати Деталі Зберегти в галереї + Поділитися Відкрити панель навігації Обрано: %d Очистити @@ -86,6 +88,10 @@ Переслати Видалити + Виберіть номер телефону + %s ∙ початково + Один раз + Завжди Обрано: %d %1$d із %2$d результатів Надіслати як групове повідомлення @@ -126,6 +132,7 @@ Доставлено %s Не вдалося надіслати. Торкніться, щоб повторити спробу Деталі + Адреса скопійована Заголовок бесіди Сповіщення Тема @@ -136,7 +143,7 @@ Видалити бесіду Не вдалося завантажити медіа Збережено в галереї - Резервне копіювання та відновлення + Резервне копіювання Резервне копіювання повідомлень Відновити з резервної копії Остання резервна копія @@ -144,7 +151,7 @@ Ніколи Відновити Оберіть резервну копію - Для використання резервного копіювання та відновлення - розблокуйте QKSMS+ + Для використання резервного копіювання та відновлення розблокуйте QKSMS+ Триває створення резервної копії… Триває відновлення… Відновити з резервної копії @@ -166,7 +173,7 @@ Збереження резервної копії… Синхронізація повідомлень… Готово! - Резервне копіювання та відновлення + Резервне копіювання Заплановані Автоматично надсилайте повідомлення саме в той час, який ви захочете Агов! Коли в тебе день народження? @@ -189,6 +196,7 @@ Повністю чорний нічний режим Час початку Час завершення + Автоматичні кольори контактів Розмір шрифта Використовувати системний шрифт Автоматичні emoji @@ -199,6 +207,7 @@ Кнопка 2 Кнопка 3 Попередній перегляд сповіщень + Екран заблокований Вібро Звук Ні @@ -228,6 +237,8 @@ Видалення акцентів з символів у вихідних SMS-повідомленнях Лише номери мобільних При написанні повідомлення показувати лише мобільні номери + Надсилати довгі повідомлення як MMS + Якщо частини довгого повідомлення не відправляються або роблять це в хибному порядку, ви можете відправити їх як MMS натомість. Оплата згідно тарифів вашого оператора Автоматичне стиснення MMS-вкладень Синхронізація повідомлень Повторна синхронізація ваших повідомлень з власною базою даних SMS Android @@ -337,7 +348,7 @@ Скасувати Скопійовано Бесіду заархівовано - Для використання Ви повинні розблокувати QKSMS+ + Для використання ви повинні розблокувати QKSMS+ Нове повідомлення %s нових повідомлень @@ -370,9 +381,10 @@ Довга + Автоматично 100 Кб 200 Кб - 300 Кб (рекомендовано) + 300 Кб 600 Кб 1000 Кб 2000 Кб diff --git a/presentation/src/main/res/values-ur/strings.xml b/presentation/src/main/res/values-ur/strings.xml index 4f3a02e8c..8833a810c 100644 --- a/presentation/src/main/res/values-ur/strings.xml +++ b/presentation/src/main/res/values-ur/strings.xml @@ -30,9 +30,11 @@ Type a name or number Skip Continue + Add person Call Details Save to gallery + Share Open navigation drawer %d selected Clear @@ -84,6 +86,10 @@ Forward Delete + Choose a phone number + %s ∙ Default + Just once + Always %d selected %1$d of %2$d results Send as group message @@ -124,6 +130,7 @@ Delivered %s Failed to send. Tap to try again Details + Address copied Conversation title Notifications Theme @@ -185,6 +192,7 @@ Pure black night mode Start time End time + Automatic contact colors Font size Use system font Automatic emoji @@ -195,6 +203,7 @@ Button 2 Button 3 Notification previews + Wake screen Vibration Sound None @@ -224,6 +233,8 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database @@ -360,9 +371,10 @@ Long + Automatic 100KB 200KB - 300KB (Recommended) + 300KB 600KB 1000KB 2000KB diff --git a/presentation/src/main/res/values-vi/strings.xml b/presentation/src/main/res/values-vi/strings.xml index 721d8c754..24b7ee63c 100644 --- a/presentation/src/main/res/values-vi/strings.xml +++ b/presentation/src/main/res/values-vi/strings.xml @@ -30,9 +30,11 @@ Nhập tên hoặc số điện thoại Bỏ qua Tiếp tục + Thêm người Cuộc gọi Chi tiết Lưu vào bộ sưu tập + Chia sẻ Mở menu điều hướng %d đã chọn Dọn sạch @@ -83,6 +85,10 @@ Chuyển tiếp Xóa + Chọn một số điện thoại + %s ∙ Mặc định + Chỉ lần này + Luôn luôn %d đã chọn %1$d trên %2$d kết quả Gửi theo nhóm tin nhắn @@ -123,6 +129,7 @@ Đã chuyển %s Gửi thất bại. Bấm để thử lại lần nữa Chi tiết + Địa chỉ đã được sao chép Tiêu đề cuộc trò chuyện Thông báo Chủ đề @@ -163,7 +170,7 @@ Sao lưu và khôi phục Hẹn giờ tin nhắn Tự động gửi tin nhắn vào lúc mà bạn muốn - Này, khi nào thì tới sinh nhật bạn ? + Này, khi nào thì đến sinh nhật bạn? Vào ngày 23 tháng 12 đó Chúc mừng sinh nhật! Nhìn xem, tôi đúng là một người bạn tuyệt vời, nhớ ngày sinh nhật của bạn đấy @@ -183,6 +190,7 @@ Chế độ ban đêm tinh khiết Thời gian bắt đầu Thời gian kết thúc + Tự động hiện màu sắc của liên hệ Cỡ chữ Sử dụng font chữ hệ thống Emoji tự động @@ -193,6 +201,7 @@ Nút 2 Nút 3 Xem trước các thông báo + Đánh thức màn hình Rung Âm thanh Không dùng @@ -222,6 +231,8 @@ Bỏ dấu ký tự khi gửi SMS Lọc số di động Khi soạn tin mới, chỉ hiển thị liên hệ có số di động + Gửi đoạn tin nhắn dài như MMS + Nếu đoạn tin nhắn dài bị lỗi khi gửi, hoặc gửi sai thứ tự, bạn có thể gửi chúng như tin nhắn MMS thay thế. Cước phí bổ sung có thể bị áp dụng Tự động nén các tệp đính kèm MMS Đồng bộ tin nhắn Đồng bộ lại tin nhắn với thiết bị @@ -355,12 +366,13 @@ Dài + Tự động 100KB 200KB - 300KB (khuyến nghị) + 300KB 600KB 1000KB - 2M + 2000KB Không nén diff --git a/presentation/src/main/res/values-zh-rCN/strings.xml b/presentation/src/main/res/values-zh-rCN/strings.xml index 9519b0b41..c931c7a84 100644 --- a/presentation/src/main/res/values-zh-rCN/strings.xml +++ b/presentation/src/main/res/values-zh-rCN/strings.xml @@ -30,16 +30,18 @@ 撰写 跳过 继续 + 添加人物 致电 详情 保存到图库 + 分享 打开导航栏 已选择 %d 项 清除 存档 取消存档 删除 - Add to contacts + 添加到通讯录 固定到顶部 取消固定 标记为已读 @@ -47,7 +49,7 @@ 拦截 正在同步信息… 你:%s - Draft: %s + 草稿: %s 信息中的结果 %d 条信息 对话会在这里显示 @@ -83,6 +85,10 @@ 转发 删除 + 选择电话号码 + %s ∙ 默认 + 仅一次 + 始终 已选择%d个项目 已显示%2$d中的%1$d条结果 群发消息 @@ -123,6 +129,7 @@ 已送达 %s 发送失败。点击再试一次 详情 + 地址已复制 对话标题 通知 主题 @@ -172,8 +179,8 @@ 预约短信 现在发送 - Copy text - Delete + 复制文本 + 删除 外观 通用 @@ -183,6 +190,7 @@ 纯黑夜间模式 开始时间 结束时间 + 自动化联系人颜色 字体大小 使用系统字体 自动颜文字 @@ -193,6 +201,7 @@ 按钮2 按钮3 通知预览 + 唤醒屏幕 振动 铃声 @@ -217,11 +226,13 @@ 送达确认 确认短信已成功送达 签名 - Add a signature to the end of your messages + 在您的信息结尾添加签名 删除读音符号 删除短信中字母上的读音符号 仅限手机号码 撰写邮件时, 只显示手机号码 + 将长信息作为彩信发送 + 如果您无法发送较长的信息,或者是以错误的顺序发送,您可以将其作为彩信发送。可能会收取额外的费用 自动压缩彩信附件 同步消息 重新与安卓原生短信数据库进行同步 @@ -230,32 +241,32 @@ 启用了调试日志记录 调试日志记录已禁用 输入时长 (秒) - Blocking - Drop messages - Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + 屏蔽 + 丢弃信息 + 丢弃屏蔽的发件人所发送的信息 + 已屏蔽的会话 + 屏蔽管理器 QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + QKSMS中内建的屏蔽功能 + 自动在适当的位置过滤您的来电和信息!Community IQ™允许您阻止社区中已知的垃圾发件人所发送的垃圾信息 使用\"Should I Answer\"应用过滤未知号码的消息 - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here - Block - Unblock + 复制屏蔽的号码 + 继续前往%s并复制到您现有的已屏蔽号码 + 已屏蔽的号码 + 您所屏蔽的号码将会在这里显示 + 屏蔽新的号码 + 屏蔽文本自 + 电话号码 + 屏蔽 + 已屏蔽的信息 + 您所屏蔽的号码将会在这里显示 + 屏蔽 + 取消屏蔽 - Continue to %s and block these numbers + 继续前往%s并屏蔽这个(些)号码 - Continue to %s and allow these numbers + 继续前往%s并允许这个(些)号码 关于 版本 @@ -314,8 +325,8 @@ 呼叫 删除 - Yes - Continue + + 继续 取消 删除 保存 @@ -332,10 +343,10 @@ 消息未发送 给%s的短信发送失败 - System - Disabled - Always on - Automatic + 系统 + 已禁用 + 始终打开 + 自动 显示姓名和消息 @@ -355,13 +366,14 @@ + Automatic 100KB 200KB - 300KB (推荐) + 300KB 600KB 1000KB 2000KB - 不压缩 + No compression 好的 diff --git a/presentation/src/main/res/values-zh/strings.xml b/presentation/src/main/res/values-zh/strings.xml index 13894b527..dde414220 100644 --- a/presentation/src/main/res/values-zh/strings.xml +++ b/presentation/src/main/res/values-zh/strings.xml @@ -30,9 +30,11 @@ 編輯 跳过 繼續 + 增加傳送人 致電 詳細資訊 保存到圖庫 + 分享 打開導航抽屜 已選擇%d項 清除 @@ -83,6 +85,10 @@ 轉寄 刪除 + 選一個號碼 + %s ∙ 預設 + 就這一次 + 總是如此 已選取 %d 個項目 已顯示 %2$d 條中的 %1$d 條結果 群發訊息 @@ -123,7 +129,8 @@ 已傳遞 %s 發送失敗,點擊重試 詳細資訊 - 會話標題 + Address copied + 對話標題 通知 主題 存檔 @@ -183,6 +190,7 @@ 純黑色夜間模式 開始時間 結束時間 + Automatic contact colors 字體大小 使用系統字體 自動 emoji 表情 @@ -193,6 +201,7 @@ 按鈕2 按鈕3 通知預覽 + Wake screen 震動 音效 @@ -222,6 +231,8 @@ 刪除訊息中字母上的讀音符號 僅限手機號碼 撰寫訊息時,只顯示手機號碼 + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply 自動壓縮MMS附件 同步消息 重新與安卓原生訊息數據庫進行同步 @@ -324,7 +335,7 @@ 設置 復原 已複製 - 存檔的會話 + 存檔的對話 您必須解鎖QKSMS+才能使用此功能 %s條新訊息 @@ -355,13 +366,14 @@ + Automatic 100KB 200KB - 300KB (推荐) + 300KB 600KB 1000KB 2000KB - 不压缩 + No compression 好的 -- GitLab From 51478055197f8af6f651eb40b5e71be23af1314e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 12 Jan 2020 23:39:31 -0500 Subject: [PATCH 097/213] Increment to v3.8.0-beta1 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index c393a19c4..7925b3fbf 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2209 - versionName "3.7.10" + versionCode 2210 + versionName "3.8.0-beta1" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From 6ea4eb9e80c99fa946fd4c756a7a3344f1d302e0 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 13 Jan 2020 18:53:57 -0500 Subject: [PATCH 098/213] Fix crash when starting new conversation Fixes #1555 --- .../java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 201d7fa0b..eb9df557b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -248,7 +248,7 @@ class ComposeViewModel @Inject constructor( conversationRepo.getRecipients() .asSequence() .filter { recipient -> recipient.contact?.lookupKey == lookupKey } - .first { recipient -> phoneNumberUtils.compare(recipient.address, address) } + .firstOrNull { recipient -> phoneNumberUtils.compare(recipient.address, address) } ?: Recipient( address = address, contact = lookupKey?.let(contactRepo::getUnmanagedContact)) -- GitLab From f8e3e72c185cbbd7fa0b6a41f23e60f9493814bb Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 13 Jan 2020 19:00:20 -0500 Subject: [PATCH 099/213] Fix conversation swipe actions --- .../QKSMS/feature/conversations/ConversationsAdapter.kt | 9 +++++++++ 1 file changed, 9 insertions(+) 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 cf741f840..8f1c00410 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 @@ -45,6 +45,11 @@ class ConversationsAdapter @Inject constructor( private val phoneNumberUtils: PhoneNumberUtils ) : QkRealmAdapter() { + init { + // This is how we access the threadId for the swipe actions + setHasStableIds(true) + } + 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) @@ -109,6 +114,10 @@ class ConversationsAdapter @Inject constructor( holder.unread.setTint(colors.theme(recipient).theme) } + override fun getItemId(position: Int): Long { + return getItem(position)?.id ?: -1 + } + override fun getItemViewType(position: Int): Int { return if (getItem(position)?.unread == false) 0 else 1 } -- GitLab From b5a5cfb8a31a5eb2060455141e4c8d7ed51439ab Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 13 Jan 2020 21:01:01 -0500 Subject: [PATCH 100/213] Increment to v3.8.0-beta2 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 7925b3fbf..bed6a5523 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2210 - versionName "3.8.0-beta1" + versionCode 2211 + versionName "3.8.0-beta2" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From 55e36be6f65b1dcf77de161a3e3e5165cf90439e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 17 Jan 2020 20:17:43 -0500 Subject: [PATCH 101/213] Updated translations --- presentation/src/main/res/values-in/strings.xml | 4 ++-- presentation/src/main/res/values-it/strings.xml | 4 ++-- .../src/main/res/values-pt-rBR/strings.xml | 16 ++++++++-------- presentation/src/main/res/values-pt/strings.xml | 16 ++++++++-------- presentation/src/main/res/values-sv/strings.xml | 8 ++++---- .../src/main/res/values-zh-rCN/strings.xml | 4 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/presentation/src/main/res/values-in/strings.xml b/presentation/src/main/res/values-in/strings.xml index 9f3ec415d..d3d82560d 100644 --- a/presentation/src/main/res/values-in/strings.xml +++ b/presentation/src/main/res/values-in/strings.xml @@ -366,14 +366,14 @@ Lama - Automatic + Otomatis 100KB 200KB 300KB 600KB 1000KB 2000KB - No compression + Tanpa kompresi Oke diff --git a/presentation/src/main/res/values-it/strings.xml b/presentation/src/main/res/values-it/strings.xml index 74ce06252..9a898a3db 100644 --- a/presentation/src/main/res/values-it/strings.xml +++ b/presentation/src/main/res/values-it/strings.xml @@ -371,14 +371,14 @@ Lungo - Automatic + Automatico 100KB 200KB 300KB 600KB 1000KB 2000KB - No compression + Nessuna compressione Okay diff --git a/presentation/src/main/res/values-pt-rBR/strings.xml b/presentation/src/main/res/values-pt-rBR/strings.xml index 1d0a47836..d9bd784c0 100644 --- a/presentation/src/main/res/values-pt-rBR/strings.xml +++ b/presentation/src/main/res/values-pt-rBR/strings.xml @@ -371,14 +371,14 @@ Longo - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Automático + 100 KB + 200 KB + 300 KB + 600 KB + 1000 KB + 2000 KB + Sem compressão Ok diff --git a/presentation/src/main/res/values-pt/strings.xml b/presentation/src/main/res/values-pt/strings.xml index eeb81d35a..aa0b2dc2a 100644 --- a/presentation/src/main/res/values-pt/strings.xml +++ b/presentation/src/main/res/values-pt/strings.xml @@ -371,14 +371,14 @@ Longo - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Automático + 100 KB + 200 KB + 300 KB + 600 KB + 1000 KB + 2000 KB + Sem compressão Ok diff --git a/presentation/src/main/res/values-sv/strings.xml b/presentation/src/main/res/values-sv/strings.xml index 95d430f5b..30a8a5b13 100644 --- a/presentation/src/main/res/values-sv/strings.xml +++ b/presentation/src/main/res/values-sv/strings.xml @@ -371,14 +371,14 @@ Lång - Automatic + Automatisk 100KB - 200KB + 200kB 300KB - 600KB + 600kB 1000KB 2000KB - No compression + Ingen komprimering Okej diff --git a/presentation/src/main/res/values-zh-rCN/strings.xml b/presentation/src/main/res/values-zh-rCN/strings.xml index c931c7a84..a0e3ebece 100644 --- a/presentation/src/main/res/values-zh-rCN/strings.xml +++ b/presentation/src/main/res/values-zh-rCN/strings.xml @@ -366,14 +366,14 @@ - Automatic + 自动 100KB 200KB 300KB 600KB 1000KB 2000KB - No compression + 不压缩 好的 -- GitLab From ccefa088259dc7aa16409a920990e914c78f5d74 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 17 Jan 2020 20:17:57 -0500 Subject: [PATCH 102/213] Increment to v3.8.0 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index bed6a5523..196f006b1 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2211 - versionName "3.8.0-beta2" + versionCode 2212 + versionName "3.8.0" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From c0b2f619617105cb61c5a90c651a9db9445f7a1b Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 25 Jan 2020 13:35:47 -0500 Subject: [PATCH 103/213] Set the default MMS size back to 300kb --- domain/src/main/java/com/moez/QKSMS/util/Preferences.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index e994247df..d783e0249 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -110,7 +110,7 @@ class Preferences @Inject constructor( val unicode = rxPrefs.getBoolean("unicode", false) val mobileOnly = rxPrefs.getBoolean("mobileOnly", false) val longAsMms = rxPrefs.getBoolean("longAsMms", false) - val mmsSize = rxPrefs.getInteger("mmsSize", -1) + val mmsSize = rxPrefs.getInteger("mmsSize", 300) val logging = rxPrefs.getBoolean("logging", false) init { -- GitLab From 33b239d3b7d3e13defea35e61023b393dca55fbe Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 27 Jan 2020 18:24:14 -0500 Subject: [PATCH 104/213] Perform QK migration only after realm migration --- data/src/main/java/com/moez/QKSMS/migration/QkMigration.kt | 4 ++-- .../src/main/java/com/moez/QKSMS/common/QKApplication.kt | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkMigration.kt index 32e192d38..beafeca1f 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkMigration.kt @@ -28,13 +28,13 @@ import kotlinx.coroutines.launch import javax.inject.Inject class QkMigration @Inject constructor( - context: Context, + private val context: Context, private val conversationRepo: ConversationRepository, private val prefs: Preferences, private val qksmsBlockingClient: QksmsBlockingClient ) { - init { + fun performMigration() { GlobalScope.launch { val oldVersion = prefs.version.get() diff --git a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt index 723a8b4f0..b227d1b82 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt @@ -81,6 +81,8 @@ class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverIn .schemaVersion(QkRealmMigration.SchemaVersion) .build()) + qkMigration.performMigration() + GlobalScope.launch(Dispatchers.IO) { referralManager.trackReferrer() } -- GitLab From 118da536b79dec91ff86dbc52bf5b672633fe96f Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 27 Jan 2020 18:50:17 -0500 Subject: [PATCH 105/213] Fix crash when deleting conversation --- .../conversationinfo/ConversationInfoPresenter.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt index 2ad141bd7..07860ca2c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt @@ -80,14 +80,18 @@ class ConversationInfoPresenter @Inject constructor( disposables += markUnarchived disposables += deleteConversations - val partsObservable = messageRepo.getPartsForConversation(threadId) - .asObservable() - .filter { parts -> parts.isLoaded && parts.isValid} - disposables += Observables - .combineLatest(conversation, partsObservable) { conversation, parts -> + .combineLatest( + conversation, + messageRepo.getPartsForConversation(threadId).asObservable() + ) { conversation, parts -> val data = mutableListOf() + // If some data was deleted, this isn't the place to handle it + if (!conversation.isLoaded || !conversation.isValid || !parts.isLoaded || !parts.isValid) { + return@combineLatest + } + data += conversation.recipients.map(::ConversationInfoRecipient) data += ConversationInfoItem.ConversationInfoSettings( name = conversation.name, -- GitLab From af62b064844b5d6fe55469a300c81e1f1a06ff4a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 27 Jan 2020 18:57:54 -0500 Subject: [PATCH 106/213] Query for non-null contact group titles --- .../java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt index f2f290b5d..ad9d6532c 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactGroupImpl.kt @@ -35,7 +35,8 @@ class CursorToContactGroupImpl @Inject constructor( ContactsContract.Groups.TITLE) private const val SELECTION = "${ContactsContract.Groups.AUTO_ADD}=0 " + "AND ${ContactsContract.Groups.DELETED}=0 " + - "AND ${ContactsContract.Groups.FAVORITES}=0" + "AND ${ContactsContract.Groups.FAVORITES}=0 " + + "AND ${ContactsContract.Groups.TITLE} IS NOT NULL" private const val ID = 0 private const val TITLE = 1 -- GitLab From d125436e5e6c1553d8fbf15c2448a1c20597da9f Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 27 Jan 2020 19:03:04 -0500 Subject: [PATCH 107/213] Silence error --- .../java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index ca45deae9..1eb62d234 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -475,7 +475,7 @@ class MessageRepositoryImpl @Inject constructor( val addresses = pdu.to.map { it.string }.filter { it.isNotBlank() } val parts = message.parts.mapNotNull { part -> - val bytes = tryOrNull { + val bytes = tryOrNull(false) { context.contentResolver.openInputStream(part.getUri())?.use { inputStream -> inputStream.readBytes() } } ?: return@mapNotNull null -- GitLab From 6502dcdb82fda6d608f91a218c854114c738b113 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 27 Jan 2020 19:06:37 -0500 Subject: [PATCH 108/213] Update translations --- .../src/main/res/values-ar/strings.xml | 34 ++-- .../src/main/res/values-cs/strings.xml | 16 +- .../src/main/res/values-el/strings.xml | 32 +-- .../src/main/res/values-fa/strings.xml | 188 +++++++++--------- .../src/main/res/values-ja/strings.xml | 14 +- .../src/main/res/values-pl/strings.xml | 32 +-- 6 files changed, 158 insertions(+), 158 deletions(-) diff --git a/presentation/src/main/res/values-ar/strings.xml b/presentation/src/main/res/values-ar/strings.xml index d558c7076..2fbeee309 100644 --- a/presentation/src/main/res/values-ar/strings.xml +++ b/presentation/src/main/res/values-ar/strings.xml @@ -30,18 +30,18 @@ أكتب اسماً أو رقماً تخطي متابعة - Add person + اضافة شخص اتصال تفاصيل حفظ إلى المعرض - Share + مشاركة فتح درج التنقل %d محددة مسح أرشفة إلغاء الأرشفة حذف - Add to contacts + اضافة الى القائمة التثبيت فوق إلغاء التثبيت علّمه مقروء @@ -90,10 +90,10 @@ إعادة توجيه حذف - Choose a phone number + اختر رقم الهاتف %s ∙ Default - Just once - Always + فقط واحد + دائما %d مختارة %1$d من %2$d النتائج إرسال رسالة جماعية @@ -134,7 +134,7 @@ تم إرسال %s فشل الإرسال. إلمس لإعادة المحاولة التفاصيل - Address copied + تم نسخ العنوان عنوان المحادثة الإشعارات السمة @@ -189,8 +189,8 @@ الرسالة المجدولة أرسل الآن - Copy text - Delete + نسخ النص + حذف المظهر عام @@ -211,7 +211,7 @@ الزر 2 الزر 3 معاينات الإشعارات - Wake screen + فتح الشاشة الاهتزاز الصوت بدون نغمة @@ -392,13 +392,13 @@ طويل - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB + تلقائي + 100كيلوبايت + 200كيلوبايت + 300كيلوبايت + 600كيلوبايت + 1000كيلوبايت + 2000كيلوبايت No compression diff --git a/presentation/src/main/res/values-cs/strings.xml b/presentation/src/main/res/values-cs/strings.xml index 4c4f3c35e..6ae6060a8 100644 --- a/presentation/src/main/res/values-cs/strings.xml +++ b/presentation/src/main/res/values-cs/strings.xml @@ -381,14 +381,14 @@ Dlouhá - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Automaticky + 100 kB + 200 kB + 300 kB + 600 kB + 1 000 kB + 2 000 kB + Bez komprese OK diff --git a/presentation/src/main/res/values-el/strings.xml b/presentation/src/main/res/values-el/strings.xml index 0ea315e82..fa2df8978 100644 --- a/presentation/src/main/res/values-el/strings.xml +++ b/presentation/src/main/res/values-el/strings.xml @@ -30,11 +30,11 @@ Πληκτρολογήστε ένα όνομα ή αριθμό Παράλειψη Συνέχεια - Add person + Προσθήκη ατόμου Κλήση Λεπτομέρειες Αποθήκευση στη συλλογή - Share + Κοινή χρήση Άνοιγμα πλαισίου πλοήγησης Επιλεγμένα: %d Εκκαθάριση @@ -71,7 +71,7 @@ Ρυθμίσεις Βοήθεια & αποστολή σχολίων Πρόσκληση φίλων - Unlock amazing new features, and support development + Ξεκλειδώστε φανταστικές νέες λειτουργίες και συμβάλετε στην ανάπτυξη Σας αρέσει το QKSMS; Δείξτε μας την αγάπη σας και βαθμολογήστε μας στο Google Play! ΕΝΤΑΞΕΙ! @@ -86,10 +86,10 @@ Προώθηση Διαγραφή - Choose a phone number - %s ∙ Default - Just once - Always + Επιλέξτε έναν αριθμό τηλεφώνου + %s ∙ Προεπιλεγμένος + Μόνο μια φορά + Πάντα Επιλέχθηκαν: %d %1$d από %2$d αποτελέσματα Αποστολή ως ομαδικό μήνυμα @@ -130,7 +130,7 @@ Παραδόθηκε: %s Αποτυχία αποστολής. Πατήστε για νέα προσπάθεια Λεπτομέρειες - Address copied + Η διεύθυνση αντεγράφη Τίτλος συζήτησης Ειδοποιήσεις Θέμα εμφάνισης @@ -192,7 +192,7 @@ Νυχτερινή λειτουργία καθαρού μαύρου Ώρα έναρξης Ώρα λήξης - Automatic contact colors + Αυτόματος χρωματισμός επαφών Μέγεθος γραμματοσειράς Χρήση γραμματοσειράς συστήματος Αυτόματα emoji @@ -203,7 +203,7 @@ Κουμπί 2 Κουμπί 3 Προεπισκοπήσεις ειδοποιήσεων - Wake screen + Ενεργοποίηση οθόνης Δόνηση Ήχος Κανένα @@ -227,7 +227,7 @@ Επιβεβαιώσεις παράδοσης Confirm that messages were sent successfully - Signature + Υπογραφή Add a signature to the end of your messages Strip accents Remove accents from characters in outgoing SMS messages @@ -236,9 +236,9 @@ Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments - Sync messages - Re-sync your messages with the native Android SMS database - About QKSMS + Συγχρονισμός μηνυμάτων + Επανασυγχρονισμός μηνυμάτων με τη βάση δεδομένων SMS του Android + Σχετικά με το QKSMS Έκδοση %s Η καταγραφή σφαλμάτων ενεργοποιήθηκε Η καταγραφή σφαλμάτων απενεργοποιήθηκε @@ -252,9 +252,9 @@ Ενσωματωμένη λειτουργία φραγής του QKSMS Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app - Copy blocked numbers + Αντιγραφή αριθμών με φραγή Continue to %s and copy over your existing blocked numbers - Blocked numbers + Αριθμοί με φραγή Your blocked numbers will appear here Block a new number Block texts from diff --git a/presentation/src/main/res/values-fa/strings.xml b/presentation/src/main/res/values-fa/strings.xml index fbfe97022..a4c94a066 100644 --- a/presentation/src/main/res/values-fa/strings.xml +++ b/presentation/src/main/res/values-fa/strings.xml @@ -30,38 +30,38 @@ یک نام یا شماره را وارد کنید بیخیال ادامه - Add person + اضافه کردن شخص تماس جزئیات ذخیره در گالری - Share + اشتراک گذاری بازکردن کشو ناوبری %d انتخاب شده پاکسازی بایگانی بیرون آوردن از بایگانی حذف - Add to contacts + افزودن به مخاطبین اتصال به بالای صفحه - Unpin + رها کردن علامت گذاری به عنوان خوانده شده علامت گذاری بعنوان خوانده نشده مسدود کردن همگام سازی پیام ها… شما: %s - Draft: %s + پیش نویس: %s نتایج در پیام ها %d پیام مکالمات اینجا نمایش داده می‌شود بدون نتیجه بایگانی اینجا نمایش داده می‌شود شروع گفتگوی جدید - Love texting again + دوباره پیامک عاشقانه QKSMS را به عنوان نرم‌افزار پیش‌فرض پیامک انتخاب کنید تغییر نیاز به اجازه qksms برای ارسال و دیدن پیام ها نیاز به اجازه دارد - QKSMS needs permission to view your contacts + QKSMS نیاز به دسترسی به دیدن مخاطبین شما را دارد اجازه صندوق ورودی بایگانی @@ -71,7 +71,7 @@ تنظیمات راهنما & بازخورد دعوت دوستان - Unlock amazing new features, and support development + ویژگی های جدید شگفت انگیز را باز کرده و از توسعه نرم‌افزار پشتیبانی کنید از QKsms لذت می برید؟ علاقه ی خود رابا امتیاز دادن به ما در گوگل پلی به اشتراک بگذارید باشه! @@ -86,29 +86,29 @@ فوروارد حذف - Choose a phone number - %s ∙ Default - Just once - Always + یک شماره تلفن انتخاب کنید + %s ∙ پیش فرض + فقط یک بار + همیشه %d انتخاب شده - %1$d of %2$d results + نتایج %1$d از %2$d ارسال پیام به گروه - Recipients and replies will be visible to everyone - This is the start of your conversation. Say something nice! + دریافت کنندگان و پاسخ ها برای همه قابل مشاهده خواهد بود + این آغاز مکالمه شماست. یه چیز خوب بگو! کارت تماس - Scheduled for - Selected time must be in the future! - You must unlock QKSMS+ to use scheduled messaging - Added to scheduled messages + برنامه ریزی شده برای + زمان انتخاب شده باید در آینده باشد! + برای استفاده از پیام های برنامه ریزی شده ، باید QKSMS + را باز کنید + به پیامهای برنامه ریزی شده اضافه شد نوشتن پیام… کپی کردن متن - Forward + ارسال به دیگری حذف قبلی بعدی پاکسازی جزئیات پیام - Type: %s + نوع: %s از: %s به: %s موضوع: %s @@ -120,17 +120,17 @@ کد خطا: %d افزودن پیوست پیوست یک عکس - Take a photo + عکس گرفتن زمان بندی پیام - Attach a contact - Error reading contact + یک مخاطب ضمیمه کنید + خطا در خواندن مخاطب %s برگزیده شده، تغییر سیم کارت فرستادن پیام فرستادن… رسیده %s در ارسال خطایی رخ داد. برای ارسال دوباره، ضربه بزنید جزئیات - Address copied + آدرس کپی شد عنوان گفتگو اعلانها پوسته @@ -139,7 +139,7 @@ مسدود کردن رفع مسدودیت حذف مکالمه - Couldn\'t load media + بارگیری رسانه امکان پذیر نیست ذخیره در گالری پشتیبان گیری و بازیابی پشتیبان گیری از پیام‌ها @@ -149,70 +149,70 @@ هرگز بازگردانشانی یک پشتیبان را انتخاب کنید - Please unlock QKSMS+ to use backup and restore - Backup in progress… - Restore in progress… - Restore from backup + لطفاً QKSMS + را باز کنید تا از امکانات پشتیبان و بازیابی بهره ببرید + پشتیبان گیری در حال انجام است… + در حال بازیابی… + بازیابی از نسخه پشتیبان آیا مطمئن هسیتد که می‌خواهید پیغام‌هایتان را از پشتيباني بازیابی کنید? بازیابی را متوقف کن - Messages that have already been restored will remain on your device + پیام هایی که قبلاً بازیابی شده اند در دستگاه شما باقی خواهند ماند پشتیبان‌ها نسخه ی پشتیبان پیدا نشد - %d message - %d messages + %d پیام + %d پیام - Currently, only SMS is supported by Backup and Restore. MMS support and scheduled backups will be coming soon! + در حال حاضر ، فقط پیامک پشتیبان گیری و بازیابی پشتیبانی می شود. پشتیبان‌گیری و برنامه‌ریزی برای پشتیبان‌گیری از MMS به زودی ارائه می شود! همین حالا نسخه پشتیبان تهیه کن برگردانی بک اپ %d/%d پیام - Saving backup… - Syncing messages… - Finished! - Backup and restore - Scheduled - Automatically send a message, at the exact moment you\'d like - Hey! When was your birthday again? - It\'s on December 23rd - Happy birthday! Look at what a great friend I am, remembering your birthday + ذخیره نسخه پشتیبان… + در حال همگام سازی پیام‌ها… + تمام شد! + پشتیبان گیری و بازیابی + برنامه ریزی + در همان لحظه ای که می خواهید پیام بصورت خودکار ارسال کنید + سلام! کی دوباره تولدت بود؟ + 23 دسامبر است + تولدت مبارک! نگاه کنید که چه دوست خوبی دارم ، به یاد تولدت - Sending on December 23rd  - Schedule a message - Scheduled message + ارسال در 23 دسامبر + یک پیام تنظیم کنید + پیام برنامه ریزی شده ارسال هم‌اکنون - Copy text - Delete + کپی متن + حذف ظاهر عمومی - QK Reply + پاسخ QK پوسته حالت شب حالت شب کامل زمان شروع زمان پایان - Automatic contact colors + رنگ مخاطب خودکار اندازه قلم از فونت سیستم استفاده کن ایموجی خودکار اعلان‌ها از فونت سیستم استفاده کن - Actions - Button 1 - Button 2 - Button 3 + اقدامات + دکمه 1 + دکمه 2 + دکمه 3 بازبینی اعلان - Wake screen + صفحه بیدار لرزش صدای هیچی پاسخ QK پنجره پیام های جدید برای رد کردن ضربه بزنید - Tap outside of the popup to close it + برای بستن آن ، به بیرون پنجره ضربه بزنید ارسال با تاخیر - Swipe actions + اقدامات کشیدن تنظیم کشیدن برای گفتگو به راست بکشید به چپ بکشید @@ -228,13 +228,13 @@ تأیید تحویل تأیید ارسال موفق پیام امضا - Add a signature to the end of your messages + به انتهای پیام های خود یک امضا اضافه کنید حذف لهجه‌ها حذف کاراکترهای اضافه پیام ارسالی فقط شماره های تلفن وقتی در حال ارسال پیام هستید فقط می توانید شماره تلفن را ببنید - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply + پیام های طولانی را به عنوان MMS ارسال کن + اگر پیام های متنی طولانی شما نتوانسته اند ارسال شوند یا به ترتیب اشتباه ارسال شوند ، می توانید به جای آنها پیام های MMS ارسال کنید. هزینه های اضافی ممکن است اعمال شود فشرده سازی خودکار پیوست ها همگام سازی پیام ها همگام سازی پیام ها با پایگاه داده @@ -243,40 +243,40 @@ ورود به سیستم دیباگ فعال است ورد به سیستم دیباگ غیرفعال است زمان (ثانیه) را وارد کنید - Blocking - Drop messages - Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + انسداد + دور انداختم پیام‌ها + پیام های ورودی را از فرستنده های مسدود شده به جای مخفی کردن آنها دور اندازید + مکالمات مسدود شده + مدیریت انسداد QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + عملکرد مسدود کننده داخلی در QKSMS + تماس ها و پیام های خود را بطور خودکار فیلتر کنید! IQ Community به شما اجازه می دهد تا از پیام های ناخواسته توسط اسپم های شناخته شده در جامعه جلوگیری کنید با استفاده از برنامه «باید جواب بده»، پیامها را از شماره های ناخواسته به طور خودکار فیلتر کنید - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here - Block - Unblock + کپی شماره های مسدود شده + به %s ادامه دهید و از شماره های مسدود شده موجود خود کپی کنید + شماره‌های مسدود شده + شماره های مسدود شده شما در اینجا ظاهر می شوند + انسداد شماره جدید + انسداد پیام از طرف + شماره تلفن + انسداد + پیام‌های مسدود شده + پیامهای مسدود شده شما در اینجا ظاهر می شوند + انسداد + رفع مسدوديت - Continue to %s and block this number - Continue to %s and block these numbers + به %s ادامه دهید و این شماره را مسدود کن + به %s ادامه دهید و این شماره‌ها را مسدود کن - Continue to %s and allow this number - Continue to %s and allow these numbers + به %s ادامه دهید و به این شماره اجازه بده + به %s ادامه دهید و به این شماره‌ها اجازه بده درباره نسخه توسعه دهنده کد منبع - Changelog + گزارش دگرگونی‌ها مخاطب مجوز حق نشر @@ -329,8 +329,8 @@ تماس پاک کردن - Yes - Continue + بله + ادامه لغو حذف ذخیره @@ -371,14 +371,14 @@ طولانی - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + خودکار + ۱۰۰ کیلوبایت + ۲۰۰ کیلوبایت + ۳۰۰ کیلوبایت + ۶۰۰ کیلوبایت + ۱۰۰۰ کیلوبایت + ۲۰۰۰ کیلوبایت + بدون فشرده‌سازی باشه diff --git a/presentation/src/main/res/values-ja/strings.xml b/presentation/src/main/res/values-ja/strings.xml index ba9e1f4ac..0f8c83ede 100644 --- a/presentation/src/main/res/values-ja/strings.xml +++ b/presentation/src/main/res/values-ja/strings.xml @@ -129,7 +129,7 @@ %s を配信しました 送信に失敗しました。タップすると、もう一度やり直します 詳細 - Address copied + アドレスをコピーしました 会話のタイトル 通知 テーマ @@ -190,7 +190,7 @@ ピュアブラック夜間モード 開始時刻 終了時刻 - Automatic contact colors + 自動的に連絡先に色をつける フォントサイズ システムフォントを使用する 自動絵文字 @@ -201,7 +201,7 @@ ボタン 2 ボタン 3 通知プレビュー - Wake screen + ウェイクスクリーン 振動 サウンド 無し @@ -231,8 +231,8 @@ 送信する SMS メッセージの文字からアクセントを削除します 携帯電話番号のみ メッセージを作成するときは、携帯電話番号のみを表示します - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply + 長いメッセージをMMSとして送信する + 長いテキストメッセージの送信が失敗する場合、または間違った順序で送信される場合は、代わりにMMSメッセージとして送信できます。 追加料金が適用される場合があります MMS添付ファイルの自動圧縮 メッセージを同期 メッセージをネイティブの Android SMS データベースと再同期します @@ -366,14 +366,14 @@ 長い - Automatic + 自動 100KB 200KB 300KB 600KB 1000KB 2000KB - No compression + 圧縮なし OK diff --git a/presentation/src/main/res/values-pl/strings.xml b/presentation/src/main/res/values-pl/strings.xml index 765231102..3d0c447c6 100644 --- a/presentation/src/main/res/values-pl/strings.xml +++ b/presentation/src/main/res/values-pl/strings.xml @@ -132,7 +132,7 @@ Dostarczono %s Nie udało się wysłać wiadomości. Dotknij, aby spróbować ponownie Szczegóły - Address copied + Adres został skopiowany Tytuł rozmowy Powiadomienia Motyw @@ -196,7 +196,7 @@ Czarne tło w trybie nocnym Czas rozpoczęcia Czas zakończenia - Automatic contact colors + Automatyczne kolory kontaktów Rozmiar czcionki Używaj czcionki systemowej Automatyczne emoji @@ -207,7 +207,7 @@ Przycisk 2 Przycisk 3 Zawartość powiadomień - Wake screen + Wybudzaj ekran Wibracje Dźwięki Brak @@ -237,9 +237,9 @@ Usuwaj ogonki ze znaków w wiadomościach Tylko numery komórkowe Pokazuj kontakty tylko z numerami komórkowymi - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply - Automatyczna kompresja załączników MMSów + Wysyłaj długie wiadomości jako MMS + Jeśli dłuższe wiadomości tekstowe nie są wysyłane lub wysyłane są w złej kolejności, możesz wysyłać je jako wiadomości MMS. Mogą zostać naliczone dodatkowe opłaty + Kompresja załączników MMSów Synchronizuj wiadomości Zsynchronizuj wiadomości z systemową bazą danych O aplikacji @@ -271,13 +271,13 @@ Kontynuuj do %s i blokuj ten numer Kontynuuj do %s i blokuj te numery - Continue to %s and block these numbers + Kontynuuj do %s i blokuj te numery Kontynuuj do %s i blokuj te numery Kontynuuj do %s i odblokuj ten numer Kontynuuj do %s i odblokuj te numery - Continue to %s and allow these numbers + Kontynuuj do %s i odblokuj te numery Kontynuuj do %s i odblokuj te numery O aplikacji @@ -381,14 +381,14 @@ Długi - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Automatyczna + 100 KB + 200 KB + 300 KB + 600 KB + 1000 KB + 2000 KB + Bez kompresji OK -- GitLab From 9dfc9129613b0526c9b9d0fe00dc9956dd3c3fed Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 27 Jan 2020 19:07:04 -0500 Subject: [PATCH 109/213] Update to v3.8.1 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 196f006b1..2517fd48b 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2212 - versionName "3.8.0" + versionCode 2213 + versionName "3.8.1" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From 88accdb7a1924447d8148fb4c7c7bae5cf5bed47 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 5 Feb 2020 21:34:35 -0500 Subject: [PATCH 110/213] Forward touch events from phone number picker items --- .../compose/editing/PhoneNumberPickerAdapter.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 3464241f7..084e41a37 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 @@ -24,11 +24,13 @@ import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.extensions.forwardTouches import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.model.PhoneNumber import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.phone_number_list_item.view.* +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 @@ -50,6 +52,8 @@ class PhoneNumberPickerAdapter @Inject constructor( 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) + view.setOnClickListener { val phoneNumber = getItem(adapterPosition) selectedItem = phoneNumber.id @@ -59,11 +63,10 @@ class PhoneNumberPickerAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val phoneNumber = getItem(position) - val view = holder.itemView - view.number.radioButton.isChecked = phoneNumber.id == selectedItem - view.number.titleView.text = phoneNumber.address - view.number.summaryView.text = when (phoneNumber.isDefault) { + holder.number.radioButton.isChecked = phoneNumber.id == selectedItem + holder.number.titleView.text = phoneNumber.address + holder.number.summaryView.text = when (phoneNumber.isDefault) { true -> context.getString(R.string.compose_number_picker_default, phoneNumber.type) false -> phoneNumber.type } -- GitLab From bf808a84b69173da4aeadc333413b0f8d252f537 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 5 Feb 2020 22:35:28 -0500 Subject: [PATCH 111/213] Fix max height for QK Dialog --- presentation/src/main/res/layout/qk_dialog.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/res/layout/qk_dialog.xml b/presentation/src/main/res/layout/qk_dialog.xml index f38decc29..daf3e9fd4 100644 --- a/presentation/src/main/res/layout/qk_dialog.xml +++ b/presentation/src/main/res/layout/qk_dialog.xml @@ -22,8 +22,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?android:attr/windowBackground" - android:orientation="vertical"> + android:background="?android:attr/windowBackground"> @@ -78,6 +81,7 @@ android:textColor="?android:attr/textColorSecondary" android:textStyle="bold" android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/list" app:layout_goneMarginRight="8dp" @@ -95,6 +99,7 @@ android:textColor="?android:attr/textColorSecondary" android:textStyle="bold" android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/positiveButton" app:layout_constraintTop_toBottomOf="@id/list" app:textSize="secondary" -- GitLab From e47bfb54ee6afd0da38eb248eaae101e4e38c9e5 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 5 Feb 2020 22:52:29 -0500 Subject: [PATCH 112/213] Do a better job at filtering duplicate numbers --- .../com/moez/QKSMS/repository/SyncRepositoryImpl.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index 8cb37238b..eefe1b833 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -276,16 +276,16 @@ class SyncRepositoryImpl @Inject constructor( val uniqueNumbers = mutableListOf() contacts.value .flatMap { it.numbers } - .sortedBy { it.accountType } .forEach { number -> number.isDefault = defaultNumberIds.any { id -> id == number.id } - val duplicate = uniqueNumbers.any { other -> - number.accountType != other.accountType - && phoneNumberUtils.compare(number.address, other.address) + val duplicate = uniqueNumbers.find { other -> + phoneNumberUtils.compare(number.address, other.address) } - if (!duplicate) { + if (duplicate == null) { uniqueNumbers += number + } else if (!duplicate.isDefault && number.isDefault) { + duplicate.isDefault = true } } -- GitLab From 86f764a288b9e91affc45381330542fc13bbc319 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 5 Feb 2020 23:19:40 -0500 Subject: [PATCH 113/213] Reset swipe state if marking already read conversation as read --- .../feature/conversations/ConversationItemTouchCallback.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt index 7b834c3c8..301bbc02f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt @@ -140,7 +140,7 @@ class ConversationItemTouchCallback @Inject constructor( // This will trigger the animation back to neutral state val action = if (direction == ItemTouchHelper.RIGHT) rightAction else leftAction - if (action != Preferences.SWIPE_ACTION_ARCHIVE && action != Preferences.SWIPE_ACTION_READ) { + if (action != Preferences.SWIPE_ACTION_ARCHIVE) { adapter?.notifyItemChanged(viewHolder.adapterPosition) } } -- GitLab From 70f078fa9bfdd715fede2e05ed80c77d13ed975a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 5 Feb 2020 23:35:27 -0500 Subject: [PATCH 114/213] For avatar initials, strip off any names after a comma --- .../main/java/com/moez/QKSMS/common/widget/AvatarView.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 054f20958..00428b995 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -85,9 +85,11 @@ class AvatarView @JvmOverloads constructor( icon.setTint(theme.textPrimary) if (name?.isNotEmpty() == true) { - val initials = name?.split(" ").orEmpty() - .filter { name -> name.isNotEmpty() } - .map { name -> name[0].toString() } + val initials = name + ?.substringBefore(',') + ?.split(" ").orEmpty() + .filter { subname -> subname.isNotEmpty() } + .map { subname -> subname[0].toString() } initial.text = if (initials.size > 1) initials.first() + initials.last() else initials.first() icon.visibility = GONE -- GitLab From 5939e4540ce85b877532cc8f78f67e61f74ff467 Mon Sep 17 00:00:00 2001 From: raphj Date: Fri, 31 Jan 2020 11:50:44 +0100 Subject: [PATCH 115/213] Use the share intent subject besides the text When sharing a text, some applications also set the subject field in the share intent. For instance, when sharing a video with NewPipe, the title of the video is put in the subject of the intent. This change makes use of the subject field when providing the shared text. --- .../QKSMS/feature/compose/ComposeActivityModule.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index dcf9af1fb..6929a0699 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -55,14 +55,19 @@ class ComposeActivityModule { @Provides @Named("text") fun provideSharedText(activity: ComposeActivity): String { - return activity.intent.extras?.getString(Intent.EXTRA_TEXT) + var subject = activity.intent.getStringExtra(Intent.EXTRA_SUBJECT) ?: ""; + if (subject != "") { + subject += "\n" + } + + return subject + (activity.intent.extras?.getString(Intent.EXTRA_TEXT) ?: activity.intent.extras?.getString("sms_body") ?: activity.intent?.decodedDataString() ?.substringAfter('?') // Query string ?.split(',') ?.firstOrNull { param -> param.startsWith("body") } ?.substringAfter('=') - ?: "" + ?: "") } @Provides @@ -88,4 +93,4 @@ class ComposeActivityModule { return data } -} \ No newline at end of file +} -- GitLab From 0ba3ab5839d799551fe4bf2de5585026a157842a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 29 Feb 2020 15:19:05 -0500 Subject: [PATCH 116/213] Remember group message toggle state --- domain/src/main/java/com/moez/QKSMS/util/Preferences.kt | 1 + .../java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index d783e0249..070884adc 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -84,6 +84,7 @@ class Preferences @Inject constructor( val sia = rxPrefs.getBoolean("sia", false) // User configurable + val sendAsGroup = rxPrefs.getBoolean("sendAsGroup", true) val nightMode = rxPrefs.getInteger("nightMode", when (Build.VERSION.SDK_INT >= 29) { true -> NIGHT_MODE_SYSTEM false -> NIGHT_MODE_OFF diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index eb9df557b..dc6cbfda3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -188,6 +188,10 @@ class ComposeViewModel @Inject constructor( .distinctUntilChanged() .subscribe { title -> newState { copy(conversationtitle = title) } } + disposables += prefs.sendAsGroup.asObservable() + .distinctUntilChanged() + .subscribe { enabled -> newState { copy(sendAsGroup = enabled) } } + disposables += attachments .subscribe { attachments -> newState { copy(attachments = attachments) } } @@ -389,7 +393,7 @@ class ComposeViewModel @Inject constructor( // Toggle the group sending mode view.sendAsGroupIntent .autoDisposable(view.scope()) - .subscribe { newState { copy(sendAsGroup = !sendAsGroup) } } + .subscribe { prefs.sendAsGroup.set(!prefs.sendAsGroup.get()) } // Scroll to search position searchSelection -- GitLab From 0ff582ddd05050e4b3b67bd9fa355aa15ca07d37 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 29 Feb 2020 15:20:54 -0500 Subject: [PATCH 117/213] Change mark as read to mark read --- presentation/src/main/res/values/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 13dfecddb..00956fecd 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -50,8 +50,8 @@ Add to contacts Pin to top Unpin - Mark as read - Mark as unread + Mark read + Mark unread Block Syncing messages… You: %s @@ -244,8 +244,8 @@ Archive Delete Call - Mark as read - Mark as unread + Mark read + Mark unread Delivery confirmations Confirm that messages were sent successfully @@ -350,7 +350,7 @@ Loading… View more conversations - Mark as read + Mark read Call Delete Show more @@ -379,7 +379,7 @@ None - Mark as read + Mark read Reply Call Delete -- GitLab From 853acaa42c6dc4f780017726969255cd354db584 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 29 Feb 2020 15:33:44 -0500 Subject: [PATCH 118/213] Fix messageList visibility --- .../java/com/moez/QKSMS/feature/compose/ComposeActivity.kt | 2 +- .../com/moez/QKSMS/feature/compose/ComposeViewModel.kt | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) 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 a2d6cd0ab..bab258b14 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 @@ -216,7 +216,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { sendAsGroup.setVisible(state.editingMode && state.selectedChips.size >= 2) sendAsGroupSwitch.isChecked = state.sendAsGroup - messageList.setVisible(state.sendAsGroup) + messageList.setVisible(!state.editingMode || state.sendAsGroup) messageAdapter.data = state.messages messageAdapter.highlight = state.searchSelectionId diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index dc6cbfda3..bf7719345 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -640,6 +640,7 @@ class ComposeViewModel @Inject constructor( Preferences.SEND_DELAY_LONG -> 10000 else -> 0 } + val sendAsGroup = !state.editingMode || state.sendAsGroup when { // Scheduling a message @@ -650,13 +651,13 @@ class ComposeViewModel @Inject constructor( .map { it.getUri() } .map { it.toString() } val params = AddScheduledMessage - .Params(state.scheduled, subId, addresses, state.sendAsGroup, body, uris) + .Params(state.scheduled, subId, addresses, sendAsGroup, body, uris) addScheduledMessage.execute(params) context.makeToast(R.string.compose_scheduled_toast) } // Sending a group message - state.sendAsGroup -> { + sendAsGroup -> { sendMessage.execute(SendMessage .Params(subId, conversation.id, addresses, body, attachments, delay)) } @@ -691,7 +692,7 @@ class ComposeViewModel @Inject constructor( this.attachments.onNext(ArrayList()) if (state.editingMode) { - newState { copy(editingMode = false, sendAsGroup = true, hasError = !state.sendAsGroup) } + newState { copy(editingMode = false, hasError = !sendAsGroup) } } } .autoDisposable(view.scope()) -- GitLab From e2f4b92609d4c507b0c7f1dd58d837f1b3e7e05f Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 29 Feb 2020 19:54:40 -0500 Subject: [PATCH 119/213] Add block/archive notification actions, and block swipe action Closes #1575, closes #943 --- .../moez/QKSMS/migration/QkRealmMigration.kt | 32 +++++++++++- .../QKSMS/receiver/BlockThreadReceiver.kt | 52 +++++++++++++++++++ .../QKSMS/receiver/MarkArchivedReceiver.kt | 40 ++++++++++++++ .../java/com/moez/QKSMS/util/Preferences.kt | 17 +++--- presentation/src/main/AndroidManifest.xml | 2 + .../common/util/NotificationManagerImpl.kt | 46 +++++++++++----- .../ConversationItemTouchCallback.kt | 3 +- .../moez/QKSMS/feature/main/MainViewModel.kt | 1 + .../settings/swipe/SwipeActionsPresenter.kt | 3 +- .../settings/swipe/SwipeActionsState.kt | 4 +- .../android/BroadcastReceiverBuilderModule.kt | 10 ++++ ...ack_24dp.xml => ic_archive_white_24dp.xml} | 2 +- ...black_24dp.xml => ic_block_white_24dp.xml} | 2 +- .../res/layout/conversation_info_settings.xml | 4 +- .../src/main/res/layout/drawer_view.xml | 4 +- .../main/res/layout/qksms_plus_activity.xml | 2 +- .../res/layout/swipe_actions_controller.xml | 4 +- .../src/main/res/menu/blocked_messages.xml | 2 +- presentation/src/main/res/menu/main.xml | 4 +- presentation/src/main/res/values/strings.xml | 7 ++- 20 files changed, 201 insertions(+), 40 deletions(-) create mode 100644 data/src/main/java/com/moez/QKSMS/receiver/BlockThreadReceiver.kt create mode 100644 data/src/main/java/com/moez/QKSMS/receiver/MarkArchivedReceiver.kt rename presentation/src/main/res/drawable/{ic_archive_black_24dp.xml => ic_archive_white_24dp.xml} (97%) rename presentation/src/main/res/drawable/{ic_block_black_24dp.xml => ic_block_white_24dp.xml} (97%) diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index 74f3ad66d..c8dc96b97 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -36,7 +36,7 @@ class QkRealmMigration @Inject constructor( ) : RealmMigration { companion object { - const val SchemaVersion: Long = 9 + const val SchemaVersion: Long = 10 } @SuppressLint("ApplySharedPref") @@ -193,6 +193,36 @@ class QkRealmMigration @Inject constructor( version++ } + if (version == 9L) { + val migrateNotificationAction = { pref: Int -> + when (pref) { + 1 -> Preferences.NOTIFICATION_ACTION_READ + 2 -> Preferences.NOTIFICATION_ACTION_REPLY + 3 -> Preferences.NOTIFICATION_ACTION_CALL + 4 -> Preferences.NOTIFICATION_ACTION_DELETE + else -> pref + } + } + + val migrateSwipeAction = { pref: Int -> + when (pref) { + 2 -> Preferences.SWIPE_ACTION_DELETE + 3 -> Preferences.SWIPE_ACTION_CALL + 4 -> Preferences.SWIPE_ACTION_READ + 5 -> Preferences.SWIPE_ACTION_UNREAD + else -> pref + } + } + + if (prefs.notifAction1.isSet) prefs.notifAction1.set(migrateNotificationAction(prefs.notifAction1.get())) + if (prefs.notifAction2.isSet) prefs.notifAction2.set(migrateNotificationAction(prefs.notifAction2.get())) + if (prefs.notifAction3.isSet) prefs.notifAction3.set(migrateNotificationAction(prefs.notifAction3.get())) + if (prefs.swipeLeft.isSet) prefs.swipeLeft.set(migrateSwipeAction(prefs.swipeLeft.get())) + if (prefs.swipeRight.isSet) prefs.swipeRight.set(migrateSwipeAction(prefs.swipeRight.get())) + + version++ + } + check(version >= newVersion) { "Migration missing from v$oldVersion to v$newVersion" } } diff --git a/data/src/main/java/com/moez/QKSMS/receiver/BlockThreadReceiver.kt b/data/src/main/java/com/moez/QKSMS/receiver/BlockThreadReceiver.kt new file mode 100644 index 000000000..68143ed8d --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/receiver/BlockThreadReceiver.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.moez.QKSMS.blocking.BlockingClient +import com.moez.QKSMS.interactor.MarkBlocked +import com.moez.QKSMS.repository.ConversationRepository +import com.moez.QKSMS.util.Preferences +import dagger.android.AndroidInjection +import javax.inject.Inject + +class BlockThreadReceiver : BroadcastReceiver() { + + @Inject lateinit var blockingClient: BlockingClient + @Inject lateinit var conversationRepo: ConversationRepository + @Inject lateinit var markBlocked: MarkBlocked + @Inject lateinit var prefs: Preferences + + override fun onReceive(context: Context, intent: Intent) { + AndroidInjection.inject(this, context) + + val pendingResult = goAsync() + val threadId = intent.getLongExtra("threadId", 0) + val conversation = conversationRepo.getConversation(threadId)!! + val blockingManager = prefs.blockingManager.get() + + blockingClient + .block(conversation.recipients.map { it.address }) + .andThen(markBlocked.buildObservable(MarkBlocked.Params(listOf(threadId), blockingManager, null))) + .subscribe { pendingResult.finish() } + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/receiver/MarkArchivedReceiver.kt b/data/src/main/java/com/moez/QKSMS/receiver/MarkArchivedReceiver.kt new file mode 100644 index 000000000..55fdf2324 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/receiver/MarkArchivedReceiver.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.moez.QKSMS.interactor.MarkArchived +import dagger.android.AndroidInjection +import javax.inject.Inject + +class MarkArchivedReceiver : BroadcastReceiver() { + + @Inject lateinit var markArchived: MarkArchived + + override fun onReceive(context: Context, intent: Intent) { + AndroidInjection.inject(this, context) + + val pendingResult = goAsync() + val threadId = intent.getLongExtra("threadId", 0) + markArchived.execute(listOf(threadId)) { pendingResult.finish() } + } + +} \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index 070884adc..92794cafc 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -52,10 +52,12 @@ class Preferences @Inject constructor( const val NOTIFICATION_PREVIEWS_NONE = 2 const val NOTIFICATION_ACTION_NONE = 0 - const val NOTIFICATION_ACTION_READ = 1 - const val NOTIFICATION_ACTION_REPLY = 2 - const val NOTIFICATION_ACTION_CALL = 3 - const val NOTIFICATION_ACTION_DELETE = 4 + const val NOTIFICATION_ACTION_ARCHIVE = 1 + const val NOTIFICATION_ACTION_DELETE = 2 + const val NOTIFICATION_ACTION_BLOCK = 3 + const val NOTIFICATION_ACTION_CALL = 4 + const val NOTIFICATION_ACTION_READ = 5 + const val NOTIFICATION_ACTION_REPLY = 6 const val SEND_DELAY_NONE = 0 const val SEND_DELAY_SHORT = 1 @@ -65,9 +67,10 @@ class Preferences @Inject constructor( const val SWIPE_ACTION_NONE = 0 const val SWIPE_ACTION_ARCHIVE = 1 const val SWIPE_ACTION_DELETE = 2 - const val SWIPE_ACTION_CALL = 3 - const val SWIPE_ACTION_READ = 4 - const val SWIPE_ACTION_UNREAD = 5 + const val SWIPE_ACTION_BLOCK = 3 + const val SWIPE_ACTION_CALL = 4 + const val SWIPE_ACTION_READ = 5 + const val SWIPE_ACTION_UNREAD = 6 const val BLOCKING_MANAGER_QKSMS = 0 const val BLOCKING_MANAGER_CC = 1 diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index b082c22fd..5813bec96 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -149,6 +149,8 @@ + + diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index 1ba84dac3..5687eee01 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -45,7 +45,9 @@ import com.moez.QKSMS.feature.compose.ComposeActivity import com.moez.QKSMS.feature.qkreply.QkReplyActivity import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.mapper.CursorToPartImpl +import com.moez.QKSMS.receiver.BlockThreadReceiver import com.moez.QKSMS.receiver.DeleteMessagesReceiver +import com.moez.QKSMS.receiver.MarkArchivedReceiver import com.moez.QKSMS.receiver.MarkReadReceiver import com.moez.QKSMS.receiver.MarkSeenReceiver import com.moez.QKSMS.receiver.RemoteMessagingReceiver @@ -227,9 +229,36 @@ class NotificationManagerImpl @Inject constructor( .distinct() .mapNotNull { action -> when (action) { + Preferences.NOTIFICATION_ACTION_ARCHIVE -> { + val intent = Intent(context, MarkArchivedReceiver::class.java).putExtra("threadId", threadId) + val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 30000, intent, + PendingIntent.FLAG_UPDATE_CURRENT) + NotificationCompat.Action.Builder(R.drawable.ic_archive_white_24dp, actionLabels[action], pi) + .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_ARCHIVE).build() + } + + Preferences.NOTIFICATION_ACTION_DELETE -> { + val messageIds = messages.map { it.id }.toLongArray() + val intent = Intent(context, DeleteMessagesReceiver::class.java) + .putExtra("threadId", threadId) + .putExtra("messageIds", messageIds) + val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 40000, intent, + PendingIntent.FLAG_UPDATE_CURRENT) + NotificationCompat.Action.Builder(R.drawable.ic_delete_white_24dp, actionLabels[action], pi) + .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_DELETE).build() + } + + Preferences.NOTIFICATION_ACTION_BLOCK -> { + val intent = Intent(context, BlockThreadReceiver::class.java).putExtra("threadId", threadId) + val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 50000, intent, + PendingIntent.FLAG_UPDATE_CURRENT) + NotificationCompat.Action.Builder(R.drawable.ic_block_white_24dp, actionLabels[action], pi) + .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MUTE).build() + } + Preferences.NOTIFICATION_ACTION_READ -> { val intent = Intent(context, MarkReadReceiver::class.java).putExtra("threadId", threadId) - val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 30000, intent, + val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 60000, intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_check_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ).build() @@ -240,7 +269,7 @@ class NotificationManagerImpl @Inject constructor( getReplyAction(threadId) } else { val intent = Intent(context, QkReplyActivity::class.java).putExtra("threadId", threadId) - val pi = PendingIntent.getActivity(context, threadId.toInt() + 40000, intent, + val pi = PendingIntent.getActivity(context, threadId.toInt() + 70000, intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action .Builder(R.drawable.ic_reply_white_24dp, actionLabels[action], pi) @@ -252,23 +281,12 @@ class NotificationManagerImpl @Inject constructor( val address = conversation.recipients[0]?.address val intentAction = if (permissions.hasCalling()) Intent.ACTION_CALL else Intent.ACTION_DIAL val intent = Intent(intentAction, Uri.parse("tel:$address")) - val pi = PendingIntent.getActivity(context, threadId.toInt() + 50000, intent, + val pi = PendingIntent.getActivity(context, threadId.toInt() + 80000, intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_call_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_CALL).build() } - Preferences.NOTIFICATION_ACTION_DELETE -> { - val messageIds = messages.map { it.id }.toLongArray() - val intent = Intent(context, DeleteMessagesReceiver::class.java) - .putExtra("threadId", threadId) - .putExtra("messageIds", messageIds) - val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 60000, intent, - PendingIntent.FLAG_UPDATE_CURRENT) - NotificationCompat.Action.Builder(R.drawable.ic_delete_white_24dp, actionLabels[action], pi) - .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_DELETE).build() - } - else -> null } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt index 301bbc02f..cb06c2131 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt @@ -147,8 +147,9 @@ class ConversationItemTouchCallback @Inject constructor( private fun iconForAction(action: Int, tint: Int): Bitmap? { val res = when (action) { - Preferences.SWIPE_ACTION_ARCHIVE -> R.drawable.ic_archive_black_24dp + Preferences.SWIPE_ACTION_ARCHIVE -> R.drawable.ic_archive_white_24dp Preferences.SWIPE_ACTION_DELETE -> R.drawable.ic_delete_white_24dp + Preferences.SWIPE_ACTION_BLOCK -> R.drawable.ic_block_white_24dp Preferences.SWIPE_ACTION_CALL -> R.drawable.ic_call_white_24dp Preferences.SWIPE_ACTION_READ -> R.drawable.ic_check_white_24dp Preferences.SWIPE_ACTION_UNREAD -> R.drawable.ic_markunread_black_24dp diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index a1e27f939..ac483a776 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -423,6 +423,7 @@ class MainViewModel @Inject constructor( when (action) { Preferences.SWIPE_ACTION_ARCHIVE -> markArchived.execute(listOf(threadId)) { view.showArchivedSnackbar() } Preferences.SWIPE_ACTION_DELETE -> view.showDeleteDialog(listOf(threadId)) + Preferences.SWIPE_ACTION_BLOCK -> view.showBlockingDialog(listOf(threadId), true) Preferences.SWIPE_ACTION_CALL -> conversationRepo.getConversation(threadId)?.recipients?.firstOrNull()?.address?.let(navigator::makePhoneCall) Preferences.SWIPE_ACTION_READ -> markRead.execute(listOf(threadId)) Preferences.SWIPE_ACTION_UNREAD -> markUnread.execute(listOf(threadId)) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsPresenter.kt index f895b5ae5..ca02aac6d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsPresenter.kt @@ -70,8 +70,9 @@ class SwipeActionsPresenter @Inject constructor( @DrawableRes private fun iconForAction(action: Int) = when (action) { - Preferences.SWIPE_ACTION_ARCHIVE -> R.drawable.ic_archive_black_24dp + Preferences.SWIPE_ACTION_ARCHIVE -> R.drawable.ic_archive_white_24dp Preferences.SWIPE_ACTION_DELETE -> R.drawable.ic_delete_white_24dp + Preferences.SWIPE_ACTION_BLOCK -> R.drawable.ic_block_white_24dp Preferences.SWIPE_ACTION_CALL -> R.drawable.ic_call_white_24dp Preferences.SWIPE_ACTION_READ -> R.drawable.ic_check_white_24dp Preferences.SWIPE_ACTION_UNREAD -> R.drawable.ic_markunread_black_24dp diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsState.kt index 37e6c61c1..cc7d14bae 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsState.kt @@ -22,9 +22,9 @@ import androidx.annotation.DrawableRes import com.moez.QKSMS.R data class SwipeActionsState( - @DrawableRes val rightIcon: Int = R.drawable.ic_archive_black_24dp, + @DrawableRes val rightIcon: Int = R.drawable.ic_archive_white_24dp, val rightLabel: String = "", - @DrawableRes val leftIcon: Int = R.drawable.ic_archive_black_24dp, + @DrawableRes val leftIcon: Int = R.drawable.ic_archive_white_24dp, val leftLabel: String = "" ) \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/android/BroadcastReceiverBuilderModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/android/BroadcastReceiverBuilderModule.kt index 7392b44f5..77cf22287 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/android/BroadcastReceiverBuilderModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/android/BroadcastReceiverBuilderModule.kt @@ -20,9 +20,11 @@ package com.moez.QKSMS.injection.android import com.moez.QKSMS.feature.widget.WidgetProvider import com.moez.QKSMS.injection.scope.ActivityScope +import com.moez.QKSMS.receiver.BlockThreadReceiver import com.moez.QKSMS.receiver.BootReceiver import com.moez.QKSMS.receiver.DefaultSmsChangedReceiver import com.moez.QKSMS.receiver.DeleteMessagesReceiver +import com.moez.QKSMS.receiver.MarkArchivedReceiver import com.moez.QKSMS.receiver.MarkReadReceiver import com.moez.QKSMS.receiver.MarkSeenReceiver import com.moez.QKSMS.receiver.MmsReceivedReceiver @@ -42,6 +44,10 @@ import dagger.android.ContributesAndroidInjector @Module abstract class BroadcastReceiverBuilderModule { + @ActivityScope + @ContributesAndroidInjector() + abstract fun bindBlockThreadReceiver(): BlockThreadReceiver + @ActivityScope @ContributesAndroidInjector() abstract fun bindBootReceiver(): BootReceiver @@ -54,6 +60,10 @@ abstract class BroadcastReceiverBuilderModule { @ContributesAndroidInjector() abstract fun bindDeleteMessagesReceiver(): DeleteMessagesReceiver + @ActivityScope + @ContributesAndroidInjector + abstract fun bindMarkArchivedReceiver(): MarkArchivedReceiver + @ActivityScope @ContributesAndroidInjector() abstract fun bindMarkReadReceiver(): MarkReadReceiver diff --git a/presentation/src/main/res/drawable/ic_archive_black_24dp.xml b/presentation/src/main/res/drawable/ic_archive_white_24dp.xml similarity index 97% rename from presentation/src/main/res/drawable/ic_archive_black_24dp.xml rename to presentation/src/main/res/drawable/ic_archive_white_24dp.xml index 53346afd0..9a621d282 100644 --- a/presentation/src/main/res/drawable/ic_archive_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_archive_white_24dp.xml @@ -22,6 +22,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/presentation/src/main/res/drawable/ic_block_black_24dp.xml b/presentation/src/main/res/drawable/ic_block_white_24dp.xml similarity index 97% rename from presentation/src/main/res/drawable/ic_block_black_24dp.xml rename to presentation/src/main/res/drawable/ic_block_white_24dp.xml index c3f491ef7..82499c84b 100644 --- a/presentation/src/main/res/drawable/ic_block_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_block_white_24dp.xml @@ -22,6 +22,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/presentation/src/main/res/layout/conversation_info_settings.xml b/presentation/src/main/res/layout/conversation_info_settings.xml index 50d5903af..9fc371aab 100644 --- a/presentation/src/main/res/layout/conversation_info_settings.xml +++ b/presentation/src/main/res/layout/conversation_info_settings.xml @@ -48,14 +48,14 @@ android:id="@+id/archive" android:layout_width="match_parent" android:layout_height="wrap_content" - app:icon="@drawable/ic_archive_black_24dp" + app:icon="@drawable/ic_archive_white_24dp" app:title="@string/info_archive" /> diff --git a/presentation/src/main/res/layout/swipe_actions_controller.xml b/presentation/src/main/res/layout/swipe_actions_controller.xml index 7be0191be..a5b706801 100644 --- a/presentation/src/main/res/layout/swipe_actions_controller.xml +++ b/presentation/src/main/res/layout/swipe_actions_controller.xml @@ -104,7 +104,7 @@ app:layout_constraintStart_toStartOf="@id/rightBackground" app:layout_constraintTop_toTopOf="@id/rightBackground" tools:backgroundTint="@color/tools_theme" - tools:src="@drawable/ic_archive_black_24dp" + tools:src="@drawable/ic_archive_white_24dp" tools:tint="@color/textPrimaryDark" /> diff --git a/presentation/src/main/res/menu/main.xml b/presentation/src/main/res/menu/main.xml index 557b1ccf3..579d60abc 100644 --- a/presentation/src/main/res/menu/main.xml +++ b/presentation/src/main/res/menu/main.xml @@ -22,7 +22,7 @@ @@ -78,7 +78,7 @@ diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 00956fecd..9953bcfb0 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -243,6 +243,7 @@ None Archive Delete + Block Call Mark read Mark unread @@ -379,10 +380,12 @@ None + Archive + Delete + Block + Call Mark read Reply - Call - Delete Yes -- GitLab From f13fb350e1cf028837bd13106b58411b05eeabd6 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 29 Feb 2020 21:26:12 -0500 Subject: [PATCH 120/213] Literal names for delay settings --- presentation/src/main/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 9953bcfb0..f59de5476 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -431,9 +431,9 @@ No delay - Short - Medium - Long + 3 seconds + 5 seconds + 10 seconds -- GitLab From ad48977c1aaae78170a3030ab0818d6d179807d3 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 29 Feb 2020 21:40:48 -0500 Subject: [PATCH 121/213] Limit line count for contact group --- .../QKSMS/feature/compose/editing/ComposeItemAdapter.kt | 2 ++ presentation/src/main/res/layout/contact_list_item.xml | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) 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 3acb379eb..2681d520b 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 @@ -123,6 +123,7 @@ class ComposeItemAdapter @Inject constructor( holder.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> recipient.contact?.name ?: recipient.address } + holder.subtitle.collapseEnabled = conversation.recipients.size > 1 holder.numbers.isVisible = conversation.recipients.size == 1 (holder.numbers.adapter as PhoneNumberAdapter).data = conversation.recipients @@ -158,6 +159,7 @@ class ComposeItemAdapter @Inject constructor( holder.subtitle.isVisible = true holder.subtitle.text = group.contacts.joinToString(", ") { it.name } + holder.subtitle.collapseEnabled = group.contacts.size > 1 holder.numbers.isVisible = false } diff --git a/presentation/src/main/res/layout/contact_list_item.xml b/presentation/src/main/res/layout/contact_list_item.xml index d445cf22c..e35f9e032 100644 --- a/presentation/src/main/res/layout/contact_list_item.xml +++ b/presentation/src/main/res/layout/contact_list_item.xml @@ -85,9 +85,11 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="2dp" + android:ellipsize="end" + android:lines="1" android:textColor="?android:attr/textColorTertiary" app:layout_constraintBottom_toTopOf="@id/numbers" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toEndOf="@id/title" app:layout_constraintStart_toStartOf="@id/title" app:layout_constraintTop_toBottomOf="@id/title" app:textSize="secondary" @@ -100,7 +102,7 @@ android:layout_marginBottom="8dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toEndOf="@id/title" app:layout_constraintStart_toStartOf="@id/title" app:layout_constraintTop_toBottomOf="@id/subtitle" tools:listitem="@layout/contact_number_list_item" /> -- GitLab From 5512b344d67b1fd9e5921e7ba6d99b94e702a629 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 29 Feb 2020 21:49:35 -0500 Subject: [PATCH 122/213] Dismiss notification for failed message appropriately --- .../com/moez/QKSMS/common/util/NotificationManagerImpl.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index 5687eee01..c70ff96ee 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -99,6 +99,7 @@ class NotificationManagerImpl @Inject constructor( // If there are no messages to be displayed, make sure that the notification is dismissed if (messages.isEmpty()) { notificationManager.cancel(threadId.toInt()) + notificationManager.cancel(threadId.toInt() + 100000) return } @@ -336,7 +337,7 @@ class NotificationManagerImpl @Inject constructor( val taskStackBuilder = TaskStackBuilder.create(context) taskStackBuilder.addParentStack(ComposeActivity::class.java) taskStackBuilder.addNextIntent(contentIntent) - val contentPI = taskStackBuilder.getPendingIntent(threadId.toInt() + 40000, PendingIntent.FLAG_UPDATE_CURRENT) + val contentPI = taskStackBuilder.getPendingIntent(threadId.toInt() + 90000, PendingIntent.FLAG_UPDATE_CURRENT) val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId)) .setContentTitle(context.getString(R.string.notification_message_failed_title)) @@ -350,12 +351,12 @@ class NotificationManagerImpl @Inject constructor( .setLights(Color.WHITE, 500, 2000) .setVibrate(if (prefs.vibration(threadId).get()) VIBRATE_PATTERN else longArrayOf(0)) - notificationManager.notify(threadId.toInt() + 50000, notification.build()) + notificationManager.notify(threadId.toInt() + 100000, notification.build()) } private fun getReplyAction(threadId: Long): NotificationCompat.Action { val replyIntent = Intent(context, RemoteMessagingReceiver::class.java).putExtra("threadId", threadId) - val replyPI = PendingIntent.getBroadcast(context, threadId.toInt() + 40000, replyIntent, + val replyPI = PendingIntent.getBroadcast(context, threadId.toInt() + 70000, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT) val title = context.resources.getStringArray(R.array.notification_actions)[ -- GitLab From 13509d0d66dec05cb445331e77a9b1bfdf389f15 Mon Sep 17 00:00:00 2001 From: ewft Date: Sun, 9 Feb 2020 15:43:53 +0100 Subject: [PATCH 123/213] Dual SIM fixes an Improvement : Fix SIM ID in message view Add SIM name in message view Improve layout Signed-off-by: ewft --- .../java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt | 7 ++++++- presentation/src/main/res/layout/compose_activity.xml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) 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 f11b33983..d608308de 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 @@ -226,7 +226,12 @@ class MessagesAdapter @Inject constructor( val simIndex = subs.takeIf { it.size > 1 }?.indexOfFirst { it.subscriptionId == message.subId } ?: -1 holder.timestamp.text = dateFormatter.getMessageTimestamp(message.date) - holder.simIndex.text = "${simIndex + 1}" + if (simIndex != -1){ + holder.simIndex.text = "${subs[simIndex].simSlotIndex + 1} - " + subs[simIndex].displayName + } else { + holder.simIndex.text = "${simIndex + 1}" + } + holder.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD || message.subId != previous?.subId && simIndex != -1) diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index 3015b5750..11b0c497e 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -264,7 +264,7 @@ android:tint="?android:attr/textColorSecondary" android:visibility="gone" app:layout_constraintBottom_toBottomOf="@id/messageBackground" - app:layout_constraintEnd_toStartOf="@id/send" /> + app:layout_constraintEnd_toEndOf="@id/messageBackground" /> Date: Sat, 29 Feb 2020 23:07:30 -0500 Subject: [PATCH 124/213] Refactor code for displaying SIM index in message list --- .../moez/QKSMS/feature/compose/MessagesAdapter.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) 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 d608308de..c27fa4f74 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 @@ -223,20 +223,15 @@ class MessagesAdapter @Inject constructor( // Bind the timestamp val timeSincePrevious = TimeUnit.MILLISECONDS.toMinutes(message.date - (previous?.date ?: 0)) - val simIndex = subs.takeIf { it.size > 1 }?.indexOfFirst { it.subscriptionId == message.subId } ?: -1 + val subscription = subs.find { sub -> sub.subscriptionId == message.subId } holder.timestamp.text = dateFormatter.getMessageTimestamp(message.date) - if (simIndex != -1){ - holder.simIndex.text = "${subs[simIndex].simSlotIndex + 1} - " + subs[simIndex].displayName - } else { - holder.simIndex.text = "${simIndex + 1}" - } - + holder.simIndex.text = subscription?.let { sub -> sub.simSlotIndex + 1 }?.toString() holder.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD - || message.subId != previous?.subId && simIndex != -1) - holder.sim.setVisible(message.subId != previous?.subId && simIndex != -1) - holder.simIndex.setVisible(message.subId != previous?.subId && simIndex != -1) + || message.subId != previous?.subId && subscription != null) + holder.sim.setVisible(message.subId != previous?.subId && subscription != null) + holder.simIndex.setVisible(message.subId != previous?.subId && subscription != null) // Bind the grouping val media = message.parts.filter { !it.isSmil() && !it.isText() } -- GitLab From 70275b230142b68c6618501cfc2d8709f584bd24 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 29 Feb 2020 23:09:22 -0500 Subject: [PATCH 125/213] Fix SIM selector UI --- presentation/src/main/res/layout/compose_activity.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index 11b0c497e..19900e0ad 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -255,9 +255,9 @@ Date: Sat, 29 Feb 2020 23:31:12 -0500 Subject: [PATCH 126/213] Vibrate and show toast when SIM is manually changed --- presentation/src/main/AndroidManifest.xml | 1 + .../com/moez/QKSMS/feature/compose/ComposeActivity.kt | 2 +- .../com/moez/QKSMS/feature/compose/ComposeViewModel.kt | 9 +++++++++ .../com/moez/QKSMS/feature/compose/MessagesAdapter.kt | 2 +- presentation/src/main/res/values/strings.xml | 2 ++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index 5813bec96..f6937d888 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -32,6 +32,7 @@ + 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 bab258b14..6b21cf1b5 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 @@ -234,7 +234,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { sim.setVisible(state.subscription != null) sim.contentDescription = getString(R.string.compose_sim_cd, state.subscription?.displayName) - simIndex.text = "${state.subscription?.simSlotIndex?.plus(1)}" + simIndex.text = state.subscription?.simSlotIndex?.plus(1)?.toString() send.isEnabled = state.canSend send.imageAlpha = if (state.canSend) 255 else 128 diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index bf7719345..59edb5457 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -20,8 +20,10 @@ package com.moez.QKSMS.feature.compose import android.content.Context import android.net.Uri +import android.os.Vibrator import android.provider.ContactsContract import android.telephony.SmsMessage +import androidx.core.content.getSystemService import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel @@ -616,6 +618,13 @@ class ComposeViewModel @Inject constructor( subIndex < subs.size - 1 -> subs[subIndex + 1] else -> subs[0] } + + if (subscription != null) { + context.getSystemService()?.vibrate(40) + context.makeToast(context.getString(R.string.compose_sim_changed_toast, + subscription.simSlotIndex + 1, subscription.displayName)) + } + newState { copy(subscription = subscription) } } .autoDisposable(view.scope()) 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 c27fa4f74..933e82957 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 @@ -226,7 +226,7 @@ class MessagesAdapter @Inject constructor( val subscription = subs.find { sub -> sub.subscriptionId == message.subId } holder.timestamp.text = dateFormatter.getMessageTimestamp(message.date) - holder.simIndex.text = subscription?.let { sub -> sub.simSlotIndex + 1 }?.toString() + holder.simIndex.text = subscription?.simSlotIndex?.plus(1)?.toString() holder.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD || message.subId != previous?.subId && subscription != null) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index f59de5476..81147bf94 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -137,6 +137,8 @@ Schedule message Attach a contact Error reading contact + + SIM %1$d (%2$s) selected %s selected, change SIM card Send message -- GitLab From 37d8c45379e351e4c556c72542fae67ac857ce1a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 13 Mar 2020 22:47:55 -0400 Subject: [PATCH 127/213] Implement ViewBinding --- build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 4 +- presentation/build.gradle | 9 +- .../com/moez/QKSMS/common/MenuItemAdapter.kt | 45 +++-- .../moez/QKSMS/common/base/FlowableAdapter.kt | 3 +- .../com/moez/QKSMS/common/base/QkActivity.kt | 12 +- .../com/moez/QKSMS/common/base/QkAdapter.kt | 3 +- .../moez/QKSMS/common/base/QkController.kt | 32 ++-- .../moez/QKSMS/common/base/QkRealmAdapter.kt | 4 +- .../QKSMS/common/base/QkThemedActivity.kt | 3 +- .../moez/QKSMS/common/base/QkViewHolder.kt | 14 +- .../util/extensions/ActivityExtensions.kt | 9 + .../common/util/extensions/ViewExtensions.kt | 6 + .../moez/QKSMS/common/widget/AvatarView.kt | 30 +-- .../moez/QKSMS/common/widget/FieldDialog.kt | 13 +- .../QKSMS/common/widget/GroupAvatarView.kt | 19 +- .../QKSMS/common/widget/PagerTitleView.kt | 11 +- .../QKSMS/common/widget/PreferenceView.kt | 26 ++- .../com/moez/QKSMS/common/widget/QkDialog.kt | 34 ++-- .../common/widget/RadioPreferenceView.kt | 20 +- .../QKSMS/feature/backup/BackupActivity.kt | 10 +- .../QKSMS/feature/backup/BackupAdapter.kt | 23 +-- .../QKSMS/feature/backup/BackupController.kt | 91 ++++----- .../feature/blocking/BlockingActivity.kt | 9 +- .../feature/blocking/BlockingController.kt | 25 +-- .../manager/BlockingManagerController.kt | 25 ++- .../messages/BlockedMessagesAdapter.kt | 51 +++-- .../messages/BlockedMessagesController.kt | 12 +- .../blocking/numbers/BlockedNumbersAdapter.kt | 18 +- .../numbers/BlockedNumbersController.kt | 28 ++- .../feature/changelog/ChangelogAdapter.kt | 18 +- .../feature/changelog/ChangelogDialog.kt | 15 +- .../feature/compose/AttachmentAdapter.kt | 54 +++--- .../QKSMS/feature/compose/ComposeActivity.kt | 134 ++++++------- .../QKSMS/feature/compose/MessagesAdapter.kt | 70 ++++--- .../feature/compose/editing/ChipsAdapter.kt | 26 ++- .../compose/editing/ComposeItemAdapter.kt | 121 ++++++------ .../compose/editing/DetailedChipView.kt | 37 ++-- .../compose/editing/PhoneNumberAdapter.kt | 20 +- .../editing/PhoneNumberPickerAdapter.kt | 25 +-- .../QKSMS/feature/compose/part/FileBinder.kt | 42 +++-- .../QKSMS/feature/compose/part/MediaBinder.kt | 21 ++- .../QKSMS/feature/compose/part/PartBinder.kt | 31 ++- .../feature/compose/part/PartsAdapter.kt | 27 ++- .../QKSMS/feature/compose/part/VCardBinder.kt | 40 ++-- .../feature/contacts/ContactsActivity.kt | 26 +-- .../ConversationInfoActivity.kt | 9 +- .../ConversationInfoAdapter.kt | 115 ++++++------ .../ConversationInfoController.kt | 15 +- .../conversations/ConversationsAdapter.kt | 59 +++--- .../QKSMS/feature/gallery/GalleryActivity.kt | 20 +- .../feature/gallery/GalleryPagerAdapter.kt | 54 +++--- .../moez/QKSMS/feature/main/MainActivity.kt | 176 ++++++++---------- .../moez/QKSMS/feature/main/SearchAdapter.kt | 31 ++- .../NotificationPrefsActivity.kt | 66 +++---- .../moez/QKSMS/feature/plus/PlusActivity.kt | 70 ++++--- .../QKSMS/feature/qkreply/QkReplyActivity.kt | 52 +++--- .../feature/scheduled/ScheduledActivity.kt | 37 ++-- .../scheduled/ScheduledMessageAdapter.kt | 33 ++-- .../ScheduledMessageAttachmentAdapter.kt | 20 +- .../feature/settings/SettingsActivity.kt | 9 +- .../feature/settings/SettingsController.kt | 73 ++++---- .../feature/settings/about/AboutController.kt | 13 +- .../settings/swipe/SwipeActionsController.kt | 37 ++-- .../feature/themepicker/HSVPickerView.kt | 41 ++-- .../QKSMS/feature/themepicker/ThemeAdapter.kt | 35 ++-- .../themepicker/ThemePickerController.kt | 42 ++--- .../main/res/layout/collapsing_toolbar.xml | 1 - .../main/res/layout/contact_chip_detailed.xml | 18 +- .../src/main/res/layout/main_activity.xml | 14 +- .../main/res/layout/main_permission_hint.xml | 18 +- .../src/main/res/layout/main_syncing.xml | 4 +- .../main/res/layout/message_list_item_in.xml | 6 + .../main/res/layout/message_list_item_out.xml | 5 + .../main/res/layout/qksms_plus_activity.xml | 4 +- .../main/res/layout/scheduled_activity.xml | 4 +- .../main/res/layout/settings_theme_widget.xml | 24 +-- 77 files changed, 1208 insertions(+), 1197 deletions(-) diff --git a/build.gradle b/build.gradle index 802a92280..c4540a17c 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { ext.mockito_version = '2.18.3' ext.moshi_version = '1.8.0' ext.okhttp3_version = '4.1.0' - ext.realm_version = '5.8.0' + ext.realm_version = '6.1.0' ext.realm_adapters_version = '3.1.0' ext.rxandroid_version = '2.0.1' ext.rxdogtag_version = '0.2.0' @@ -42,7 +42,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.2' + classpath 'com.android.tools.build:gradle:3.6.1' classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'io.fabric.tools:gradle:1.29.0' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4b9cec46b..303be1d37 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Dec 03 23:30:52 EST 2019 +#Tue Mar 03 19:56:50 EST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/presentation/build.gradle b/presentation/build.gradle index 2517fd48b..7240ab02b 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 { @@ -70,6 +69,10 @@ android { noAnalytics { dimension "analytics" } } + viewBinding { + enabled = true + } + if (System.getenv("CI") == "true") { signingConfigs.release.storeFile = file("../keystore") signingConfigs.release.storePassword = System.getenv("keystore_password") @@ -78,10 +81,6 @@ android { } } -androidExtensions { - experimental = true -} - configurations { noAnalyticsDebug noAnalyticsRelease diff --git a/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt index 656305482..eba4fed2a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt @@ -20,26 +20,26 @@ package com.moez.QKSMS.common import android.content.Context import android.content.res.ColorStateList -import android.view.LayoutInflater import android.view.ViewGroup import androidx.annotation.ArrayRes import androidx.recyclerview.widget.RecyclerView -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.databinding.MenuListItemBinding import io.reactivex.disposables.CompositeDisposable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.menu_list_item.* -import kotlinx.android.synthetic.main.menu_list_item.view.* import javax.inject.Inject data class MenuItem(val title: String, val actionId: Int) -class MenuItemAdapter @Inject constructor(private val context: Context, private val colors: Colors) : QkAdapter() { +class MenuItemAdapter @Inject constructor( + private val context: Context, + private val colors: Colors +) : QkAdapter() { val menuItemClicks: Subject = PublishSubject.create() @@ -47,13 +47,13 @@ class MenuItemAdapter @Inject constructor(private val context: Context, private var selectedItem: Int? = null set(value) { - val old = data.map { it.actionId }.indexOfFirst { it == field } - val new = data.map { it.actionId }.indexOfFirst { it == value } + val old = data.map { it.actionId }.indexOfFirst { it == field }.takeIf { it != -1 } + val new = data.map { it.actionId }.indexOfFirst { it == value }.takeIf { it != -1 } field = value - old.let { notifyItemChanged(it) } - new.let { notifyItemChanged(it) } + old?.let(::notifyItemChanged) + new?.let(::notifyItemChanged) } fun setData(@ArrayRes titles: Int, @ArrayRes values: Int = -1) { @@ -63,31 +63,28 @@ class MenuItemAdapter @Inject constructor(private val context: Context, private .mapIndexed { index, title -> MenuItem(title, valueInts?.getOrNull(index) ?: index) } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val layoutInflater = LayoutInflater.from(parent.context) - val view = layoutInflater.inflate(R.layout.menu_list_item, parent, false) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, MenuListItemBinding::inflate).apply { + val states = arrayOf( + intArrayOf(android.R.attr.state_activated), + intArrayOf(-android.R.attr.state_activated)) - val states = arrayOf( - intArrayOf(android.R.attr.state_activated), - intArrayOf(-android.R.attr.state_activated)) + val text = parent.context.resolveThemeColor(android.R.attr.textColorTertiary) + binding.check.imageTintList = ColorStateList(states, intArrayOf(colors.theme().theme, text)) - val text = parent.context.resolveThemeColor(android.R.attr.textColorTertiary) - view.check.imageTintList = ColorStateList(states, intArrayOf(colors.theme().theme, text)) - - return QkViewHolder(view).apply { - view.setOnClickListener { + binding.root.setOnClickListener { val menuItem = getItem(adapterPosition) menuItemClicks.onNext(menuItem.actionId) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val menuItem = getItem(position) - holder.title.text = menuItem.title - holder.check.isActivated = (menuItem.actionId == selectedItem) - holder.check.setVisible(selectedItem != null) + holder.binding.title.text = menuItem.title + holder.binding.check.isActivated = (menuItem.actionId == selectedItem) + holder.binding.check.setVisible(selectedItem != null) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/FlowableAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/FlowableAdapter.kt index 1a71d5163..7cd3f6156 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/FlowableAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/FlowableAdapter.kt @@ -20,6 +20,7 @@ package com.moez.QKSMS.common.base import androidx.annotation.CallSuper import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding import io.reactivex.Flowable import io.reactivex.disposables.Disposable @@ -27,7 +28,7 @@ import io.reactivex.disposables.Disposable * Base RecyclerView.Adapter that provides some convenience when creating a new Adapter, such as * data list handing and item animations */ -abstract class FlowableAdapter : QkAdapter() { +abstract class FlowableAdapter : QkAdapter() { var flowable: Flowable>? = null set(value) { 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 379224fd7..8da859f06 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 @@ -22,14 +22,20 @@ import android.annotation.SuppressLint import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import com.moez.QKSMS.R +import com.moez.QKSMS.common.widget.QkTextView import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.toolbar.* abstract class QkActivity : AppCompatActivity() { + val toolbar by lazy { findViewById(R.id.toolbar)!! } + protected val menu: Subject = BehaviorSubject.create() + protected val toolbarTitle by lazy { findViewById(R.id.toolbarTitle) } @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { @@ -47,8 +53,8 @@ abstract class QkActivity : AppCompatActivity() { } } - override fun setContentView(layoutResID: Int) { - super.setContentView(layoutResID) + override fun setContentView(view: View?) { + super.setContentView(view) setSupportActionBar(toolbar) title = title // The title may have been set before layout inflation } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt index 0101f4351..d55d8e721 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt @@ -22,6 +22,7 @@ import android.view.View import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding import com.moez.QKSMS.common.util.extensions.setVisible import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject @@ -30,7 +31,7 @@ import io.reactivex.subjects.Subject * Base RecyclerView.Adapter that provides some convenience when creating a new Adapter, such as * data list handing and item animations */ -abstract class QkAdapter : RecyclerView.Adapter() { +abstract class QkAdapter : RecyclerView.Adapter>() { var data: List = ArrayList() set(value) { 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 deefa8e21..33d6b0c63 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,17 @@ package com.moez.QKSMS.common.base import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.LayoutRes import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +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 com.moez.QKSMS.R +import com.moez.QKSMS.common.widget.QkTextView -abstract class QkController, State, Presenter : QkPresenter> : LifecycleController(), LayoutContainer { +abstract class QkController, State, Presenter : QkPresenter, Binding : ViewBinding>( + private val bindingInflater: (LayoutInflater, ViewGroup, Boolean) -> Binding +) : LifecycleController() { abstract var presenter: Presenter @@ -39,16 +41,15 @@ abstract class QkController, State, Present protected val themedActivity: QkThemedActivity? get() = activity as? QkThemedActivity - override var containerView: View? = null + private val toolbar by lazy { view?.findViewById(R.id.toolbar) } + private val toolbarTitle by lazy { view?.findViewById(R.id.toolbarTitle) } - @LayoutRes - var layoutRes: Int = 0 + lateinit var binding: Binding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { - return inflater.inflate(layoutRes, container, false).also { - containerView = it - onViewCreated() - } + binding = bindingInflater(inflater, container, false) + onViewCreated() + return binding.root } open fun onViewCreated() { @@ -60,18 +61,13 @@ abstract class QkController, State, Present fun setTitle(title: CharSequence?) { activity?.title = title - view?.toolbarTitle?.text = title + toolbarTitle?.text = title } fun showBackButton(show: Boolean) { appCompatActivity?.supportActionBar?.setDisplayHomeAsUpEnabled(show) } - override fun onDestroyView(view: View) { - containerView = null - clearFindViewByIdCache() - } - override fun onDestroy() { super.onDestroy() presenter.onCleared() diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt index 7b1d8997e..31fb5306d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt @@ -20,6 +20,7 @@ package com.moez.QKSMS.common.base import android.view.View import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding import com.moez.QKSMS.common.util.extensions.setVisible import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject @@ -30,7 +31,8 @@ import io.realm.RealmRecyclerViewAdapter import io.realm.RealmResults import timber.log.Timber -abstract class QkRealmAdapter : RealmRecyclerViewAdapter(null, true) { +abstract class QkRealmAdapter + : RealmRecyclerViewAdapter>(null, true) { /** * This view can be set, and the adapter will automatically control the visibility of this view 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 14b858b2c..e6bfd358c 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 @@ -44,7 +44,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 @@ -134,7 +133,7 @@ abstract class QkThemedActivity : QkActivity() { // Set the color for the overflow and navigation icon val textSecondary = resolveThemeColor(android.R.attr.textColorSecondary) - toolbar?.overflowIcon = toolbar?.overflowIcon?.apply { setTint(textSecondary) } + toolbar.overflowIcon = toolbar.overflowIcon?.apply { setTint(textSecondary) } // Update the colours of the menu items Observables.combineLatest(menu, theme) { menu, theme -> 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 56e674349..b246c5b28 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 @@ -18,10 +18,16 @@ */ package com.moez.QKSMS.common.base -import android.view.View +import android.view.LayoutInflater +import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.extensions.LayoutContainer +import androidx.viewbinding.ViewBinding + +class QkViewHolder(val binding: T) : RecyclerView.ViewHolder(binding.root) { + + constructor( + parent: ViewGroup, + bindingInflator: (LayoutInflater, ViewGroup, Boolean) -> T + ) : this(bindingInflator(LayoutInflater.from(parent.context), parent, false)) -class QkViewHolder(view: View) : RecyclerView.ViewHolder(view), LayoutContainer { - override val containerView: View = view } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt index 3d4f59f59..c99257466 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt @@ -20,7 +20,10 @@ package com.moez.QKSMS.common.util.extensions import android.app.Activity import android.content.Context +import android.view.LayoutInflater import android.view.inputmethod.InputMethodManager +import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding fun Activity.dismissKeyboard() { window.currentFocus?.let { focus -> @@ -30,3 +33,9 @@ fun Activity.dismissKeyboard() { focus.clearFocus() } } + +inline fun AppCompatActivity.viewBinding( + crossinline bindingInflater: (LayoutInflater) -> T +): Lazy = lazy(LazyThreadSafetyMode.NONE) { + bindingInflater(layoutInflater) +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt index f2bf92d17..eec96d543 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt @@ -23,6 +23,7 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.PorterDuff import android.os.Build +import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -31,6 +32,7 @@ import android.widget.EditText import android.widget.ImageView import android.widget.ProgressBar import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding import androidx.viewpager.widget.ViewPager var ViewGroup.animateLayoutChanges: Boolean @@ -123,3 +125,7 @@ fun RecyclerView.scrapViews() { recycledViewPool.clear() adapter?.notifyDataSetChanged() } + +inline fun ViewGroup.viewBinding(crossinline bindingInflater: (LayoutInflater, ViewGroup) -> T): T { + return bindingInflater(LayoutInflater.from(context), this) +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 00428b995..6cca5a68a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -20,17 +20,17 @@ package com.moez.QKSMS.common.widget import android.content.Context import android.util.AttributeSet -import android.view.View import android.widget.FrameLayout import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.AvatarViewBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.util.GlideApp -import kotlinx.android.synthetic.main.avatar_view.view.* import javax.inject.Inject class AvatarView @JvmOverloads constructor( @@ -39,21 +39,21 @@ class AvatarView @JvmOverloads constructor( @Inject lateinit var colors: Colors @Inject lateinit var navigator: Navigator + private lateinit var theme: Colors.Theme + + private val binding = viewBinding(AvatarViewBinding::inflate) private var lookupKey: String? = null private var name: String? = null private var photoUri: String? = null private var lastUpdated: Long? = null - private var theme: Colors.Theme init { if (!isInEditMode) { appComponent.inject(this) + theme = colors.theme() } - theme = colors.theme() - - View.inflate(context, R.layout.avatar_view, this) setBackgroundResource(R.drawable.circle) clipToOutline = true } @@ -81,8 +81,8 @@ class AvatarView @JvmOverloads constructor( private fun updateView() { // Apply theme setBackgroundTint(theme.theme) - initial.setTextColor(theme.textPrimary) - icon.setTint(theme.textPrimary) + binding.initial.setTextColor(theme.textPrimary) + binding.icon.setTint(theme.textPrimary) if (name?.isNotEmpty() == true) { val initials = name @@ -91,18 +91,18 @@ class AvatarView @JvmOverloads constructor( .filter { subname -> subname.isNotEmpty() } .map { subname -> subname[0].toString() } - initial.text = if (initials.size > 1) initials.first() + initials.last() else initials.first() - icon.visibility = GONE + binding.initial.text = if (initials.size > 1) initials.first() + initials.last() else initials.first() + binding.icon.visibility = GONE } else { - initial.text = null - icon.visibility = VISIBLE + binding.initial.text = null + binding.icon.visibility = VISIBLE } - photo.setImageDrawable(null) + binding.photo.setImageDrawable(null) photoUri?.let { photoUri -> - GlideApp.with(photo) + GlideApp.with(binding.photo) .load(photoUri) - .into(photo) + .into(binding.photo) } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt index 1649edf68..3efb12625 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt @@ -20,28 +20,27 @@ package com.moez.QKSMS.common.widget import android.app.Activity import android.content.DialogInterface -import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import com.moez.QKSMS.R -import kotlinx.android.synthetic.main.field_dialog.view.* +import com.moez.QKSMS.databinding.FieldDialogBinding class FieldDialog(context: Activity, hint: String, listener: (String) -> Unit) : AlertDialog(context) { - private val layout = LayoutInflater.from(context).inflate(R.layout.field_dialog, null) + private val binding = FieldDialogBinding.inflate(context.layoutInflater) init { - layout.field.hint = hint + binding.field.hint = hint - setView(layout) + setView(binding.root) setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.button_cancel)) { _, _ -> } setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.button_delete)) { _, _ -> listener("") } setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.button_save)) { _, _ -> - listener(layout.field.text.toString()) + listener(binding.field.text.toString()) } } fun setText(text: String): FieldDialog { - layout.field.setText(text) + binding.field.setText(text) return this } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt index 85b240a15..3237773ca 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt @@ -20,16 +20,15 @@ package com.moez.QKSMS.common.widget import android.content.Context import android.util.AttributeSet -import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams -import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.getColorCompat import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.GroupAvatarViewBinding import com.moez.QKSMS.model.Recipient -import kotlinx.android.synthetic.main.group_avatar_view.view.* class GroupAvatarView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -41,9 +40,7 @@ class GroupAvatarView @JvmOverloads constructor( updateView() } - init { - View.inflate(context, R.layout.group_avatar_view, this) - } + private val binding = viewBinding(GroupAvatarViewBinding::inflate) override fun onFinishInflate() { super.onFinishInflate() @@ -54,18 +51,18 @@ class GroupAvatarView @JvmOverloads constructor( } private fun updateView() { - avatar1Frame.setBackgroundTint(when (recipients.size > 1) { + binding.avatar1Frame.setBackgroundTint(when (recipients.size > 1) { true -> context.resolveThemeColor(android.R.attr.windowBackground) false -> context.getColorCompat(android.R.color.transparent) }) - avatar1Frame.updateLayoutParams { + binding.avatar1Frame.updateLayoutParams { matchConstraintPercentWidth = if (recipients.size > 1) 0.75f else 1.0f } - avatar2.isVisible = recipients.size > 1 + binding.avatar2.isVisible = recipients.size > 1 - recipients.getOrNull(0).run(avatar1::setRecipient) - recipients.getOrNull(1).run(avatar2::setRecipient) + recipients.getOrNull(0).run(binding.avatar1::setRecipient) + recipients.getOrNull(1).run(binding.avatar2::setRecipient) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt index 0d3d5db6b..ad38263a7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt @@ -25,10 +25,10 @@ import android.view.LayoutInflater import android.widget.LinearLayout import android.widget.TextView import androidx.viewpager.widget.ViewPager -import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.forEach import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.databinding.TabViewBinding import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.repository.ConversationRepository @@ -36,7 +36,6 @@ import com.uber.autodispose.android.ViewScopeProvider import com.uber.autodispose.autoDisposable import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.tab_view.view.* import javax.inject.Inject class PagerTitleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { @@ -66,11 +65,11 @@ class PagerTitleView @JvmOverloads constructor(context: Context, attrs: Attribut removeAllViews() pager?.adapter?.count?.forEach { position -> - val view = LayoutInflater.from(context).inflate(R.layout.tab_view, this, false) - view.label.text = pager?.adapter?.getPageTitle(position) - view.setOnClickListener { pager?.currentItem = position } + val binding = TabViewBinding.inflate(LayoutInflater.from(context), this, false) + binding.label.text = pager?.adapter?.getPageTitle(position) + binding.root.setOnClickListener { pager?.currentItem = position } - addView(view) + addView(binding.root) } childCount.forEach { index -> 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 4e1a724c7..366f1408d 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 @@ -28,8 +28,9 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.resolveThemeAttribute import com.moez.QKSMS.common.util.extensions.resolveThemeColorStateList import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.PreferenceViewBinding import com.moez.QKSMS.injection.appComponent -import kotlinx.android.synthetic.main.preference_view.view.* class PreferenceView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -42,7 +43,7 @@ class PreferenceView @JvmOverloads constructor( if (isInEditMode) { findViewById(R.id.titleView).text = value } else { - titleView.text = value + binding.titleView.text = value } } @@ -57,22 +58,23 @@ class PreferenceView @JvmOverloads constructor( setVisible(value?.isNotEmpty() == true) } } else { - summaryView.text = value - summaryView.setVisible(value?.isNotEmpty() == true) + binding.summaryView.text = value + binding.summaryView.setVisible(value?.isNotEmpty() == true) } } + val binding: PreferenceViewBinding = viewBinding(PreferenceViewBinding::inflate) + init { if (!isInEditMode) { appComponent.inject(this) } - View.inflate(context, R.layout.preference_view, this) setBackgroundResource(context.resolveThemeAttribute(R.attr.selectableItemBackground)) orientation = HORIZONTAL gravity = Gravity.CENTER_VERTICAL - icon.imageTintList = context.resolveThemeColorStateList(android.R.attr.textColorSecondary) + binding.icon.imageTintList = context.resolveThemeColorStateList(android.R.attr.textColorSecondary) context.obtainStyledAttributes(attrs, R.styleable.PreferenceView).run { title = getString(R.styleable.PreferenceView_title) @@ -80,17 +82,21 @@ class PreferenceView @JvmOverloads constructor( // If there's a custom view used for the preference's widget, inflate it getResourceId(R.styleable.PreferenceView_widget, -1).takeIf { it != -1 }?.let { id -> - View.inflate(context, id, widgetFrame) + View.inflate(context, id, binding.widgetFrame) } // If an icon is being used, set up the icon view getResourceId(R.styleable.PreferenceView_icon, -1).takeIf { it != -1 }?.let { id -> - icon.setVisible(true) - icon.setImageResource(id) + binding.icon.setVisible(true) + binding.icon.setImageResource(id) } recycle() } } -} \ No newline at end of file + fun widget(): T { + return binding.widgetFrame.getChildAt(0) as T + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt index f275c8a0c..f38687b06 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt @@ -19,17 +19,15 @@ package com.moez.QKSMS.common.widget import android.app.Activity -import android.view.LayoutInflater import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter -import kotlinx.android.synthetic.main.qk_dialog.view.* +import com.moez.QKSMS.databinding.QkDialogBinding class QkDialog(private val context: Activity) : AlertDialog(context) { - private val view = LayoutInflater.from(context).inflate(R.layout.qk_dialog, null) + private val binding = QkDialogBinding.inflate(context.layoutInflater) @StringRes var titleRes: Int? = null @@ -41,8 +39,8 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { var title: String? = null set(value) { field = value - view.title.text = value - view.title.isVisible = !value.isNullOrBlank() + binding.title.text = value + binding.title.isVisible = !value.isNullOrBlank() } @StringRes @@ -55,15 +53,15 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { var subtitle: String? = null set(value) { field = value - view.subtitle.text = value - view.subtitle.isVisible = !value.isNullOrBlank() + binding.subtitle.text = value + binding.subtitle.isVisible = !value.isNullOrBlank() } - var adapter: QkAdapter<*>? = null + var adapter: QkAdapter<*, *>? = null set(value) { field = value - view.list.isVisible = value != null - view.list.adapter = value + binding.list.isVisible = value != null + binding.list.adapter = value } var positiveButtonListener: (() -> Unit)? = null @@ -72,9 +70,9 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { var positiveButton: Int? = null set(value) { field = value - value?.run(view.positiveButton::setText) - view.positiveButton.isVisible = value != null - view.positiveButton.setOnClickListener { + value?.run(binding.positiveButton::setText) + binding.positiveButton.isVisible = value != null + binding.positiveButton.setOnClickListener { positiveButtonListener?.invoke() ?: dismiss() } } @@ -85,9 +83,9 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { var negativeButton: Int? = null set(value) { field = value - value?.run(view.negativeButton::setText) - view.negativeButton.isVisible = value != null - view.negativeButton.setOnClickListener { + value?.run(binding.negativeButton::setText) + binding.negativeButton.isVisible = value != null + binding.negativeButton.setOnClickListener { negativeButtonListener?.invoke() ?: dismiss() } } @@ -99,7 +97,7 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { } init { - setView(view) + setView(binding.root) } } 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 9f129c1af..abb7f3713 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 @@ -30,8 +30,9 @@ import com.moez.QKSMS.common.util.extensions.forwardTouches import com.moez.QKSMS.common.util.extensions.resolveThemeAttribute import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.RadioPreferenceViewBinding import com.moez.QKSMS.injection.appComponent -import kotlinx.android.synthetic.main.radio_preference_view.view.* import javax.inject.Inject class RadioPreferenceView @JvmOverloads constructor( @@ -47,7 +48,7 @@ class RadioPreferenceView @JvmOverloads constructor( if (isInEditMode) { findViewById(R.id.titleView).text = value } else { - titleView.text = value + binding.titleView.text = value } } @@ -62,17 +63,18 @@ class RadioPreferenceView @JvmOverloads constructor( setVisible(value?.isNotEmpty() == true) } } else { - summaryView.text = value - summaryView.setVisible(value?.isNotEmpty() == true) + binding.summaryView.text = value + binding.summaryView.setVisible(value?.isNotEmpty() == true) } } + val binding: RadioPreferenceViewBinding = viewBinding(RadioPreferenceViewBinding::inflate) + init { if (!isInEditMode) { appComponent.inject(this) } - View.inflate(context, R.layout.radio_preference_view, this) setBackgroundResource(context.resolveThemeAttribute(R.attr.selectableItemBackground)) val states = arrayOf( @@ -84,8 +86,8 @@ class RadioPreferenceView @JvmOverloads constructor( false -> colors.theme().theme } val textSecondary = context.resolveThemeColor(android.R.attr.textColorTertiary) - radioButton.buttonTintList = ColorStateList(states, intArrayOf(themeColor, textSecondary)) - radioButton.forwardTouches(this) + binding.radioButton.buttonTintList = ColorStateList(states, intArrayOf(themeColor, textSecondary)) + binding.radioButton.forwardTouches(this) context.obtainStyledAttributes(attrs, R.styleable.RadioPreferenceView)?.run { title = getString(R.styleable.RadioPreferenceView_title) @@ -93,11 +95,11 @@ class RadioPreferenceView @JvmOverloads constructor( // If there's a custom view used for the preference's widget, inflate it getResourceId(R.styleable.RadioPreferenceView_widget, -1).takeIf { it != -1 }?.let { id -> - View.inflate(context, id, widgetFrame) + View.inflate(context, id, binding.widgetFrame) } recycle() } } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupActivity.kt index 65c52b3ce..327fe4f13 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupActivity.kt @@ -22,22 +22,22 @@ import android.os.Bundle import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.ContainerActivityBinding import dagger.android.AndroidInjection -import kotlinx.android.synthetic.main.container_activity.* - class BackupActivity : QkThemedActivity() { + private val binding by viewBinding(ContainerActivityBinding::inflate) private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.container_activity) + setContentView(binding.root) - router = Conductor.attachRouter(this, container, savedInstanceState) + router = Conductor.attachRouter(this, binding.container, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(BackupController())) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt index 038295dc6..57350e22c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt @@ -20,42 +20,37 @@ package com.moez.QKSMS.feature.backup import android.content.Context import android.text.format.Formatter -import android.view.LayoutInflater import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.FlowableAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.DateFormatter +import com.moez.QKSMS.databinding.BackupListItemBinding import com.moez.QKSMS.model.BackupFile import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.backup_list_item.* import javax.inject.Inject class BackupAdapter @Inject constructor( private val context: Context, private val dateFormatter: DateFormatter -) : FlowableAdapter() { +) : FlowableAdapter() { val backupSelected: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val layoutInflater = LayoutInflater.from(parent.context) - val view = layoutInflater.inflate(R.layout.backup_list_item, parent, false) - - return QkViewHolder(view).apply { - view.setOnClickListener { backupSelected.onNext(getItem(adapterPosition)) } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, BackupListItemBinding::inflate).apply { + binding.root.setOnClickListener { backupSelected.onNext(getItem(adapterPosition)) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val backup = getItem(position) - val count = backup.messages - holder.title.text = dateFormatter.getDetailedTimestamp(backup.date) - holder.messages.text = context.resources.getQuantityString(R.plurals.backup_message_count, count, count) - holder.size.text = Formatter.formatFileSize(context, backup.size) + holder.binding.title.text = dateFormatter.getDetailedTimestamp(backup.date) + holder.binding.messages.text = context.resources.getQuantityString(R.plurals.backup_message_count, count, count) + holder.binding.size.text = Formatter.formatFileSize(context, backup.size) } } \ No newline at end of file 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 1439f5f47..dc0e691c8 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,6 +22,7 @@ import android.Manifest import android.app.Activity import android.content.res.ColorStateList import android.graphics.Typeface +import android.view.LayoutInflater import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat @@ -36,18 +37,19 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setPositiveButton import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.widget.PreferenceView +import com.moez.QKSMS.databinding.BackupControllerBinding +import com.moez.QKSMS.databinding.BackupListDialogBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.model.BackupFile import com.moez.QKSMS.repository.BackupRepository 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.backup_list_dialog.view.* -import kotlinx.android.synthetic.main.preference_view.view.* import javax.inject.Inject -class BackupController : QkController(), BackupView { +class BackupController : QkController( + BackupControllerBinding::inflate +), BackupView { @Inject lateinit var adapter: BackupAdapter @Inject lateinit var dateFormatter: DateFormatter @@ -58,11 +60,11 @@ class BackupController : QkController( private val stopRestoreSubject: Subject = PublishSubject.create() private val backupFilesDialog by lazy { - val view = View.inflate(activity, R.layout.backup_list_dialog, null) + val binding = BackupListDialogBinding.inflate(LayoutInflater.from(activity)) .apply { files.adapter = adapter.apply { emptyView = empty } } AlertDialog.Builder(activity!!) - .setView(view) + .setView(binding.root) .setCancelable(true) .create() } @@ -87,7 +89,6 @@ class BackupController : QkController( init { appComponent.inject(this) - layoutRes = R.layout.backup_controller } override fun onAttach(view: View) { @@ -101,17 +102,17 @@ 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 } + binding.linearLayout.children + .mapNotNull { view -> view as? PreferenceView } + .map { preferenceView -> preferenceView.binding.titleView } .forEach { it.setTypeface(it.typeface, Typeface.BOLD) } } @@ -123,51 +124,51 @@ class BackupController : QkController( 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 = 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.isVisible = state.backupProgress.indeterminate || running?.max ?: 0 > 0 + 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 = 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.isVisible = state.restoreProgress.indeterminate || running?.max ?: 0 > 0 + 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 } } - backup.summary = state.lastBackup + binding.backup.summary = state.lastBackup adapter.data = state.backups - 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 }) @@ -175,18 +176,18 @@ class BackupController : QkController( override fun activityVisible(): Observable<*> = activityVisibleSubject - override fun restoreClicks(): Observable<*> = restore.clicks() + override fun restoreClicks(): Observable<*> = binding.restore.clicks() override fun restoreFileSelected(): Observable = adapter.backupSelected .doOnNext { backupFilesDialog.dismiss() } override fun restoreConfirmed(): Observable<*> = confirmRestoreSubject - override fun stopRestoreClicks(): Observable<*> = progressCancel.clicks() + override fun stopRestoreClicks(): Observable<*> = binding.progressCancel.clicks() override fun stopRestoreConfirmed(): Observable<*> = stopRestoreSubject - override fun fabClicks(): Observable<*> = fab.clicks() + override fun fabClicks(): Observable<*> = binding.fab.clicks() override fun requestStoragePermission() { ActivityCompat.requestPermissions(activity!!, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 0) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt index e028400fb..9b5d59a54 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt @@ -22,21 +22,22 @@ import android.os.Bundle import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.ContainerActivityBinding import dagger.android.AndroidInjection -import kotlinx.android.synthetic.main.container_activity.* class BlockingActivity : QkThemedActivity() { + private val binding by viewBinding(ContainerActivityBinding::inflate) private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.container_activity) + setContentView(binding.root) - router = Conductor.attachRouter(this, container, savedInstanceState) + router = Conductor.attachRouter(this, binding.container, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(BlockingController())) } 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 1289b9c49..c73e15670 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 @@ -26,20 +26,22 @@ import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges +import com.moez.QKSMS.common.widget.QkSwitch +import com.moez.QKSMS.databinding.BlockingControllerBinding import com.moez.QKSMS.feature.blocking.manager.BlockingManagerController import com.moez.QKSMS.feature.blocking.messages.BlockedMessagesController import com.moez.QKSMS.feature.blocking.numbers.BlockedNumbersController import com.moez.QKSMS.injection.appComponent -import kotlinx.android.synthetic.main.blocking_controller.* -import kotlinx.android.synthetic.main.settings_switch_widget.view.* import javax.inject.Inject -class BlockingController : QkController(), BlockingView { +class BlockingController : QkController( + BlockingControllerBinding::inflate +), BlockingView { - override val blockingManagerIntent by lazy { blockingManager.clicks() } - override val blockedNumbersIntent by lazy { blockedNumbers.clicks() } - override val blockedMessagesIntent by lazy { blockedMessages.clicks() } - override val dropClickedIntent by lazy { drop.clicks() } + override val blockingManagerIntent by lazy { binding.blockingManager.clicks() } + override val blockedNumbersIntent by lazy { binding.blockedNumbers.clicks() } + override val blockedMessagesIntent by lazy { binding.blockedMessages.clicks() } + override val dropClickedIntent by lazy { binding.drop.clicks() } @Inject lateinit var colors: Colors @Inject override lateinit var presenter: BlockingPresenter @@ -47,12 +49,11 @@ class BlockingController : QkController().isChecked = state.dropEnabled + binding.blockedMessages.isEnabled = !state.dropEnabled } override fun openBlockedNumbers() { 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 ed64b1d5c..400ac7202 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 @@ -7,18 +7,16 @@ import androidx.core.view.isVisible import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkController +import com.moez.QKSMS.databinding.BlockingManagerControllerBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.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 kotlinx.android.synthetic.main.radio_preference_view.view.* import javax.inject.Inject -class BlockingManagerController : QkController(), - BlockingManagerView { +class BlockingManagerController : QkController(BlockingManagerControllerBinding::inflate), BlockingManagerView { @Inject override lateinit var presenter: BlockingManagerPresenter @@ -27,7 +25,6 @@ class BlockingManagerController : QkController = activityResumedSubject - override fun qksmsClicked(): Observable<*> = qksms.clicks() - override fun callControlClicked(): Observable<*> = callControl.clicks() - override fun siaClicked(): Observable<*> = shouldIAnswer.clicks() + override fun qksmsClicked(): Observable<*> = binding.qksms.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/messages/BlockedMessagesAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt index 9a4c37e38..7a48e2aa2 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 @@ -20,7 +20,6 @@ package com.moez.QKSMS.feature.blocking.messages import android.content.Context import android.graphics.Typeface -import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible import com.moez.QKSMS.R @@ -28,65 +27,63 @@ import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.databinding.BlockedListItemBinding import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.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( private val context: Context, private val dateFormatter: DateFormatter -) : QkRealmAdapter() { +) : QkRealmAdapter() { 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) - - 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)) - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, BlockedListItemBinding::inflate).apply { + if (viewType == 0) { + binding.title.setTypeface(binding.title.typeface, Typeface.BOLD) + binding.date.setTypeface(binding.date.typeface, Typeface.BOLD) + binding.date.setTextColor(parent.context.resolveThemeColor(android.R.attr.textColorPrimary)) + } - return QkViewHolder(view).apply { - view.setOnClickListener { + 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 } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return - holder.containerView.isActivated = isSelected(conversation.id) + holder.binding.root.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) + holder.binding.avatars.recipients = conversation.recipients + holder.binding.title.collapseEnabled = conversation.recipients.size > 1 + holder.binding.title.text = conversation.getTitle() + holder.binding.date.text = dateFormatter.getConversationTimestamp(conversation.date) - holder.blocker.text = when (conversation.blockingClient) { + holder.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() + holder.binding.reason.text = conversation.blockReason + holder.binding.blocker.isVisible = holder.binding.blocker.text.isNotEmpty() + holder.binding.reason.isVisible = holder.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 92dfcac39..eae709285 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 @@ -27,16 +27,15 @@ import android.view.View import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.databinding.BlockedMessagesControllerBinding import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.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(), - BlockedMessagesView { +class BlockedMessagesController : QkController(BlockedMessagesControllerBinding::inflate), BlockedMessagesView { override val menuReadyIntent: Subject = PublishSubject.create() override val optionsItemIntent: Subject = PublishSubject.create() @@ -54,13 +53,12 @@ class BlockedMessagesController : QkController() { +class BlockedNumbersAdapter : QkRealmAdapter() { val unblockAddress: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.blocked_number_list_item, parent, false) - return QkViewHolder(view).apply { - containerView.unblock.setOnClickListener { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, BlockedNumberListItemBinding::inflate).apply { + binding.unblock.setOnClickListener { val number = getItem(adapterPosition) ?: return@setOnClickListener unblockAddress.onNext(number.id) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val item = getItem(position)!! - holder.number.text = item.address + holder.binding.number.text = item.address } } 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 8b0927eca..3e167351d 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 @@ -18,7 +18,6 @@ */ package com.moez.QKSMS.feature.blocking.numbers -import android.view.LayoutInflater import android.view.View import androidx.appcompat.app.AlertDialog import com.jakewharton.rxbinding2.view.clicks @@ -27,17 +26,17 @@ import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.databinding.BlockedNumbersAddDialogBinding +import com.moez.QKSMS.databinding.BlockedNumbersControllerBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.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 javax.inject.Inject -class BlockedNumbersController : QkController(), - BlockedNumbersView { +class BlockedNumbersController : QkController(BlockedNumbersControllerBinding::inflate), BlockedNumbersView { @Inject override lateinit var presenter: BlockedNumbersPresenter @Inject lateinit var colors: Colors @@ -49,7 +48,6 @@ 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 textWatcher = BlockedNumberTextWatcher(layout.input, phoneNumberUtils) + val binding = BlockedNumbersAddDialogBinding.inflate(activity?.layoutInflater!!) + val textWatcher = BlockedNumberTextWatcher(binding.input, phoneNumberUtils) val dialog = AlertDialog.Builder(activity!!) - .setView(layout) + .setView(binding.root) .setPositiveButton(R.string.blocked_numbers_dialog_block) { _, _ -> - saveAddressSubject.onNext(layout.input.text.toString()) + saveAddressSubject.onNext(binding.input.text.toString()) } .setNegativeButton(R.string.button_cancel) { _, _ -> } .setOnDismissListener { textWatcher.dispose() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt index 43de03391..98e954d74 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt @@ -20,15 +20,16 @@ package com.moez.QKSMS.feature.changelog import android.content.Context import android.graphics.Typeface -import android.view.LayoutInflater import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.databinding.ChangelogListItemBinding import com.moez.QKSMS.manager.ChangelogManager -import kotlinx.android.synthetic.main.changelog_list_item.* -class ChangelogAdapter(private val context: Context) : QkAdapter() { +class ChangelogAdapter( + private val context: Context +) : QkAdapter() { data class ChangelogItem(val type: Int, val label: String) @@ -52,19 +53,18 @@ class ChangelogAdapter(private val context: Context) : QkAdapter { + return QkViewHolder(parent, ChangelogListItemBinding::inflate).apply { if (viewType == 0) { - changelogItem.setTypeface(changelogItem.typeface, Typeface.BOLD) + binding.changelogItem.setTypeface(binding.changelogItem.typeface, Typeface.BOLD) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val item = getItem(position) - holder.changelogItem.text = item.label + holder.binding.changelogItem.text = item.label } override fun getItemViewType(position: Int): Int { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt index 04dc1d4ce..7cf1c2a0b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt @@ -18,15 +18,14 @@ */ package com.moez.QKSMS.feature.changelog -import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R +import com.moez.QKSMS.databinding.ChangelogDialogBinding import com.moez.QKSMS.feature.main.MainActivity import com.moez.QKSMS.manager.ChangelogManager import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.changelog_dialog.view.* class ChangelogDialog(activity: MainActivity) { @@ -36,17 +35,17 @@ class ChangelogDialog(activity: MainActivity) { private val adapter = ChangelogAdapter(activity) init { - val layout = LayoutInflater.from(activity).inflate(R.layout.changelog_dialog, null) + val binding = ChangelogDialogBinding.inflate(activity.layoutInflater) dialog = AlertDialog.Builder(activity) .setCancelable(true) - .setView(layout) + .setView(binding.root) .create() - layout.version.text = activity.getString(R.string.changelog_version, BuildConfig.VERSION_NAME) - layout.changelog.adapter = adapter - layout.more.setOnClickListener { dialog.dismiss(); moreClicks.onNext(Unit) } - layout.dismiss.setOnClickListener { dialog.dismiss() } + binding.version.text = activity.getString(R.string.changelog_version, BuildConfig.VERSION_NAME) + binding.changelog.adapter = adapter + binding.more.setOnClickListener { dialog.dismiss(); moreClicks.onNext(Unit) } + binding.dismiss.setOnClickListener { dialog.dismiss() } } fun show(changelog: ChangelogManager.Changelog) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt index 3563798bc..7ef4c887e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt @@ -19,12 +19,13 @@ package com.moez.QKSMS.feature.compose import android.content.Context -import android.view.LayoutInflater import android.view.ViewGroup +import androidx.viewbinding.ViewBinding import com.bumptech.glide.Glide -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.databinding.AttachmentContactListItemBinding +import com.moez.QKSMS.databinding.AttachmentImageListItemBinding import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.model.Attachment import ezvcard.Ezvcard @@ -33,14 +34,11 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.attachment_contact_list_item.* -import kotlinx.android.synthetic.main.attachment_image_list_item.* -import kotlinx.android.synthetic.main.attachment_image_list_item.view.* import javax.inject.Inject class AttachmentAdapter @Inject constructor( private val context: Context -) : QkAdapter() { +) : QkAdapter() { companion object { private const val VIEW_TYPE_IMAGE = 0 @@ -49,38 +47,42 @@ class AttachmentAdapter @Inject constructor( val attachmentDeleted: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val inflater = LayoutInflater.from(parent.context) - val view = when (viewType) { - VIEW_TYPE_IMAGE -> inflater.inflate(R.layout.attachment_image_list_item, parent, false) - .apply { thumbnailBounds.clipToOutline = true } - - VIEW_TYPE_CONTACT -> inflater.inflate(R.layout.attachment_contact_list_item, parent, false) - + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val holder: QkViewHolder = when (viewType) { + VIEW_TYPE_IMAGE -> QkViewHolder(parent, AttachmentImageListItemBinding::inflate) + VIEW_TYPE_CONTACT -> QkViewHolder(parent, AttachmentContactListItemBinding::inflate) else -> null!! // Impossible } - return QkViewHolder(view).apply { - view.setOnClickListener { + return holder.apply { + if (binding is AttachmentImageListItemBinding) { + binding.thumbnailBounds.clipToOutline = true + } + + binding.root.setOnClickListener { val attachment = getItem(adapterPosition) attachmentDeleted.onNext(attachment) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val attachment = getItem(position) - when (attachment) { - is Attachment.Image -> Glide.with(context) - .load(attachment.getUri()) - .into(holder.thumbnail) + when { + attachment is Attachment.Image && holder.binding is AttachmentImageListItemBinding -> { + Glide.with(context) + .load(attachment.getUri()) + .into(holder.binding.thumbnail) + } - is Attachment.Contact -> Observable.just(attachment.vCard) - .mapNotNull { vCard -> Ezvcard.parse(vCard).first() } - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { vcard -> holder.name?.text = vcard.formattedName.value } + attachment is Attachment.Contact && holder.binding is AttachmentContactListItemBinding -> { + Observable.just(attachment.vCard) + .mapNotNull { vCard -> Ezvcard.parse(vCard).first() } + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { vcard -> holder.binding.name.text = vcard.formattedName.value } + } } } 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 6b21cf1b5..f47227ea4 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 @@ -54,6 +54,8 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.util.extensions.showKeyboard +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.ComposeActivityBinding import com.moez.QKSMS.feature.compose.editing.ChipsAdapter import com.moez.QKSMS.feature.contacts.ContactsActivity import com.moez.QKSMS.model.Attachment @@ -64,7 +66,6 @@ import dagger.android.AndroidInjection import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.compose_activity.* import java.text.SimpleDateFormat import java.util.* import javax.inject.Inject @@ -93,28 +94,29 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } override val menuReadyIntent: Observable = menu.map { Unit } override val optionsItemIntent: Subject = PublishSubject.create() - override val sendAsGroupIntent by lazy { sendAsGroupBackground.clicks() } + override val sendAsGroupIntent by lazy { binding.sendAsGroupBackground.clicks() } override val messageClickIntent: Subject by lazy { messageAdapter.clicks } override val messagePartClickIntent: Subject by lazy { messageAdapter.partClicks } override val messagesSelectedIntent by lazy { messageAdapter.selectionChanges } override val cancelSendingIntent: Subject by lazy { messageAdapter.cancelSending } override val attachmentDeletedIntent: Subject by lazy { attachmentAdapter.attachmentDeleted } - override val textChangedIntent by lazy { message.textChanges() } - override val attachIntent by lazy { Observable.merge(attach.clicks(), attachingBackground.clicks()) } - override val cameraIntent by lazy { Observable.merge(camera.clicks(), cameraLabel.clicks()) } - override val galleryIntent by lazy { Observable.merge(gallery.clicks(), galleryLabel.clicks()) } - override val scheduleIntent by lazy { Observable.merge(schedule.clicks(), scheduleLabel.clicks()) } - override val attachContactIntent by lazy { Observable.merge(contact.clicks(), contactLabel.clicks()) } + override val textChangedIntent by lazy { binding.message.textChanges() } + override val attachIntent by lazy { Observable.merge(binding.attach.clicks(), binding.attachingBackground.clicks()) } + override val cameraIntent by lazy { Observable.merge(binding.camera.clicks(), binding.cameraLabel.clicks()) } + override val galleryIntent by lazy { Observable.merge(binding.gallery.clicks(), binding.galleryLabel.clicks()) } + override val scheduleIntent by lazy { Observable.merge(binding.schedule.clicks(), binding.scheduleLabel.clicks()) } + override val attachContactIntent by lazy { Observable.merge(binding.contact.clicks(), binding.contactLabel.clicks()) } override val attachmentSelectedIntent: 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 { send.clicks() } + override val changeSimIntent by lazy { binding.sim.clicks() } + override val scheduleCancelIntent by lazy { binding.scheduledCancel.clicks() } + override val sendIntent by lazy { binding.send.clicks() } override val viewQksmsPlusIntent: Subject = PublishSubject.create() override val backPressedIntent: Subject = PublishSubject.create() + private val binding by viewBinding(ComposeActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ComposeViewModel::class.java] } private var cameraDestination: Uri? = null @@ -122,33 +124,33 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.compose_activity) + 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 - attachments.adapter = attachmentAdapter + binding.attachments.adapter = attachmentAdapter - message.supportsInputContent = true + binding.message.supportsInputContent = true theme - .doOnNext { loading.setTint(it.theme) } - .doOnNext { attach.setBackgroundTint(it.theme) } - .doOnNext { attach.setTint(it.textPrimary) } + .doOnNext { binding.loading.setTint(it.theme) } + .doOnNext { binding.attach.setBackgroundTint(it.theme) } + .doOnNext { binding.attach.setTint(it.textPrimary) } .doOnNext { messageAdapter.theme = it } .autoDisposable(scope()) .subscribe() @@ -157,7 +159,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) + binding.messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) } } @@ -185,59 +187,59 @@ 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.add)?.isVisible = state.editingMode - toolbar.menu.findItem(R.id.call)?.isVisible = !state.editingMode && 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 = !state.editingMode && state.selectedMessages > 0 - toolbar.menu.findItem(R.id.details)?.isVisible = !state.editingMode && state.selectedMessages == 1 - toolbar.menu.findItem(R.id.delete)?.isVisible = !state.editingMode && state.selectedMessages > 0 - toolbar.menu.findItem(R.id.forward)?.isVisible = !state.editingMode && state.selectedMessages == 1 - 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.copy)?.isVisible = !state.editingMode && state.selectedMessages > 0 + 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 + binding.toolbar.menu.findItem(R.id.forward)?.isVisible = !state.editingMode && state.selectedMessages == 1 + 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.editingMode && state.selectedChips.size >= 2) - sendAsGroupSwitch.isChecked = state.sendAsGroup + binding.sendAsGroup.setVisible(state.editingMode && state.selectedChips.size >= 2) + binding.sendAsGroupSwitch.isChecked = state.sendAsGroup - messageList.setVisible(!state.editingMode || state.sendAsGroup) + binding.messageList.setVisible(!state.editingMode || state.sendAsGroup) 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) - attachments.setVisible(state.attachments.isNotEmpty()) + binding.attachments.setVisible(state.attachments.isNotEmpty()) attachmentAdapter.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 - 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() - 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 clearSelection() = messageAdapter.clearSelection() @@ -287,7 +289,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.SharingKey, sharing) @@ -296,12 +298,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) } @@ -325,17 +327,17 @@ class ComposeActivity : QkThemedActivity(), ComposeView { startActivityForResult(Intent.createChooser(intent, null), AttachPhotoRequestCode) } - override fun setDraft(draft: String) = message.setText(draft) + override fun setDraft(draft: String) = binding.message.setText(draft) 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() 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 933e82957..2e62ca91c 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 @@ -46,6 +46,7 @@ import com.moez.QKSMS.common.util.extensions.setPadding import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.compat.SubscriptionManagerCompat +import com.moez.QKSMS.databinding.MessageListItemInBinding import com.moez.QKSMS.extensions.isSmil import com.moez.QKSMS.extensions.isText import com.moez.QKSMS.feature.compose.BubbleUtils.canGroup @@ -59,15 +60,6 @@ import com.moez.QKSMS.util.Preferences 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.attachments -import kotlinx.android.synthetic.main.message_list_item_in.body -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 java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -82,7 +74,7 @@ class MessagesAdapter @Inject constructor( private val phoneNumberUtils: PhoneNumberUtils, private val prefs: Preferences, private val textViewStyler: TextViewStyler -) : QkRealmAdapter() { +) : QkRealmAdapter() { companion object { private const val VIEW_TYPE_MESSAGE_IN = 0 @@ -138,7 +130,7 @@ class MessagesAdapter @Inject constructor( * this a unique viewType even though it uses the same view, so that regular messages * don't need clipToOutline set to true, and they don't need to worry about images */ - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { // Use the parent's context to inflate the layout, otherwise link clicks will crash the app val layoutInflater = LayoutInflater.from(parent.context) @@ -152,24 +144,26 @@ class MessagesAdapter @Inject constructor( view = layoutInflater.inflate(R.layout.message_list_item_in, parent, false) } + val binding = MessageListItemInBinding.bind(view) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - view.body.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE + binding.body.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE } val partsAdapter = partsAdapterProvider.get() partsAdapter.clicks.subscribe(partClicks) - view.attachments.adapter = partsAdapter - view.attachments.setRecycledViewPool(partsViewPool) - view.body.forwardTouches(view) + binding.attachments.adapter = partsAdapter + binding.attachments.setRecycledViewPool(partsViewPool) + binding.body.forwardTouches(view) - return QkViewHolder(view).apply { + return QkViewHolder(binding).apply { view.setOnClickListener { val message = getItem(adapterPosition) ?: return@setOnClickListener when (toggleSelection(message.id, false)) { true -> view.isActivated = isSelected(message.id) false -> { clicks.onNext(message.id) - expanded[message.id] = view.status.visibility != View.VISIBLE + expanded[message.id] = binding.status.visibility != View.VISIBLE notifyItemChanged(adapterPosition) } } @@ -183,7 +177,7 @@ class MessagesAdapter @Inject constructor( } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val message = getItem(position) ?: return val previous = if (position == 0) null else getItem(position - 1) val next = if (position == itemCount - 1) null else getItem(position + 1) @@ -194,10 +188,10 @@ class MessagesAdapter @Inject constructor( } // Update the selected state - holder.containerView.isActivated = isSelected(message.id) || highlight == message.id + holder.binding.root.isActivated = isSelected(message.id) || highlight == message.id // Bind the cancel view - holder.cancel?.let { cancel -> + holder.binding.cancel.let { cancel -> val isCancellable = message.isSending() && message.date > System.currentTimeMillis() cancel.setVisible(isCancellable) cancel.clicks().subscribe { cancelSending.onNext(message.id) } @@ -225,25 +219,25 @@ class MessagesAdapter @Inject constructor( val timeSincePrevious = TimeUnit.MILLISECONDS.toMinutes(message.date - (previous?.date ?: 0)) val subscription = subs.find { sub -> sub.subscriptionId == message.subId } - holder.timestamp.text = dateFormatter.getMessageTimestamp(message.date) - holder.simIndex.text = subscription?.simSlotIndex?.plus(1)?.toString() + holder.binding.timestamp.text = dateFormatter.getMessageTimestamp(message.date) + holder.binding.simIndex.text = subscription?.simSlotIndex?.plus(1)?.toString() - holder.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD + holder.binding.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD || message.subId != previous?.subId && subscription != null) - holder.sim.setVisible(message.subId != previous?.subId && subscription != null) - holder.simIndex.setVisible(message.subId != previous?.subId && subscription != null) + holder.binding.sim.setVisible(message.subId != previous?.subId && subscription != null) + holder.binding.simIndex.setVisible(message.subId != previous?.subId && subscription != null) // Bind the grouping val media = message.parts.filter { !it.isSmil() && !it.isText() } - holder.containerView.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context)) + holder.binding.root.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context)) // Bind the avatar and bubble colour if (!message.isMe()) { - holder.avatar.setRecipient(contactCache[message.address]) - holder.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) + holder.binding.avatar.setRecipient(contactCache[message.address]) + holder.binding.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) - holder.body.setTextColor(theme.textPrimary) - holder.body.setBackgroundTint(theme.theme) + holder.binding.body.setTextColor(theme.textPrimary) + holder.binding.body.setBackgroundTint(theme.theme) } // Bind the body text @@ -269,29 +263,29 @@ class MessagesAdapter @Inject constructor( } } val emojiOnly = messageText.isNotBlank() && messageText.matches(EMOJI_REGEX) - textViewStyler.setTextSize(holder.body, when (emojiOnly) { + textViewStyler.setTextSize(holder.binding.body, when (emojiOnly) { true -> TextViewStyler.SIZE_EMOJI false -> TextViewStyler.SIZE_PRIMARY }) - holder.body.text = messageText - holder.body.setVisible(message.isSms() || messageText.isNotBlank()) - holder.body.setBackgroundResource(getBubble( + holder.binding.body.text = messageText + holder.binding.body.setVisible(message.isSms() || messageText.isNotBlank()) + holder.binding.body.setBackgroundResource(getBubble( emojiOnly = emojiOnly, canGroupWithPrevious = canGroup(message, previous) || media.isNotEmpty(), canGroupWithNext = canGroup(message, next), isMe = message.isMe())) // Bind the attachments - val partsAdapter = holder.attachments.adapter as PartsAdapter + val partsAdapter = holder.binding.attachments.adapter as PartsAdapter partsAdapter.theme = theme partsAdapter.setData(message, previous, next, holder) } - private fun bindStatus(holder: QkViewHolder, message: Message, next: Message?) { + private fun bindStatus(holder: QkViewHolder, message: Message, next: Message?) { val age = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - message.date) - holder.status.text = when { + holder.binding.status.text = when { message.isSending() -> context.getString(R.string.message_status_sending) message.isDelivered() -> context.getString(R.string.message_status_delivered, dateFormatter.getTimestamp(message.dateSent)) @@ -305,7 +299,7 @@ class MessagesAdapter @Inject constructor( else -> dateFormatter.getTimestamp(message.date) } - holder.status.setVisible(when { + holder.binding.status.setVisible(when { expanded[message.id] == true -> true message.isSending() -> true message.isFailedMessage() -> true diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt index b415d32f3..a6ebc5d01 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt @@ -20,7 +20,6 @@ package com.moez.QKSMS.feature.compose.editing import android.content.Context import android.os.Build -import android.view.LayoutInflater import android.view.ViewGroup import android.widget.RelativeLayout import androidx.recyclerview.widget.RecyclerView @@ -30,37 +29,35 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint +import com.moez.QKSMS.databinding.ContactChipBinding import com.moez.QKSMS.model.Recipient import io.reactivex.subjects.PublishSubject -import kotlinx.android.synthetic.main.contact_chip.* import javax.inject.Inject -class ChipsAdapter @Inject constructor() : QkAdapter() { +class ChipsAdapter @Inject constructor() : QkAdapter() { var view: RecyclerView? = null val chipDeleted: PublishSubject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val inflater = LayoutInflater.from(parent.context) - val view = inflater.inflate(R.layout.contact_chip, parent, false) - return QkViewHolder(view).apply { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, ContactChipBinding::inflate).apply { // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - content.setBackgroundTint(view.context.resolveThemeColor(R.attr.bubbleColor)) + binding.content.setBackgroundTint(parent.context.resolveThemeColor(R.attr.bubbleColor)) } - view.setOnClickListener { + binding.root.setOnClickListener { val chip = getItem(adapterPosition) - showDetailedChip(view.context, chip) + showDetailedChip(parent.context, chip) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val recipient = getItem(position) - holder.avatar.setRecipient(recipient) - holder.name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address + holder.binding.avatar.setRecipient(recipient) + holder.binding.name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address } /** @@ -76,8 +73,9 @@ class ChipsAdapter @Inject constructor() : QkAdapter() { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - layoutParams.topMargin = 24.dpToPx(context) + layoutParams.topMargin = 32.dpToPx(context) layoutParams.marginStart = 56.dpToPx(context) + layoutParams.marginEnd = 8.dpToPx(context) rootView.addView(detailedChipView, layoutParams) detailedChipView.show() 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 2681d520b..f20941ca0 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 @@ -18,7 +18,6 @@ */ package com.moez.QKSMS.feature.compose.editing -import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView @@ -28,6 +27,7 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.forwardTouches import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.databinding.ContactListItemBinding import com.moez.QKSMS.extensions.associateByNotNull import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.ContactGroup @@ -38,14 +38,12 @@ import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign 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( private val colors: Colors, private val conversationRepo: ConversationRepository -) : QkAdapter() { +) : QkAdapter() { val clicks: Subject = PublishSubject.create() val longClicks: Subject = PublishSubject.create() @@ -59,22 +57,19 @@ class ComposeItemAdapter @Inject constructor( notifyDataSetChanged() } - 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) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, ContactListItemBinding::inflate).apply { + binding.icon.setTint(colors.theme().theme) - view.icon.setTint(colors.theme().theme) + binding.numbers.setRecycledViewPool(numbersViewPool) + binding.numbers.adapter = PhoneNumberAdapter() + binding.numbers.forwardTouches(binding.root) - view.numbers.setRecycledViewPool(numbersViewPool) - view.numbers.adapter = PhoneNumberAdapter() - view.numbers.forwardTouches(view) - - return QkViewHolder(view).apply { - view.setOnClickListener { + binding.root.setOnClickListener { val item = getItem(adapterPosition) clicks.onNext(item) } - view.setOnLongClickListener { + binding.root.setOnLongClickListener { val item = getItem(adapterPosition) longClicks.onNext(item) true @@ -82,7 +77,7 @@ class ComposeItemAdapter @Inject constructor( } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val prevItem = if (position > 0) getItem(position - 1) else null val item = getItem(position) @@ -95,92 +90,92 @@ class ComposeItemAdapter @Inject constructor( } } - private fun bindNew(holder: QkViewHolder, contact: Contact) { - holder.index.isVisible = false + private fun bindNew(holder: QkViewHolder, contact: Contact) { + holder.binding.index.isVisible = false - holder.icon.isVisible = false + holder.binding.icon.isVisible = false - holder.avatar.recipients = listOf(createRecipient(contact)) + holder.binding.avatar.recipients = listOf(createRecipient(contact)) - holder.title.text = contact.numbers.joinToString { it.address } + holder.binding.title.text = contact.numbers.joinToString { it.address } - holder.subtitle.isVisible = false + holder.binding.subtitle.isVisible = false - holder.numbers.isVisible = false + holder.binding.numbers.isVisible = false } - private fun bindRecent(holder: QkViewHolder, conversation: Conversation, prev: ComposeItem?) { - holder.index.isVisible = false + private fun bindRecent(holder: QkViewHolder, conversation: Conversation, prev: ComposeItem?) { + holder.binding.index.isVisible = false - holder.icon.isVisible = prev !is ComposeItem.Recent - holder.icon.setImageResource(R.drawable.ic_history_black_24dp) + holder.binding.icon.isVisible = prev !is ComposeItem.Recent + holder.binding.icon.setImageResource(R.drawable.ic_history_black_24dp) - holder.avatar.recipients = conversation.recipients + holder.binding.avatar.recipients = conversation.recipients - holder.title.text = conversation.getTitle() + holder.binding.title.text = conversation.getTitle() - holder.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() - holder.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> + holder.binding.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() + holder.binding.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> recipient.contact?.name ?: recipient.address } - holder.subtitle.collapseEnabled = conversation.recipients.size > 1 + holder.binding.subtitle.collapseEnabled = conversation.recipients.size > 1 - holder.numbers.isVisible = conversation.recipients.size == 1 - (holder.numbers.adapter as PhoneNumberAdapter).data = conversation.recipients + holder.binding.numbers.isVisible = conversation.recipients.size == 1 + (holder.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(holder: QkViewHolder, contact: Contact, prev: ComposeItem?) { + holder.binding.index.isVisible = false - holder.icon.isVisible = prev !is ComposeItem.Starred - holder.icon.setImageResource(R.drawable.ic_star_black_24dp) + holder.binding.icon.isVisible = prev !is ComposeItem.Starred + holder.binding.icon.setImageResource(R.drawable.ic_star_black_24dp) - holder.avatar.recipients = listOf(createRecipient(contact)) + holder.binding.avatar.recipients = listOf(createRecipient(contact)) - holder.title.text = contact.name + holder.binding.title.text = contact.name - holder.subtitle.isVisible = false + holder.binding.subtitle.isVisible = false - holder.numbers.isVisible = true - (holder.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + holder.binding.numbers.isVisible = true + (holder.binding.numbers.adapter as PhoneNumberAdapter).data = contact.numbers } - private fun bindGroup(holder: QkViewHolder, group: ContactGroup, prev: ComposeItem?) { - holder.index.isVisible = false + private fun bindGroup(holder: QkViewHolder, group: ContactGroup, prev: ComposeItem?) { + holder.binding.index.isVisible = false - holder.icon.isVisible = prev !is ComposeItem.Group - holder.icon.setImageResource(R.drawable.ic_people_black_24dp) + holder.binding.icon.isVisible = prev !is ComposeItem.Group + holder.binding.icon.setImageResource(R.drawable.ic_people_black_24dp) - holder.avatar.recipients = group.contacts.map(::createRecipient) + holder.binding.avatar.recipients = group.contacts.map(::createRecipient) - holder.title.text = group.title + holder.binding.title.text = group.title - holder.subtitle.isVisible = true - holder.subtitle.text = group.contacts.joinToString(", ") { it.name } - holder.subtitle.collapseEnabled = group.contacts.size > 1 + holder.binding.subtitle.isVisible = true + holder.binding.subtitle.text = group.contacts.joinToString(", ") { it.name } + holder.binding.subtitle.collapseEnabled = group.contacts.size > 1 - holder.numbers.isVisible = false + holder.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(holder: QkViewHolder, contact: Contact, prev: ComposeItem?) { + holder.binding.index.isVisible = true + holder.binding.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" + holder.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 + holder.binding.icon.isVisible = false - holder.avatar.recipients = listOf(createRecipient(contact)) + holder.binding.avatar.recipients = listOf(createRecipient(contact)) - holder.title.text = contact.name + holder.binding.title.text = contact.name - holder.subtitle.isVisible = false + holder.binding.subtitle.isVisible = false - holder.numbers.isVisible = true - (holder.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + holder.binding.numbers.isVisible = true + (holder.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 aa90cd994..0b9475df7 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,44 +19,53 @@ package com.moez.QKSMS.feature.compose.editing import android.content.Context +import android.util.AttributeSet import android.view.View import android.view.animation.AlphaAnimation -import android.widget.RelativeLayout +import androidx.constraintlayout.widget.ConstraintLayout import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.ContactChipDetailedBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.model.Recipient -import kotlinx.android.synthetic.main.contact_chip_detailed.view.* import javax.inject.Inject -class DetailedChipView(context: Context) : RelativeLayout(context) { +class DetailedChipView(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { @Inject lateinit var colors: Colors + private val binding = viewBinding(ContactChipDetailedBinding::inflate) + init { - View.inflate(context, R.layout.contact_chip_detailed, this) - appComponent.inject(this) + if (!isInEditMode) { + appComponent.inject(this) + visibility = View.GONE + } setOnClickListener { hide() } + setBackgroundResource(R.drawable.rounded_rectangle_2dp) - visibility = View.GONE + elevation = 8.dpToPx(context) .toFloat() +// updateLayoutParams { setMargins(8.dpToPx(context) ) } isFocusable = true isFocusableInTouchMode = true } 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) + setBackgroundTint(theme.theme) + binding.name.setTextColor(theme.textPrimary) + binding.info.setTextColor(theme.textTertiary) + binding.delete.setTint(theme.textPrimary) } } @@ -77,7 +86,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/PhoneNumberAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt index 534f013e9..9a914e167 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt @@ -18,27 +18,23 @@ */ package com.moez.QKSMS.feature.compose.editing -import android.view.LayoutInflater import android.view.ViewGroup -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.databinding.ContactNumberListItemBinding import com.moez.QKSMS.model.PhoneNumber -import kotlinx.android.synthetic.main.contact_number_list_item.* -class PhoneNumberAdapter : QkAdapter() { +class PhoneNumberAdapter : QkAdapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val inflater = LayoutInflater.from(parent.context) - val view = inflater.inflate(R.layout.contact_number_list_item, parent, false) - return QkViewHolder(view) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, ContactNumberListItemBinding::inflate) } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val number = getItem(position) - holder.address.text = number.address - holder.type.text = number.type + holder.binding.address.text = number.address + holder.binding.type.text = number.type } override fun areItemsTheSame(old: PhoneNumber, new: PhoneNumber): Boolean { @@ -49,4 +45,4 @@ class PhoneNumberAdapter : QkAdapter() { return old.type == new.type && old.address == new.address } -} \ No newline at end of file +} 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 084e41a37..74b5e4295 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 @@ -19,24 +19,21 @@ package com.moez.QKSMS.feature.compose.editing import android.content.Context -import android.view.LayoutInflater import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.extensions.forwardTouches +import com.moez.QKSMS.databinding.PhoneNumberListItemBinding import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.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( private val context: Context -) : QkAdapter() { +) : QkAdapter() { val selectedItemChanges: Subject> = BehaviorSubject.create() @@ -48,25 +45,23 @@ class PhoneNumberPickerAdapter @Inject constructor( selectedItemChanges.onNext(Optional(value)) } - 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) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, PhoneNumberListItemBinding::inflate).apply { + binding.number.binding.radioButton.forwardTouches(itemView) - view.setOnClickListener { + binding.root.setOnClickListener { val phoneNumber = getItem(adapterPosition) selectedItem = phoneNumber.id } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val phoneNumber = getItem(position) - holder.number.radioButton.isChecked = phoneNumber.id == selectedItem - holder.number.titleView.text = phoneNumber.address - holder.number.summaryView.text = when (phoneNumber.isDefault) { + holder.binding.number.binding.radioButton.isChecked = phoneNumber.id == selectedItem + holder.binding.number.binding.titleView.text = phoneNumber.address + holder.binding.number.binding.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/FileBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt index ff12a1bdd..cb055e40b 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 @@ -28,35 +28,37 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.databinding.MmsFileListItemBinding import com.moez.QKSMS.feature.compose.BubbleUtils import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart 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() { +class FileBinder @Inject constructor( + colors: Colors, + private val context: Context +) : PartBinder(MmsFileListItemBinding::inflate) { - override val partLayout = R.layout.mms_file_list_item override var theme = colors.theme() // This is the last binder we check. If we're here, we can bind the part override fun canBindPart(part: MmsPart) = true @SuppressLint("CheckResult") - override fun bindPart( - holder: QkViewHolder, + override fun bindPartInternal( + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) - .let(holder.fileBackground::setBackgroundResource) + .let(holder.binding.fileBackground::setBackgroundResource) - holder.containerView.setOnClickListener { clicks.onNext(part.id) } + holder.binding.root.setOnClickListener { clicks.onNext(part.id) } Observable.just(part.getUri()) .map(context.contentResolver::openInputStream) @@ -71,23 +73,23 @@ class FileBinder @Inject constructor(colors: Colors, private val context: Contex } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { size -> holder.size.text = size } + .subscribe { size -> holder.binding.size.text = size } - holder.filename.text = part.name + holder.binding.filename.text = part.name - val params = holder.fileBackground.layoutParams as FrameLayout.LayoutParams + val params = holder.binding.fileBackground.layoutParams as FrameLayout.LayoutParams if (!message.isMe()) { - holder.fileBackground.layoutParams = params.apply { gravity = Gravity.START } - holder.fileBackground.setBackgroundTint(theme.theme) - holder.icon.setTint(theme.textPrimary) - holder.filename.setTextColor(theme.textPrimary) - holder.size.setTextColor(theme.textTertiary) + holder.binding.fileBackground.layoutParams = params.apply { gravity = Gravity.START } + holder.binding.fileBackground.setBackgroundTint(theme.theme) + holder.binding.icon.setTint(theme.textPrimary) + holder.binding.filename.setTextColor(theme.textPrimary) + holder.binding.size.setTextColor(theme.textTertiary) } else { - holder.fileBackground.layoutParams = params.apply { gravity = Gravity.END } - 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)) + holder.binding.fileBackground.layoutParams = params.apply { gravity = Gravity.END } + holder.binding.fileBackground.setBackgroundTint(holder.binding.root.context.resolveThemeColor(R.attr.bubbleColor)) + holder.binding.icon.setTint(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorSecondary)) + holder.binding.filename.setTextColor(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorPrimary)) + holder.binding.size.setTextColor(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorTertiary)) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt index 1ac437a5c..78c5a3209 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt @@ -19,44 +19,45 @@ package com.moez.QKSMS.feature.compose.part import android.content.Context -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.BubbleImageView +import com.moez.QKSMS.databinding.MmsPreviewListItemBinding import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.util.GlideApp -import kotlinx.android.synthetic.main.mms_preview_list_item.* import javax.inject.Inject -class MediaBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { +class MediaBinder @Inject constructor( + colors: Colors, + private val context: Context +) : PartBinder(MmsPreviewListItemBinding::inflate) { - override val partLayout = R.layout.mms_preview_list_item override var theme = colors.theme() override fun canBindPart(part: MmsPart) = part.isImage() || part.isVideo() - override fun bindPart( - holder: QkViewHolder, + override fun bindPartInternal( + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { - holder.video.setVisible(part.isVideo()) - holder.containerView.setOnClickListener { clicks.onNext(part.id) } + holder.binding.video.setVisible(part.isVideo()) + holder.binding.root.setOnClickListener { clicks.onNext(part.id) } - holder.thumbnail.bubbleStyle = when { + holder.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 else -> BubbleImageView.Style.ONLY } - GlideApp.with(context).load(part.getUri()).fitCenter().into(holder.thumbnail) + GlideApp.with(context).load(part.getUri()).fitCenter().into(holder.binding.thumbnail) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt index d56f8547d..c4e27ba27 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt @@ -18,6 +18,9 @@ */ package com.moez.QKSMS.feature.compose.part +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.model.Message @@ -25,18 +28,36 @@ import com.moez.QKSMS.model.MmsPart import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -abstract class PartBinder { +abstract class PartBinder( + val bindingInflater: (LayoutInflater, ViewGroup, Boolean) -> Binding +) { val clicks: Subject = PublishSubject.create() - abstract val partLayout: Int - abstract var theme: Colors.Theme + fun bindPart( + holder: QkViewHolder, + part: MmsPart, + message: Message, + canGroupWithPrevious: Boolean, + canGroupWithNext: Boolean + ): Boolean { + val castHolder = holder as? QkViewHolder + + if (!canBindPart(part) || castHolder == null) { + return false + } + + bindPartInternal(castHolder, part, message, canGroupWithPrevious, canGroupWithNext) + + return true + } + abstract fun canBindPart(part: MmsPart): Boolean - abstract fun bindPart( - holder: QkViewHolder, + protected abstract fun bindPartInternal( + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, 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 567f49268..6f5437d2d 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 @@ -18,20 +18,20 @@ */ package com.moez.QKSMS.feature.compose.part -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.viewbinding.ViewBinding import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.forwardTouches +import com.moez.QKSMS.databinding.MessageListItemInBinding import com.moez.QKSMS.extensions.isSmil import com.moez.QKSMS.extensions.isText import com.moez.QKSMS.feature.compose.BubbleUtils.canGroup import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import io.reactivex.Observable -import kotlinx.android.synthetic.main.message_list_item_in.* import javax.inject.Inject class PartsAdapter @Inject constructor( @@ -39,7 +39,7 @@ class PartsAdapter @Inject constructor( fileBinder: FileBinder, mediaBinder: MediaBinder, vCardBinder: VCardBinder -) : QkAdapter() { +) : QkAdapter() { private val partBinders = listOf(mediaBinder, vCardBinder, fileBinder) @@ -54,34 +54,31 @@ class PartsAdapter @Inject constructor( private lateinit var message: Message private var previous: Message? = null private var next: Message? = null - private var holder: QkViewHolder? = null + private var holder: QkViewHolder? = null private var bodyVisible: Boolean = true - fun setData(message: Message, previous: Message?, next: Message?, holder: QkViewHolder) { + fun setData(message: Message, previous: Message?, next: Message?, holder: QkViewHolder) { this.message = message this.previous = previous this.next = next this.holder = holder - this.bodyVisible = holder.body.visibility == View.VISIBLE + this.bodyVisible = holder.binding.body.visibility == View.VISIBLE this.data = message.parts.filter { !it.isSmil() && !it.isText() } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val layout = partBinders.getOrNull(viewType)?.partLayout ?: 0 - val view = LayoutInflater.from(parent.context).inflate(layout, parent, false) - holder?.containerView?.let(view::forwardTouches) - return QkViewHolder(view) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, partBinders[viewType].bindingInflater).apply { + holder?.binding?.root?.let(binding.root::forwardTouches) + } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val part = data[position] val canGroupWithPrevious = canGroup(message, previous) || position > 0 val canGroupWithNext = canGroup(message, next) || position < itemCount - 1 || bodyVisible - partBinders - .firstOrNull { it.canBindPart(part) } - ?.bindPart(holder, part, message, canGroupWithPrevious, canGroupWithNext) + partBinders.find { binder -> binder.bindPart(holder, part, message, canGroupWithPrevious, canGroupWithNext) } } override fun getItemViewType(position: Int): Int { 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 9e068137d..8c0f10508 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 com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.databinding.MmsVcardListItemBinding import com.moez.QKSMS.extensions.isVCard import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.feature.compose.BubbleUtils @@ -36,48 +37,49 @@ 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() { +class VCardBinder @Inject constructor( + colors: Colors, + private val context: Context +) : PartBinder(MmsVcardListItemBinding::inflate) { - override val partLayout = R.layout.mms_vcard_list_item override var theme = colors.theme() override fun canBindPart(part: MmsPart) = part.isVCard() - override fun bindPart( - holder: QkViewHolder, + override fun bindPartInternal( + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) - .let(holder.vCardBackground::setBackgroundResource) + .let(holder.binding.vCardBackground::setBackgroundResource) - holder.containerView.setOnClickListener { clicks.onNext(part.id) } + holder.binding.root.setOnClickListener { clicks.onNext(part.id) } Observable.just(part.getUri()) .map(context.contentResolver::openInputStream) .mapNotNull { inputStream -> inputStream.use { Ezvcard.parse(it).first() } } .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { vcard -> holder.name?.text = vcard.formattedName.value } + .subscribe { vcard -> holder.binding.name?.text = vcard.formattedName.value } - val params = holder.vCardBackground.layoutParams as FrameLayout.LayoutParams + val params = holder.binding.vCardBackground.layoutParams as FrameLayout.LayoutParams if (!message.isMe()) { - holder.vCardBackground.layoutParams = params.apply { gravity = Gravity.START } - holder.vCardBackground.setBackgroundTint(theme.theme) - holder.vCardAvatar.setTint(theme.textPrimary) - holder.name.setTextColor(theme.textPrimary) - holder.label.setTextColor(theme.textTertiary) + holder.binding.vCardBackground.layoutParams = params.apply { gravity = Gravity.START } + holder.binding.vCardBackground.setBackgroundTint(theme.theme) + holder.binding.vCardAvatar.setTint(theme.textPrimary) + holder.binding.name.setTextColor(theme.textPrimary) + holder.binding.label.setTextColor(theme.textTertiary) } else { - holder.vCardBackground.layoutParams = params.apply { gravity = Gravity.END } - 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)) + holder.binding.vCardBackground.layoutParams = params.apply { gravity = Gravity.END } + holder.binding.vCardBackground.setBackgroundTint(holder.binding.root.context.resolveThemeColor(R.attr.bubbleColor)) + holder.binding.vCardAvatar.setTint(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorSecondary)) + holder.binding.name.setTextColor(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorPrimary)) + holder.binding.label.setTextColor(holder.binding.root.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 f5ca9f71d..af2904a82 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 @@ -34,7 +34,9 @@ import com.moez.QKSMS.common.util.extensions.hideKeyboard import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.showKeyboard +import com.moez.QKSMS.common.util.extensions.viewBinding import com.moez.QKSMS.common.widget.QkDialog +import com.moez.QKSMS.databinding.ContactsActivityBinding import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.ComposeItemAdapter @@ -44,7 +46,6 @@ import dagger.android.AndroidInjection 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 { @@ -58,14 +59,15 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { @Inject lateinit var phoneNumberAdapter: PhoneNumberPickerAdapter @Inject lateinit var viewModelFactory: ViewModelFactory - 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 } override val phoneNumberActionIntent: Subject = PublishSubject.create() + private val binding by viewBinding(ContactsActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ContactsViewModel::class.java] } private val phoneNumberDialog by lazy { @@ -83,20 +85,20 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.contacts_activity) + setContentView(binding.root) showBackButton(true) viewModel.bindView(this) - contacts.adapter = contactsAdapter + binding.contacts.adapter = contactsAdapter // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - search.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) + binding.search.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) } } override fun render(state: ContactsState) { - cancel.isVisible = state.query.length > 1 + binding.cancel.isVisible = state.query.length > 1 contactsAdapter.data = state.composeItems @@ -110,17 +112,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(ChipsKey, 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 1b6e50378..949bfa925 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 @@ -22,21 +22,22 @@ import android.os.Bundle import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.ContainerActivityBinding import dagger.android.AndroidInjection -import kotlinx.android.synthetic.main.container_activity.* class ConversationInfoActivity : QkThemedActivity() { + private val binding by viewBinding(ContainerActivityBinding::inflate) private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.container_activity) + 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 f75034171..04ec5798e 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 @@ -1,9 +1,9 @@ package com.moez.QKSMS.feature.conversationinfo import android.content.Context -import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.viewbinding.ViewBinding import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter @@ -11,20 +11,20 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.databinding.ConversationInfoSettingsBinding +import com.moez.QKSMS.databinding.ConversationMediaListItemBinding +import com.moez.QKSMS.databinding.ConversationRecipientListItemBinding import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.feature.conversationinfo.ConversationInfoItem.* import com.moez.QKSMS.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( private val context: Context, private val colors: Colors -) : QkAdapter() { +) : QkAdapter() { val recipientClicks: Subject = PublishSubject.create() val recipientLongClicks: Subject = PublishSubject.create() @@ -36,90 +36,97 @@ class ConversationInfoAdapter @Inject constructor( val deleteClicks: Subject = PublishSubject.create() val mediaClicks: Subject = PublishSubject.create() - 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) - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val holder: QkViewHolder = when (viewType) { + 0 -> QkViewHolder(parent, ConversationRecipientListItemBinding::inflate) + 1 -> QkViewHolder(parent, ConversationInfoSettingsBinding::inflate) + 2 -> QkViewHolder(parent, ConversationMediaListItemBinding::inflate) + else -> throw IllegalStateException() + } - itemView.setOnLongClickListener { - val item = getItem(adapterPosition) as? ConversationInfoRecipient - item?.value?.id?.run(recipientLongClicks::onNext) - true + return holder.apply { + when (binding) { + is ConversationRecipientListItemBinding -> { + 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 + } + + binding.theme.setOnClickListener { + val item = getItem(adapterPosition) as? ConversationInfoRecipient + item?.value?.id?.run(themeClicks::onNext) + } } - theme.setOnClickListener { - val item = getItem(adapterPosition) as? ConversationInfoRecipient - item?.value?.id?.run(themeClicks::onNext) + is ConversationInfoSettingsBinding -> { + binding.groupName.clicks().subscribe(nameClicks) + binding.notifications.clicks().subscribe(notificationClicks) + binding.archive.clicks().subscribe(archiveClicks) + binding.block.clicks().subscribe(blockClicks) + binding.delete.clicks().subscribe(deleteClicks) } - } - 1 -> QkViewHolder(inflater.inflate(R.layout.conversation_info_settings, parent, false)).apply { - groupName.clicks().subscribe(nameClicks) - notifications.clicks().subscribe(notificationClicks) - archive.clicks().subscribe(archiveClicks) - block.clicks().subscribe(blockClicks) - 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) + is ConversationMediaListItemBinding -> { + itemView.setOnClickListener { + val item = getItem(adapterPosition) as? ConversationInfoMedia + item?.value?.id?.run(mediaClicks::onNext) + } } } - - else -> throw IllegalStateException() } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - when (val item = getItem(position)) { - is ConversationInfoRecipient -> { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val item = getItem(position) + when { + item is ConversationInfoRecipient && holder.binding is ConversationRecipientListItemBinding -> { val recipient = item.value - holder.avatar.setRecipient(recipient) + holder.binding.avatar.setRecipient(recipient) - holder.name.text = recipient.contact?.name ?: recipient.address + holder.binding.name.text = recipient.contact?.name ?: recipient.address - holder.address.text = recipient.address - holder.address.setVisible(recipient.contact != null) + holder.binding.address.text = recipient.address + holder.binding.address.setVisible(recipient.contact != null) - holder.add.setVisible(recipient.contact == null) + holder.binding.add.setVisible(recipient.contact == null) val theme = colors.theme(recipient) - holder.theme.setTint(theme.theme) + holder.binding.theme.setTint(theme.theme) } - is ConversationInfoSettings -> { - holder.groupName.isVisible = item.recipients.size > 1 - holder.groupName.summary = item.name + item is ConversationInfoSettings && holder.binding is ConversationInfoSettingsBinding -> { + holder.binding.groupName.isVisible = item.recipients.size > 1 + holder.binding.groupName.summary = item.name - holder.notifications.isEnabled = !item.blocked + holder.binding.notifications.isEnabled = !item.blocked - holder.archive.isEnabled = !item.blocked - holder.archive.title = context.getString(when (item.archived) { + holder.binding.archive.isEnabled = !item.blocked + holder.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) { + holder.binding.block.title = context.getString(when (item.blocked) { true -> R.string.info_unblock false -> R.string.info_block }) } - is ConversationInfoMedia -> { + item is ConversationInfoMedia && holder.binding is ConversationMediaListItemBinding -> { val part = item.value GlideApp.with(context) .load(part.getUri()) .fitCenter() - .into(holder.thumbnail) + .into(holder.binding.thumbnail) - holder.video.isVisible = part.isVideo() + holder.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 95a1f241b..293e9b757 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 @@ -28,6 +28,7 @@ import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.extensions.scrapViews import com.moez.QKSMS.common.widget.FieldDialog +import com.moez.QKSMS.databinding.ConversationInfoControllerBinding import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoModule import com.moez.QKSMS.feature.themepicker.ThemePickerController @@ -37,12 +38,12 @@ import com.uber.autodispose.autoDisposable 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(ConversationInfoControllerBinding::inflate), ConversationInfoView { @Inject override lateinit var presenter: ConversationInfoPresenter @Inject lateinit var blockingDialog: BlockingDialog @@ -62,14 +63,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 +76,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 8f1c00410..c2c9e7be6 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 @@ -31,10 +31,9 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.databinding.ConversationListItemBinding import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.util.PhoneNumberUtils -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( @@ -43,64 +42,62 @@ class ConversationsAdapter @Inject constructor( private val dateFormatter: DateFormatter, private val navigator: Navigator, private val phoneNumberUtils: PhoneNumberUtils -) : QkRealmAdapter() { +) : QkRealmAdapter() { init { // This is how we access the threadId for the swipe actions setHasStableIds(true) } - 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) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, ConversationListItemBinding::inflate).apply { + if (viewType == 1) { + val textColorPrimary = parent.context.resolveThemeColor(android.R.attr.textColorPrimary) - if (viewType == 1) { - val textColorPrimary = parent.context.resolveThemeColor(android.R.attr.textColorPrimary) + binding.title.setTypeface(binding.title.typeface, Typeface.BOLD) - view.title.setTypeface(view.title.typeface, Typeface.BOLD) + binding.snippet.setTypeface(binding.snippet.typeface, Typeface.BOLD) + binding.snippet.setTextColor(textColorPrimary) + binding.snippet.maxLines = 5 - view.snippet.setTypeface(view.snippet.typeface, Typeface.BOLD) - view.snippet.setTextColor(textColorPrimary) - view.snippet.maxLines = 5 + binding.unread.isVisible = true - view.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 { + 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 } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return - holder.containerView.isActivated = isSelected(conversation.id) + holder.binding.root.isActivated = isSelected(conversation.id) - holder.avatars.recipients = conversation.recipients - holder.title.collapseEnabled = conversation.recipients.size > 1 - holder.title.text = conversation.getTitle() - holder.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) - holder.snippet.text = when { + holder.binding.avatars.recipients = conversation.recipients + holder.binding.title.collapseEnabled = conversation.recipients.size > 1 + holder.binding.title.text = conversation.getTitle() + holder.binding.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) + holder.binding.snippet.text = when { conversation.draft.isNotEmpty() -> context.getString(R.string.main_draft, conversation.draft) conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) else -> conversation.snippet } - holder.pinned.isVisible = conversation.pinned + holder.binding.pinned.isVisible = conversation.pinned // If the last message wasn't incoming, then the colour doesn't really matter anyway val lastMessage = conversation.lastMessage @@ -111,7 +108,7 @@ class ConversationsAdapter @Inject constructor( } } - holder.unread.setTint(colors.theme(recipient).theme) + holder.binding.unread.setTint(colors.theme(recipient).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 f2c261b68..fe8e0fa2b 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 @@ -33,12 +33,13 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkActivity import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.GalleryActivityBinding import com.moez.QKSMS.model.MmsPart import dagger.android.AndroidInjection 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 { @@ -49,20 +50,21 @@ class GalleryActivity : QkActivity(), GalleryView { val partId by lazy { intent.getLongExtra("partId", 0L) } + private val binding by viewBinding(GalleryActivityBinding::inflate) + private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[GalleryViewModel::class.java] } private val optionsItemSubject: Subject = PublishSubject.create() private val pageChangedSubject: Subject = PublishSubject.create() - private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[GalleryViewModel::class.java] } override fun onCreate(savedInstanceState: Bundle?) { delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.gallery_activity) + 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 +76,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 +84,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) + binding.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 f3a4405bc..96050060f 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 @@ -19,9 +19,9 @@ package com.moez.QKSMS.feature.gallery import android.content.Context -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.viewbinding.ViewBinding import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ExoPlayerFactory import com.google.android.exoplayer2.source.ExtractorMediaSource @@ -30,22 +30,23 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.util.Util import com.google.android.mms.ContentType -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.databinding.GalleryImagePageBinding +import com.moez.QKSMS.databinding.GalleryInvalidPageBinding +import com.moez.QKSMS.databinding.GalleryVideoPageBinding import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.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 -class GalleryPagerAdapter @Inject constructor(private val context: Context) : QkRealmAdapter() { +class GalleryPagerAdapter @Inject constructor( + private val context: Context +) : QkRealmAdapter() { companion object { private const val VIEW_TYPE_INVALID = 0 @@ -58,60 +59,61 @@ class GalleryPagerAdapter @Inject constructor(private val context: Context) : Qk private val contentResolver = context.contentResolver private val exoPlayers = Collections.newSetFromMap(WeakHashMap()) - 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 { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val holder: QkViewHolder = when (viewType) { + VIEW_TYPE_IMAGE -> QkViewHolder(parent, GalleryImagePageBinding::inflate) + VIEW_TYPE_VIDEO -> QkViewHolder(parent, GalleryVideoPageBinding::inflate) + else -> QkViewHolder(parent, GalleryInvalidPageBinding::inflate) + } + return holder.apply { + if (binding is GalleryImagePageBinding) { // 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) } } } - VIEW_TYPE_VIDEO -> inflater.inflate(R.layout.gallery_video_page, parent, false) - - else -> inflater.inflate(R.layout.gallery_invalid_page, parent, false) - - }.apply { setOnClickListener(clicks::onNext) }) + binding.root.setOnClickListener(clicks::onNext) + } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val part = getItem(position) ?: return - when (getItemViewType(position)) { - VIEW_TYPE_IMAGE -> { + when { + getItemViewType(position) == VIEW_TYPE_IMAGE && holder.binding is GalleryImagePageBinding -> { // 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(holder.binding.image) else -> GlideApp.with(context) .asBitmap() .load(part.getUri()) - .into(holder.image) + .into(holder.binding.image) } } - VIEW_TYPE_VIDEO -> { + getItemViewType(position) == VIEW_TYPE_VIDEO && holder.binding is GalleryVideoPageBinding -> { val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(null) val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory) val exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector) - holder.video.player = exoPlayer + holder.binding.video.player = exoPlayer exoPlayers.add(exoPlayer) val dataSourceFactory = DefaultDataSourceFactory(context, Util.getUserAgent(context, "QKSMS")) 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 ca2106afc..ea68d8f43 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 @@ -29,12 +29,10 @@ import android.view.Gravity import android.view.Menu import android.view.MenuItem import android.view.View -import android.view.ViewStub import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.app.ActivityCompat import androidx.core.view.GravityCompat import androidx.core.view.isVisible -import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.ItemTouchHelper @@ -52,6 +50,8 @@ import com.moez.QKSMS.common.util.extensions.scrapViews import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.MainActivityBinding import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.feature.changelog.ChangelogDialog import com.moez.QKSMS.feature.conversations.ConversationItemTouchCallback @@ -65,10 +65,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 { @@ -84,10 +80,10 @@ class MainActivity : QkThemedActivity(), MainView { 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 drawerOpenIntent: Observable by lazy { - drawerLayout + binding.drawerLayout .drawerOpen(Gravity.START) .doOnNext { dismissKeyboard() } } @@ -95,31 +91,32 @@ class MainActivity : QkThemedActivity(), MainView { 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 }, - settings.clicks().map { NavItem.SETTINGS }, - plus.clicks().map { NavItem.PLUS }, - help.clicks().map { NavItem.HELP }, - invite.clicks().map { NavItem.INVITE })) + 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.settings.clicks().map { NavItem.SETTINGS }, + binding.drawer.plus.clicks().map { NavItem.PLUS }, + binding.drawer.help.clicks().map { NavItem.HELP }, + 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 plusBannerIntent by lazy { binding.drawer.plusBanner.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 swipeConversationIntent by lazy { itemTouchCallback.swipes } override val changelogMoreIntent by lazy { changelogDialog.moreClicks } override val undoArchiveIntent: Subject = PublishSubject.create() - override val snackbarButtonIntent: Subject = PublishSubject.create() + override val snackbarButtonIntent by lazy { binding.snackbar.button.clicks() } + private val binding by viewBinding(MainActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[MainViewModel::class.java] } - private val toggle by lazy { ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.main_drawer_open_cd, 0) } + private val toggle by lazy { ActionBarDrawerToggle(this, 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) } + private val progressAnimator by lazy { ObjectAnimator.ofInt(binding.syncing.progress, "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) } @@ -128,32 +125,21 @@ class MainActivity : QkThemedActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.main_activity) + setContentView(binding.root) viewModel.bindView(this) onNewIntentIntent.onNext(intent) - (snackbar as? ViewStub)?.setOnInflateListener { _, _ -> - snackbarButton.clicks() - .autoDisposable(scope(Lifecycle.Event.ON_DESTROY)) - .subscribe(snackbarButtonIntent) - } - - (syncing as? ViewStub)?.setOnInflateListener { _, _ -> - syncingProgress?.progressTintList = ColorStateList.valueOf(theme.blockingFirst().theme) - 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 @@ -167,28 +153,28 @@ class MainActivity : QkThemedActivity(), MainView { resolveThemeColor(android.R.attr.textColorSecondary) .let { textSecondary -> ColorStateList(states, intArrayOf(theme.theme, textSecondary)) } .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) + binding.syncing.progress.progressTintList = ColorStateList.valueOf(theme.theme) + binding.syncing.progress.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) } // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - toolbarSearch.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) + binding.toolbarSearch.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) } } @@ -227,45 +213,45 @@ class MainActivity : QkThemedActivity(), MainView { else -> 0 } - toolbarSearch.setVisible(state.page is Inbox && state.page.selected == 0 || state.page is Searching) - toolbarTitle.setVisible(toolbarSearch.visibility != View.VISIBLE) + binding.toolbarSearch.setVisible(state.page is Inbox && state.page.selected == 0 || state.page is Searching) + binding.toolbarTitle.setVisible(binding.toolbarSearch.visibility != View.VISIBLE) - toolbar.menu.findItem(R.id.archive)?.isVisible = state.page is Inbox && selectedConversations != 0 - toolbar.menu.findItem(R.id.unarchive)?.isVisible = state.page is Archived && selectedConversations != 0 - toolbar.menu.findItem(R.id.delete)?.isVisible = selectedConversations != 0 - toolbar.menu.findItem(R.id.add)?.isVisible = addContact && selectedConversations != 0 - toolbar.menu.findItem(R.id.pin)?.isVisible = markPinned && selectedConversations != 0 - toolbar.menu.findItem(R.id.unpin)?.isVisible = !markPinned && selectedConversations != 0 - toolbar.menu.findItem(R.id.read)?.isVisible = markRead && selectedConversations != 0 - toolbar.menu.findItem(R.id.unread)?.isVisible = !markRead && selectedConversations != 0 - toolbar.menu.findItem(R.id.block)?.isVisible = selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.archive)?.isVisible = state.page is Inbox && selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.unarchive)?.isVisible = state.page is Archived && selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.delete)?.isVisible = selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.add)?.isVisible = addContact && selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.pin)?.isVisible = markPinned && selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.unpin)?.isVisible = !markPinned && selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.read)?.isVisible = markRead && selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.unread)?.isVisible = !markRead && selectedConversations != 0 + binding.toolbar.menu.findItem(R.id.block)?.isVisible = selectedConversations != 0 - 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.plus.isVisible = state.upgraded + 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 { state.page is Inbox || state.page is Archived } + binding.compose.setVisible(state.page is Inbox || state.page is Archived) + conversationsAdapter.emptyView = binding.empty.takeIf { state.page is Inbox || state.page is Archived } 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 -> { @@ -274,20 +260,20 @@ 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) } } - 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) { @@ -298,30 +284,30 @@ class MainActivity : QkThemedActivity(), MainView { is SyncRepository.SyncProgress.Running -> { syncing.isVisible = true - syncingProgress.max = state.syncing.max - progressAnimator.apply { setIntValues(syncingProgress.progress, state.syncing.progress) }.start() - syncingProgress.isIndeterminate = state.syncing.indeterminate + binding.syncing.progress.max = state.syncing.max + progressAnimator.apply { setIntValues(binding.syncing.progress.progress, state.syncing.progress) }.start() + binding.syncing.progress.isIndeterminate = state.syncing.indeterminate snackbar.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) + binding.snackbar.title.setText(R.string.main_default_sms_title) + binding.snackbar.message.setText(R.string.main_default_sms_message) + binding.snackbar.button.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) + binding.snackbar.title.setText(R.string.main_permission_required) + binding.snackbar.message.setText(R.string.main_permission_sms) + binding.snackbar.button.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) + binding.snackbar.title.setText(R.string.main_permission_required) + binding.snackbar.message.setText(R.string.main_permission_contacts) + binding.snackbar.button.setText(R.string.main_permission_allow) } } } @@ -342,7 +328,7 @@ class MainActivity : QkThemedActivity(), MainView { } override fun showBackButton(show: Boolean) { - toggle.onDrawerSlide(drawer, if (show) 1f else 0f) + toggle.onDrawerSlide(binding.drawer.root, if (show) 1f else 0f) toggle.drawerArrowDrawable.color = when (show) { true -> resolveThemeColor(android.R.attr.textColorSecondary) false -> resolveThemeColor(android.R.attr.textColorPrimary) @@ -362,7 +348,7 @@ class MainActivity : QkThemedActivity(), MainView { override fun clearSearch() { dismissKeyboard() - toolbarSearch.text = null + binding.toolbarSearch.text = null } override fun clearSelection() { @@ -370,7 +356,7 @@ class MainActivity : QkThemedActivity(), MainView { } override fun themeChanged() { - recyclerView.scrapViews() + binding.recyclerView.scrapViews() } override fun showBlockingDialog(conversations: List, block: Boolean) { @@ -392,7 +378,7 @@ class MainActivity : QkThemedActivity(), MainView { } override fun showArchivedSnackbar() { - Snackbar.make(drawerLayout, R.string.toast_archived, Snackbar.LENGTH_LONG).apply { + Snackbar.make(binding.drawerLayout, R.string.toast_archived, Snackbar.LENGTH_LONG).apply { setAction(R.string.button_undo) { undoArchiveIntent.onNext(Unit) } setActionTextColor(colors.theme().theme) show() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt index 9a964a208..e59dde89e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt @@ -31,10 +31,9 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.databinding.SearchListItemBinding import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.model.SearchResult -import kotlinx.android.synthetic.main.search_list_item.* -import kotlinx.android.synthetic.main.search_list_item.view.* import javax.inject.Inject class SearchAdapter @Inject constructor( @@ -42,26 +41,24 @@ class SearchAdapter @Inject constructor( private val context: Context, private val dateFormatter: DateFormatter, private val navigator: Navigator -) : QkAdapter() { +) : QkAdapter() { private val highlightColor: Int by lazy { colors.theme().highlight } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val layoutInflater = LayoutInflater.from(parent.context) - val view = layoutInflater.inflate(R.layout.search_list_item, parent, false) - return QkViewHolder(view).apply { - view.setOnClickListener { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, SearchListItemBinding::inflate).apply { + binding.root.setOnClickListener { val result = getItem(adapterPosition) navigator.showConversation(result.conversation.id, result.query.takeIf { result.messages > 0 }) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val previous = data.getOrNull(position - 1) val result = getItem(position) - holder.resultsHeader.setVisible(result.messages > 0 && previous?.messages == 0) + holder.binding.resultsHeader.setVisible(result.messages > 0 && previous?.messages == 0) val query = result.query val title = SpannableString(result.conversation.getTitle()) @@ -71,23 +68,23 @@ class SearchAdapter @Inject constructor( title.setSpan(BackgroundColorSpan(highlightColor), index, index + query.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) index = title.indexOf(query, index + query.length, true) } - holder.title.text = title + holder.binding.title.text = title - holder.avatars.recipients = result.conversation.recipients + holder.binding.avatars.recipients = result.conversation.recipients when (result.messages == 0) { true -> { - holder.date.setVisible(true) - holder.date.text = dateFormatter.getConversationTimestamp(result.conversation.date) - holder.snippet.text = when (result.conversation.me) { + holder.binding.date.setVisible(true) + holder.binding.date.text = dateFormatter.getConversationTimestamp(result.conversation.date) + holder.binding.snippet.text = when (result.conversation.me) { true -> context.getString(R.string.main_sender_you, result.conversation.snippet) false -> result.conversation.snippet } } false -> { - holder.date.setVisible(false) - holder.snippet.text = context.getString(R.string.main_message_results, result.messages) + holder.binding.date.setVisible(false) + holder.binding.snippet.text = context.getString(R.string.main_message_results, result.messages) } } } 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 210b5d3fe..570ca299d 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 @@ -33,15 +33,16 @@ import com.moez.QKSMS.common.QkDialog import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.util.extensions.viewBinding import com.moez.QKSMS.common.widget.PreferenceView +import com.moez.QKSMS.common.widget.QkSwitch +import com.moez.QKSMS.databinding.NotificationPrefsActivityBinding import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable import dagger.android.AndroidInjection import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.notification_prefs_activity.* -import kotlinx.android.synthetic.main.settings_switch_widget.view.* import javax.inject.Inject class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { @@ -55,6 +56,7 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { override val ringtoneSelectedIntent: Subject = PublishSubject.create() override val actionsSelectedIntent by lazy { actionsDialog.adapter.menuItemClicks } + private val binding by viewBinding(NotificationPrefsActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[NotificationPrefsViewModel::class.java] } @@ -62,27 +64,27 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.notification_prefs_activity) + setContentView(binding.root) setTitle(R.string.title_notification_prefs) showBackButton(true) viewModel.bindView(this) - preferences.postDelayed({ preferences?.animateLayoutChanges = true }, 100) + binding.preferences.postDelayed({ binding.preferences.animateLayoutChanges = true }, 100) val hasOreo = Build.VERSION.SDK_INT >= 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,29 +97,29 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { title = state.conversationTitle } - notifications.checkbox.isChecked = state.notificationsEnabled - previews.summary = state.previewSummary + binding.notifications.widget().isChecked = state.notificationsEnabled + binding.previews.summary = state.previewSummary previewModeDialog.adapter.selectedItem = state.previewId - wake.checkbox.isChecked = state.wakeEnabled - vibration.checkbox.isChecked = state.vibrationEnabled - 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 - - 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.wake.widget().isChecked = state.wakeEnabled + binding.vibration.widget().isChecked = state.vibrationEnabled + binding.ringtone.summary = state.ringtoneName + + 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 + + binding.qkreplyDivider.isVisible = state.threadId == 0L + binding.qkreplyTitle.isVisible = state.threadId == 0L + binding.qkreply.widget().isChecked = state.qkReplyEnabled + binding.qkreply.isVisible = state.threadId == 0L + binding.qkreplyTapDismiss.isVisible = state.threadId == 0L + binding.qkreplyTapDismiss.isEnabled = state.qkReplyEnabled + binding.qkreplyTapDismiss.widget().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 1df78e045..eaa01042e 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 @@ -24,7 +24,6 @@ import androidx.core.view.children import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import com.jakewharton.rxbinding2.view.clicks -import com.jakewharton.rxbinding2.view.enabled import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity @@ -34,13 +33,11 @@ import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.util.extensions.viewBinding import com.moez.QKSMS.common.widget.PreferenceView +import com.moez.QKSMS.databinding.QksmsPlusActivityBinding import com.moez.QKSMS.feature.plus.experiment.UpgradeButtonExperiment import dagger.android.AndroidInjection -import io.reactivex.Observable -import kotlinx.android.synthetic.main.collapsing_toolbar.* -import kotlinx.android.synthetic.main.preference_view.view.* -import kotlinx.android.synthetic.main.qksms_plus_activity.* import javax.inject.Inject class PlusActivity : QkThemedActivity(), PlusView { @@ -49,67 +46,68 @@ class PlusActivity : QkThemedActivity(), PlusView { @Inject lateinit var upgradeButtonExperiment: UpgradeButtonExperiment @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + private val binding by viewBinding(QksmsPlusActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[PlusViewModel::class.java] } - override val upgradeIntent by lazy { upgrade.clicks() } - override val upgradeDonateIntent by lazy { upgradeDonate.clicks() } - override val donateIntent by lazy { donate.clicks() } - override val themeClicks by lazy { themes.clicks() } - override val scheduleClicks by lazy { schedule.clicks() } - override val backupClicks by lazy { backup.clicks() } - override val delayedClicks by lazy { delayed.clicks() } - override val nightClicks by lazy { night.clicks() } + override val upgradeIntent by lazy { binding.upgrade.clicks() } + override val upgradeDonateIntent by lazy { binding.upgradeDonate.clicks() } + override val donateIntent by lazy { binding.donate.clicks() } + override val themeClicks by lazy { binding.themes.clicks() } + override val scheduleClicks by lazy { binding.schedule.clicks() } + override val backupClicks by lazy { binding.backup.clicks() } + override val delayedClicks by lazy { binding.delayed.clicks() } + override val nightClicks by lazy { binding.night.clicks() } override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.qksms_plus_activity) + setContentView(binding.root) setTitle(R.string.title_qksms_plus) showBackButton(true) viewModel.bindView(this) - free.setVisible(false) + binding.free.setVisible(false) if (!prefs.systemFont.get()) { fontProvider.getLato { lato -> val typeface = Typeface.create(lato, Typeface.BOLD) - collapsingToolbar.setCollapsedTitleTypeface(typeface) - collapsingToolbar.setExpandedTitleTypeface(typeface) + binding.appBarLayout.collapsingToolbar.setCollapsedTitleTypeface(typeface) + binding.appBarLayout.collapsingToolbar.setExpandedTitleTypeface(typeface) } } // Make the list titles bold - linearLayout.children - .mapNotNull { it as? PreferenceView } - .map { it.titleView } + binding.linearLayout.children + .mapNotNull { view -> view as? PreferenceView } + .map { preferenceView -> preferenceView.binding.titleView } .forEach { it.setTypeface(it.typeface, Typeface.BOLD) } val textPrimary = resolveThemeColor(android.R.attr.textColorPrimary) - collapsingToolbar.setCollapsedTitleTextColor(textPrimary) - collapsingToolbar.setExpandedTitleColor(textPrimary) + binding.appBarLayout.collapsingToolbar.setCollapsedTitleTextColor(textPrimary) + binding.appBarLayout.collapsingToolbar.setExpandedTitleColor(textPrimary) val theme = colors.theme().theme - donate.setBackgroundTint(theme) - upgrade.setBackgroundTint(theme) - thanksIcon.setTint(theme) + binding.donate.setBackgroundTint(theme) + binding.upgrade.setBackgroundTint(theme) + binding.thanksIcon.setTint(theme) } override fun render(state: PlusState) { - description.text = getString(R.string.qksms_plus_description_summary, state.upgradePrice) - upgrade.text = getString(upgradeButtonExperiment.variant, state.upgradePrice, state.currency) - upgradeDonate.text = getString(R.string.qksms_plus_upgrade_donate, state.upgradeDonatePrice, state.currency) + binding.description.text = getString(R.string.qksms_plus_description_summary, state.upgradePrice) + binding.upgrade.text = getString(upgradeButtonExperiment.variant, state.upgradePrice, state.currency) + binding.upgradeDonate.text = getString(R.string.qksms_plus_upgrade_donate, state.upgradeDonatePrice, state.currency) val fdroid = BuildConfig.FLAVOR == "noAnalytics" - free.setVisible(fdroid) - toUpgrade.setVisible(!fdroid && !state.upgraded) - upgraded.setVisible(!fdroid && state.upgraded) + binding.free.setVisible(fdroid) + binding.toUpgrade.setVisible(!fdroid && !state.upgraded) + binding.upgraded.setVisible(!fdroid && state.upgraded) - themes.isEnabled = state.upgraded - schedule.isEnabled = state.upgraded - backup.isEnabled = state.upgraded - delayed.isEnabled = state.upgraded - night.isEnabled = state.upgraded + binding.themes.isEnabled = state.upgraded + binding.schedule.isEnabled = state.upgraded + binding.backup.isEnabled = state.upgraded + binding.delayed.isEnabled = state.upgraded + binding.night.isEnabled = state.upgraded } override fun initiatePurchaseFlow(billingManager: BillingManager, sku: String) { 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 392196d41..c7237397c 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 @@ -36,11 +36,12 @@ import com.moez.QKSMS.common.util.extensions.autoScrollToStart import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.QkreplyActivityBinding import com.moez.QKSMS.feature.compose.MessagesAdapter import dagger.android.AndroidInjection import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.qkreply_activity.* import javax.inject.Inject class QkReplyActivity : QkThemedActivity(), QkReplyView { @@ -49,10 +50,11 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { @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 binding by viewBinding(QkreplyActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[QkReplyViewModel::class.java] } override fun onCreate(savedInstanceState: Bundle?) { @@ -61,27 +63,27 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { super.onCreate(savedInstanceState) setFinishOnTouchOutside(prefs.qkreplyTapDismiss.get()) - setContentView(R.layout.qkreply_activity) + 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) }) // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - toolbar.setBackgroundTint(resolveThemeColor(R.attr.colorPrimary)) - background.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) - messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) - composeBackgroundGradient.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) - composeBackgroundSolid.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) + binding.toolbar.setBackgroundTint(resolveThemeColor(R.attr.colorPrimary)) + binding.background.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) + binding.messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) + binding.composeBackgroundGradient.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) + binding.composeBackgroundSolid.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) } } @@ -94,24 +96,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 9bcc3af5f..669d8f9c5 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 @@ -30,9 +30,9 @@ import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.FontProvider import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.ScheduledActivityBinding import dagger.android.AndroidInjection -import kotlinx.android.synthetic.main.collapsing_toolbar.* -import kotlinx.android.synthetic.main.scheduled_activity.* import javax.inject.Inject @@ -45,15 +45,16 @@ class ScheduledActivity : QkThemedActivity(), ScheduledView { override val messageClickIntent by lazy { messageAdapter.clicks } override val messageMenuIntent by lazy { dialog.adapter.menuItemClicks } - 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() } + private val binding by viewBinding(ScheduledActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ScheduledViewModel::class.java] } override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.scheduled_activity) + setContentView(binding.root) setTitle(R.string.scheduled_title) showBackButton(true) viewModel.bindView(this) @@ -61,33 +62,33 @@ class ScheduledActivity : QkThemedActivity(), ScheduledView { if (!prefs.systemFont.get()) { fontProvider.getLato { lato -> val typeface = Typeface.create(lato, Typeface.BOLD) - collapsingToolbar.setCollapsedTitleTypeface(typeface) - collapsingToolbar.setExpandedTitleTypeface(typeface) + binding.appBarLayout.collapsingToolbar.setCollapsedTitleTypeface(typeface) + binding.appBarLayout.collapsingToolbar.setExpandedTitleTypeface(typeface) } } dialog.title = getString(R.string.scheduled_options_title) dialog.adapter.setData(R.array.scheduled_options) - messageAdapter.emptyView = empty - messages.adapter = messageAdapter + messageAdapter.emptyView = binding.empty + binding.messages.adapter = messageAdapter 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) } } override fun render(state: ScheduledState) { messageAdapter.updateData(state.scheduledMessages) - compose.isVisible = state.upgraded - upgrade.isVisible = !state.upgraded + binding.compose.isVisible = state.upgraded + binding.upgrade.isVisible = !state.upgraded } override fun showMessageOptions() { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt index 23c09227c..2a66db1bb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt @@ -20,14 +20,13 @@ package com.moez.QKSMS.feature.scheduled import android.content.Context import android.net.Uri -import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.DateFormatter +import com.moez.QKSMS.databinding.ScheduledMessageListItemBinding import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.ScheduledMessage @@ -35,8 +34,6 @@ import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.util.PhoneNumberUtils import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.scheduled_message_list_item.* -import kotlinx.android.synthetic.main.scheduled_message_list_item.view.* import javax.inject.Inject class ScheduledMessageAdapter @Inject constructor( @@ -44,7 +41,7 @@ class ScheduledMessageAdapter @Inject constructor( private val contactRepo: ContactRepository, private val dateFormatter: DateFormatter, private val phoneNumberUtils: PhoneNumberUtils -) : QkRealmAdapter() { +) : QkRealmAdapter() { private val contacts by lazy { contactRepo.getContacts() } private val contactCache = ContactCache() @@ -52,36 +49,34 @@ class ScheduledMessageAdapter @Inject constructor( val clicks: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.scheduled_message_list_item, parent, false) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, ScheduledMessageListItemBinding::inflate).apply { + binding.attachments.adapter = ScheduledMessageAttachmentAdapter(context) + binding.attachments.setRecycledViewPool(imagesViewPool) - view.attachments.adapter = ScheduledMessageAttachmentAdapter(context) - view.attachments.setRecycledViewPool(imagesViewPool) - - return QkViewHolder(view).apply { - view.setOnClickListener { + binding.root.setOnClickListener { val message = getItem(adapterPosition) ?: return@setOnClickListener clicks.onNext(message.id) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val message = getItem(position) ?: return // GroupAvatarView only accepts recipients, so map the phone numbers to recipients - holder.avatars.recipients = message.recipients.map { address -> Recipient(address = address) } + holder.binding.avatars.recipients = message.recipients.map { address -> Recipient(address = address) } - holder.recipients.text = message.recipients.joinToString(",") { address -> + holder.binding.recipients.text = message.recipients.joinToString(",") { address -> contactCache[address]?.name?.takeIf { it.isNotBlank() } ?: address } - holder.date.text = dateFormatter.getScheduledTimestamp(message.date) - holder.body.text = message.body + holder.binding.date.text = dateFormatter.getScheduledTimestamp(message.date) + holder.binding.body.text = message.body - val adapter = holder.attachments.adapter as ScheduledMessageAttachmentAdapter + val adapter = holder.binding.attachments.adapter as ScheduledMessageAttachmentAdapter adapter.data = message.attachments.map(Uri::parse) - holder.attachments.isVisible = message.attachments.isNotEmpty() + holder.binding.attachments.isVisible = message.attachments.isNotEmpty() } /** diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt index 558c8af7a..80b7b9a4b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt @@ -20,31 +20,27 @@ package com.moez.QKSMS.feature.scheduled import android.content.Context import android.net.Uri -import android.view.LayoutInflater import android.view.ViewGroup -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.databinding.ScheduledMessageImageListItemBinding import com.moez.QKSMS.util.GlideApp -import kotlinx.android.synthetic.main.attachment_image_list_item.view.* -import kotlinx.android.synthetic.main.scheduled_message_image_list_item.* import javax.inject.Inject class ScheduledMessageAttachmentAdapter @Inject constructor( private val context: Context -) : QkAdapter() { +) : QkAdapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.scheduled_message_image_list_item, parent, false) - view.thumbnail.clipToOutline = true - - return QkViewHolder(view) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, ScheduledMessageImageListItemBinding::inflate).apply { + binding.thumbnail.clipToOutline = true + } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val attachment = getItem(position) - GlideApp.with(context).load(attachment).into(holder.thumbnail) + GlideApp.with(context).load(attachment).into(holder.binding.thumbnail) } } 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 0d2bd59ee..95276b1b5 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 @@ -22,21 +22,22 @@ import android.os.Bundle import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction -import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity +import com.moez.QKSMS.common.util.extensions.viewBinding +import com.moez.QKSMS.databinding.ContainerActivityBinding import dagger.android.AndroidInjection -import kotlinx.android.synthetic.main.container_activity.* class SettingsActivity : QkThemedActivity() { + private val binding by viewBinding(ContainerActivityBinding::inflate) private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(R.layout.container_activity) + 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 d035766ea..f2d652a75 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 @@ -41,6 +41,8 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.FieldDialog import com.moez.QKSMS.common.widget.PreferenceView +import com.moez.QKSMS.common.widget.QkSwitch +import com.moez.QKSMS.databinding.SettingsControllerBinding import com.moez.QKSMS.feature.settings.about.AboutController import com.moez.QKSMS.feature.settings.swipe.SwipeActionsController import com.moez.QKSMS.feature.themepicker.ThemePickerController @@ -52,13 +54,11 @@ import com.uber.autodispose.autoDisposable 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 javax.inject.Inject -class SettingsController : QkController(), SettingsView { +class SettingsController : QkController( + SettingsControllerBinding::inflate +), SettingsView { @Inject lateinit var context: Context @Inject lateinit var colors: Colors @@ -78,12 +78,11 @@ class SettingsController : QkController> = PublishSubject.create() private val signatureSubject: 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()) @@ -91,7 +90,7 @@ class SettingsController : QkController= 29) { true -> nightModeDialog.adapter.setData(R.array.night_modes) @@ -103,7 +102,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 @@ -138,56 +137,56 @@ class SettingsController : QkController = mmsSizeDialog.adapter.menuItemClicks override fun render(state: SettingsState) { - themePreview.setBackgroundTint(state.theme) - night.summary = state.nightModeSummary + binding.theme.widget().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.widget().isChecked = state.black - autoEmoji.checkbox.isChecked = state.autoEmojiEnabled + binding.autoEmoji.widget().isChecked = state.autoEmojiEnabled - delayed.summary = state.sendDelaySummary + binding.delayed.summary = state.sendDelaySummary sendDelayDialog.adapter.selectedItem = state.sendDelayId - delivery.checkbox.isChecked = state.deliveryEnabled + binding.delivery.widget().isChecked = state.deliveryEnabled - 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.widget().isChecked = state.autoColor - systemFont.checkbox.isChecked = state.systemFontEnabled + binding.systemFont.widget().isChecked = state.systemFontEnabled - unicode.checkbox.isChecked = state.stripUnicodeEnabled - mobileOnly.checkbox.isChecked = state.mobileOnly - longAsMms.checkbox.isChecked = state.longAsMms + binding.unicode.widget().isChecked = state.stripUnicodeEnabled + binding.mobileOnly.widget().isChecked = state.mobileOnly + binding.longAsMms.widget().isChecked = state.longAsMms - mmsSize.summary = state.maxMmsSizeSummary + binding.mmsSize.summary = state.maxMmsSizeSummary mmsSizeDialog.adapter.selectedItem = state.maxMmsSizeId 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 } } } override fun showQksmsPlusSnackbar() { view?.run { - Snackbar.make(contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { + Snackbar.make(binding.root, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusSubject.onNext(Unit) } setActionTextColor(colors.theme().theme) show() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt index 7385a29c9..ec81ea094 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt @@ -24,22 +24,23 @@ import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.widget.PreferenceView +import com.moez.QKSMS.databinding.AboutControllerBinding import com.moez.QKSMS.injection.appComponent import io.reactivex.Observable -import kotlinx.android.synthetic.main.about_controller.* import javax.inject.Inject -class AboutController : QkController(), AboutView { +class AboutController : QkController( + AboutControllerBinding::inflate +), AboutView { @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 +50,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 8121ad443..2d53d7c30 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 @@ -28,16 +28,20 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.databinding.SwipeActionsControllerBinding import com.moez.QKSMS.injection.appComponent +import com.uber.autodispose.android.autoDisposable +import com.uber.autodispose.android.lifecycle.autoDisposable import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable 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( + SwipeActionsControllerBinding::inflate), SwipeActionsView { @Inject override lateinit var presenter: SwipeActionsPresenter @Inject lateinit var actionsDialog: QkDialog @@ -50,25 +54,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 +93,13 @@ class SwipeActionsController : QkController = BehaviorSubject.create() + private val binding = viewBinding(HsvPickerViewBinding::inflate) + private val hues = arrayOf(0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000) .map { it.toInt() }.toIntArray() @@ -52,12 +53,10 @@ class HSVPickerView @JvmOverloads constructor( } init { - View.inflate(context, R.layout.hsv_picker_view, this) - var swatchX = 0f var swatchY = 0f - saturation.setOnTouchListener { _, event -> + binding.saturation.setOnTouchListener { _, event -> setupBounds() when (event.action) { MotionEvent.ACTION_DOWN -> { @@ -68,8 +67,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 +84,7 @@ class HSVPickerView @JvmOverloads constructor( var hueThumbX = 0f - hueGroup.setOnTouchListener { _, event -> + binding.hueGroup.setOnTouchListener { _, event -> setupBounds() when (event.action) { MotionEvent.ACTION_DOWN -> { @@ -96,8 +95,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 +110,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 +125,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 +144,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 +155,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 cf99614e7..3993551b4 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 com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors @@ -33,17 +32,16 @@ import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.databinding.ThemeListItemBinding +import com.moez.QKSMS.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( private val context: Context, private val colors: Colors -) : QkAdapter>() { +) : QkAdapter, ThemePaletteListItemBinding>() { val colorSelected: Subject = PublishSubject.create() @@ -61,15 +59,14 @@ 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 - - return QkViewHolder(view) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + return QkViewHolder(parent, ThemePaletteListItemBinding::inflate).apply { + binding.palette.flexWrap = FlexWrap.WRAP + binding.palette.flexDirection = FlexDirection.ROW + } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val palette = getItem(position) val screenWidth = Resources.getSystem().displayMetrics.widthPixels @@ -81,15 +78,15 @@ class ThemeAdapter @Inject constructor( } val swatchPadding = (screenWidth - size * 5) / 12 - holder.palette.removeAllViews() - holder.palette.setPadding(swatchPadding, swatchPadding, swatchPadding, swatchPadding) + holder.binding.palette.removeAllViews() + holder.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 { + ThemeListItemBinding.inflate(LayoutInflater.from(context), holder.binding.palette, false).apply { // Send clicks to the selected subject - setOnClickListener { colorSelected.onNext(color) } + root.setOnClickListener { colorSelected.onNext(color) } // Apply the color to the view theme.setBackgroundTint(color) @@ -99,15 +96,15 @@ class ThemeAdapter @Inject constructor( check.setTint(iconTint) // Update the size so that the spacing is perfectly even - layoutParams = (layoutParams as FlexboxLayout.LayoutParams).apply { + root.layoutParams = (root.layoutParams as FlexboxLayout.LayoutParams).apply { height = size width = size isWrapBefore = index % 5 == 0 setMargins(swatchPadding, swatchPadding, swatchPadding, swatchPadding) } - } + }.root } - .forEach { theme -> holder.palette.addView(theme) } + .forEach { theme -> holder.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 ae4dc56f2..7c6301406 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 @@ -29,18 +29,19 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.databinding.ThemePickerControllerBinding import com.moez.QKSMS.feature.themepicker.injection.ThemePickerModule import com.moez.QKSMS.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( + ThemePickerControllerBinding::inflate +), ThemePickerView { @Inject override lateinit var presenter: ThemePickerPresenter @@ -56,19 +57,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) { @@ -86,12 +85,13 @@ class ThemePickerController( super.onDetach(view) themedActivity?.supportActionBar?.let { toolbar -> - ObjectAnimator.ofFloat(toolbar, "elevation", toolbar.elevation, 8.dpToPx(toolbar.themedContext).toFloat()).start() + ObjectAnimator.ofFloat(toolbar, "elevation", toolbar.elevation, 8.dpToPx(toolbar.themedContext).toFloat()) + .start() } } 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,26 +100,26 @@ 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 } diff --git a/presentation/src/main/res/layout/collapsing_toolbar.xml b/presentation/src/main/res/layout/collapsing_toolbar.xml index 1994eb710..5f2ad571f 100644 --- a/presentation/src/main/res/layout/collapsing_toolbar.xml +++ b/presentation/src/main/res/layout/collapsing_toolbar.xml @@ -20,7 +20,6 @@ diff --git a/presentation/src/main/res/layout/contact_chip_detailed.xml b/presentation/src/main/res/layout/contact_chip_detailed.xml index 786e160fd..64ab9a592 100755 --- a/presentation/src/main/res/layout/contact_chip_detailed.xml +++ b/presentation/src/main/res/layout/contact_chip_detailed.xml @@ -17,25 +17,21 @@ ~ You should have received a copy of the GNU General Public License ~ along with QKSMS. If not, see . --> - + tools:backgroundTint="@color/tools_theme" + tools:parentTag="com.moez.QKSMS.feature.compose.editing.DetailedChipView"> @@ -84,4 +80,4 @@ app:layout_constraintEnd_toEndOf="parent" tools:tint="@color/white" /> - \ No newline at end of file + \ No newline at end of file diff --git a/presentation/src/main/res/layout/main_activity.xml b/presentation/src/main/res/layout/main_activity.xml index f19d171f7..8dec910d1 100644 --- a/presentation/src/main/res/layout/main_activity.xml +++ b/presentation/src/main/res/layout/main_activity.xml @@ -119,20 +119,20 @@ android:background="?android:attr/divider" app:layout_constraintBottom_toTopOf="@id/snackbar" /> - - @@ -144,4 +144,4 @@ android:layout_height="match_parent" android:layout_gravity="start" /> - \ No newline at end of file + diff --git a/presentation/src/main/res/layout/main_permission_hint.xml b/presentation/src/main/res/layout/main_permission_hint.xml index 932134456..c7e9bd540 100644 --- a/presentation/src/main/res/layout/main_permission_hint.xml +++ b/presentation/src/main/res/layout/main_permission_hint.xml @@ -24,22 +24,22 @@ android:layout_height="wrap_content"> - \ No newline at end of file + diff --git a/presentation/src/main/res/layout/main_syncing.xml b/presentation/src/main/res/layout/main_syncing.xml index d3b17e59c..8f18bc38a 100644 --- a/presentation/src/main/res/layout/main_syncing.xml +++ b/presentation/src/main/res/layout/main_syncing.xml @@ -23,7 +23,7 @@ android:orientation="vertical"> + + + + - + - + . --> - - - - - \ No newline at end of file + android:id="@+id/themePreview" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:background="@drawable/circle" + tools:backgroundTint="@color/tools_theme" /> -- GitLab From 5b6b0e2345e63fa36615327c70c88558dbaa482a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 13 Mar 2020 22:49:18 -0400 Subject: [PATCH 128/213] Only show SIM indicator when there is more than one SIM --- .../java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2e62ca91c..6515746c0 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 @@ -224,8 +224,8 @@ class MessagesAdapter @Inject constructor( holder.binding.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD || message.subId != previous?.subId && subscription != null) - holder.binding.sim.setVisible(message.subId != previous?.subId && subscription != null) - holder.binding.simIndex.setVisible(message.subId != previous?.subId && subscription != null) + holder.binding.sim.setVisible(message.subId != previous?.subId && subscription != null && subs.size > 1) + holder.binding.simIndex.setVisible(message.subId != previous?.subId && subscription != null && subs.size > 1) // Bind the grouping val media = message.parts.filter { !it.isSmil() && !it.isText() } -- GitLab From 0f36c2c5ff348bf4a7f63ce6e88395a60bf444d5 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 22 Mar 2020 18:27:13 -0400 Subject: [PATCH 129/213] Fix padding for contact search Closes #1590 --- presentation/src/main/res/layout/contacts_activity.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/res/layout/contacts_activity.xml b/presentation/src/main/res/layout/contacts_activity.xml index 89181ebff..3ded424f1 100644 --- a/presentation/src/main/res/layout/contacts_activity.xml +++ b/presentation/src/main/res/layout/contacts_activity.xml @@ -69,7 +69,7 @@ android:imeOptions="flagNoExtractUi" android:inputType="textFilter|textNoSuggestions" android:paddingStart="16dp" - android:paddingEnd="16dp" + android:paddingEnd="40dp" android:privateImeOptions="nm" android:textColor="?android:attr/textColorPrimary" android:textColorHint="?android:attr/textColorTertiary" -- GitLab From 057806e98a8c079e525225b183185716b2299c70 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 29 Mar 2020 16:52:06 -0400 Subject: [PATCH 130/213] Fix MessageList visibility when composing --- .../main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f47227ea4..ad188ce99 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 @@ -218,7 +218,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { binding.sendAsGroup.setVisible(state.editingMode && state.selectedChips.size >= 2) binding.sendAsGroupSwitch.isChecked = state.sendAsGroup - binding.messageList.setVisible(!state.editingMode || state.sendAsGroup) + binding.messageList.setVisible(!state.editingMode || state.sendAsGroup || state.selectedChips.size == 1) messageAdapter.data = state.messages messageAdapter.highlight = state.searchSelectionId -- GitLab From b56111ab0843d542eb10e7d9fd07df0640e40edd Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 29 Mar 2020 21:45:59 -0400 Subject: [PATCH 131/213] Allow sharing vCard from other apps --- presentation/src/main/AndroidManifest.xml | 5 ++++ .../feature/compose/ComposeActivityModule.kt | 28 +++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index f6937d888..bdd875b43 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -90,6 +90,11 @@ + + + + + () - activity.intent.getParcelableExtra(Intent.EXTRA_STREAM)?.run(sharedImages::add) - activity.intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.run(sharedImages::addAll) - return Attachments(sharedImages.map { Attachment.Image(it) }) + val uris = mutableListOf() + activity.intent.getParcelableExtra(Intent.EXTRA_STREAM)?.run(uris::add) + activity.intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.run(uris::addAll) + return Attachments(uris.mapNotNull { uri -> + val mimeType = activity.contentResolver.getType(uri) + when { + ContentType.isImageType(mimeType) -> { + Attachment.Image(uri) + } + + ContentType.TEXT_VCARD.equals(mimeType, true) -> { + val inputStream = activity.contentResolver.openInputStream(uri) + val text = inputStream?.reader(Charset.forName("utf-8"))?.readText() + text?.let(Attachment::Contact) + } + + else -> null + } + }) } @Provides @@ -88,7 +106,7 @@ class ComposeActivityModule { private fun Intent.decodedDataString(): String? { val data = data?.toString() if (data?.contains('%') == true) { - return URLDecoder.decode(data, "UTF-8") + return URLDecoder.decode(data, "UTF-8") } return data } -- GitLab From 02bf2823e711fa76a9849eefa5ed2909529e06e0 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 29 Mar 2020 22:05:58 -0400 Subject: [PATCH 132/213] Fix empty state for search view --- .../src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt | 1 + 1 file changed, 1 insertion(+) 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 ea68d8f43..088a8179f 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 @@ -235,6 +235,7 @@ class MainActivity : QkThemedActivity(), MainView { 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 = binding.empty.takeIf { state.page is Searching } when (state.page) { is Inbox -> { -- GitLab From 408af960197a2bee25a2799a4036dc76ce45eb32 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 29 Mar 2020 22:59:50 -0400 Subject: [PATCH 133/213] Limit search to single line --- presentation/src/main/res/layout/main_activity.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/presentation/src/main/res/layout/main_activity.xml b/presentation/src/main/res/layout/main_activity.xml index 8dec910d1..38de075e9 100644 --- a/presentation/src/main/res/layout/main_activity.xml +++ b/presentation/src/main/res/layout/main_activity.xml @@ -55,6 +55,8 @@ android:background="@drawable/rounded_rectangle_24dp" android:backgroundTint="?attr/bubbleColor" android:hint="@string/title_conversations" + android:inputType="text" + android:maxLines="1" android:paddingStart="16dp" android:paddingEnd="16dp" android:textColorHint="?android:attr/textColorTertiary" -- GitLab From 555f1eebb50b81c709015eb3ca59bb6c9eb8995b Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 9 May 2020 15:22:59 -0400 Subject: [PATCH 134/213] Upgrade gradle build tools --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c4540a17c..6440f7711 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'io.fabric.tools:gradle:1.29.0' -- GitLab From fe177c714a02da678e1bca9501abd82fde352d4c Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 9 May 2020 15:23:08 -0400 Subject: [PATCH 135/213] Upgrade ShortcutBadger --- presentation/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 7240ab02b..6f0f6797f 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -179,7 +179,7 @@ dependencies { implementation "com.google.android:flexbox:0.3.1" implementation "com.jakewharton.timber:timber:$timber_version" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" - implementation "me.leolin:ShortcutBadger:1.1.21" + implementation "me.leolin:ShortcutBadger:1.1.22" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation project(":android-smsmms") implementation project(":common") -- GitLab From edbc99a0a388fe972736900d756bbe0f4e67ece2 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 10 May 2020 13:26:20 -0400 Subject: [PATCH 136/213] Fix crash when composing Closes #1588 --- .../QKSMS/feature/contacts/ContactsViewModel.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt index c101d218e..e9960816d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/contacts/ContactsViewModel.kt @@ -38,6 +38,7 @@ import com.moez.QKSMS.util.PhoneNumberUtils import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.schedulers.Schedulers @@ -179,9 +180,8 @@ class ContactsViewModel @Inject constructor( .map { composeItem -> composeItem to false } .mergeWith(view.composeItemLongPressedIntent.map { composeItem -> composeItem to true }) .observeOn(Schedulers.io()) - .autoDisposable(view.scope()) - .subscribe { (composeItem, force) -> - view.finish(HashMap(composeItem.getContacts().associate { contact -> + .map { (composeItem, force) -> + HashMap(composeItem.getContacts().associate { contact -> if (contact.numbers.size == 1 || contact.getDefaultNumber() != null && !force) { val address = contact.getDefaultNumber()?.address ?: contact.numbers[0]!!.address address to contact.lookupKey @@ -203,10 +203,14 @@ class ContactsViewModel @Inject constructor( } number.address to contact.lookupKey - } ?: return@subscribe + } ?: return@map hashMapOf() } - })) + }) } + .filter { result -> result.isNotEmpty() } + .observeOn(AndroidSchedulers.mainThread()) + .autoDisposable(view.scope()) + .subscribe { result -> view.finish(result) } } } -- GitLab From 7fd7b2ce13a3cbeb1bc0bb5ec9f10070cf86449d Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 10 May 2020 13:58:30 -0400 Subject: [PATCH 137/213] Better legibility for drafts Closes #1576 --- .../conversations/ConversationsAdapter.kt | 35 ++++++++++++------- .../res/layout/conversation_list_item.xml | 2 ++ presentation/src/main/res/values/strings.xml | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) 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 c2c9e7be6..370be6bce 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 @@ -22,6 +22,10 @@ import android.content.Context import android.graphics.Typeface import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.text.bold +import androidx.core.text.buildSpannedString +import androidx.core.text.color +import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator @@ -86,29 +90,34 @@ class ConversationsAdapter @Inject constructor( override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return + // If the last message wasn't incoming, then the colour doesn't really matter anyway + val lastMessage = conversation.lastMessage + val recipient = when { + conversation.recipients.size == 1 || lastMessage == null -> conversation.recipients.firstOrNull() + else -> conversation.recipients.find { recipient -> + phoneNumberUtils.compare(recipient.address, lastMessage.address) + } + } + val theme = colors.theme(recipient).theme + holder.binding.root.isActivated = isSelected(conversation.id) holder.binding.avatars.recipients = conversation.recipients holder.binding.title.collapseEnabled = conversation.recipients.size > 1 - holder.binding.title.text = conversation.getTitle() + holder.binding.title.text = buildSpannedString { + append(conversation.getTitle()) + if (conversation.draft.isNotEmpty()) { + color(theme) { append(" " + context.getString(R.string.main_draft)) } + } + } holder.binding.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) holder.binding.snippet.text = when { - conversation.draft.isNotEmpty() -> context.getString(R.string.main_draft, conversation.draft) + conversation.draft.isNotEmpty() -> conversation.draft conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) else -> conversation.snippet } holder.binding.pinned.isVisible = conversation.pinned - - // If the last message wasn't incoming, then the colour doesn't really matter anyway - val lastMessage = conversation.lastMessage - val recipient = when { - conversation.recipients.size == 1 || lastMessage == null -> conversation.recipients.firstOrNull() - else -> conversation.recipients.find { recipient -> - phoneNumberUtils.compare(recipient.address, lastMessage.address) - } - } - - holder.binding.unread.setTint(colors.theme(recipient).theme) + holder.binding.unread.setTint(theme) } override fun getItemId(position: Int): Long { diff --git a/presentation/src/main/res/layout/conversation_list_item.xml b/presentation/src/main/res/layout/conversation_list_item.xml index 1ed7eebfc..3a255ef0e 100644 --- a/presentation/src/main/res/layout/conversation_list_item.xml +++ b/presentation/src/main/res/layout/conversation_list_item.xml @@ -51,9 +51,11 @@ android:lines="1" app:layout_constraintBottom_toTopOf="@id/snippet" app:layout_constraintEnd_toStartOf="@id/date" + app:layout_constraintHorizontal_bias="0" app:layout_constraintStart_toEndOf="@id/avatars" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" + app:layout_constraintWidth_default="wrap" tools:text="@tools:sample/full_names" /> Block Syncing messages… You: %s - Draft: %s + Draft Results in messages %d messages Your conversations will appear here -- GitLab From 35ccd053749d471b4e9aec52dfd3f7d6207ab7bf Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 10 May 2020 14:28:52 -0400 Subject: [PATCH 138/213] Update draft display in widget --- .../com/moez/QKSMS/feature/widget/WidgetAdapter.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt index f8011ef8a..e31596b52 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt @@ -27,6 +27,8 @@ import android.view.View import android.widget.RemoteViews import android.widget.RemoteViewsService import androidx.core.text.bold +import androidx.core.text.buildSpannedString +import androidx.core.text.color import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter @@ -139,7 +141,12 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { // Name remoteViews.setTextColor(R.id.name, textPrimary) - remoteViews.setTextViewText(R.id.name, boldText(conversation.getTitle(), conversation.unread)) + remoteViews.setTextViewText(R.id.name, boldText(buildSpannedString { + append(conversation.getTitle()) + if (conversation.draft.isNotEmpty()) { + color(theme.theme) { append(" " + context.getString(R.string.main_draft)) } + } + }, conversation.unread)) // Date val timestamp = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) @@ -148,7 +155,7 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { // Snippet val snippet = when { - conversation.draft.isNotEmpty() -> context.getString(R.string.main_draft, conversation.draft) + conversation.draft.isNotEmpty() -> conversation.draft conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) else -> conversation.snippet } @@ -172,7 +179,7 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { return view } - private fun boldText(text: String?, shouldBold: Boolean): CharSequence? = when { + private fun boldText(text: CharSequence?, shouldBold: Boolean): CharSequence? = when { shouldBold -> SpannableStringBuilder() .bold { append(text) } else -> text -- GitLab From 32f205c8256410718952bffd8874985e29fd5bc6 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 10 May 2020 21:17:18 -0400 Subject: [PATCH 139/213] Remove deprecated usage of layout_constraintWidth_default="wrap" --- presentation/src/main/res/layout/conversation_list_item.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/res/layout/conversation_list_item.xml b/presentation/src/main/res/layout/conversation_list_item.xml index 3a255ef0e..516d4d558 100644 --- a/presentation/src/main/res/layout/conversation_list_item.xml +++ b/presentation/src/main/res/layout/conversation_list_item.xml @@ -43,19 +43,19 @@ Date: Sat, 12 Dec 2020 18:52:59 -0500 Subject: [PATCH 140/213] Remove Mixpanel --- data/build.gradle | 2 -- .../com/moez/QKSMS/manager/AnalyticsManagerImpl.kt | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/data/build.gradle b/data/build.gradle index f8c517b1a..ed2f1ceef 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -36,7 +36,6 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "AMPLITUDE_API_KEY", "\"${System.getenv("AMPLITUDE_API_KEY")}\"" - buildConfigField "String", "MIXPANEL_API_KEY", "\"${System.getenv("MIXPANEL_API_KEY")}\"" } productFlavors { @@ -94,7 +93,6 @@ dependencies { implementation project(':common') implementation project(':domain') withAnalyticsImplementation "com.amplitude:android-sdk:2.16.0" - withAnalyticsImplementation "com.mixpanel.android:mixpanel-android:5.2.1" } repositories { diff --git a/data/src/withAnalytics/java/com/moez/QKSMS/manager/AnalyticsManagerImpl.kt b/data/src/withAnalytics/java/com/moez/QKSMS/manager/AnalyticsManagerImpl.kt index edf364984..2cc27c1c2 100644 --- a/data/src/withAnalytics/java/com/moez/QKSMS/manager/AnalyticsManagerImpl.kt +++ b/data/src/withAnalytics/java/com/moez/QKSMS/manager/AnalyticsManagerImpl.kt @@ -22,7 +22,6 @@ import android.content.Context import com.amplitude.api.Amplitude import com.amplitude.api.AmplitudeClient import com.amplitude.api.Identify -import com.mixpanel.android.mpmetrics.MixpanelAPI import com.moez.QKSMS.data.BuildConfig import org.json.JSONArray import org.json.JSONObject @@ -34,7 +33,6 @@ import javax.inject.Singleton class AnalyticsManagerImpl @Inject constructor(context: Context) : AnalyticsManager { private val amplitude: AmplitudeClient = Amplitude.getInstance().initialize(context, BuildConfig.AMPLITUDE_API_KEY) - private val mixpanel: MixpanelAPI = MixpanelAPI.getInstance(context, BuildConfig.MIXPANEL_API_KEY) init { amplitude.trackSessionEvents(true) @@ -47,20 +45,11 @@ class AnalyticsManagerImpl @Inject constructor(context: Context) : AnalyticsMana .also { Timber.v("$event: $it") } amplitude.logEvent(event, propertiesJson) - - synchronized(mixpanel) { - mixpanel.track(event, propertiesJson) - } } override fun setUserProperty(key: String, value: Any) { Timber.v("$key: $value") - // Set the value in Mixpanel - val properties = JSONObject() - properties.put(key, value) - mixpanel.registerSuperProperties(properties) - // Set the value in Amplitude val identify = Identify() when (value) { -- GitLab From 605f32041d1440b259c5c05a75f2116039e92d19 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 12 Dec 2020 19:07:31 -0500 Subject: [PATCH 141/213] Filter non-alphanumeric initials --- .../com/moez/QKSMS/common/widget/AvatarView.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 6cca5a68a..05e37b6ce 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -44,7 +44,7 @@ class AvatarView @JvmOverloads constructor( private val binding = viewBinding(AvatarViewBinding::inflate) private var lookupKey: String? = null - private var name: String? = null + private var fullName: String? = null private var photoUri: String? = null private var lastUpdated: Long? = null @@ -63,7 +63,7 @@ class AvatarView @JvmOverloads constructor( */ fun setRecipient(recipient: Recipient?) { lookupKey = recipient?.contact?.lookupKey - name = recipient?.contact?.name + fullName = recipient?.contact?.name photoUri = recipient?.contact?.photoUri lastUpdated = recipient?.contact?.lastUpdate theme = colors.theme(recipient) @@ -84,12 +84,14 @@ class AvatarView @JvmOverloads constructor( binding.initial.setTextColor(theme.textPrimary) binding.icon.setTint(theme.textPrimary) - if (name?.isNotEmpty() == true) { - val initials = name + if (fullName?.isNotEmpty() == true) { + val initials = fullName ?.substringBefore(',') ?.split(" ").orEmpty() - .filter { subname -> subname.isNotEmpty() } - .map { subname -> subname[0].toString() } + .filter { name -> name.isNotEmpty() } + .map { name -> name[0] } + .filter { initial -> initial.isLetterOrDigit() } + .map { initial -> initial.toString() } binding.initial.text = if (initials.size > 1) initials.first() + initials.last() else initials.first() binding.icon.visibility = GONE -- GitLab From e4f16b4d59d9e0afc59b5d3234ab00595fa5cdc7 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 12 Dec 2020 23:07:11 -0500 Subject: [PATCH 142/213] Dismiss keyboard for date picker --- .../java/com/moez/QKSMS/feature/compose/ComposeActivity.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 ad188ce99..0e003bc6c 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 @@ -47,6 +47,7 @@ import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.autoScrollToStart +import com.moez.QKSMS.common.util.extensions.dismissKeyboard import com.moez.QKSMS.common.util.extensions.hideKeyboard import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.scrapViews @@ -279,6 +280,9 @@ class ComposeActivity : QkThemedActivity(), ComposeView { }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), DateFormat.is24HourFormat(this)) .show() }, 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 + binding.message.hideKeyboard() } override fun requestContact() { -- GitLab From 83c484d3ffb41efc7e0bb7eb371b183b2d73981e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 20 Dec 2020 20:29:12 -0500 Subject: [PATCH 143/213] Don't make threadId for DeleteMessages optional --- .../main/java/com/moez/QKSMS/interactor/DeleteMessages.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/DeleteMessages.kt b/domain/src/main/java/com/moez/QKSMS/interactor/DeleteMessages.kt index 8c0969b63..3f573cdf1 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/DeleteMessages.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/DeleteMessages.kt @@ -31,15 +31,13 @@ class DeleteMessages @Inject constructor( private val updateBadge: UpdateBadge ) : Interactor() { - data class Params(val messageIds: List, val threadId: Long? = null) + data class Params(val messageIds: List, val threadId: Long) override fun buildObservable(params: Params): Flowable<*> { return Flowable.just(params.messageIds.toLongArray()) .doOnNext { messageIds -> messageRepo.deleteMessages(*messageIds) } // Delete the messages - .doOnNext { - params.threadId?.let { conversationRepo.updateConversations(it) } // Update the conversation - } - .doOnNext { params.threadId?.let { notificationManager.update(it) } } + .doOnNext { conversationRepo.updateConversations(params.threadId) } // Update the conversation + .doOnNext { notificationManager.update(params.threadId) } .flatMap { updateBadge.buildObservable(Unit) } // Update the badge } -- GitLab From df181caf9e754c69a5c889076047ff49248b7f9e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 20 Dec 2020 20:29:24 -0500 Subject: [PATCH 144/213] Update conversation after cancelling delayed message --- .../QKSMS/interactor/CancelDelayedMessage.kt | 19 +++++++++++++------ .../QKSMS/feature/compose/ComposeViewModel.kt | 4 +++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/CancelDelayedMessage.kt b/domain/src/main/java/com/moez/QKSMS/interactor/CancelDelayedMessage.kt index 4c37e3fcd..429c2a754 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/CancelDelayedMessage.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/CancelDelayedMessage.kt @@ -18,16 +18,23 @@ */ package com.moez.QKSMS.interactor +import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository import io.reactivex.Flowable import javax.inject.Inject -class CancelDelayedMessage @Inject constructor(private val messageRepo: MessageRepository) : Interactor() { +class CancelDelayedMessage @Inject constructor( + private val conversationRepo: ConversationRepository, + private val messageRepo: MessageRepository +) : Interactor() { - override fun buildObservable(params: Long): Flowable<*> { - return Flowable.just(params) - .doOnNext { id -> messageRepo.cancelDelayedSms(id) } - .doOnNext { id -> messageRepo.deleteMessages(id) } + data class Params(val messageId: Long, val threadId: Long) + + override fun buildObservable(params: Params): Flowable<*> { + return Flowable.just(Unit) + .doOnNext { messageRepo.cancelDelayedSms(params.messageId) } + .doOnNext { messageRepo.deleteMessages(params.messageId) } + .doOnNext { conversationRepo.updateConversations(params.threadId) } // Update the conversation } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 59edb5457..a2abfb2f8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -450,7 +450,9 @@ class ComposeViewModel @Inject constructor( .mapNotNull(messageRepo::getMessage) .doOnNext { message -> view.setDraft(message.getText()) } .autoDisposable(view.scope()) - .subscribe { message -> cancelMessage.execute(message.id) } + .subscribe { message -> + cancelMessage.execute(CancelDelayedMessage.Params(message.id, message.threadId)) + } // Set the current conversation Observables -- GitLab From 0cfca58350ac7001cabf7f2f96289c054e65f872 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 20 Dec 2020 23:51:01 -0500 Subject: [PATCH 145/213] Add preference for auto-delete --- .../java/com/moez/QKSMS/util/Preferences.kt | 1 + .../{FieldDialog.kt => TextInputDialog.kt} | 8 +-- .../ConversationInfoController.kt | 6 +-- .../feature/settings/SettingsController.kt | 28 ++++++++--- .../feature/settings/SettingsPresenter.kt | 12 ++++- .../QKSMS/feature/settings/SettingsState.kt | 1 + .../QKSMS/feature/settings/SettingsView.kt | 4 +- .../settings/autodelete/AutoDeleteDialog.kt | 50 +++++++++++++++++++ .../ic_baseline_history_toggle_off_24.xml | 10 ++++ .../layout/settings_auto_delete_dialog.xml | 35 +++++++++++++ .../main/res/layout/settings_controller.xml | 8 +++ ...field_dialog.xml => text_input_dialog.xml} | 0 presentation/src/main/res/values/strings.xml | 9 ++++ 13 files changed, 157 insertions(+), 15 deletions(-) rename presentation/src/main/java/com/moez/QKSMS/common/widget/{FieldDialog.kt => TextInputDialog.kt} (82%) create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/settings/autodelete/AutoDeleteDialog.kt create mode 100644 presentation/src/main/res/drawable/ic_baseline_history_toggle_off_24.xml create mode 100644 presentation/src/main/res/layout/settings_auto_delete_dialog.xml rename presentation/src/main/res/layout/{field_dialog.xml => text_input_dialog.xml} (100%) diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index 92794cafc..919fab278 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -113,6 +113,7 @@ class Preferences @Inject constructor( val signature = rxPrefs.getString("signature", "") val unicode = rxPrefs.getBoolean("unicode", false) val mobileOnly = rxPrefs.getBoolean("mobileOnly", false) + val autoDelete = rxPrefs.getInteger("autoDelete", 0) val longAsMms = rxPrefs.getBoolean("longAsMms", false) val mmsSize = rxPrefs.getInteger("mmsSize", 300) val logging = rxPrefs.getBoolean("logging", false) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/TextInputDialog.kt similarity index 82% rename from presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt rename to presentation/src/main/java/com/moez/QKSMS/common/widget/TextInputDialog.kt index 3efb12625..0c9ee56d8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/TextInputDialog.kt @@ -22,11 +22,11 @@ import android.app.Activity import android.content.DialogInterface import androidx.appcompat.app.AlertDialog import com.moez.QKSMS.R -import com.moez.QKSMS.databinding.FieldDialogBinding +import com.moez.QKSMS.databinding.TextInputDialogBinding -class FieldDialog(context: Activity, hint: String, listener: (String) -> Unit) : AlertDialog(context) { +class TextInputDialog(context: Activity, hint: String, listener: (String) -> Unit) : AlertDialog(context) { - private val binding = FieldDialogBinding.inflate(context.layoutInflater) + private val binding = TextInputDialogBinding.inflate(context.layoutInflater) init { binding.field.hint = hint @@ -39,7 +39,7 @@ class FieldDialog(context: Activity, hint: String, listener: (String) -> Unit) : } } - fun setText(text: String): FieldDialog { + fun setText(text: String): TextInputDialog { binding.field.setText(text) return this } 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 293e9b757..07682cf98 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 @@ -27,7 +27,7 @@ import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.extensions.scrapViews -import com.moez.QKSMS.common.widget.FieldDialog +import com.moez.QKSMS.common.widget.TextInputDialog import com.moez.QKSMS.databinding.ConversationInfoControllerBinding import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoModule @@ -50,8 +50,8 @@ class ConversationInfoController( @Inject lateinit var navigator: Navigator @Inject lateinit var adapter: ConversationInfoAdapter - private val nameDialog: FieldDialog by lazy { - FieldDialog(activity!!, activity!!.getString(R.string.info_name), nameChangeSubject::onNext) + private val nameDialog: TextInputDialog by lazy { + TextInputDialog(activity!!, activity!!.getString(R.string.info_name), nameChangeSubject::onNext) } private val nameChangeSubject: Subject = PublishSubject.create() 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 f2d652a75..eace81b34 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 @@ -39,11 +39,12 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.widget.FieldDialog +import com.moez.QKSMS.common.widget.TextInputDialog import com.moez.QKSMS.common.widget.PreferenceView import com.moez.QKSMS.common.widget.QkSwitch import com.moez.QKSMS.databinding.SettingsControllerBinding import com.moez.QKSMS.feature.settings.about.AboutController +import com.moez.QKSMS.feature.settings.autodelete.AutoDeleteDialog import com.moez.QKSMS.feature.settings.swipe.SwipeActionsController import com.moez.QKSMS.feature.themepicker.ThemePickerController import com.moez.QKSMS.injection.appComponent @@ -69,14 +70,18 @@ class SettingsController : QkController = PublishSubject.create() private val startTimeSelectedSubject: Subject> = PublishSubject.create() private val endTimeSelectedSubject: Subject> = PublishSubject.create() private val signatureSubject: Subject = PublishSubject.create() + private val autoDeleteSubject: Subject = PublishSubject.create() private val progressAnimator by lazy { ObjectAnimator.ofInt(binding.syncingProgress, "progress", 0, 0) } @@ -132,7 +137,9 @@ class SettingsController : QkController = sendDelayDialog.adapter.menuItemClicks - override fun signatureSet(): Observable = signatureSubject + override fun signatureChanged(): Observable = signatureSubject + + override fun autoDeleteChanged(): Observable = autoDeleteSubject override fun mmsSizeSelected(): Observable = mmsSizeDialog.adapter.menuItemClicks @@ -167,6 +174,13 @@ class SettingsController : QkController().isChecked = state.stripUnicodeEnabled binding.mobileOnly.widget().isChecked = state.mobileOnly + + 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) + } + binding.longAsMms.widget().isChecked = state.longAsMms binding.mmsSize.summary = state.maxMmsSizeSummary @@ -198,13 +212,13 @@ class SettingsController : QkController + TimePickerDialog(activity, { _, newHour, newMinute -> startTimeSelectedSubject.onNext(Pair(newHour, newMinute)) }, hour, minute, DateFormat.is24HourFormat(activity)).show() } override fun showEndTimePicker(hour: Int, minute: Int) { - TimePickerDialog(activity, TimePickerDialog.OnTimeSetListener { _, newHour, newMinute -> + TimePickerDialog(activity, { _, newHour, newMinute -> endTimeSelectedSubject.onNext(Pair(newHour, newMinute)) }, hour, minute, DateFormat.is24HourFormat(activity)).show() } @@ -215,6 +229,8 @@ class SettingsController : QkController newState { copy(mobileOnly = enabled) } } + disposables += prefs.autoDelete.asObservable() + .subscribe { autoDelete -> newState { copy(autoDelete = autoDelete) } } + disposables += prefs.longAsMms.asObservable() .subscribe { enabled -> newState { copy(longAsMms = enabled) } } @@ -183,6 +186,8 @@ class SettingsPresenter @Inject constructor( R.id.mobileOnly -> prefs.mobileOnly.set(!prefs.mobileOnly.get()) + R.id.autoDelete -> view.showAutoDeleteDialog(prefs.autoDelete.get()) + R.id.longAsMms -> prefs.longAsMms.set(!prefs.longAsMms.get()) R.id.mmsSize -> view.showMmsSizePicker() @@ -242,11 +247,16 @@ class SettingsPresenter @Inject constructor( .autoDisposable(view.scope()) .subscribe() - view.signatureSet() + view.signatureChanged() .doOnNext(prefs.signature::set) .autoDisposable(view.scope()) .subscribe() + view.autoDeleteChanged() + .doOnNext(prefs.autoDelete::set) + .autoDisposable(view.scope()) + .subscribe() + view.mmsSizeSelected() .autoDisposable(view.scope()) .subscribe(prefs.mmsSize::set) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt index f18ccd800..35cf2252a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt @@ -41,6 +41,7 @@ data class SettingsState( val splitSmsEnabled: Boolean = false, val stripUnicodeEnabled: Boolean = false, val mobileOnly: Boolean = false, + val autoDelete: Int = 0, val longAsMms: Boolean = false, val maxMmsSizeSummary: String = "100KB", val maxMmsSizeId: Int = 100, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt index 80431508e..f03506093 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt @@ -31,7 +31,8 @@ interface SettingsView : QkViewContract { fun nightEndSelected(): Observable> fun textSizeSelected(): Observable fun sendDelaySelected(): Observable - fun signatureSet(): Observable + fun signatureChanged(): Observable + fun autoDeleteChanged(): Observable fun mmsSizeSelected(): Observable fun showQksmsPlusSnackbar() @@ -41,6 +42,7 @@ interface SettingsView : QkViewContract { fun showTextSizePicker() fun showDelayDurationDialog() fun showSignatureDialog(signature: String) + fun showAutoDeleteDialog(days: Int) fun showMmsSizePicker() fun showSwipeActions() fun showThemePicker() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/autodelete/AutoDeleteDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/autodelete/AutoDeleteDialog.kt new file mode 100644 index 000000000..260b0f4fe --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/autodelete/AutoDeleteDialog.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.settings.autodelete + +import android.app.Activity +import android.content.DialogInterface +import androidx.appcompat.app.AlertDialog +import com.moez.QKSMS.R +import com.moez.QKSMS.databinding.SettingsAutoDeleteDialogBinding + +class AutoDeleteDialog(context: Activity, listener: (Int) -> Unit) : AlertDialog(context) { + + private val binding = SettingsAutoDeleteDialogBinding.inflate(context.layoutInflater) + + init { + setView(binding.root) + setTitle(R.string.settings_auto_delete) + setMessage(context.getString(R.string.settings_auto_delete_dialog_message)) + setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.button_cancel)) { _, _ -> } + setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.settings_auto_delete_never)) { _, _ -> listener(0) } + setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.button_save)) { _, _ -> + listener(binding.field.text.toString().toIntOrNull() ?: 0) + } + } + + fun setExpiry(days: Int): AutoDeleteDialog { + when (days) { + 0 -> binding.field.text = null + else -> binding.field.setText(days.toString()) + } + return this + } + +} diff --git a/presentation/src/main/res/drawable/ic_baseline_history_toggle_off_24.xml b/presentation/src/main/res/drawable/ic_baseline_history_toggle_off_24.xml new file mode 100644 index 000000000..ece916dcc --- /dev/null +++ b/presentation/src/main/res/drawable/ic_baseline_history_toggle_off_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/presentation/src/main/res/layout/settings_auto_delete_dialog.xml b/presentation/src/main/res/layout/settings_auto_delete_dialog.xml new file mode 100644 index 000000000..92830e5c5 --- /dev/null +++ b/presentation/src/main/res/layout/settings_auto_delete_dialog.xml @@ -0,0 +1,35 @@ + + + diff --git a/presentation/src/main/res/layout/settings_controller.xml b/presentation/src/main/res/layout/settings_controller.xml index 0ae7a8cf0..4fe6e9b3d 100644 --- a/presentation/src/main/res/layout/settings_controller.xml +++ b/presentation/src/main/res/layout/settings_controller.xml @@ -177,6 +177,14 @@ app:title="@string/settings_mobile_title" app:widget="@layout/settings_switch_widget" /> + + Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + + Never + After 1 day + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments -- GitLab From b087de8f73cc09c6eb3f272b0df1a77ef35fed71 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 22 Dec 2020 00:00:42 -0500 Subject: [PATCH 146/213] Show warning when enabling autoDelete --- .../QKSMS/repository/MessageRepositoryImpl.kt | 15 ++++++++++++- .../repository/ConversationRepository.kt | 2 +- .../QKSMS/repository/MessageRepository.kt | 5 +++++ .../feature/settings/SettingsController.kt | 21 +++++++++++++++++-- .../feature/settings/SettingsPresenter.kt | 13 ++++++++++++ .../QKSMS/feature/settings/SettingsView.kt | 1 + presentation/src/main/res/values/strings.xml | 1 + 7 files changed, 54 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index 1eb62d234..b91846cef 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -66,8 +66,11 @@ import java.io.File import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException +import java.util.* +import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton +import kotlin.collections.ArrayList import kotlin.math.sqrt @Singleton @@ -724,4 +727,14 @@ class MessageRepositoryImpl @Inject constructor( } } -} \ No newline at end of file + override fun getOldMessageCounts(maxAgeDays: Int): Map { + return Realm.getDefaultInstance().use { realm -> + realm.where(Message::class.java) + .lessThan("date", now() - TimeUnit.DAYS.toMillis(maxAgeDays.toLong())) + .findAll() + .groupingBy { message -> message.threadId } + .eachCount() + } + } + +} diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt index 7ccf33ff5..e94d1c9fa 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt @@ -91,4 +91,4 @@ interface ConversationRepository { fun deleteConversations(vararg threadIds: Long) -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt index 217e312a0..ce09b206f 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt @@ -102,4 +102,9 @@ interface MessageRepository { fun deleteMessages(vararg messageIds: Long) + /** + * Returns the number of messages older than [maxAgeDays] per conversation + */ + fun getOldMessageCounts(maxAgeDays: Int): Map + } 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 eace81b34..104a305b4 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 @@ -24,6 +24,7 @@ import android.content.Context import android.os.Build import android.text.format.DateFormat import android.view.View +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.bluelinelabs.conductor.RouterTransaction import com.google.android.material.snackbar.Snackbar @@ -39,9 +40,9 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.widget.TextInputDialog import com.moez.QKSMS.common.widget.PreferenceView import com.moez.QKSMS.common.widget.QkSwitch +import com.moez.QKSMS.common.widget.TextInputDialog import com.moez.QKSMS.databinding.SettingsControllerBinding import com.moez.QKSMS.feature.settings.about.AboutController import com.moez.QKSMS.feature.settings.autodelete.AutoDeleteDialog @@ -55,7 +56,11 @@ import com.uber.autodispose.autoDisposable import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext import javax.inject.Inject +import kotlin.coroutines.resume class SettingsController : QkController( SettingsControllerBinding::inflate @@ -192,7 +197,8 @@ class SettingsController : QkController { binding.syncingProgress.isVisible = true binding.syncingProgress.max = state.syncProgress.max - progressAnimator.apply { setIntValues(binding.syncingProgress.progress, state.syncProgress.progress) }.start() + progressAnimator.apply { setIntValues(binding.syncingProgress.progress, state.syncProgress.progress) } + .start() binding.syncingProgress.isIndeterminate = state.syncProgress.indeterminate } } @@ -231,6 +237,17 @@ class SettingsController : QkController { cont -> + AlertDialog.Builder(activity!!) + .setMessage(context.resources.getString(R.string.settings_auto_delete_warning, messages)) + .setOnCancelListener { cont.resume(false) } + .setNegativeButton(R.string.button_cancel) { _, _ -> cont.resume(false) } + .setPositiveButton(R.string.button_yes) { _, _ -> cont.resume(true) } + .show() + } + } + override fun showMmsSizePicker() = mmsSizeDialog.show(activity!!) override fun showSwipeActions() { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt index 17923df90..708d06715 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt @@ -28,6 +28,7 @@ import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.interactor.SyncMessages import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.repository.SyncRepository import com.moez.QKSMS.util.NightModeManager import com.moez.QKSMS.util.Preferences @@ -35,6 +36,8 @@ import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.runBlocking import timber.log.Timber import java.util.* import java.util.concurrent.TimeUnit @@ -47,6 +50,7 @@ class SettingsPresenter @Inject constructor( private val context: Context, private val billingManager: BillingManager, private val dateFormatter: DateFormatter, + private val messageRepo: MessageRepository, private val navigator: Navigator, private val nightModeManager: NightModeManager, private val prefs: Preferences, @@ -253,6 +257,15 @@ class SettingsPresenter @Inject constructor( .subscribe() view.autoDeleteChanged() + .observeOn(Schedulers.io()) + .filter { maxAge -> + if (maxAge == 0) { + return@filter true + } + + val counts = messageRepo.getOldMessageCounts(maxAge) + runBlocking { view.showAutoDeleteWarningDialog(counts.values.sum()) } + } .doOnNext(prefs.autoDelete::set) .autoDisposable(view.scope()) .subscribe() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt index f03506093..853b23d61 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt @@ -43,6 +43,7 @@ interface SettingsView : QkViewContract { fun showDelayDurationDialog() fun showSignatureDialog(signature: String) fun showAutoDeleteDialog(days: Int) + suspend fun showAutoDeleteWarningDialog(messages: Int): Boolean fun showMmsSizePicker() fun showSwipeActions() fun showThemePicker() diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 09dc8371e..50f4752a7 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -262,6 +262,7 @@ Messages will be deleted after the specified number of days Number of days Never + Are you sure you would like to enable this setting? %1$d messages will be deleted Never After 1 day -- GitLab From 0d6a8c7d12cfaa0a8a0196f8aea358aa30f6c1fe Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 26 Dec 2020 19:54:22 -0500 Subject: [PATCH 147/213] Schedule daily job for deleting old messages --- .../util/extensions/ContextExtensions.kt | 5 ++ .../QKSMS/repository/MessageRepositoryImpl.kt | 13 ++++ .../moez/QKSMS/service/AutoDeleteService.kt | 62 +++++++++++++++++++ .../QKSMS/interactor/DeleteOldMessages.kt | 44 +++++++++++++ .../QKSMS/repository/MessageRepository.kt | 5 ++ presentation/src/main/AndroidManifest.xml | 3 + .../feature/settings/SettingsPresenter.kt | 12 ++++ .../injection/android/ServiceBuilderModule.kt | 7 ++- 8 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 data/src/main/java/com/moez/QKSMS/service/AutoDeleteService.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/interactor/DeleteOldMessages.kt diff --git a/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt index 90fc26bbc..f5534a9fc 100644 --- a/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt +++ b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt @@ -18,6 +18,7 @@ */ package com.moez.QKSMS.common.util.extensions +import android.app.job.JobScheduler import android.content.Context import android.content.res.ColorStateList import android.graphics.Color @@ -25,6 +26,7 @@ import android.util.TypedValue import android.widget.Toast import androidx.annotation.StringRes import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService import com.moez.QKSMS.util.tryOrNull fun Context.getColorCompat(colorRes: Int): Int { @@ -84,3 +86,6 @@ fun Context.isInstalled(packageName: String): Boolean { val Context.versionCode: Int get() = packageManager.getPackageInfo(packageName, 0).versionCode + +val Context.jobScheduler: JobScheduler + get() = getSystemService()!! diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index b91846cef..a5438d346 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -737,4 +737,17 @@ class MessageRepositoryImpl @Inject constructor( } } + override fun deleteOldMessages(maxAgeDays: Int) { + return Realm.getDefaultInstance().use { realm -> + val messages = realm.where(Message::class.java) + .lessThan("date", now() - TimeUnit.DAYS.toMillis(maxAgeDays.toLong())) + .findAll() + + val uris = messages.map { it.getUri() } + + realm.executeTransaction { messages.deleteAllFromRealm() } + + uris.forEach { uri -> context.contentResolver.delete(uri, null, null) } + } + } } diff --git a/data/src/main/java/com/moez/QKSMS/service/AutoDeleteService.kt b/data/src/main/java/com/moez/QKSMS/service/AutoDeleteService.kt new file mode 100644 index 000000000..b29edca71 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/service/AutoDeleteService.kt @@ -0,0 +1,62 @@ +package com.moez.QKSMS.service + +import android.annotation.SuppressLint +import android.app.job.JobInfo +import android.app.job.JobParameters +import android.app.job.JobService +import android.content.ComponentName +import android.content.Context +import androidx.core.content.getSystemService +import com.moez.QKSMS.common.util.extensions.jobScheduler +import com.moez.QKSMS.interactor.DeleteOldMessages +import dagger.android.AndroidInjection +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import kotlinx.coroutines.Job +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class AutoDeleteService : JobService() { + + companion object { + private const val JobId = 8120235 + + @SuppressLint("MissingPermission") // Added in [presentation]'s AndroidManifest.xml + fun scheduleJob(context: Context) { + Timber.i("Scheduling job") + val serviceComponent = ComponentName(context, AutoDeleteService::class.java) + val periodicJob = JobInfo.Builder(JobId, serviceComponent) + .setPeriodic(TimeUnit.DAYS.toMillis(1)) + .setPersisted(true) + .build() + + context.jobScheduler.schedule(periodicJob) + } + + fun cancelJob(context: Context) { + Timber.i("Canceling job") + context.jobScheduler.cancel(JobId) + } + } + + @Inject lateinit var deleteOldMessages: DeleteOldMessages + + private val disposables = CompositeDisposable() + + override fun onStartJob(params: JobParameters?): Boolean { + Timber.i("onStartJob") + AndroidInjection.inject(this) + disposables += deleteOldMessages + deleteOldMessages.execute(Unit) { + jobFinished(params, false) + } + return true + } + + override fun onStopJob(params: JobParameters?): Boolean { + Timber.i("onStopJob") + disposables.dispose() + return true + } +} diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/DeleteOldMessages.kt b/domain/src/main/java/com/moez/QKSMS/interactor/DeleteOldMessages.kt new file mode 100644 index 000000000..7479933db --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/interactor/DeleteOldMessages.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.interactor + +import com.moez.QKSMS.repository.ConversationRepository +import com.moez.QKSMS.repository.MessageRepository +import com.moez.QKSMS.util.Preferences +import io.reactivex.Flowable +import timber.log.Timber +import javax.inject.Inject + +class DeleteOldMessages @Inject constructor( + private val conversationRepo: ConversationRepository, + private val messageRepo: MessageRepository, + private val prefs: Preferences +) : Interactor() { + + override fun buildObservable(params: Unit): Flowable<*> = Flowable.fromCallable { + val maxAge = prefs.autoDelete.get().takeIf { it > 0 } ?: return@fromCallable + val counts = messageRepo.getOldMessageCounts(maxAge) + val threadIds = counts.keys.toLongArray() + + Timber.d("Deleting ${counts.values.sum()} old messages from ${threadIds.size} conversations") + messageRepo.deleteOldMessages(maxAge) + conversationRepo.updateConversations(*threadIds) + } + +} diff --git a/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt index ce09b206f..a3ef7c19f 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt @@ -107,4 +107,9 @@ interface MessageRepository { */ fun getOldMessageCounts(maxAgeDays: Int): Map + /** + * Deletes all messages older than [maxAgeDays] + */ + fun deleteOldMessages(maxAgeDays: Int) + } diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index bdd875b43..796dc6a33 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -184,6 +184,9 @@ + + when (maxAge == 0) { + true -> AutoDeleteService.cancelJob(context) + false -> { + AutoDeleteService.scheduleJob(context) + deleteOldMessages.execute(Unit) + } + } + } .doOnNext(prefs.autoDelete::set) .autoDisposable(view.scope()) .subscribe() diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/android/ServiceBuilderModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/android/ServiceBuilderModule.kt index ddc701bdb..ffc30e5c6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/android/ServiceBuilderModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/android/ServiceBuilderModule.kt @@ -22,12 +22,17 @@ import com.moez.QKSMS.feature.backup.RestoreBackupService import com.moez.QKSMS.injection.scope.ActivityScope import com.moez.QKSMS.service.HeadlessSmsSendService import com.moez.QKSMS.receiver.SendSmsReceiver +import com.moez.QKSMS.service.AutoDeleteService import dagger.Module import dagger.android.ContributesAndroidInjector @Module abstract class ServiceBuilderModule { + @ActivityScope + @ContributesAndroidInjector() + abstract fun bindAutoDeleteService(): AutoDeleteService + @ActivityScope @ContributesAndroidInjector() abstract fun bindHeadlessSmsSendService(): HeadlessSmsSendService @@ -40,4 +45,4 @@ abstract class ServiceBuilderModule { @ContributesAndroidInjector() abstract fun bindSendSmsReceiver(): SendSmsReceiver -} \ No newline at end of file +} -- GitLab From 0eb2bee2b8f52bcae092b4e7b8a3a39125f696a2 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 26 Dec 2020 19:56:52 -0500 Subject: [PATCH 148/213] Upgrade build tools --- presentation/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 6f0f6797f..7ae8cd88f 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -23,7 +23,7 @@ apply plugin: 'kotlin-kapt' android { compileSdkVersion 29 - buildToolsVersion "29.0.2" + buildToolsVersion "29.0.3" flavorDimensions "analytics" defaultConfig { -- GitLab From e51cdfe69fcd6f41a2f0658a33f3a5f4a23d98dc Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 26 Dec 2020 19:57:01 -0500 Subject: [PATCH 149/213] Improve debug logging format --- .../main/java/com/moez/QKSMS/common/util/FileLoggingTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/FileLoggingTree.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/FileLoggingTree.kt index bf5c6c8a1..aa1dd6322 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/FileLoggingTree.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/FileLoggingTree.kt @@ -53,7 +53,7 @@ class FileLoggingTree @Inject constructor(private val prefs: Preferences) : Timb } // Format the log to be written to the file - val log = "$timestamp $priorityString/$tag: $message ${Log.getStackTraceString(t)}
".toByteArray() + val log = "$timestamp $priorityString/$tag: $message ${Log.getStackTraceString(t)}\n".toByteArray() // Ensure that only one thread is writing to the file at a time synchronized(fileLock) { @@ -62,7 +62,7 @@ class FileLoggingTree @Inject constructor(private val prefs: Preferences) : Timb val dir = File(Environment.getExternalStorageDirectory(), "QKSMS/Logs").apply { mkdirs() } // Create the file - val file = File(dir, "${SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(System.currentTimeMillis())}.html") + val file = File(dir, "${SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(System.currentTimeMillis())}.log") // Write the log to the file FileOutputStream(file, true).use { fileOutputStream -> fileOutputStream.write(log) } -- GitLab From e6f216fab48401c620d8be9de48c1b776a12e916 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 27 Dec 2020 00:03:47 -0500 Subject: [PATCH 150/213] Auto delete warning popup improvements --- .../com/moez/QKSMS/feature/settings/SettingsController.kt | 3 ++- .../java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt | 4 ++++ presentation/src/main/res/values/strings.xml | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) 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 104a305b4..5159f07e1 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 @@ -240,7 +240,8 @@ class SettingsController : QkController { cont -> AlertDialog.Builder(activity!!) - .setMessage(context.resources.getString(R.string.settings_auto_delete_warning, messages)) + .setTitle(R.string.settings_auto_delete_warning) + .setMessage(context.resources.getString(R.string.settings_auto_delete_warning_message, messages)) .setOnCancelListener { cont.resume(false) } .setNegativeButton(R.string.button_cancel) { _, _ -> cont.resume(false) } .setPositiveButton(R.string.button_yes) { _, _ -> cont.resume(true) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt index a00d5a6fa..df35038f8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt @@ -267,6 +267,10 @@ class SettingsPresenter @Inject constructor( } val counts = messageRepo.getOldMessageCounts(maxAge) + if (counts.values.sum() == 0) { + return@filter true + } + runBlocking { view.showAutoDeleteWarningDialog(counts.values.sum()) } } .doOnNext { maxAge -> diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 50f4752a7..1261abf01 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -262,7 +262,8 @@ Messages will be deleted after the specified number of days Number of days Never - Are you sure you would like to enable this setting? %1$d messages will be deleted + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now Never After 1 day -- GitLab From fa6b06580606ee287c9a145bef4d9397e300200e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 28 Dec 2020 02:05:40 -0500 Subject: [PATCH 151/213] Trim whitespace when searching Closes #1667 --- .../src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index ac483a776..20933a823 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -194,6 +194,8 @@ class MainViewModel @Inject constructor( query } .filter { query -> query.length >= 2 } + .map { query -> query.trim() } + .distinctUntilChanged() .doOnNext { newState { val page = (page as? Searching) ?: Searching() -- GitLab From fe1967a8768308f5b278c1a716b650fb4975ddab Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 28 Dec 2020 17:03:24 -0500 Subject: [PATCH 152/213] Clean up permissionManagerImpl --- .../QKSMS/manager/PermissionManagerImpl.kt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/manager/PermissionManagerImpl.kt b/data/src/main/java/com/moez/QKSMS/manager/PermissionManagerImpl.kt index f910428c9..93620b69d 100644 --- a/data/src/main/java/com/moez/QKSMS/manager/PermissionManagerImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/manager/PermissionManagerImpl.kt @@ -21,7 +21,7 @@ package com.moez.QKSMS.manager import android.Manifest import android.app.role.RoleManager import android.content.Context -import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.content.pm.PackageManager import android.os.Build import android.provider.Telephony import androidx.core.content.ContextCompat @@ -38,28 +38,31 @@ class PermissionManagerImpl @Inject constructor(private val context: Context) : } override fun hasReadSms(): Boolean { - return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == PERMISSION_GRANTED + return hasPermission(Manifest.permission.READ_SMS) } override fun hasSendSms(): Boolean { - return ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) == PERMISSION_GRANTED + return hasPermission(Manifest.permission.SEND_SMS) } override fun hasContacts(): Boolean { - return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) == PERMISSION_GRANTED + return hasPermission(Manifest.permission.READ_CONTACTS) } override fun hasPhone(): Boolean { - return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PERMISSION_GRANTED + return hasPermission(Manifest.permission.READ_PHONE_STATE) } override fun hasCalling(): Boolean { - return ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) == PERMISSION_GRANTED + return hasPermission(Manifest.permission.CALL_PHONE) } override fun hasStorage(): Boolean { - return ContextCompat.checkSelfPermission(context, - Manifest.permission.WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED + return hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) } -} \ No newline at end of file + private fun hasPermission(permission: String): Boolean { + return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED + } + +} -- GitLab From c030d8ca5079650628d5116d68c0a28f9824c2a0 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 30 Dec 2020 22:00:35 -0500 Subject: [PATCH 153/213] Update crashlytics --- build.gradle | 4 +--- presentation/build.gradle | 5 ++--- .../moez/QKSMS/common/util/CrashlyticsTree.kt | 16 +++++++++++++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 6440f7711..f56e5339a 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,6 @@ buildscript { ext.abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2] repositories { - maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.google.com' } jcenter() google() @@ -43,9 +42,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'io.fabric.tools:gradle:1.29.0' classpath "io.realm:realm-gradle-plugin:$realm_version" } } @@ -55,7 +54,6 @@ allprojects { google() jcenter() maven { url "https://jitpack.io" } - maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.google.com' } maven { name 'glide-snapshot' diff --git a/presentation/build.gradle b/presentation/build.gradle index 7ae8cd88f..029e65a37 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -186,8 +186,7 @@ dependencies { implementation project(':data') implementation project(':domain') - withAnalyticsImplementation 'com.google.firebase:firebase-core:16.0.9' - withAnalyticsImplementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' + withAnalyticsImplementation 'com.google.firebase:firebase-crashlytics:17.3.0' noAnalyticsDebug project(path: ':data', configuration: 'noAnalyticsDebug') noAnalyticsRelease project(path: ':data', configuration: 'noAnalyticsRelease') @@ -196,6 +195,6 @@ dependencies { } if (getGradle().getStartParameter().getTaskRequests().toString().contains("WithAnalytics")) { - apply plugin: 'io.fabric' apply plugin: 'com.google.gms.google-services' + apply plugin: 'com.google.firebase.crashlytics' } diff --git a/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/CrashlyticsTree.kt b/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/CrashlyticsTree.kt index 5dcec3685..af0d9c969 100644 --- a/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/CrashlyticsTree.kt +++ b/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/CrashlyticsTree.kt @@ -18,15 +18,25 @@ */ package com.moez.QKSMS.common.util -import com.crashlytics.android.Crashlytics +import android.util.Log +import com.google.firebase.crashlytics.FirebaseCrashlytics import timber.log.Timber class CrashlyticsTree : Timber.Tree() { override fun log(priority: Int, tag: String?, message: String?, throwable: Throwable?) { - Crashlytics.log(message) + val crashlytics = FirebaseCrashlytics.getInstance() + val priorityString = when (priority) { + Log.VERBOSE -> "V" + Log.DEBUG -> "D" + Log.INFO -> "I" + Log.WARN -> "W" + Log.ERROR -> "E" + else -> "WTF" + } - throwable?.run(Crashlytics::logException) + crashlytics.log("$priorityString/$tag: $message") + throwable?.run(crashlytics::recordException) } } -- GitLab From 0472535025fa0e4934aec3f70daf055770bb1227 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 1 Jan 2021 22:56:21 -0500 Subject: [PATCH 154/213] Set cursor to end when cancelling message Closes #1484 --- .../java/com/moez/QKSMS/feature/compose/ComposeActivity.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 0e003bc6c..8cad8642f 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 @@ -331,7 +331,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { startActivityForResult(Intent.createChooser(intent, null), AttachPhotoRequestCode) } - override fun setDraft(draft: String) = binding.message.setText(draft) + override fun setDraft(draft: String) { + binding.message.setText(draft) + binding.message.setSelection(draft.length) + } override fun scrollToMessage(id: Long) { messageAdapter.data?.second -- GitLab From 9369c81586f631278e03096faf551956df43b1a9 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 2 Jan 2021 00:47:36 -0500 Subject: [PATCH 155/213] Check if we have valid initials before determining avatar icon visibility Credit to @salehsagharchi in https://github.com/moezbhatti/qksms/pull/1622 --- .../com/moez/QKSMS/common/widget/AvatarView.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 05e37b6ce..b5a06769b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -84,15 +84,15 @@ class AvatarView @JvmOverloads constructor( binding.initial.setTextColor(theme.textPrimary) binding.icon.setTint(theme.textPrimary) - if (fullName?.isNotEmpty() == true) { - val initials = fullName - ?.substringBefore(',') - ?.split(" ").orEmpty() - .filter { name -> name.isNotEmpty() } - .map { name -> name[0] } - .filter { initial -> initial.isLetterOrDigit() } - .map { initial -> initial.toString() } + val initials = fullName + ?.substringBefore(',') + ?.split(" ").orEmpty() + .filter { name -> name.isNotEmpty() } + .map { name -> name[0] } + .filter { initial -> initial.isLetterOrDigit() } + .map { initial -> initial.toString() } + if (initials.isNotEmpty()) { binding.initial.text = if (initials.size > 1) initials.first() + initials.last() else initials.first() binding.icon.visibility = GONE } else { -- GitLab From 272ae27cc959e25236b594655009276285017af5 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 2 Jan 2021 10:57:06 -0500 Subject: [PATCH 156/213] Fix sharing to groups from simple contacts Closes #1689 --- .../com/moez/QKSMS/feature/compose/ComposeActivityModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index aff7afa40..780787cfc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -51,7 +51,7 @@ class ComposeActivityModule { ?.decodedDataString() ?.substringAfter(':') // Remove scheme ?.replaceAfter("?", "") // Remove query - ?.split(",") + ?.split(",", ";") ?: listOf() } -- GitLab From 455de368e6c14bbe235f212f0f8eb5805aa405e2 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 2 Jan 2021 14:52:02 -0500 Subject: [PATCH 157/213] Fix "View more conversations" button on widget Closes #1516 --- .../java/com/moez/QKSMS/feature/main/MainViewModel.kt | 1 + .../com/moez/QKSMS/feature/widget/WidgetAdapter.kt | 11 ++++++----- .../com/moez/QKSMS/feature/widget/WidgetProvider.kt | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index 20933a823..e1c2fc0e4 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -160,6 +160,7 @@ class MainViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe { intent -> when (intent.getStringExtra("screen")) { + "compose" -> navigator.showConversation(intent.getLongExtra("threadId", 0)) "blocking" -> navigator.showBlockedConversations() } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt index e31596b52..325771ec2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt @@ -18,8 +18,8 @@ */ package com.moez.QKSMS.feature.widget -import android.app.PendingIntent import android.appwidget.AppWidgetManager +import android.content.ComponentName import android.content.Context import android.content.Intent import android.text.SpannableStringBuilder @@ -34,6 +34,7 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.getColorCompat +import com.moez.QKSMS.feature.compose.ComposeActivity import com.moez.QKSMS.feature.main.MainActivity import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.model.Contact @@ -163,7 +164,9 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { remoteViews.setTextViewText(R.id.snippet, boldText(snippet, conversation.unread)) // Launch conversation on click - val clickIntent = Intent().putExtra("threadId", conversation.id) + val clickIntent = Intent() + .putExtra("screen", "compose") + .putExtra("threadId", conversation.id) remoteViews.setOnClickFillInIntent(R.id.conversation, clickIntent) return remoteViews @@ -171,11 +174,9 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { private fun getOverflowView(): RemoteViews { val view = RemoteViews(context.packageName, R.layout.widget_loading) - val intent = Intent(context, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) view.setTextColor(R.id.loadingText, textSecondary) view.setTextViewText(R.id.loadingText, context.getString(R.string.widget_more)) - view.setOnClickPendingIntent(R.id.loading, pendingIntent) + view.setOnClickFillInIntent(R.id.loading, Intent()) return view } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetProvider.kt b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetProvider.kt index 0bb09316c..696823f16 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetProvider.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetProvider.kt @@ -149,7 +149,7 @@ class WidgetProvider : AppWidgetProvider() { remoteViews.setOnClickPendingIntent(R.id.compose, composePI) // Conversation intent - val startActivityIntent = Intent(context, ComposeActivity::class.java) + val startActivityIntent = Intent(context, MainActivity::class.java) val startActivityPendingIntent = PendingIntent.getActivity(context, 0, startActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT) remoteViews.setPendingIntentTemplate(R.id.conversations, startActivityPendingIntent) -- GitLab From 1accd46a1048ee24a16f287c7c950036286adc7d Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 2 Jan 2021 15:55:32 -0500 Subject: [PATCH 158/213] Don't modify PendingIntent requestCode unnecessarily --- .../common/util/NotificationManagerImpl.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index c70ff96ee..f94eb3d34 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -114,10 +114,10 @@ class NotificationManagerImpl @Inject constructor( val taskStackBuilder = TaskStackBuilder.create(context) .addParentStack(ComposeActivity::class.java) .addNextIntent(contentIntent) - val contentPI = taskStackBuilder.getPendingIntent(threadId.toInt() + 10000, PendingIntent.FLAG_UPDATE_CURRENT) + val contentPI = taskStackBuilder.getPendingIntent(threadId.toInt(), PendingIntent.FLAG_UPDATE_CURRENT) val seenIntent = Intent(context, MarkSeenReceiver::class.java).putExtra("threadId", threadId) - val seenPI = PendingIntent.getBroadcast(context, threadId.toInt() + 20000, seenIntent, + val seenPI = PendingIntent.getBroadcast(context, threadId.toInt(), seenIntent, PendingIntent.FLAG_UPDATE_CURRENT) // We can't store a null preference, so map it to a null Uri if the pref string is empty @@ -232,7 +232,7 @@ class NotificationManagerImpl @Inject constructor( when (action) { Preferences.NOTIFICATION_ACTION_ARCHIVE -> { val intent = Intent(context, MarkArchivedReceiver::class.java).putExtra("threadId", threadId) - val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 30000, intent, + val pi = PendingIntent.getBroadcast(context, threadId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_archive_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_ARCHIVE).build() @@ -243,7 +243,7 @@ class NotificationManagerImpl @Inject constructor( val intent = Intent(context, DeleteMessagesReceiver::class.java) .putExtra("threadId", threadId) .putExtra("messageIds", messageIds) - val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 40000, intent, + val pi = PendingIntent.getBroadcast(context, threadId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_delete_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_DELETE).build() @@ -251,7 +251,7 @@ class NotificationManagerImpl @Inject constructor( Preferences.NOTIFICATION_ACTION_BLOCK -> { val intent = Intent(context, BlockThreadReceiver::class.java).putExtra("threadId", threadId) - val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 50000, intent, + val pi = PendingIntent.getBroadcast(context, threadId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_block_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MUTE).build() @@ -259,7 +259,7 @@ class NotificationManagerImpl @Inject constructor( Preferences.NOTIFICATION_ACTION_READ -> { val intent = Intent(context, MarkReadReceiver::class.java).putExtra("threadId", threadId) - val pi = PendingIntent.getBroadcast(context, threadId.toInt() + 60000, intent, + val pi = PendingIntent.getBroadcast(context, threadId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_check_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ).build() @@ -270,7 +270,7 @@ class NotificationManagerImpl @Inject constructor( getReplyAction(threadId) } else { val intent = Intent(context, QkReplyActivity::class.java).putExtra("threadId", threadId) - val pi = PendingIntent.getActivity(context, threadId.toInt() + 70000, intent, + val pi = PendingIntent.getActivity(context, threadId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action .Builder(R.drawable.ic_reply_white_24dp, actionLabels[action], pi) @@ -282,7 +282,7 @@ class NotificationManagerImpl @Inject constructor( val address = conversation.recipients[0]?.address val intentAction = if (permissions.hasCalling()) Intent.ACTION_CALL else Intent.ACTION_DIAL val intent = Intent(intentAction, Uri.parse("tel:$address")) - val pi = PendingIntent.getActivity(context, threadId.toInt() + 80000, intent, + val pi = PendingIntent.getActivity(context, threadId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) NotificationCompat.Action.Builder(R.drawable.ic_call_white_24dp, actionLabels[action], pi) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_CALL).build() @@ -335,9 +335,9 @@ class NotificationManagerImpl @Inject constructor( val contentIntent = Intent(context, ComposeActivity::class.java).putExtra("threadId", threadId) val taskStackBuilder = TaskStackBuilder.create(context) - taskStackBuilder.addParentStack(ComposeActivity::class.java) - taskStackBuilder.addNextIntent(contentIntent) - val contentPI = taskStackBuilder.getPendingIntent(threadId.toInt() + 90000, PendingIntent.FLAG_UPDATE_CURRENT) + .addParentStack(ComposeActivity::class.java) + .addNextIntent(contentIntent) + val contentPI = taskStackBuilder.getPendingIntent(threadId.toInt(), PendingIntent.FLAG_UPDATE_CURRENT) val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId)) .setContentTitle(context.getString(R.string.notification_message_failed_title)) @@ -356,7 +356,7 @@ class NotificationManagerImpl @Inject constructor( private fun getReplyAction(threadId: Long): NotificationCompat.Action { val replyIntent = Intent(context, RemoteMessagingReceiver::class.java).putExtra("threadId", threadId) - val replyPI = PendingIntent.getBroadcast(context, threadId.toInt() + 70000, replyIntent, + val replyPI = PendingIntent.getBroadcast(context, threadId.toInt(), replyIntent, PendingIntent.FLAG_UPDATE_CURRENT) val title = context.resources.getStringArray(R.array.notification_actions)[ -- GitLab From 055e57ed5b0248f592d28210231da2683f7a567e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 2 Jan 2021 17:46:12 -0500 Subject: [PATCH 159/213] Update translations --- .../src/main/res/values-ar/strings.xml | 48 ++- .../src/main/res/values-bn/strings.xml | 312 +++++++------- .../src/main/res/values-cs/strings.xml | 38 +- .../src/main/res/values-da/strings.xml | 32 +- .../src/main/res/values-de/strings.xml | 34 +- .../src/main/res/values-el/strings.xml | 40 +- .../src/main/res/values-es/strings.xml | 108 ++--- .../src/main/res/values-fa/strings.xml | 284 +++++++------ .../src/main/res/values-fi/strings.xml | 174 ++++---- .../src/main/res/values-fr/strings.xml | 34 +- .../src/main/res/values-hi/strings.xml | 42 +- .../src/main/res/values-hr/strings.xml | 41 +- .../src/main/res/values-hu/strings.xml | 44 +- .../src/main/res/values-in/strings.xml | 29 +- .../src/main/res/values-it/strings.xml | 40 +- .../src/main/res/values-iw/strings.xml | 60 ++- .../src/main/res/values-ja/strings.xml | 31 +- .../src/main/res/values-ko/strings.xml | 79 ++-- .../src/main/res/values-lt/strings.xml | 42 +- .../src/main/res/values-nb/strings.xml | 44 +- .../src/main/res/values-ne/strings.xml | 40 +- .../src/main/res/values-nl/strings.xml | 148 ++++--- .../src/main/res/values-pl/strings.xml | 32 +- .../src/main/res/values-pt-rBR/strings.xml | 146 ++++--- .../src/main/res/values-pt/strings.xml | 28 +- .../src/main/res/values-ro/strings.xml | 37 +- .../src/main/res/values-ru/strings.xml | 48 ++- .../src/main/res/values-sk/strings.xml | 386 +++++++++--------- .../src/main/res/values-sl/strings.xml | 80 ++-- .../src/main/res/values-sr/strings.xml | 41 +- .../src/main/res/values-sv/strings.xml | 70 ++-- .../src/main/res/values-th/strings.xml | 39 +- .../src/main/res/values-tl/strings.xml | 96 +++-- .../src/main/res/values-tr/strings.xml | 32 +- .../src/main/res/values-uk/strings.xml | 30 +- .../src/main/res/values-ur/strings.xml | 40 +- .../src/main/res/values-vi/strings.xml | 47 ++- .../src/main/res/values-zh-rCN/strings.xml | 89 ++-- .../src/main/res/values-zh/strings.xml | 49 ++- 39 files changed, 1798 insertions(+), 1236 deletions(-) diff --git a/presentation/src/main/res/values-ar/strings.xml b/presentation/src/main/res/values-ar/strings.xml index 2fbeee309..d545c94d3 100644 --- a/presentation/src/main/res/values-ar/strings.xml +++ b/presentation/src/main/res/values-ar/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s محددة، غيّر شريحة SIM إرسال الرسالة الإرسال جارٍ… @@ -229,9 +230,10 @@ لاشيء أرشفة حذف - مكالمة - تعليمها مقروءة - التحديد كمقروء + Block + Call + Mark read + Mark unread تأكيدات الإستلام تأكيد أنه تم إرسال الرسائل بنجاح @@ -241,6 +243,20 @@ حذف حركات التشكيل من المحارف في الرسائل النصية الصادرة أرقام الهواتف النقالة فقط عند إنشاء رسالة، أظهر فقط أرقام الهواتف النقالة + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After %d days + After 1 day + After %d days + After %d days + After %d days + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply الضغط التلقائي لمرفقات رسائل الوسائط @@ -330,7 +346,7 @@ QKSMS قيد التطوير النشط ، مشتراك سيتضمن كل ميزات QKSMS+ المستقبلية! تحميل… عرض المزيد من المحادثات - علّمها مقروءة + Mark read مكالمة حذف إظهار المزيد @@ -341,10 +357,12 @@ تطبيق لا شيء - علّمها مقروءة - الرد - اتصال - حذف + Archive + Delete + Block + Call + Mark read + Reply نعم الاستمرار @@ -387,9 +405,9 @@ دون تأخير - قصير - متوسط - طويل + 3 seconds + 5 seconds + 10 seconds تلقائي diff --git a/presentation/src/main/res/values-bn/strings.xml b/presentation/src/main/res/values-bn/strings.xml index f45596674..93a7c2860 100644 --- a/presentation/src/main/res/values-bn/strings.xml +++ b/presentation/src/main/res/values-bn/strings.xml @@ -1,5 +1,4 @@ - নতুন আলাপ - কম্পোজ করুন + কম্পোজ করো শর্টকাট নিষ্ক্রিয় আর্কাইভকৃত সেটিংস - নোটিফিকেশন + বিজ্ঞপ্তি থিম - ইনবক্সে খুঁজুন… - নাম বা নম্বর টাইপ করুন - এড়িয়ে যান - চালিয়ে যান - Add person - কল করুন + ইনবক্সে খুঁজো… + নাম বা নম্বর টাইপ করো + এড়িয়ে যাও + আগাও + কাউকে যুক্ত করো + কল করো বিস্তারিত - গ্যালারীতে সেভ করুন - Share - নেভিগেশন ড্রয়ার খুলুন - নির্বাচিত %d - ক্লিয়ার + গ্যালারিতে সংরক্ষণ করো + শেয়ার + ন্যাভিগেশন ড্রয়ার খুলো + %d নির্বাচিত + পরিষ্কার আর্কাইভ আনআর্কাইভ - মুছে ফেলুন - Add to contacts - উপরে থাকুক - সরিয়ে নিন - পড়া হয়েছে - পড়া হয়নি - ব্লক করুন - মেসেজ সিঙ্ক হচ্ছে... - আপনি: %s - Draft: %s - মেসেজের ভিতর পাওয়া গেছে - %d মেসেজ - আপনার আলাপ এখানে দেখা যাবে - রেজাল্ট পাওয়া যায়নি - আপনার আর্কাইভ করা আলাপ এখানে দেখা যাবে - নতুন আলাপ শুরু করুন - আবার texting ভালবাসুন - QKSMS-কে আপনার ডিফল্ট এসএমএস অ্যাপ করুন - পরিবর্তন করুন + মুছে ফেলো + পরিচিততে যুক্ত করো + উপরে রাখো + উপর থেকে সরাও + পড়া হয়েছে + পড়া হয়নি + অবরুদ্ধ + বার্তাগুলি সিঙ্ক হচ্ছে… + তুমি: %s + খসড়া + বার্তার ভিতর পাওয়া গেছে + %dটি বার্তা + তোমার আলাপ এখানে দেখা যাবে + কোনো ফলাফল নেই + তোমার আর্কাইভ করা আলাপ এখানে দেখা যাবে + নতুন আলাপ শুরু করো + আবার বার্তাকে ভালবাসুন + QKSMS-কে তোমার ডিফল্ট এসএমএস অ্যাপ করো + পরিবর্তন করো অনুমতি প্রয়োজন - QKSMS-এর এসএমএস বার্তাগুলো পাঠানো এবং দেখানো অনুমতি প্রয়োজন - QKSMS-এর আপনার পরিচিতিগুলি দেখার জন্য অনুমতি প্রয়োজন - মঞ্জুর করুন + QKSMS-এর এসএমএস বার্তাগুলো পাঠানোর এবং দেখার অনুমতি প্রয়োজন + QKSMS-এর তোমার পরিচিতিগুলি দেখার জন্য অনুমতি প্রয়োজন + মঞ্জুর করো ইনবক্স - আর্কাইভ করা + আর্কাইভকৃত পরিকল্পিত - ব্লক করা - আরও + অবরুদ্ধ + আরো সেটিংস সাহায্য & প্রতিক্রিয়া - বন্ধুদের আমন্ত্রণ জানান - দারুণ নতুন ফিচার আনলক করুন এবং উন্নয়ন সহায়তা করুন - QKSMS উপভোগ করছেন? - আমাদের প্রতি ভালবাসে দেখাতে Google Play-তে আমাদের মূল্যায়ন করুন! + বন্ধুদের আমন্ত্রণ জানাও + অসাধারণ সব নতুন বৈশিষ্ট্য পাও এবং উন্নয়নে সাহায্য করো + QKSMS উপভোগ করছো? + আমাদের প্রতি ভালবাসে দেখাতে Google Play-এ আমাদের মূল্যায়ন করো! ঠিক আছে! - বাতিল করুন - মুছে ফেলুন + বাতিল করো + মুছে ফেলো - আপনি কি নিশ্চিত যে এই আলাপটি আপনি ডিলিট করতে চান? - আপনি কি নিশ্চিত যে %d টি আলাপ আপনি ডিলিট করতে চান? + তুমি কি নিশ্চিত যে এই আলাপটি তুমি মুছে ফেলতে চাও? + তুমি কি নিশ্চিত যে %d টি আলাপ তুমি মুছতে চাও? - পাঠ্য অনুলিপি করুন - সামনে পাঠান - মুছে ফেলুন + লেখা অনুলিপি করো + অন্যকে পাঠাও + মুছো - Choose a phone number - %s ∙ Default - Just once - Always - নির্বাচিত %d - %2$d এর %1$d ফলাফল - গ্রুপ বার্তা হিসেবে প্রেরণ করুন + ফোন নম্বর নির্বাচন করো + %s ∙ পূর্বনির্ধারিত + মাত্র একবার + সর্বদা + %d নির্বাচিত + %2$d এর %1$d টি ফলাফল + গ্রুপ বার্তা হিসেবে পাঠাও প্রাপক এবং উত্তর সবার চোখে পড়বে - এখানে আপনার কথোপকথন শুরু হয়। সুন্দর কিছু বলুন! + এখানে তোমার আলাপ শুরু হয়। সুন্দর কিছু বলো! পরিচিতি কার্ড জন্য পরিকল্পিত নির্বাচিত সময় অবশ্য ভবিষ্যতে হতে হবে! পরিকল্পিত বার্তাপ্রেরক ব্যবহার করতে আপনাকে অবশ্যই QKSMS + আনলক করতে হবে - পরিকল্পিত বার্তাগুলিতে যোগ করা হয়েছে - একটি বার্তা লিখুন… + পূর্বসূচিত বার্তাগুলোতে যোগ করা হয়েছে + একটি বার্তা লিখো… পাঠ্য অনুলিপি করুন সামনে পাঠান মুছে ফেলুন @@ -121,24 +120,26 @@ সংযুক্তি যোগ করুন ছবি সংযুক্ত করুন একটি ছবি নিন - পরবর্তীতে পাঠানোর জন্য বার্তা পরিকল্পনা করুন + বার্তা পাঠানোর সময়সূচি নির্ধারণ করো একটি পরিচিতি সংযুক্ত করুন পরিচিতি পড়তে ত্রুটি + + সিম %1$d (%2$s) নির্বাচিত নির্বাচিত, %s সিম কার্ড পরিবর্তন করুন - বার্তাটি প্রেরণ করুন + বার্তা পাঠাও পাঠানো হচ্ছে… প্রেরিত হয়েছে: %s - পাঠাতে ব্যর্থ. আবার চেষ্টা করতে ট্যাপ করুন + পাঠাতে ব্যর্থ। আবার চেষ্টা করতে টিপ দাও বিস্তারিত - Address copied - কথোপকথনের শিরোনাম + ঠিকানা অনুলিপিত + আলাপের শিরোনাম নোটিফিকেশন থিম আর্কাইভ আনআর্কাইভ ব্লক করুন আনব্লক করুন - কথোপকথন মুছে ফেলুন + আলাপ মুছে ফেলো মিডিয়া লোড করা যায়নি গ্যালারীতে সেভ করুন ব্যাকআপ ও পুনরুদ্ধার @@ -160,29 +161,29 @@ কোন ব্যাকআপ পাওয়া যায়নি %d মেসেজ - %d মেসেজ গুলো + %dটি মেসেজ এইমুহুর্তে, ব্যাকআপ এবং পুনরুদ্ধার শুধুমাত্র এসএমএস এ করতে পারবেন । এমএমএস সাপর্ট ও শিডিউল ব্যাকআপ শীঘ্রই আসছে! ব্যাকআপ করুন ব্যাক-আপ পার্স করা হচ্ছে… %d/%d বার্তা ব্যাক-আপ সংরক্ষণ করা হচ্ছে… - বার্তা সিঙ্ক করা হচ্ছে… + বার্তাগুলো সিঙ্ক করা হচ্ছে… শেষ হয়েছে! ব্যাকআপ ও পুনরুদ্ধার পরিকল্পিত - স্বয়ংক্রিয়ভাবে একটি বার্তা পাঠান, ঠিক যে মুহূর্তে আপনি চান - হেই! আপনার জন্মদিন যেন কবে? - এটি ২৩শে ডিসেম্বরে - শুভ জন্মদিন! আপনার জন্মদিনকে স্মরণ রাখায় আমি কি আপনার একজন ভাল বন্ধু নই + স্বয়ংক্রিয়ভাবে একটি বার্তা পাঠাও, ঠিক যে মুহূর্তে তুমি চাও + ওই! তোমার জন্মদিন যেন কবে? + ওটা ২৩শে ডিসেম্বরে + শুভ জন্মদিন! দেখো তোমার জন্মদিন মনে রাখায় আমি কতো ভালো একটা বন্ধু ২৩শে ডিসেম্বর পাঠানো হচ্ছে  - পরবর্তীতে পাঠানোর জন্য একটি বার্তা পরিকল্পনা করুন + পরবর্তীতে পাঠানোর জন্য একটি বার্তা পরিকল্পনা করো পরিকল্পিত বার্তা - এখন পাঠান - Copy text - Delete + এখন পাঠাও + লেখা অনুলিপি করো + মুছো উপস্থিতি সাধারণ @@ -192,7 +193,7 @@ সম্পূর্ণ কালো রাত্রি মোড শুরুর সময় শেষের সময় - Automatic contact colors + যোগাযোগদের স্বয়ংক্রিয় রং ফন্টের আকার সিস্টেম ফন্ট ব্যবহার করুন স্বয়ংক্রিয় ইমোজি @@ -203,7 +204,7 @@ বাটন ২ বাটন ৩ নোটিফিকেশন প্রিভিউ - Wake screen + স্ক্রিন জাগাও কম্পন শব্দ কোনোটা না @@ -211,9 +212,9 @@ নতুন বার্তার জন্য পপ-আপ সরিয়ে দিতে ট্যাপ করুন এটি বন্ধ করার জন্য পপআপের বাইরে ট্যাপ করুন - বিলম্বে পাঠানো + বিলম্বে পাঠাও সোয়াইপের জন্য অ্যাকশনগুলি - কথোপকথনের জন্য সোয়াইপ অ্যাকশনগুলি কনফিগার করুন + আলাপের জন্য সোয়াইপ সংকেতগুলো পরিবর্তন করো ডানে সোয়াইপ করুন বামে সোয়াইপ করুন পরিবর্তন করুন @@ -221,56 +222,67 @@ কোনোটা না আর্কাইভ করুন মুছে ফেলুন + অবরুদ্ধ করো কল - পঠিত হিসেবে চিহ্নিত করুন - না পড়া চিহ্নিত কর + পড়া হয়েছে চিহ্নিত করো + পড়া হয়নি চিহ্নিত করো ডেলিভারি নিশ্চিতকরন - নিশ্চিত করুন যে বার্তা সফলভাবে পাঠানো হয়েছে + নিশ্চিত করো যে বার্তা সফলভাবে পাঠানো হয়েছে স্বাক্ষর - Add a signature to the end of your messages + তোমার বার্তাগুলোর শেষে একটা স্বাক্ষর যোগ করো কথার টান সরিয়ে দিন - বহির্গামী এসএমএস বার্তার অক্ষর থেকে কথার টান সরিয়ে দিন + বহির্গামী এসএমএস বার্তার অক্ষর থেকে কথার টান সরিয়ে নাও(accent) শুধু মোবাইল নম্বর - বার্তা লিখার করার সময়, শুধুমাত্র মোবাইল নম্বর দেখান - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply + বার্তা লিখার করার সময়, শুধুমাত্র মোবাইল নম্বর দেখাও + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + + দীর্ঘ বার্তা এমএমএস হিসেবে পাঠাও + যদি তোমার দীর্ঘ বার্তা পাঠানো ব্যর্থ হয় বা ভুল ক্রমানুসারে পাঠানো হয় তবে তুমি তার পরিবর্তে বার্তাগুলো এমএমএস বার্তা হিসাবে প্রেরণ করতে পারো। অতিরিক্ত চার্জ প্রযোজ্য হতে পারে এমএমএসের সংযুক্তি সক্রিয়ভাবে সংকোচন করুন - বার্তাগুলি সিঙ্ক করুন - আপনার বার্তাগুলি অ্যান্ড্রয়েডের নিজস্ব এসএমএস এর ডাটাবেস সাতে পুনরায় সিঙ্ক করুন + বার্তাগুলি সিঙ্ক করো + তোমার বার্তাগুলি অ্যান্ড্রয়েডের নিজস্ব এসএমএস এর ডাটাবেসের সাথে পুনরায় সিঙ্ক করো QKSMS সম্পর্কে সংস্করণ %s ডিবাগ লগিং চালু করা হয়ছে ডিবাগ লগিং বন্ধ করা হয়ছে দৈর্ঘ্য (সেকেন্ডে) লিখুন - Blocking - Drop messages - Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + অবরুদ্ধ হচ্ছে + বার্তা ফেলে দাও + অবরোধকৃত প্রেরকদের বার্তা লুকানোর জায়গায় ফেলে দাও + অবরুদ্ধ কথাবার্তা + অবরুদ্ধকরণ ব্যবস্থাপক QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers - স্বয়ংক্রিয়ভাবে \"Should I Answer\" অ্যাপ্লিকেশন ব্যবহার করে অযাচিত সংখ্যার বার্তাগুলি ফিল্টার করুন - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here - Block - Unblock + QKSMS-এর অন্তর্ভুক্ত অবরোধী সক্ষমতা(blocking) + তোমার কল আর বার্তা একটাই সুবিধাজনক জায়গা থেকে ফিল্টার করো বা ছাঁকো! Community IQ™ দিয়ে অবাঞ্ছিত বার্তা আটকানো যায় সমাজে পরিচিত স্প্যামারদের থেকে + স্বয়ংক্রিয়ভাবে \"Should I Answer\"(আমার কি উত্তর দেয়া উচিত?) অ্যাপ্লিকেশন ব্যবহার করে অযাচিত নম্বর-এর বার্তাগুলি ফিল্টার করো + অবরুদ্ধ নম্বরগুলো অনুলিপি করো + %s-এ এগিয়ে যাও এবং তোমার আগের অবরোধকৃত নম্বরগুলো অনুলিপি করো + অবরুদ্ধ নম্বরগুলো + তোমার অবরোধকৃত নম্বরগুলো এখানে আসবে + একটি নতুন নম্বর অবরোধ করো + বার্তা অবরোধ করো এদের থেকে + ফোন নম্বর + অবরুদ্ধ করুন + অবরুদ্ধ করা বার্তা + অবরুদ্ধ করা বার্তা এখানে আসবে + অবরুদ্ধ + অবরুদ্ধতা বাতিল করুন - Continue to %s and block this number - Continue to %s and block these numbers + %s-এ এগিয়ে যাও এবং এই নম্বর অবরোধ করো + %s-এ এগিয়ে যাও ও এই নম্বরগুলো অবরোধ করো - Continue to %s and allow this number - Continue to %s and allow these numbers + %s-এ এগিয়ে যাও ও এই নম্বর থেকে বার্তা আসতে দাও + %s-এ এগিয়ে যাও ও এই নম্বরগুলো থেকে বার্তা আসতে দাও সম্পর্কে সংস্করণ @@ -295,42 +307,44 @@ Material Design প্যালেট পাওয়া যায় না এমন সুন্দর রঙয়ের থীম আনলক করুন কাস্টম সক্রিয়-ইমোজি কাস্টম সক্রিয়-ইমোজি শর্টকাট তৈরি করুন - বার্তা ব্যাক আপ - আপনার বার্তাগুলো স্বয়ংক্রিয়ভাবে ব্যাক আপ করুন। এবারআপনার ফোন পরিবর্তন বা আপনার ফোন নষ্ট হওয়ায় আপনাকে কখনো এসএমএস ইতিহাস হারানোর বিষয়ে উদ্বিগ্ন হতে হবে না + বার্তাগুলোর ব্যাকআপ + তোমার বার্তাগুলো স্বয়ংক্রিয়ভাবে ব্যাকআপ করো। এবার তোমার ফোন পরিবর্তন বা নষ্ট হওয়ায় এসএমএস-এর ইতিহাস হারানো নিয়ে কখনো উদ্বিগ্ন হতে হবে না পরিকল্পিত বার্তাগুলি - পরিকল্পিত বার্তাগুলি স্বয়ংক্রিয়ভাবে একটি নির্দিষ্ট সময় ও তারিখ প্রেরণ করা হবে - বিলম্বে পাঠানো - আপনার বার্তা পাঠানোর আগে কয়েক সেকেন্ড অপেক্ষা করুন + পরিকল্পিত বার্তাগুলি স্বয়ংক্রিয়ভাবে একটি নির্দিষ্ট সময় ও তারিখে প্রেরণ করা হবে + বিলম্বে পাঠাও + তোমার বার্তা পাঠানোর আগে কয়েক সেকেন্ড অপেক্ষা করো স্বয়ংক্রিয় রাত্রি মোড দিন সময় উপর ভিত্তি করে নাইট মোড সক্ষম করুন উন্নীত আনব্লক করা - সেই সব বার্তা ব্লক কারুন যার কোন শব্দ বা নমুনার সাথে মিলে + সেই সব বার্তা ব্লক(অবরোধ) করো যা কোন শব্দ বা নমুনার সাথে মিলে সক্রিয়ভাবে সামনে পাঠান - নির্দিষ্ট প্রেরকের বার্তাগুলো স্বয়ংক্রিয়ভাবে সামনে পাঠান + নির্দিষ্ট প্রেরকের বার্তাগুলো স্বয়ংক্রিয়ভাবে সামনে পাঠাও(ফরওয়ার্ড) স্বয়ংক্রিয়-উত্তর - আগত বার্তা জন্য স্বয়ংক্রিয়ভাবে পূর্বনির্ধারিত প্রতিক্রিয়া পাঠান + আগত বার্তার জন্য স্বয়ংক্রিয়ভাবে পূর্বনির্ধারিত প্রতিক্রিয়া পাঠাও আরও QKSMS সক্রিয় উন্নয়ন অধীনে এবং QKSMS+ এর সকাল ভবিষ্যত ফিচার আপনার কেনা অ্যাপেে অন্তর্ভুক্ত থাকবে! লোড হচ্ছে… - আরও কথোপকথন প্রদর্শন করুন - পড়া চিহ্নিত করুন + আরও আলাপ দেখো + পড়া হয়েছে কল করুন মুছে ফেলুন আরও দেখান আরও কম প্রদর্শন করুন - চলমান কথোপকথন + আলাপ খুলো উপকরণ হেক্স প্রয়োগ করুন কোনোটা না - পঠিত হিসেবে চিহ্নিত করুন - উত্তর দিন + আর্কাইভ + মুছুন + অবরুদ্ধ কল - মুছে ফেলুন + পড়া হয়েছে + প্রতুত্তর - Yes - Continue + হ্যাঁ + এগিয়ে যানও বাতিল করুন মুছে ফেলুন সেভ করুন @@ -339,14 +353,14 @@ সেট করুন বাতিল করো অনুলিপি করা হয়েছে - আর্কাইভ করা কথোপকথন + আর্কাইভ করা আলাপ এই ব্যবহার করতে আপনাকে অবশ্যই QKSMS + আনলক করতে হবে - %s নতুন বার্তা - নতুন বার্তা + নতুন বার্তা + %sটি নতুন বার্তা - বার্তা পাঠাতে অক্ষম - %s এর বার্তাটি পাঠাতে ব্যর্থ হয়েছে + বার্তা পাঠাতে ব্যর্থ + %s এর কাছে বার্তাটি পাঠাতে ব্যর্থ পদ্ধতি অক্ষম @@ -354,7 +368,7 @@ স্বয়ংক্রিয় - নাম এবং বার্তা প্রদর্শন করুন + নাম এবং বার্তা প্রদর্শন করো নাম দেখান কন্টেন্ট আড়াল করুন @@ -366,19 +380,19 @@ কোনো বিলম্ব নেই - সংক্ষিপ্ত - মাঝারি - দীর্ঘ + ৩ সেকেন্ড + ৫ সেকেন্ড + ১০ সেকেন্ড - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + স্বয়ংক্রিয় + ১০০কিবা + ২০০কিবা + ৩০০কিবা + ৬০০কিবা + ১০০০কিবা + ২০০০কিবা + সংকোচন নেই(compression) ঠিক আছে @@ -391,7 +405,7 @@ না ভালোবাসি তোমাকে দুঃখিত - হেহে - তা ঠিক। + হাহা + তা ঠিক আছে। diff --git a/presentation/src/main/res/values-cs/strings.xml b/presentation/src/main/res/values-cs/strings.xml index 6ae6060a8..301b4bf52 100644 --- a/presentation/src/main/res/values-cs/strings.xml +++ b/presentation/src/main/res/values-cs/strings.xml @@ -1,5 +1,4 @@ - + Vybrána SIM %1$d (%2$s) %s vybráno, změňte SIM kartu Odeslat zprávu Odesílání… @@ -140,7 +141,7 @@ Obnovit z archivu Blokovat Odblokovat - Odstranit konverzaci + Smazat konverzaci Soubor nelze načíst Uloženo do galerie Záloha a obnovení @@ -225,8 +226,9 @@ Žádná Archivovat Smazat + Blokovat Volat - Přečteno + Označit jako přečtené Označit jako nepřečtené Potvrzení o doručení @@ -237,6 +239,18 @@ Odstranit diakritiku v odesílaných SMS zprávách Pouze mobilní čísla Při psaní zprávy zobrazit pouze mobilní telefonní čísla + Automaticky odstranit staré zprávy + Zprávy budou odstraněny po zadaném počtu dní + Počet dní + Nikdy + Odstranit staré zprávy automaticky? + Budete-li pokračovat, bude nyní odstraněno %1$d zpráv + + Po 1 dni + Po %d dnech + Po %d dnech + Po %d dnech + Odesílat dlouhé zprávy jako MMS Nedaří-li se odeslat vaše dlouhé zprávy, nebo jsou-li poslány v nesprávném pořadí, lze je odeslat jako MMS. Mohou být účtovány další poplatky. Automaticky komprimovat MMS přílohy @@ -332,15 +346,17 @@ Použít Žádné - Přečteno - Odpovědět - Volat + Archivovat Smazat + Blokovat + Volat + Označit jako přečtené + Odpovědět Ano Pokračovat Zrušit - Odstranit + Smazat Uložit Zastavit Více @@ -376,9 +392,9 @@ Bez prodlevy - Krátká - Střední - Dlouhá + 3 sekundy + 5 sekund + 10 sekund Automaticky diff --git a/presentation/src/main/res/values-da/strings.xml b/presentation/src/main/res/values-da/strings.xml index e2d01f787..f7f468573 100644 --- a/presentation/src/main/res/values-da/strings.xml +++ b/presentation/src/main/res/values-da/strings.xml @@ -1,5 +1,4 @@ - - Ny samtale + Åbn samtale Skriv Genvej deaktiveret Arkiveret @@ -49,7 +48,7 @@ Blokér Synkroniserer beskeder… Dig: %s - Udkast: %s + Udkast Resultater i beskeder %d beskeder Dine samtaler vil fremgå her @@ -124,6 +123,8 @@ Planlæg besked Vedhæft en kontakt Fejl ved læsning af kontakt + + SIM %1$d (%2$s) valgt %s valgt, skift SIM-kortet Send besked Afsender… @@ -221,6 +222,7 @@ Ingen Arkivér Slet + Blokér Ring op Markér som læst Markér som ulæst @@ -233,6 +235,16 @@ Fjern betoninger fra tegn i udgående SMS\'er Kun mobilnumre Vis kun mobilnumre ved SMS-skrivning, + Slet automatisk gamle beskeder + Beskeder slettes efter det angivne antal dage + Antal dage + Aldrig + Slet automatisk gamle beskeder? + Fortsætter du, slettes %1$d beskeder nu + + Efter 1 dag + Efter %d dage + Send lang SMS som MMS Hvis dine længere SMS\'er ikke afsendes eller afsendes i forkert rækkefølge, kan du i stedet sende dem som MMS\'er. Ekstragebyrer kan påløbe Auto-komprimér MMS-vedhæftninger @@ -324,10 +336,12 @@ Anvend Ingen - Markér som læst - Besvar - Ring op + Arkivér Slet + Blokér + Ring op + Markér som læst + Svar Ja Fortsæt @@ -366,9 +380,9 @@ Ingen forsinkelse - Kort - Mellem - Lang + 3 sekunder + 5 sekunder + 10 sekunder Automatisk diff --git a/presentation/src/main/res/values-de/strings.xml b/presentation/src/main/res/values-de/strings.xml index 4ddd2bd70..e06f954ca 100644 --- a/presentation/src/main/res/values-de/strings.xml +++ b/presentation/src/main/res/values-de/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) ausgewählt %s ausgewählt, SIM-Karte ändern Nachricht senden Wird gesendet… @@ -221,9 +222,10 @@ Keine Archivieren Löschen + Blockieren Anrufen Als gelesen markieren - Einstellung Streichbewegung + Als ungelesen markieren Zustellbestätigungen Bestätigung, dass Nachrichten erfolgreich gesendet wurden @@ -233,6 +235,16 @@ Akzente aus Zeichen in ausgehenden SMS-Nachrichten entfernen Nur Mobilfunknummern Beim Verfassen einer Nachricht nur Mobilfunknummern anzeigen + Alte Nachrichten automatisch löschen + Nachrichten werden nach der angegebenen Anzahl von Tagen gelöscht + Anzahl der Tage + Niemals + Alte Nachrichten automatisch löschen? + Wenn Sie fortfahren, werden jetzt %1$d Nachrichten gelöscht + + Nach 1 Tag + Nach %d Tagen + Lange Nachrichten als MMS senden Wenn Ihre längeren Textnachrichten nicht gesendet oder in der falsche Reihenfolge gesendet werden, können Sie sie stattdessen als MMS-Nachrichten senden. Dafür können zusätzliche Gebühren anfallen. MMS-Anhänge automatisch komprimieren @@ -324,10 +336,12 @@ Übernehmen Nichts - Als gelesen markieren - Antworten - Anrufen + Archiv Löschen + Blockieren + Anrufen + Als gelesen markieren + Antwort Ja Fortsetzen @@ -366,9 +380,9 @@ Keine Verzögerung - Kurz - Mittel - Lang + 3 Sekunden + 5 Sekunden + 10 Sekunden Automatisch diff --git a/presentation/src/main/res/values-el/strings.xml b/presentation/src/main/res/values-el/strings.xml index fa2df8978..a2884a225 100644 --- a/presentation/src/main/res/values-el/strings.xml +++ b/presentation/src/main/res/values-el/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected Επελέγη το %s, αλλάξτε την κάρτα SIM Αποστολή μηνύματος Αποστολή… @@ -221,9 +222,10 @@ None Archive Delete + Block Call - Σήμανση ως αναγνωσμένου - Σήμανση ως μη αναγνωσμένου + Mark read + Mark unread Επιβεβαιώσεις παράδοσης Confirm that messages were sent successfully @@ -233,6 +235,16 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -313,7 +325,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark as read + Mark read Call Delete Show more @@ -324,10 +336,12 @@ Apply None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -366,9 +380,9 @@ No delay - Short - Medium - Long + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-es/strings.xml b/presentation/src/main/res/values-es/strings.xml index b41dea7e4..1471c4bf8 100644 --- a/presentation/src/main/res/values-es/strings.xml +++ b/presentation/src/main/res/values-es/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) seleccionada %s seleccionado, cambia la tarjeta SIM Enviar mensaje Enviando… Entregado %s - No se envió. Pulse para volver a intentarlo + No se pudo enviar. Pulse para volver a intentarlo Detalles - Address copied + Dirección copiada Título de la conversación Notificaciones Tema @@ -181,7 +182,7 @@ Mensaje programado Enviar ahora - Copy text + Copiar texto Borrar Apariencia @@ -192,7 +193,7 @@ Modo noche negro puro Hora de inicio Hora de finalización - Automatic contact colors + Colores de contacto automáticos Tamaño de la fuente Usar fuente del sistema Emojis automáticos @@ -203,7 +204,7 @@ Botón 2 Botón 3 Previsualización de notificaciones - Wake screen + Encender pantalla Vibración Sonido Ninguno @@ -221,20 +222,31 @@ Ninguno Archivar Eliminar + Bloquear Llamar Marcar como leído Marcar como no leído - Confirmaciones de envió + Confirmaciones de envío Confirmar cuando los mensajes se envien con éxito Firma - Add a signature to the end of your messages + Añadir una firma al final de tus mensajes Detalles en tiras Quitar acentos de caracteres en los mensajes SMS salientes Sólo números móviles Al componer un mensaje, sólo mostrar números móviles - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + + Enviar mensajes largos como MMS + Si sus mensajes de texto largos no se están enviando o enviando en el orden incorrecto, puede enviarlos como mensajes MMS en su lugar. Se pueden aplicar cargos adicionales Autocomprimir archivos adjuntos MMS Sincronizar mensajes Vuelve a sincronizar tus mensajes con la base de datos nativa de Android SMS @@ -243,34 +255,34 @@ Registro de depuración activado Registro de depuración desactivado Introduzca la duración (segundos) - Blocking - Drop messages - Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + Bloqueando + Borrar mensajes + Eliminar mensajes entrantes de los remitentes bloqueados en lugar de ocultarlos + Conversaciones bloqueadas + Gestor de bloqueo QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Función de bloqueo integrado en QKSMS + ¡Filtrre automáticamente sus llamadas y mensajes en un lugar conveniente! La comunidad IQ™ te permite prevenir mensajes no deseados de spammers conocidos por la comunidad Filtra automáticamente los mensajes de números no solicitados mediante la aplicación \"¿Debo responder?\" - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here - Block - Unblock + Copiar números bloqueados + Continúa a %s y copia sobre tus números bloqueados existentes + Números bloqueados + Tus números bloqueados aparecerán aquí + Bloquear un nuevo número + Bloquear textos de + Número de teléfono + Bloquear + Mensajes bloqueados + Tus mensajes bloqueados aparecerán aquí + Bloquear + Desbloquear - Continue to %s and block this number - Continue to %s and block these numbers + Continúa a %s y bloquea este número + Continúa a %s y bloquea estos números - Continue to %s and allow this number - Continue to %s and allow these numbers + Continuar a %s y permitir este número + Continuar a %s y permitir estos números Acerca de Versión @@ -324,10 +336,12 @@ Aplicar Ninguno + Archivar + Eliminar + Bloquear + Llamar Marcar como leído Responder - Llamar - Eliminar Continuar @@ -366,19 +380,19 @@ Sin demora - Corto - Mediano - Largo + 3 segundos + 5 segundos + 10 segundos - Automatic + Automático 100KB 200KB 300KB 600KB 1000KB 2000KB - No compression + Sin compresión Ok diff --git a/presentation/src/main/res/values-fa/strings.xml b/presentation/src/main/res/values-fa/strings.xml index a4c94a066..ab04c012a 100644 --- a/presentation/src/main/res/values-fa/strings.xml +++ b/presentation/src/main/res/values-fa/strings.xml @@ -1,5 +1,4 @@ - + سیم‌کارت %1$d (%2$s) انتخاب شد + %s انتخاب شد، سیم‌کارت را عوض کنید فرستادن پیام - فرستادن… + در حال فرستادن… رسیده %s - در ارسال خطایی رخ داد. برای ارسال دوباره، ضربه بزنید + ارسال ناموفق بود. برای تلاش دوباره ضربه بزنید جزئیات - آدرس کپی شد + نشانی کپی شد عنوان گفتگو - اعلانها + اعلان‌ها پوسته - بایگانی - بدون موضوع + بایگانی کن + بیرون آوردن از بایگانی مسدود کردن رفع مسدودیت - حذف مکالمه - بارگیری رسانه امکان پذیر نیست - ذخیره در گالری - پشتیبان گیری و بازیابی - پشتیبان گیری از پیام‌ها + حذف گفتگو + نمی‌توان رسانه را بارگذاری کرد + در گالری ذخیره شد + پشتیبان‌گیری و بازیابی + پشتیبان‌گیری از پیام‌ها بازگرداندن پشتیبان اخرین پشیبان گیری - بارگذاری… + در حال بارگذاری… هرگز - بازگردانشانی - یک پشتیبان را انتخاب کنید + بازگردانی + یک پشتیبان را برگزینید لطفاً QKSMS + را باز کنید تا از امکانات پشتیبان و بازیابی بهره ببرید - پشتیبان گیری در حال انجام است… - در حال بازیابی… - بازیابی از نسخه پشتیبان - آیا مطمئن هسیتد که می‌خواهید پیغام‌هایتان را از پشتيباني بازیابی کنید? - بازیابی را متوقف کن - پیام هایی که قبلاً بازیابی شده اند در دستگاه شما باقی خواهند ماند + پشتیبان‌گیری در حال انجام است… + بازگردانی در حال انجام است… + بازگردانی از نسخه پشتیبان + آیا مطمئن هسیتد که می‌خواهید پیام‌هایتان را از این پشتيبان بازگردانی کنید؟ + بازگردانی را متوقف کن + پیام‌هایی که قبلا بازگردانی شده‌اند در دستگاه شما باقی خواهند ماند پشتیبان‌ها - نسخه ی پشتیبان پیدا نشد + نسخه پشتیبانی یافت نشد %d پیام %d پیام - در حال حاضر ، فقط پیامک پشتیبان گیری و بازیابی پشتیبانی می شود. پشتیبان‌گیری و برنامه‌ریزی برای پشتیبان‌گیری از MMS به زودی ارائه می شود! - همین حالا نسخه پشتیبان تهیه کن - برگردانی بک اپ + در حال حاضر ، فقط پشتیبان‌گیری و بازگردانی پیامک پشتیبانی می شود. پشتیبان‌گیری و برنامه‌ریزی برای پشتیبان‌گیری از MMS به زودی ارائه می شود! + اکنون پشتیبان‌گیری کن + در حال تجزیه پشتیبان… %d/%d پیام - ذخیره نسخه پشتیبان… - در حال همگام سازی پیام‌ها… + در حال ذخیره پشتیبان… + در حال همگام‌سازی پیام‌ها… تمام شد! - پشتیبان گیری و بازیابی - برنامه ریزی - در همان لحظه ای که می خواهید پیام بصورت خودکار ارسال کنید - سلام! کی دوباره تولدت بود؟ - 23 دسامبر است - تولدت مبارک! نگاه کنید که چه دوست خوبی دارم ، به یاد تولدت + پشتیبان‌گیری و بازگردانی + برنامه‌ریزی شده + در همان لحظه ای که می خواهید پیام را به‌صورت خودکار ارسال کنید + سلام! راستی تولدت کی بود؟ + روز ۲۳ دسامبر + تولدت مبارک! ببین چه دوست خوبیم، به یاد تولدتم - ارسال در 23 دسامبر - یک پیام تنظیم کنید - پیام برنامه ریزی شده + ارسال در ۲۳ دسامبر + یک پیام را برنامه‌ریزی کنید + پیام برنامه‌ریزی شده - ارسال هم‌اکنون + هم‌اکنون ارسال کن کپی متن حذف ظاهر عمومی - پاسخ QK + پاسخ سریع پوسته حالت شب حالت شب کامل - زمان شروع + زمان آغاز زمان پایان رنگ مخاطب خودکار اندازه قلم از فونت سیستم استفاده کن - ایموجی خودکار + شکلک خودکار اعلان‌ها - از فونت سیستم استفاده کن + لمس برای شخصی سازی اقدامات - دکمه 1 - دکمه 2 - دکمه 3 - بازبینی اعلان + دکمه ۱ + دکمه ۲ + دکمه ۳ + پیش نمایش اعلان صفحه بیدار لرزش - صدای + صدا هیچی - پاسخ QK + پاسخ سریع پنجره پیام های جدید برای رد کردن ضربه بزنید برای بستن آن ، به بیرون پنجره ضربه بزنید - ارسال با تاخیر + ارسال با درنگ اقدامات کشیدن تنظیم کشیدن برای گفتگو به راست بکشید @@ -219,39 +220,50 @@ تغییر هیچی - آرشیو + بایگانی پاک کردن + مسدود کردن تماس - خوانده شده - علامت گذاری بعنوان خوانده نشده + نشان به‌عنوان خوانده شده + نشان به‌عنوان خوانده نشده تأیید تحویل - تأیید ارسال موفق پیام + تأیید کن که پیام‌ها با موفقیت ارسال شدند امضا به انتهای پیام های خود یک امضا اضافه کنید حذف لهجه‌ها حذف کاراکترهای اضافه پیام ارسالی فقط شماره های تلفن وقتی در حال ارسال پیام هستید فقط می توانید شماره تلفن را ببنید + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + پیام های طولانی را به عنوان MMS ارسال کن اگر پیام های متنی طولانی شما نتوانسته اند ارسال شوند یا به ترتیب اشتباه ارسال شوند ، می توانید به جای آنها پیام های MMS ارسال کنید. هزینه های اضافی ممکن است اعمال شود فشرده سازی خودکار پیوست ها - همگام سازی پیام ها - همگام سازی پیام ها با پایگاه داده - در مورد QKSMS - نسخه ی %s - ورود به سیستم دیباگ فعال است - ورد به سیستم دیباگ غیرفعال است - زمان (ثانیه) را وارد کنید + همگام‌سازی پیام‌ها + همگام‌سازی مجدد پیام‌ها با پایگاه‌داده بومی پیامک اندروید + درباره QKSMS + نگارش %s + گزارش‌گیری اشکال‌زدایی فعال است + گزارش‌گیری اشکال‌زدایی غیرفعال است + مدت زمان را وارد کنید (ثانیه) انسداد - دور انداختم پیام‌ها - پیام های ورودی را از فرستنده های مسدود شده به جای مخفی کردن آنها دور اندازید - مکالمات مسدود شده + پیام‌ها را دور بینداز + پیام های ورودی را از فرستنده های مسدود شده به جای مخفی کردن آنها دور بیندازید + گفتگوهای مسدود شده مدیریت انسداد QKSMS عملکرد مسدود کننده داخلی در QKSMS - تماس ها و پیام های خود را بطور خودکار فیلتر کنید! IQ Community به شما اجازه می دهد تا از پیام های ناخواسته توسط اسپم های شناخته شده در جامعه جلوگیری کنید - با استفاده از برنامه «باید جواب بده»، پیامها را از شماره های ناخواسته به طور خودکار فیلتر کنید + تماس ها و پیام های خود را بطور خودکار فیلتر کنید! IQ Community به شما اجازه می دهد تا از پیام های ناخواسته توسط اسپمرهای شناخته شده در جامعه جلوگیری کنید + با استفاده از برنامه «آیا باید جواب بدم»، پیام‌ها را از شماره های ناخواسته به طور خودکار فیلتر کنید کپی شماره های مسدود شده به %s ادامه دهید و از شماره های مسدود شده موجود خود کپی کنید شماره‌های مسدود شده @@ -259,10 +271,10 @@ انسداد شماره جدید انسداد پیام از طرف شماره تلفن - انسداد + مسدود کردن پیام‌های مسدود شده پیامهای مسدود شده شما در اینجا ظاهر می شوند - انسداد + مسدود کردن رفع مسدوديت به %s ادامه دهید و این شماره را مسدود کن @@ -273,61 +285,63 @@ به %s ادامه دهید و به این شماره‌ها اجازه بده درباره - نسخه - توسعه دهنده + نگارش + توسعه دهنده کد منبع - گزارش دگرگونی‌ها + گزارش دگرگونی‌ مخاطب مجوز حق نشر حمایت از تولید کننده،بازشدن همه چیز شما میتوانید با %s از ما حمایت کنید - زمان توسعه %1$s %2$s + ارتقا مادام العمر به‌ازای %1$s %2$s - نسخه پیشرفته و حمایت %1$s %2$s - ممنون از حمایت شما! - حال شما به همه امکانات دسترسی دارید - QKSMS + برای کاربران F-Droid رایگان است! اگر میخواهید از توسعه حمایت کنید، احساس رایگان کنید تا کمک مالی شود. + نسخه پیشرفته و حمایت به‌ازای %1$s %2$s + سپاس از شما برای حمایت از QKSMS! + حال شما به همه امکانات QKSMS+ دسترسی دارید + QKSMS+ برای کاربران F-Droid رایگان است! اگر می‌خواهید از توسعه حمایت کنید، از کمک مالی بسیار قدردانی می‌شود. از طریق PayPal کمک مالی کنید به زودی - پوسته پیشرفته + پوسته‌های ویژه در حالت متریال دیزاین رنگبندی پوسته امکانپذیر نیست شکلک خودکار سفارشی - ساخت میانبرهای شخصی برای شکلک اتوماتیک - پشتیبان‌گیری پیامها + ساخت میانبرهای سفارشی برای شکلک خودکار + پشتیبان‌گیری پیام‌ها از پیام‌هایتان به‌طور خودکار پشتیبان بگیرید. دیگر نگران از دست دادن پیام ها نباشید وقتی تلفن همراهتان را عوض می‌کنید یا از دست می‌دهید پیام های برنامه ریزی شده - پیام های برنامه ریزی شده به طور خودکار در یک زمان و تاریخ خاص ارسال می شود - تاخیر در ارسال - لطفا چند ثانیه صبر کنید - حالت شب اتوماتیک + پیام‌ها را برنامه‌ریزی کنید تا در یک زمان و تاریخ خاص به طور خودکار ارسال شوند + ارسال با درنگ + قبل از ارسال پیامتان چند ثانیه صبر کنید + حالت شب خودکار فعال کردن حالت شب براساس زمان روز مسدود کردن پیشرفته - هرزنامه ها وپیام های بد را مسدود کن + پیام‌های دارای کلیدواژه‌ها یا مطابق با الگوها را مسدود کن هدایت خودکار هدایت کردن پیام از فرستندگان خاص پاسخ خودکار - ارسال جواب خودکار به پیامک ها + پاسخ خودکار به پیام‌های دریافتی با پاسخ از پیش تعیین شده بیشتر QKSMS در حال توسعه فعال است، و خرید شما شامل تمام ویژگی های آینده QKSMS + نیز هست ! - بارگذاری… - مشاهده مکالمات بیشتر - خوانده شده + درحال بارگذاری… + مشاهده گفتگوهای بیشتر + نشان به‌عنوان خوانده شده تماس پاک کردن بیشتر نمایش بده کمتر نمایش بده - بازکردن پیام + بازکردن گفتگو متریال هگز اعمال کردن هیچ - علامت گذاری به عنوان خوانده شده - پاسخ + بایگانی کن + حذف + مسدود کردن تماس - پاک کردن + نشان به‌عنوان خوانده شده + پاسخ بله ادامه @@ -345,8 +359,8 @@ پیام جدید پیام جدید - ارسال نشد - پیام به٪ s فرستاده نشد + پیام ارسال نشد + پیام به %s فرستاده نشد سامانه غیرفعال @@ -365,10 +379,10 @@ بزرگتر - بدون تاخیر - کوتاه - متوسط - طولانی + بدون درنگ + ۳ ثانیه + ۵ ثانیه + ۱۰ ثانیه خودکار @@ -388,10 +402,10 @@ به نظر خوبه چه خبر؟ موافقم - خیر + نه دوستت دارم متاسفم خخخخ - درست است + خوبه diff --git a/presentation/src/main/res/values-fi/strings.xml b/presentation/src/main/res/values-fi/strings.xml index 9446d4b9b..f26dd1083 100644 --- a/presentation/src/main/res/values-fi/strings.xml +++ b/presentation/src/main/res/values-fi/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) valittu + %s valittu, vaihda SIM-korttia Lähetä viesti Lähetetään… Toimitettu: %s Lähettäminen epäonnistui. Yritä uudelleen napauttamalla Lisätiedot - Address copied + Osoite kopioitu Keskustelun otsikko Ilmoitukset Teema @@ -152,17 +153,17 @@ Ole hyvä ja avaa QKSMS+ käyttääksesi varmuuskopiointia ja palautusta Varmuuskopiointi käynnissä… Palautus käynnissä… - Palauta varmuuskopio + Palauta varmuuskopiosta Oletko varma, että haluat palauttaa viestisi tästä varmuuskopiosta? Pysäytä palautus - Viestit, jotka on jo palautettu, pysyvät laitteessasi + Aiemmin palautetut viestit pysyvät laitteellasi Varmuuskopiot Varmuuskopioita ei löytynyt %d viesti %d viestiä - Tällä hetkellä vain SMS-viestejä voidaan varmuuskopioita ja palauttaa. MMS-viestien tuki ja ajoitetut varmuuskopiot saapuvat pian! + Vain tekstiviestejä voidaan varmuuskopioita ja palauttaa tällä hetkellä. Tuki multimediaviesteille ja varmuuskopiointien ajastus tulevat pian! Varmuuskopioi nyt Varmuuskopiota jäsennetään… %d/%d viestiä @@ -172,7 +173,7 @@ Varmuuskopiointi ja palautus Ajoitettu Lähetä viesti automaattisesti haluamallasi ajankohdalla - Hey! When was your birthday again? + Hei! Milloin syntymäpäiväsi olikaan? Se on joulukuun 23. päivänä Hyvää syntymäpäivää! Katso miten hyvä ystävä olen, muistan syntymäpäiväsi @@ -184,36 +185,36 @@ Kopioi teksti Poista - Ulkoasu + Ulkonäkö Yleiset - QK Vastaus + QK-vastaus Teema Yötila Musta yötilä Aloitusaika Lopetusaika - Automatic contact colors + Automaattiset yhteystietojen värit Fonttikoko Käytä järjestelmäfonttia Automaattinen emoji Ilmoitukset Muokkaa napauttamalla Toiminnot - Nappi 1 - Nappi 2 - Nappi 3 - Esikatselut ilmoituksessa - Wake screen + Painike 1 + Painike 2 + Painike 3 + Ilmoituksien esikatselut + Herätä näyttö Värinä Ääni Ei mitään - QK Vastaus + QK-vastaus Ponnahdusikkuna uusille viesteille Sulje napauttamalla - Tap outside of the popup to close it + Sulje ponnahdusikkuna napauttamalla sen ulkopuolelle Lykätty lähetys Pyyhkäisytoiminnot - Configure swipe actions for conversations + Muokkaa keskustelujen pyyhkäisytoimintoja Pyyhkäisy oikealle Pyyhkäisy vasemmalle MUUTA @@ -221,6 +222,7 @@ Ei mitään Arkistoi Poista + Estä Soita Merkitse luetuksi Merkitse lukemattomaksi @@ -228,32 +230,42 @@ Välitystiedot Vahvista, että viestit lähetettiin onnistuneesti Allekirjoitus - Add a signature to the end of your messages + Lisää allekirjoitus viestiesi loppuun Käytä yksinkertaisia merkkejä - Käytä yksinkertaisia merkkejä lähtevissä SMS-viesteissä + Käytä yksinkertaisia merkkejä lähetettävissä tekstiviesteissä Vain matkapuhelinnumerot - When composing a message, only show mobile numbers - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply - Pakkaa MMS-liitteet automaattisesti + Ehdota vain matkapuhelinnumeroita viestejä kirjoittaessasi + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + + Lähetä pitkät viestit multimediaviesteinä + Jos pidempien viestiesi lähetys epäonnistuu, tai viestit lähtevät väärässä järjestyksessä, voit lähettää ne multimediaviesteinä. Operaattori saattaa veloittaa tästä lisämaksuja. + Pakkaa multimediaviestien liitteet automaattisesti Synkronoi viestit - Re-sync your messages with the native Android SMS database + Synkronoi viestisi uudelleen Androidin oman SMS-tietokannan kanssa Tietoja QKSMS:tä Versio %s - Debug logging enabled - Debug logging disabled + Virheenkorjauslokien kirjaaminen käytössä + Virheenkorjauslokien kirjaaminen pois käytöstä Syötä kesto (sekuntia) Esto - Drop messages - Drop incoming messages from blocked senders instead of hiding them + Pudota viestit + Pudottaa estettyjen yhteystietojen lähettämät viestit, niiden piilottamisen sijasta Estetyt keskustelut Estojen hallinta QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + QKSMS:ään sisäänrakennettu esto-toiminto + Suodata puhelusi ja soittosi automaattisesti, yhdessä kätevässä paikassa! Community IQ™ antaa sinun estää haluamattomia viestejä, yhteisölle tutuilta roskapostin lähettäjiltä + Suodata haluamattomien numeroin lähettämät viestit automaattisesti, \"Should I Answer\" -sovellusta käyttämällä Kopioi estetyt numerot - Continue to %s and copy over your existing blocked numbers + Jatka, ja kopioi jo estämäsi numerot, sovellukseen %s Estetyt numerot Estämäsi numerot näkyvät tässä Estä uusi numero @@ -265,12 +277,12 @@ Estä Salli - Continue to %s and block this number - Continue to %s and block these numbers + Jatka sovellukseen %s, ja estä tämä numero + Jatka sovellukseen %s, ja estä nämä numerot - Continue to %s and allow this number - Continue to %s and allow these numbers + Jatka sovellukseen %s, ja salli tämä numero + Jatka sovellukseen %s, ja salli nämä numerot Tietoja Versio @@ -279,38 +291,38 @@ Muutosloki Yhteystiedot Lisenssi - Tekijänoikeus - Tue kehitystä, avaa kaikki - You can save a starving developer for just %s + Tekijänoikeudet + Tue kehitystä, avaa kaiken + Voit pelastaa nälkää näkevän kehittäjän antamalla %s - Lifetime upgrade for %1$s %2$s + Elinikäinen päivitys hintaan %1$s %2$s - Unlock + donate for %1$s %2$s - Kiitos kun tuit QKSMS:ää! - You now have access to all QKSMS+ features - QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Ominaisuuksien avaus + lahjoitus hintaan %1$s %2$s + Kiitos, kun tuit QKSMS:ää! + Olet nyt oikeutettu käyttämään kaikkia QKSMS+ -ominaisuuksia + QKSMS+ on ilmainen F-Droidin käyttäjille! Jos haluaisit tukea kehitystyötä tulevaisuudessa, arvostaisimme lahjoitustasi todella paljon. Lahjoita PayPalilla Tulossa pian Premium-teemat - Unlock beautiful theme colors not available on the Material Design palette - Custom auto-emoji - Create custom auto-emoji shortcuts + Avaa upeat teemavärit, joita ei ole saatavilla Material Design -väripaletissa + Mukautettu auto-emoji + Luo mukautettuja auto-emoji -pikakuvakkeita Viestien varmuuskopiointi - Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Varmuuskopioi viestisi automaattisesti. Sinun ei enää ikinä tarvitse huolehtia historiasi katoamisesta, jos vaihdat tai hukkaat puhelimesi Ajoitetut viestit Ajoita viestit lähtemään automaattisesti tiettyinä aikoina ja päivinä Lykätty lähetys Odota muutama sekunti ennen viestin lähettämistä Automaattinen yötila - Enable night mode based on the time of day + Ota yötila käyttöön päivänajan perusteella Edistynyt estäminen - Block messages that contain keywords or match patterns + Estä avainsanoja tai vastaavuuksia sisältävät viestit Automaattinen välitys - Välitä tiettyjen lähettäjien viestit automaattisesti + Edelleenlähetä tiettyjen lähettäjien viestit automaattisesti Automaattinen vastaus - Automatically respond to incoming messages with a preset response + Vastaa saapuviin viesteihin automaattisesti, etukäteen luoduilla vastauksilla Lisää - QKSMS is under active development, and your purchase will include all future QKSMS+ features! + QKSMS:ää kehitetään aktiivisesti, ja ostoksesi sisältää kaikki tulevat QKSMS+ -ominaisuudet! Ladataan… Näytä lisää keskusteluja Merkitse luetuksi @@ -319,15 +331,17 @@ Näytä lisää Näytä vähemmän Avaa keskustelu - Materiaali + Material HEX Käytä Ei mitään + Arkistoi + Poista + Estä + Soita Merkitse luetuksi Vastaa - Soita - Poista Kyllä Jatka @@ -337,7 +351,7 @@ Lopeta Lisää Aseta - Peru + Kumoa Kopioitu Arkistoidut keskustelut Sinun pitää avata QKSMS+ käyttääksesi tätä @@ -366,22 +380,22 @@ Ei viivettä - Lyhyt - Keskikokoinen - Pitkä + 3 sekuntia + 5 sekuntia + 10 sekuntia - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Automaattinen + 100 kt + 200 kt + 300 kt + 600 kt + 1000 kt + 2000 kt + Ei pakkausta - Kyllä + Okei Anna minulle hetki Olen matkalla Kiitos diff --git a/presentation/src/main/res/values-fr/strings.xml b/presentation/src/main/res/values-fr/strings.xml index 96fbf8de6..27ae8db75 100644 --- a/presentation/src/main/res/values-fr/strings.xml +++ b/presentation/src/main/res/values-fr/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) sélectionnée %s sélectionné, changer la carte SIM Envoyer un message Envoi… @@ -221,6 +222,7 @@ Aucune Archiver Supprimer + Bloquer Appeler Marquer comme lu Marquer comme non-lu @@ -233,6 +235,16 @@ Supprimer les accents des caractères lors de l\'envoi de messages SMS Numéros de mobile Lorsque vous composez un message, afficher uniquement les numéros de mobile + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + Envoyer les messages longs en MMS Si vos messages texte plus longs ne parviennent pas à être envoyés, ou s\'ils sont envoyés dans le désordre, vous pouvez les envoyer sous forme de messages MMS à la place. Des frais supplémentaires peuvent s\'appliquer Compresser automatiquement les pièces jointes des MMS @@ -324,10 +336,12 @@ Appliquer Aucune + Archiver + Supprimer + Bloquer + Appeler Marquer comme lu Répondre - Appeler - Supprimer Oui Continuer @@ -366,16 +380,16 @@ Aucun retard - Court - Moyen - Long + 3 secondes + 5 secondes + 10 secondes Automatique 100KB 200KB 300KB - 1000KB + 600KB 1000KB 2000KB Pas de compression diff --git a/presentation/src/main/res/values-hi/strings.xml b/presentation/src/main/res/values-hi/strings.xml index 7d0dcbf39..2116b8ecf 100644 --- a/presentation/src/main/res/values-hi/strings.xml +++ b/presentation/src/main/res/values-hi/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selected, change SIM card संदेश भेजें भेज रहा है… @@ -221,9 +222,10 @@ None संग्रह मिटायें - कॉल करें - Mark as read - Mark as unread + Block + Call + Mark read + Mark unread वितरण पुष्टि Confirm that messages were sent successfully @@ -233,6 +235,16 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -313,7 +325,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! तैयार हो रहा है… अधिक वार्तालाप देखें - Mark as read + Mark read कॉल मिटायें अधिक दिखाएं @@ -324,10 +336,12 @@ लागू करें None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -366,9 +380,9 @@ कोइ विलम्ब नही - Short - मध्यम - बड़ा + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-hr/strings.xml b/presentation/src/main/res/values-hr/strings.xml index 7ba312454..c58b8e68a 100644 --- a/presentation/src/main/res/values-hr/strings.xml +++ b/presentation/src/main/res/values-hr/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selected, change SIM card Pošalji poruku Slanje… @@ -223,9 +224,10 @@ None Archive Delete + Block Call - Mark as read - Mark as unread + Mark read + Mark unread Potvrde o dostavi Budite sigurni da su se poruke uspješno poslale @@ -235,6 +237,17 @@ Uklonite naglaske sa znakova u odlaznim SMS porukama Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -317,7 +330,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! Učitavanje… View more conversations - Mark as read + Mark read Call Delete Show more @@ -328,10 +341,12 @@ Apply None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -371,9 +386,9 @@ No delay - Short - Medium - Long + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-hu/strings.xml b/presentation/src/main/res/values-hu/strings.xml index 78f78b584..c5c5bd6d0 100644 --- a/presentation/src/main/res/values-hu/strings.xml +++ b/presentation/src/main/res/values-hu/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s kiválasztva, cserélj SIM kártyát Üzenet küldése Küldés… @@ -221,9 +222,10 @@ Nincs Archiválás Törlés - Hívás - Megjelölés olvasottként - Megjelölés olvasatlanként + Block + Call + Mark read + Mark unread Elküldés megerősítése Megerősíti, hogy az üzenetek sikeresen el lettek-e küldve @@ -233,6 +235,16 @@ A kimenő SMS-üzenetek ékezetes karaktereinek átalakítása Csak mobilszámok Üzenet írásánál csak a mobilszámokat mutassa + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + A hosszú üzenetek MMS-ként küldése Ha a hosszabb üzenetek küldése sikertelen, vagy rossz sorrendben érkeznek akkor lehet őket MMS-ként küldeni, de ez díjköteles lehet MMS-mellékletek automatikus tömörítése @@ -313,7 +325,7 @@ A QKSMS folyamatos fejlesztés alatt áll és a vásárlásod a jövőbeni QKSMS+ funkciókat is tartalmazza! Betöltés… További társalgások megtekintése - Olvasottnak jelölés + Mark read Hívás Törlés Több megjelenítése @@ -324,10 +336,12 @@ Alkalmaz Nincs - Megjelölés olvasottként - Válasz - Hívás - Törlés + Archive + Delete + Block + Call + Mark read + Reply Igen Folytatás @@ -366,9 +380,9 @@ Nincs késleltetés - Rövid - Közepes - Hosszú + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-in/strings.xml b/presentation/src/main/res/values-in/strings.xml index d3d82560d..11a69da83 100644 --- a/presentation/src/main/res/values-in/strings.xml +++ b/presentation/src/main/res/values-in/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) dipilih %s dipilih, ganti kartu SIM Kirim pesan Mengirim… @@ -219,6 +220,7 @@ Tidak ada Arsipkan Hapus + Blokir Panggil Tandai dibaca Tandai belum dibaca @@ -231,6 +233,15 @@ Membuang aksen dari karakter di dalam pesan SMS keluar Hanya nomor seluler Saat menulis pesan, hanya tampilkan nomor seluler + Otomatis menghapus pesan lama + Pesan akan dihapus setelah rentang hari berikut + Rentang hari + Tidak pernah + Otomatis hapus pesan lama? + Jika anda lanjutkan, %1$d pesan akan dihapus sekarang + + Setelah %d hari + Kirim pesan panjang sebagai MMS Jika pesan teks panjang anda gagal dikirim, atau dikirim dalam urutan yang salah, anda bisa mengirimkannya sebagai pesan MMS. Mungkin akan dikenai tagihan tambahan Otomatis kompres lampiran MMS @@ -320,10 +331,12 @@ Terapkan Tidak ada + Arsip + Hapus + Blokir + Panggil Tandai dibaca Balas - Panggil - Hapus Ya Lanjutkan @@ -361,9 +374,9 @@ Tanpa tundaan - Singkat - Sedang - Lama + 3 detik + 5 detik + 10 detik Otomatis diff --git a/presentation/src/main/res/values-it/strings.xml b/presentation/src/main/res/values-it/strings.xml index 9a898a3db..cee6c8e57 100644 --- a/presentation/src/main/res/values-it/strings.xml +++ b/presentation/src/main/res/values-it/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selezionata %s selezionato, cambiare SIM Invia messaggio Invio in corso… @@ -221,9 +222,10 @@ Nessuna Archivia Elimina + Blocca Chiama - Segna come letto - Segna come non letto + Segna come \"letto\" + Segna come \"da leggere\" Conferme di recapito Conferma che i messaggi siano stati inviati con successo @@ -233,6 +235,16 @@ Rimuovi gli accenti dai caratteri negli SMS in uscita Solo numeri mobili Durante la composizione di un messaggio, visualizza solo i numeri di cellulare + Elimina automaticamente i vecchi messaggi + I messaggi verranno eliminati dopo il numero di giorni specificato + Numero di giorni + Mai + Eliminare i vecchi messaggi automaticamente? + Scegliendo di procedere, %1$d messaggi verranno eliminati ora + + Dopo 1 giorno + Dopo %d giorni + Invia messaggi lunghi come MMS Se i tuoi messaggi di testo più lunghi non vengono inviati o vengono inviati nell\'ordine sbagliato, puoi invece inviarli come messaggi MMS. Potrebbero essere applicati costi aggiuntivi Compressione automatica degli allegati MMS @@ -313,7 +325,7 @@ QKSMS è sotto sviluppo attivo, e l\'acquisto includerà tutte le caratteristiche future di QKSMS+! Caricamento… Mostra altre conversazioni - Segna come letto + Segna come \"letto\" Chiama Elimina Mostra dettagli @@ -324,10 +336,12 @@ Applica Nessuna - Segna come letto - Rispondi - Chiama + Archivia Elimina + Blocca + Chiama + Segna come \"letto\" + Rispondi Continua @@ -366,9 +380,9 @@ Nessun ritardo - Breve - Medio - Lungo + 3 secondi + 5 secondi + 10 secondi Automatico diff --git a/presentation/src/main/res/values-iw/strings.xml b/presentation/src/main/res/values-iw/strings.xml index 2f0ef49e0..9d91c28ec 100644 --- a/presentation/src/main/res/values-iw/strings.xml +++ b/presentation/src/main/res/values-iw/strings.xml @@ -1,5 +1,4 @@ - + נבחר SIM מס׳ %1$d ‏(%2$s) %s נבחרו, החלפת כרטיס SIM שליחת הודעה מתבצעת שליחה… @@ -217,7 +218,7 @@ יש לגעת מחוץ לחלונית הקופצת כדי לסגור אותה שליחה בעיכוב פעולות גרירה - להגדיר פעולות גרירה לדיונים + להגדיר פעולות גרירה להתכתבויות גרירה ימינה גרירה שמאלה החלפה @@ -225,9 +226,10 @@ ללא לארכיון מחיקה - התקשרות - סימון כנקרא - סימון כלא נקרא + חסימה + להתקשר + סימון כהודעה שנקראה + סימון כהודעה שלא נקראה אישור מסירה אישור שההודעות נשלחו בהצלחה @@ -237,6 +239,18 @@ הסרת סימנים דיאקריטיים מתווים במסרונים יוצאים מספרי ניידים בלבד בעת כתיבת הודעה, להציג רק מספרי טלפון של ניידים + למחוק הודעות ישנות אוטומטית + הודעות תימחקנה לאחר כמות מסוימת של ימים + מספר הימים + אף פעם + למחוק הודעות ישנות אוטומטית? + המשך הפעולה יוביל למחיקת %1$d הודעות כעת + + לאחר יום + לאחר יומיים + לאחר %d ימים + לאחר %d ימים + לשלוח הודעות ארוכות כ־MMS אם יש לך בעיה בשליחת הודעות טקסט ארוכות יותר או בעיה ברצף שליחת ההודעה, ניתן לשלוח כהודעת MMS במקום. עלול לגרור עלויות נוספות דחיסה אוטומטית של קבצים מצורפים להודעות MMS @@ -250,7 +264,7 @@ חסימה השמטת הודעות להשמיט הודעות נכנסות משולחים חסומים במקום להסתיר אותן - דיונים חסומים + התכתבויות חסומות מנהל חסימות QKSMS תכונת חסימה מובנית בתוך QKSMS @@ -321,21 +335,23 @@ הפיתוח של QKSMS מתקיים בימים אלו והרכישה שלך תכלול את כל התכונות העתידיות של QKSMS+‎! בטעינה… הצגת התכתבויות נוספות - לסמן כנקראה + סימון כהודעה שנקראה להתקשר מחיקה להציג יותר להציג פחות - פתיחת דיון + פתיחת התכתבות Material הקסה החלה ללא - סימון כנקראה - תגובה - התקשרות + לארכיון מחיקה + חסימה + להתקשר + סימון כהודעה שנקראה + להשיב כן המשך @@ -347,7 +363,7 @@ הגדרה ביטול הועתק - דיון בארכיון + התכתבות בארכיון עליך לשחרר את QKSMS+‎ כדי להשתמש בזה הודעה חדשה @@ -376,9 +392,9 @@ ללא השהיה - קצרה - בינונית - ארוכה + 3 שניות + 5 שניות + 10 שניות אוטומטי diff --git a/presentation/src/main/res/values-ja/strings.xml b/presentation/src/main/res/values-ja/strings.xml index 0f8c83ede..b277ee049 100644 --- a/presentation/src/main/res/values-ja/strings.xml +++ b/presentation/src/main/res/values-ja/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) を選択しました %s 選択、SIMカードを交換 メッセージを送る 送信中… @@ -219,7 +220,8 @@ 無し アーカイブ 削除 - 電話 + ブロック + 通話 既読にする 未読にする @@ -231,6 +233,15 @@ 送信する SMS メッセージの文字からアクセントを削除します 携帯電話番号のみ メッセージを作成するときは、携帯電話番号のみを表示します + 古いメッセージを自動的に削除 + 指定された日数の後にメッセージが削除されます + 日数 + 自動削除しない + 古いメッセージを自動的に削除しますか? + 続行すると、今すぐ %1$d 件のメッセージが削除されます + + %d 日後 + 長いメッセージをMMSとして送信する 長いテキストメッセージの送信が失敗する場合、または間違った順序で送信される場合は、代わりにMMSメッセージとして送信できます。 追加料金が適用される場合があります MMS添付ファイルの自動圧縮 @@ -320,10 +331,12 @@ 適用 無し + アーカイブ + 削除 + ブロック + 通話 既読にする 返信 - 電話 - 削除 はい 続行 @@ -361,9 +374,9 @@ 遅延なし - 短い - ミディアム - 長い + 3秒 + 5秒 + 10秒 自動 diff --git a/presentation/src/main/res/values-ko/strings.xml b/presentation/src/main/res/values-ko/strings.xml index af4128d37..174e6f286 100644 --- a/presentation/src/main/res/values-ko/strings.xml +++ b/presentation/src/main/res/values-ko/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) 선택 됨 %s 선택됨, SIM 카드를 변경하세요. 메시지 보내기 전송 중... %s 전송됨 전송 실패, 눌러서 다시 시도하세요 자세히 - Address copied + 주소 복사 됨 대화 제목 알림 테마 @@ -151,7 +152,7 @@ QKSMS+을 구입하여 백업과 복원기능을 사용하세요 백업 중... 복원 중... - 복원이 되었습니다 + 복원을 할까요? 이 백업 파일로부터 복원하시겠어요? 복원 중지 이미 저장된 메시지들은 내 기기에 남아있습니다. @@ -169,10 +170,10 @@ 끝! 백업과 복원 예약됨 - 원하는 그 시간에 자동으로 메시지를 보냅니다. + 원하는 시간에 자동으로 메시지를 보냅니다. 잠깐만! 생일이 언제였더라..? 12월 23일이야 - 생일 축하해! 너의 생일까지 기억해주는 이렇게 좋은 친구가 어디있겠어ㅋㅋ + 생일 축하해! 너의 생일까지 기억해주는 이렇게 좋은 친구가 어디있겠어 12월 23일에 보냅니다. 메시지 예약 전송하기 @@ -190,25 +191,25 @@ 완전한 블랙 테마 시작 시간 끝나는 시간 - Automatic contact colors + 연락처 색 자동 설정 글꼴 크기 시스템 글꼴 사용 자동 이모티콘 알림 - 사용자 지정 탭 + 탭해서 사용자화 동작 버튼 1 버튼 2 버튼 3 알림 미리 보기 - Wake screen + 화면 깨우기 진동 소리 없음 - 빠른 응답 + QK Reply 새로운 메시지에 대한 팝업 탭해서 닫기 - 아무곳을 터치하면 팝업이 닫힙니다. + 아무데나 탭하면 팝업이 닫힙니다. 전송 지연 쓸어넘기기 설정 대화를 쓸어넘겼을 때 할 작업 @@ -219,9 +220,10 @@ 없음 아카이브 삭제 + 차단 전화 읽음으로 표시 - 읽음으로 표시 + 읽지않음으로 표시 전송 확인 전송 성공을 확인합니다 @@ -231,8 +233,17 @@ SMS를 주고받을 때, 대문자 없애기 전화번호만 보이기 메시지를 쓸 때, 전화번호만 보이게 하기 - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After %d days + + 장문 메시지를 MMS로 보내기 + 만약 장문의 메시지가 보내지지 않거나, 잘못된 순서로 보내진다면, MMS 메시지로 보낼 수 있습니다. (MMS로 인한 추가요금이 발생할 수 있습니다) 자동으로 MMS 첨부 파일 압축하기 메시지 동기화하기 기본 안드로이드 SMS 데이터베이스에 다시 동기화하기 @@ -263,10 +274,10 @@ 차단 차단해제 - Continue to %s and block these numbers + %s 하고 이 번호 차단하기 - Continue to %s and allow these numbers + %s 하고 이 번호 허용하기 정보 버전 @@ -320,13 +331,15 @@ 적용하기 없음 + 보관 + 삭제 + 차단 + 전화 읽음으로 표시 답장 - 전화하기 - 삭제하기 - Yes - Continue + + 계속 취소 삭제 저장 @@ -345,10 +358,10 @@ 메시지 전송 실패 %s 에게 보낼 메시지 전송이 실패했습니다 - System - Disabled - Always on - Automatic + 시스템 설정 + 사용 안 함 + 항상 켬 + 자동 이름 및 메시지 표시 @@ -363,19 +376,19 @@ 지연 없음 - 짧게 - 중간 - 길게 + 3초 + 5초 + 10초 - Automatic + 자동 100KB 200KB 300KB 600KB 1000KB 2000KB - No compression + 무압축 확인 diff --git a/presentation/src/main/res/values-lt/strings.xml b/presentation/src/main/res/values-lt/strings.xml index 15332f97a..f527eecf7 100644 --- a/presentation/src/main/res/values-lt/strings.xml +++ b/presentation/src/main/res/values-lt/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selected, change SIM card Siųsti žinutę Siunčiama… @@ -225,9 +226,10 @@ None Archive Delete + Block Call - Mark as read - Mark as unread + Mark read + Mark unread Žinučių pristatymo patvirtinimai Patvirtinti, kad pranešimai buvo sėkmingai išsiųsti @@ -237,6 +239,18 @@ Ištrinti jūsų SMS žinučių simbolių akcentus Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + After %d days + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -321,7 +335,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! Kraunama… Žiūrėti daugiau pokalbių - Žymėti skaitytą + Mark read Skambinti Delete Rodyti daugiau @@ -332,10 +346,12 @@ Pritaikyti None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -376,9 +392,9 @@ Be delsimo - Trumpas - Vidutinis - Ilgas + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-nb/strings.xml b/presentation/src/main/res/values-nb/strings.xml index 54dafb565..47ecbf767 100644 --- a/presentation/src/main/res/values-nb/strings.xml +++ b/presentation/src/main/res/values-nb/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s er valgt, velg annet SIM-kort Send melding Sender … @@ -221,9 +222,10 @@ Ingen Arkivér Slett - Ring - Marker som lest - Marker som ulest + Block + Call + Mark read + Mark unread Leveringsbekreftelse Bekreft at melding ble sendt @@ -233,6 +235,16 @@ Ta bort akutt-tegn fra utgående meldinger Kun mobilnummre Vis kun mobilnummre når du skriver ny melding + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Komprimer MMS-vedlegg @@ -313,7 +325,7 @@ QKSMS er i stadig utvikling og ditt kjøp inkluderer alle fremtidige QKSMS+ funksjoner! Laster inn … Last inn flere samtaler - Marker som lest + Mark read Ring Slett Vis mer @@ -324,10 +336,12 @@ Bruk Ingen - Marker som lest - Svar - Ring - Slett + Archive + Delete + Block + Call + Mark read + Reply Ja Continue @@ -366,9 +380,9 @@ Ingen forsinkelse - Kort - Middels - Lang + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-ne/strings.xml b/presentation/src/main/res/values-ne/strings.xml index 7c86ccfc2..cf88bc382 100644 --- a/presentation/src/main/res/values-ne/strings.xml +++ b/presentation/src/main/res/values-ne/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selected, change SIM card Send message Sending… @@ -221,9 +222,10 @@ None Archive Delete + Block Call - Mark as read - Mark as unread + Mark read + Mark unread Delivery confirmations Confirm that messages were sent successfully @@ -233,6 +235,16 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -313,7 +325,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark as read + Mark read Call Delete Show more @@ -324,10 +336,12 @@ Apply None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -366,9 +380,9 @@ No delay - Short - Medium - Long + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-nl/strings.xml b/presentation/src/main/res/values-nl/strings.xml index 8465731fd..acaf0a76b 100644 --- a/presentation/src/main/res/values-nl/strings.xml +++ b/presentation/src/main/res/values-nl/strings.xml @@ -1,5 +1,4 @@ - + Simkaart %1$d (%2$s) geselecteerd %s geselecteerd, wijzig SIM-kaart Verstuur bericht Verzenden… Afgeleverd %s Fout bij verzenden. Tik om opnieuw te proberen Details - Address copied + Adres gekopieerd Titel van conversatie Notificaties Thema @@ -181,8 +182,8 @@ Gepland bericht Nu versturen - Copy text - Delete + Tekst kopiëren + Verwijderen Uiterlijk Algemeen @@ -192,7 +193,7 @@ Pure zwarte nachtmodus Starttijd Eindtijd - Automatic contact colors + Automatische contactkleuren Tekst grootte Gebruik lettertype van systeem Automatische emoji @@ -203,7 +204,7 @@ Button 2 Button 3 Notificatievoorbeeld - Wake screen + Scherm activeren Trillen Geluid Geen @@ -221,20 +222,31 @@ Geen Archiveren Verwijderen + Blokkeren Bellen - Markeer als gelezen - Markeer als ongelezen + Markeren als gelezen + Markeren als ongelezen Afleverrapporten Bevestig dat berichten met succes zijn verzonden - Signature - Add a signature to the end of your messages + Handtekening + Voeg een handtekening toe aan het eind van je berichten Verwijder accenten Verwijder accenten van karakters voor uitgaande berichten Alleen GSM-nummers Enkel mobiele nummers tonen wanneer je een bericht opstelt - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + + Verstuur lange berichten als MMS + Als je lange tekstberichten niet kunnen worden verzonden, of in de verkeerde volgorde, kun je ze in plaats daarvan als MMS-berichten versturen. Extra kosten kunnen van toepassing zijn. Auto-compress MMS bijlagen Synchroniseer berichten Hersynchroniseer jouw berichten vanuit de SMS database op je telefoon @@ -243,34 +255,34 @@ Foutenregistratie ingeschakeld Foutenregistratie uitgeschakeld Voer duur (seconden) in - Blocking - Drop messages - Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + Geblokkeerd + Berichten weggooien + Verwijder inkomende berichten van geblokkeerde afzenders in plaats van ze te verbergen + Geblokkeerde gesprekken + Blokkeermanager QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Ingebouwde blokkeerfunctie in QKSMS + Filter je oproepen en berichten automatisch op één handige plaats! Community IQ™ stelt je in staat om ongewenste berichten van de bij community bekende spammers te blokkeren Automatisch filteren van berichten van ongevraagde nummers met behulp van de app \"Should I Answer\" - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here - Block - Unblock + Kopieer geblokkeerde nummers + Ga verder naar %s en kopieer je bestaande geblokkeerde nummers + Geblokkeerde nummers + Je geblokkeerde nummers verschijnen hier + Blokkeer een nieuw nummer + Blokkeer berichten van + Telefoonnummer + Blokkeren + Geblokkeerde berichten + Je geblokkeerde berichten verschijnen hier + Blokkeren + Deblokkeer - Continue to %s and block this number - Continue to %s and block these numbers + Ga verder naar %s en blokkeer dit nummer + Ga verder naar %s en blokkeer deze nummers - Continue to %s and allow this number - Continue to %s and allow these numbers + Ga door naar %s en sta dit nummer toe + Ga door naar %s en sta deze nummers toe Over Versie @@ -313,7 +325,7 @@ QKSMS is in actieve ontwikkeling, uw aankoop bevat alle toekomstige nieuwigheden exclusief voor QKSMS+! Laden… Bekijk meer conversaties - Markeer als gelezen + Markeren als gelezen Bel Verwijderen Toon meer @@ -324,13 +336,15 @@ Toepassen Geen - Markeren als gelezen - Beantwoorden - Bellen + Archiveren Verwijderen + Blokkeren + Bel + Markeer als gelezen + Beantwoorden - Yes - Continue + Ja + Doorgaan Annuleren Verwijderen Opslaan @@ -348,10 +362,10 @@ Bericht niet verzonden Het bericht naar %s is niet verzonden - System - Disabled - Always on - Automatic + Systeem + Uitgeschakeld + Altijd aan + Automatisch Toon naam en bericht @@ -366,19 +380,19 @@ Geen vertraging - Kort - Medium - Lang + 3 seconden + 5 seconden + 10 seconden - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Automatisch + 100kB + 200kB + 300kB + 600kB + 1000kB + 2000kB + Geen compressie Oké diff --git a/presentation/src/main/res/values-pl/strings.xml b/presentation/src/main/res/values-pl/strings.xml index 3d0c447c6..bdf2dc19d 100644 --- a/presentation/src/main/res/values-pl/strings.xml +++ b/presentation/src/main/res/values-pl/strings.xml @@ -1,5 +1,4 @@ - + Wybrano kartę SIM %1$d (%2$s) Wybrano %s, zmień kartę SIM Wyślij wiadomość Wysyłanie… @@ -225,6 +226,7 @@ Brak Archiwizuj Usuń + Zablokuj Zadzwoń Oznacz jako przeczytane Oznacz jako nieprzeczytane @@ -237,6 +239,18 @@ Usuwaj ogonki ze znaków w wiadomościach Tylko numery komórkowe Pokazuj kontakty tylko z numerami komórkowymi + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + After %d days + After %d days + Wysyłaj długie wiadomości jako MMS Jeśli dłuższe wiadomości tekstowe nie są wysyłane lub wysyłane są w złej kolejności, możesz wysyłać je jako wiadomości MMS. Mogą zostać naliczone dodatkowe opłaty Kompresja załączników MMSów @@ -332,10 +346,12 @@ Zastosuj Brak + Archiwizuj + Usuń + Zablokuj + Zadzwoń Oznacz jako przeczytane Odpowiedz - Zadzwoń - Usuń Tak Kontynuuj @@ -376,9 +392,9 @@ Bez opóźnienia - Krótki - Średni - Długi + 3 sekundy + 5 sekund + 10 sekund Automatyczna @@ -388,7 +404,7 @@ 600 KB 1000 KB 2000 KB - Bez kompresji + Wyłączona OK diff --git a/presentation/src/main/res/values-pt-rBR/strings.xml b/presentation/src/main/res/values-pt-rBR/strings.xml index d9bd784c0..d9e86e707 100644 --- a/presentation/src/main/res/values-pt-rBR/strings.xml +++ b/presentation/src/main/res/values-pt-rBR/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selecionado, altere o cartão SIM Enviar mensagem Enviando… Entregue %s Falha ao enviar. Toque para tentar novamente Detalhes - Endereço copiado + Address copied Título da conversa Notificações Tema @@ -181,8 +182,8 @@ Mensagem agendada Enviar agora - Copiar texto - Apagar + Copy text + Delete Aparência Geral @@ -192,7 +193,7 @@ Modo noturno preto puro Hora de início Hora de término - Cores automáticas para os contactos + Automatic contact colors Tamanho da fonte Usar fonte do sistema Emoji automático @@ -203,7 +204,7 @@ Botão 2 Botão 3 Pré-visualização de notificações - Ligar ecrã + Wake screen Vibração Som Nenhum @@ -221,20 +222,31 @@ Nenhuma Arquivar Excluir - Chamar - Marcar como lida - Marcar como não lida + Block + Call + Mark read + Mark unread Confirmações de entrega Confirme que as mensagens foram enviadas com sucesso Assinatura - Adicionar uma assinatura às mensagens + Add a signature to the end of your messages Remover acentos Remover acentos de caracteres em mensagens SMS enviadas Somente números de celular Ao escrever uma mensagem, mostrar apenas os números de celular - Enviar mensagens longas como MMS - Se as mensagens longas não estiverem a ser enviadas ou estão a ser enviadas pela ordem errada, pode tentar enviar como MMS. Podem ser aplicados custos adicionais. + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + + Send long messages as MMS + If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Comprimir anexos MMS automaticamente Sincronizar mensagens Re-sincronizar suas mensagens com o banco de dados de SMS nativo do Android @@ -243,34 +255,34 @@ Log de depuração ativado Log de depuração desativado Digite a duração (segundos) - Bloqueio - Descartar mensagens - As mensagens enviadas pelos remetentes bloqueados serão descartadas e não ocultadas - Conversas bloqueadas - Gestor de bloqueios + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager QKSMS - Funcionalidade de bloqueio, nativa no QKSMS - Filtrar automaticamente as chamadas e as mensagens com a aplicação Community IQ™, com a qual pode descartar as mensagens que forem enviadas pelo \'spammers\' reconhecidos na comunidade + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automaticamente filtrar mensagens não solicitadas usando o aplicativo \"Should I Answer\" - Copiar números bloqueados - Continuar para %s e substituir os números bloqueados - Números bloqueados - Os números bloqueados aparecerão aqui - Bloquear um novo número - Bloquear mensagens de - Número de telefone - Bloquear - Mensagens bloqueadas - As mensagens bloqueadas aparecerão aqui - Bloquear - Desbloquear + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock - Continuar para %s e bloquear este número - Continuar para %s e bloquear estes números + Continue to %s and block this number + Continue to %s and block these numbers - Continuar para %s e permitir este número - Continuar para %s e permitir estes números + Continue to %s and allow this number + Continue to %s and allow these numbers Sobre Versão @@ -313,7 +325,7 @@ O QKSMS está em desenvolvimento ativo e sua compra incluirá todos os futuros recursos do QKSMS+! Carregando… Ver mais conversas - Marcar como lida + Mark read Ligar Excluir Mostrar mais @@ -324,13 +336,15 @@ Aplicar Nenhuma - Marcar como lida - Responder - Chamar - Excluir + Archive + Delete + Block + Call + Mark read + Reply - Sim - Continuar + Yes + Continue Cancelar Excluir Salvar @@ -348,10 +362,10 @@ Mensagem não enviada A mensagem para %s falhou ao enviar - Sistema - Desativado - Sempre - Automático + System + Disabled + Always on + Automatic Exibir nome e mensagem @@ -366,19 +380,19 @@ Sem atraso - Curto - Médio - Longo + 3 seconds + 5 seconds + 10 seconds - Automático - 100 KB - 200 KB - 300 KB - 600 KB - 1000 KB - 2000 KB - Sem compressão + Automatic + 100KB + 200KB + 300KB + 600KB + 1000KB + 2000KB + No compression Ok diff --git a/presentation/src/main/res/values-pt/strings.xml b/presentation/src/main/res/values-pt/strings.xml index aa0b2dc2a..9459bfe18 100644 --- a/presentation/src/main/res/values-pt/strings.xml +++ b/presentation/src/main/res/values-pt/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selecionado %s selecionado, alterar cartão SIM Enviar mensagem A enviar… @@ -221,6 +222,7 @@ Nenhuma Arquivar Apagar + Bloquear Ligar Marcar como lida Marcar como não lida @@ -233,6 +235,16 @@ Remover acentos dos caracteres nas mensagens enviadas Apenas números de telemóvel Ao escrever uma mensagem, mostrar apenas os números móveis + Apagar mensagens antigas automaticamente + As mensagens serão eliminadas após o número de dias especificado + Número de dias + Nunca + Apagar mensagens antigas automaticamente? + Se continuar, %1$d mensagens serão eliminadas + + Após 1 dia + Após %d dias + Enviar mensagens longas como MMS Se as mensagens longas não estiverem a ser enviadas ou estão a ser enviadas pela ordem errada, pode tentar enviar como MMS. Podem ser aplicados custos adicionais. Comprimir anexos MMS automaticamente @@ -324,10 +336,12 @@ Aplicar Nenhuma + Arquivar + Apagar + Bloquear + Ligar Marcar como lida Responder - Ligar - Apagar Sim Continuar @@ -366,9 +380,9 @@ Não - Curto - Médio - Longo + 3 segundos + 5 segundos + 10 segundos Automático diff --git a/presentation/src/main/res/values-ro/strings.xml b/presentation/src/main/res/values-ro/strings.xml index 8c6e6ae86..0637d1473 100644 --- a/presentation/src/main/res/values-ro/strings.xml +++ b/presentation/src/main/res/values-ro/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selected, change SIM card Send message Sending… @@ -223,9 +224,10 @@ None Archive Delete + Block Call - Mark as read - Mark as unread + Mark read + Mark unread Delivery confirmations Confirm that messages were sent successfully @@ -235,6 +237,17 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -317,7 +330,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark as read + Mark read Call Delete Show more @@ -328,10 +341,12 @@ Apply None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -371,9 +386,9 @@ No delay - Short - Medium - Long + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-ru/strings.xml b/presentation/src/main/res/values-ru/strings.xml index 45c5717b3..70f8a15ff 100644 --- a/presentation/src/main/res/values-ru/strings.xml +++ b/presentation/src/main/res/values-ru/strings.xml @@ -1,5 +1,4 @@ - + Выбрана SIM-карта %1$d (%2$s) Выбрано %s, сменить SIM-карту Отправить сообщение Отправка… @@ -166,7 +167,7 @@ %d сообщений %d сообщений - В настоящее время поддерживается резервное копирование и восстановление только SMS. Поддержка MMS и запланированные резервные копии появятся в ближайшее время! + В настоящее время поддерживается резервное копирование и восстановление только SMS. Поддержка MMS и регулярные резервные копии появятся в ближайшее время! Резервировать Анализ резервной копии… %d/%d сообщений @@ -174,7 +175,7 @@ Синхронизация сообщений… Завершено! Резервирование - Запланировать + Планировщик Автоматически отправить сообщение когда пожелаете Привет! Когда у вас был день рождения? Он был 23 декабря @@ -225,9 +226,10 @@ Нет Архивировать Удалить + Блокировать Позвонить - Отметить прочитанным - Отметить непрочитанным + Пометить прочитанным + Пометить непрочитанным Отчёты о доставке Подтверждение успешной отправки сообщений @@ -237,6 +239,18 @@ Удаление акцентов с символов в исходящих SMS-сообщениях Только мобильные номера При составлении сообщения показывать только мобильные номера + Удалять старые сообщения автоматически + Сообщения будут удалены после указанного количества дней + Количество дней + Никогда + Удалить старые сообщения автоматически? + Если продолжить, будет удалено сообщений: %1$d + + Через день + Через %d дня + Через %d дней + Через %d дней + Отправлять длинные сообщения как MMS Если не удаётся отправить длинные текстовые сообщения или они отправляются в неправильном порядке, вы можете отправить их в виде MMS. Может взиматься дополнительная плата. Автоматически сжимать вложения MMS @@ -321,7 +335,7 @@ QKSMS находится в активной разработке и ваша покупка будет включать в себя все функции QKSMS+ в будущем! Загрузка… Посмотреть больше бесед - Отметить прочитанным + Прочитано Позвонить Удалить Показать ещё @@ -332,10 +346,12 @@ Применить Нет + В архив + Удалить + Заблокировать + Позвонить Прочитано Ответить - Позвонить - Удалить Да Продолжить @@ -376,9 +392,9 @@ Без задержки - Короткая - Средняя - Длинная + 3 секунды + 5 секунд + 10 секунд Автоматически diff --git a/presentation/src/main/res/values-sk/strings.xml b/presentation/src/main/res/values-sk/strings.xml index ff0d89323..955be4fbb 100644 --- a/presentation/src/main/res/values-sk/strings.xml +++ b/presentation/src/main/res/values-sk/strings.xml @@ -1,5 +1,4 @@ - Nová konverzácia - Písať - Shortcut disabled + Nová správa + Skratka je vypnutá Archivované Nastavenia Upozornenia @@ -30,18 +29,18 @@ Nová správa Preskočiť Pokračovať - Add person + Pridať osobu Volať Podrobnosti Uložiť do galérie - Share + Zdielať Otvoriť navigačné menu %d vybraných Vymazať Archivovať Zrušiť archiváciu Odstrániť - Add to contacts + Pridať do kontaktov Pripnúť na začiatok Odopnúť Označiť ako prečítané @@ -49,7 +48,7 @@ Zablokovať Synchronizácia správ… Vy: %s - Draft: %s + Koncept Výsledky v správach %d správ Tu sa zobrazia vaše konverzácie @@ -78,37 +77,37 @@ ODMIETNUŤ Zmazať - Are you sure you would like to delete this conversation? - Are you sure you would like to delete %d conversations? - Are you sure you would like to delete %d conversations? - Are you sure you would like to delete %d conversations? + Ste si istý, že chcete odstrániť túto konverzáciu? + Ste si istý, že chcete odstrániť %d konverzácie? + Ste si istý, že chcete odstrániť %d konverzácií? + Ste si istý, že chcete odstrániť %d konverzácií? Kopírovať text Preposlať Odstrániť - Choose a phone number - %s ∙ Default - Just once - Always - %d selected - %1$d of %2$d results + Vybrať telefónne číslo + %s ∙ Východzie + Iba raz + Vždy + %d vybratých + %1$d z %2$d výsledkov Poslať ako skupinovú správu - Recipients and replies will be visible to everyone + Príjemcovia a odpovede budú viditeľné všetkým Toto je začiatok vašej konverzácie. Napíšte niečo pekného! - Contact card - Scheduled for - Selected time must be in the future! - You must unlock QKSMS+ to use scheduled messaging - Added to scheduled messages + Karta kontaktu + Naplánované na + Vybraný čas musí ešte len nastať! + Ak si prajete používať plánovanie správ, aktivujte si QKSMS+ + Pridané do naplánovaných správ Napíšte správu… Kopírovať text Preposlať Odstrániť - Previous - Next - Clear + Predchádzajúci + Ďalší + Vymazať Detaily správy Typ: %s Od: %s @@ -121,19 +120,21 @@ Doručené: %s Kód chyby: %d Pridať prílohu - Attach a photo - Take a photo - Schedule message + Priložiť fotografiu + Urobiť fotografiu + Naplánovať správu Pripojiť kontakt - Error reading contact - %s selected, change SIM card + Chyba čítania kontaktu + + Vybrána SIM %1$d (%2$s) + %s vybrané, zmeňte SIM kartu Odoslať správu Odosiela sa… Doručené %s - Failed to send. Tap to try again + Odoslanie zlyhalo. Klepnutím skúste znovu Detaily - Address copied - Conversation title + Adresa skopírovaná + Názov konverzácie Upozornenia Motív Archivovať @@ -141,144 +142,157 @@ Blokovať Odblokovať Odstrániť konverzáciu - Couldn\'t load media - Saved to gallery - Backup and restore - Backing up messages - Restoring from backup + Súbor sa nepodarilo načítať + Uložené do galérie + Záloha a obnovenie + Zálohovanie správ + Obnovenie zo zálohy Posledná záloha Načítavanie… Nikdy Obnoviť Zvoľte zálohu - Please unlock QKSMS+ to use backup and restore + Ak si prajete používať funkciu zálohovania a obnovy, prosím aktivujte si QKSMS+ Prebieha zálohovanie… Prebieha obnova… Obnoviť zo zálohy - Are you sure you would like to restore your messages from this backup? + Ste si istý obnovou všetkých správ z tejto zálohy? Zastaviť obnovu - Messages that have already been restored will remain on your device + Už obnovené správy ostanú vo vašom zariadení Zálohy Nenašli sa žiadne zálohy - %d message - %d messages - %d messages + %d správa + %d správy + %d správ %d správ - Currently, only SMS is supported by Backup and Restore. MMS support and scheduled backups will be coming soon! + Momentálne sú pre zálohovanie a obnovenie podporované iba SMS. MMS podpora a plánované zálohy budú pridané už čoskoro! Zálohovať Analýza zálohy… %d/%d správ Ukladá sa záloha… Synchronizácia správ… - Finished! + Dokončené! Zálohovanie a obnovenie - Scheduled - Automatically send a message, at the exact moment you\'d like - Hey! When was your birthday again? - It\'s on December 23rd - Happy birthday! Look at what a great friend I am, remembering your birthday + Naplánované + Automaticky odošle správu v presnom okamihu, ktorý si prajete + Nazdar! Kedy máš narodeniny? + 23. decembra + Všetko najlepšie! Vidíš akého máš dobrého kamaráta, pamätám si tvoje narodeniny. - Sending on December 23rd  - Schedule a message - Scheduled message + Pošle sa 23. decembra + Naplánovať správu + Naplánovaná správa Odoslať teraz - Copy text - Delete + Kopírovať text + Odstrániť Vzhľad Všeobecné - QK Reply + Rýchla odpoveď Motív Nočný režim - Pure black night mode + Čisto čierny nočný režim Čas spustenia Čas ukončenia - Automatic contact colors + Automatické farby kontaktov Veľkosť písma Použiť systémové písmo Automatické emoji Upozornenia - Tap to customize + Klepnutím prizpôsobíte Akcie Tlačidlo 1 Tlačidlo 2 Tlačidlo 3 Obsah upozornení - Wake screen - Vibration - Sound - None - QK Reply - Popup for new messages - Tap to dismiss - Tap outside of the popup to close it - Delayed sending - Swipe actions - Configure swipe actions for conversations - Right swipe - Left swipe - CHANGE + Rozsvietiť obrazovku + Vybrácie + Zvuk + Žiadny + Rýchla odpoveď + Vyskakovacie okno pre nové správy + Klepnutím zavriete + Zavriete klepnutím mimo vyskakovacie okno + Oneskorené odoslanie + Akcie potiahnutím + Nastavenie akcií potiahnutím pre konverzácie + Potiahnutie doprava + Potiahnutie doľava + ZMENIŤ - None + Žiadny Archivovať Odstrániť - Zavolať - Mark as read - Mark as unread + Zablokovať + Volať + Označiť ako prečítané + Označiť ako neprečítané - Delivery confirmations - Confirm that messages were sent successfully - Signature - Add a signature to the end of your messages - Strip accents - Remove accents from characters in outgoing SMS messages - Mobile numbers only - When composing a message, only show mobile numbers - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply - Auto-compress MMS attachments - Sync messages - Re-sync your messages with the native Android SMS database + Potvrdenie o doručení + Potvrdiť úspešné doručenie správy + Podpis + Pridať podpis na koniec vašich správ + Odstrániť diakritiku + Odstrániť diakritiku v odosielaných správach + Iba telefónne čísla + Pri písaní správ, zobraziť iba telefónne čísla + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + After %d days + After %d days + + Dlhé správy odoslať ako MMS + Ak zlyháva odosielanie dlhých správ alebo sú odoslané v nesprávnom poradí, môžete ich poslať ako MMS. Môžu byť účtované ďalšie poplatky. + Automaticky komprimovať MMS prílohy + Synchronizovať správy + Opätovne synchronizovať vaše správy s natívnou SMS databázou O aplikácii QKSMS Verzia %s - Debug logging enabled - Debug logging disabled - Enter duration (seconds) - Blocking - Drop messages - Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + Ladiace záznamy povolené + Ladiace záznamy vypnuté + Zadajte trvanie v sekundách + Blokovanie + Zahodiť správy + Zahodiť prichádzajúce správy od blokovaných odosielateľov namiesto ich skrytia. + Blokované konverzácie + Správca blokovaní QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here - Block - Unblock + Vstavané blokovanie v QKSMS + Automaticky filtrujte svoje hovory a správy na jednom mieste! Community IQ™ vám poskytuje ochranu pred spamom od komunitou označených spammerov. + Automaticky filtrovať správy od nevyžiadaných čísiel pomocou aplikácie \"Should I Answer?\" + Kopírovať blokované čísla + Pokračovať do %s a skopírovať existujúce zablokované čísla + Blokované čísla + Tu sa objavia vami blokované čísla + Zablokovať nové číslo + Zablokovať správy od + Telefónne číslo + Zablokovať + Blokované správy + Tu sa objavia vami blokované čísla + Zablokovať + Odblokovať - Continue to %s and block this number - Continue to %s and block these numbers - Continue to %s and block these numbers - Continue to %s and block these numbers + Pokračovať na %s a zablokovať toto číslo + Pokračovať na %s a zablokovať tieto čísla + Pokračovať na %s a zablokovať tieto čísla + Pokračovať na %s a zablokovať tieto čísla - Continue to %s and allow this number - Continue to %s and allow these numbers - Continue to %s and allow these numbers - Continue to %s and allow these numbers + Pokračovať na %s a povoliť toto číslo + Pokračovať na %s a povoliť tieto čísla + Pokračovať na %s a povoliť tieto čísla + Pokračovať na %s a povoliť tieto čísla O aplikácii Verzia @@ -288,39 +302,39 @@ Konktakt Licencia Autorské práva - Support development, unlock everything - You can save a starving developer for just %s + Podporiť vývoj, odomknúť všetko + Môžete zachrániť hladujúceho vývojára iba za %s - Lifetime upgrade for %1$s %2$s + Doživotná aktivácia za %1$s %2$s - Unlock + donate for %1$s %2$s - Thank you for supporting QKSMS! - You now have access to all QKSMS+ features - QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. - Donate via PayPal + Odomknúť + darovať za %1$s %2$s + Ďakujeme, že ste podporili QKSMS! + Odteraz máte prístup ku všetkým QKSMS+ funkciám + QKSMS+ je zdarma pre používateľov F-Droid! Ak si prajete podporiť vývoj, neváhajte prispieť. + Prispieť cez PayPal Už čoskoro - Premium themes - Unlock beautiful theme colors not available on the Material Design palette - Custom auto-emoji - Create custom auto-emoji shortcuts - Message backup - Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost - Scheduled messages - Schedule messages to automatically be sent at a specific time and date - Delayed sending - Wait a few seconds before sending your message + Prémiové témy + Odomknúť nádherné farebné témy, ktoré nie sú dostupné v Material Design palete + Vlastné automatické emoji + Vytvorte si skratku pre vlastné automatické emoji + Zálohovanie správ + Automaticky zálohujte svoje správy. Už sa nemusíte obávať straty správ pri strate alebo výmene telefónu + Plánované správy + Plánované správy budú automaticky odoslané v určený dátum a čas + Oneskorené odoslanie + Vyčkať pár sekúnd pred odoslaním správy Automatický nočný režim - Enable night mode based on the time of day + Zapnutie nočného režimu podľa dennej doby Rozšírené blokovanie Blokujte správy, ktoré obsahujú kľúčové slová, alebo odpovedajú vzoru - Auto-forward - Automatically forward messages from certain senders - Auto-respond - Automatically respond to incoming messages with a preset response + Automatické preposlanie + Automaticky preposlať správy od určitých odosielateľov + Automatická odpoveď + Automaticky odpovedať na príchodzie správy s prednastavenou odpoveďou Viac - QKSMS is under active development, and your purchase will include all future QKSMS+ features! + QKSMS je v aktívnom vývoji a váš nákup bude obsahovať aj všetky budúce QKSMS+ funkcie! Načítavanie… - View more conversations + Zobraziť viacej konverácií Označiť ako prečítané Volať Odstrániť @@ -331,37 +345,39 @@ HEX Použiť - None + Žiadny + Archivovať + Odstrániť + Zablokovať + Volať Označiť ako prečítané - Reply - Call - Delete + Odpovedať - Yes - Continue - Cancel - Delete - Save - Stop - More - Set - Undo - Copied - Archived conversation - You must unlock QKSMS+ to use this + Áno + Pokračovať + Zrušiť + Odstrániť + Uložiť + Zastaviť + Viac + Nastaviť + Vrátiť späť + Skopírované + Archivované konverzácie + Pre použitie aktivujte QKSMS+ - New message - %s new messages - %s new messages - %s new messages + Nová správa + %s nové správy + %s nových správ + %s nových správ - Message not sent - The message to %s failed to send + Správa nebola odoslaná + Zlyhalo odoslanie správy pre %s - System - Disabled - Always on - Automatic + Systém + Vypnuté + Vždy zapnuté + Automaticky Zobraziť meno a správu @@ -376,19 +392,19 @@ Bez oneskorenia - Short - Medium - Long + 3 sekundy + 5 sekúnd + 10 sekúnd - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Automaticky + 100 kB + 200 kB + 300 kB + 600 kB + 1 000 kB + 2 000 kB + Bez kompresie V poriadku diff --git a/presentation/src/main/res/values-sl/strings.xml b/presentation/src/main/res/values-sl/strings.xml index eb4a56a04..593a3ac58 100644 --- a/presentation/src/main/res/values-sl/strings.xml +++ b/presentation/src/main/res/values-sl/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s izbran, zamenjaj kartico SIM Pošlji sporočilo Pošiljanje… @@ -167,7 +168,7 @@ %d sporočila
Trenutno so za ustvarjanje in obnovo varnostnih kopij podprta le sporočila SMS. MMS podpora in varnostne kopije pridejo kmalu! - Ustvari varnostno kopijo sedaj + Ustvari varnostno kopijo Razčlenjevanje varnostne kopije… %d/%d sporočil Shranjevanje varnostne kopije… @@ -193,7 +194,7 @@ QK Odgovor Teme Nočni način - Čisto črni nočni način + Črn nočni način Začetni čas Čas končanja Samodejne barve stikov @@ -225,9 +226,10 @@ Brez Arhiviraj Izbriši - Pokliči - Označi kot prebrano - Označi kot neprebrano + Block + Call + Mark read + Mark unread Potrditev dostavitve sporočila Potrdi, da so bila sporočila dostavljena uspešno @@ -237,6 +239,18 @@ Odstrani narečja iz besedila odhodnih SMS sporočil Samo telefonske številke Ko sestavljaš sporočilo, prikaži samo telefonske številke + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + After %d days + After %d days + Pošlji dolga sporočila kot MMS Če se vaša daljša sporočila ne pošljejo, ali se pošiljajo v napačnem vrstnem redu, jih lahko pošljete kot MMS sporočilo. To lahko povzroči dodatne stroške Samodejno stisni MMS priponke @@ -321,7 +335,7 @@ QKSMS je pod aktivnim razvojem in vaš nakup QKSMS+ bo vseboval vse prihodnje funkcije! Nalaganje… Poglej več pogovorov - Označi kot prebrano + Mark read Pokliči Izbriši Pokaži več @@ -332,10 +346,12 @@ Potrdi Brez - Označi kot prebrano - Odgovori - Klic - Izbriši + Archive + Delete + Block + Call + Mark read + Reply Da Nadaljuj @@ -359,9 +375,9 @@ Sporočilo za %s ni bilo poslano Sistem - Onemogočeno + Onemogočen Vedno vključen - Samodejno + Samodejen Prikaži ime in sporočilo @@ -369,26 +385,26 @@ Skrij vsebino - Majhno - Običajno - Veliko - Večje + Majhna + Običajna + Velika + Večja Brez zakasnitve - Kratko - Srednje - Dolgo + 3 seconds + 5 seconds + 10 seconds - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Samodejno + 100 kB + 200 kB + 300 kB + 600 kB + 1000 kB + 2000 kB + Brez stiskanja V redu diff --git a/presentation/src/main/res/values-sr/strings.xml b/presentation/src/main/res/values-sr/strings.xml index c69447caa..14a2fc5a4 100644 --- a/presentation/src/main/res/values-sr/strings.xml +++ b/presentation/src/main/res/values-sr/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selected, change SIM card Send message Sending… @@ -223,9 +224,10 @@ None Archive Delete + Block Call - Mark as read - Mark as unread + Mark read + Mark unread Delivery confirmations Confirm that messages were sent successfully @@ -235,6 +237,17 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -317,7 +330,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark as read + Mark read Call Delete Show more @@ -328,10 +341,12 @@ Apply None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -371,9 +386,9 @@ No delay - Short - Medium - Long + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-sv/strings.xml b/presentation/src/main/res/values-sv/strings.xml index 30a8a5b13..48b4c6fdc 100644 --- a/presentation/src/main/res/values-sv/strings.xml +++ b/presentation/src/main/res/values-sv/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) valdes %s valt, byt SIM-kort Skicka meddelandet Skickar… Levererade %s Misslyckades med att skicka. Knacka för att försöka igen Detaljer - Address copied + Adress kopierades Titel för konversation Aviseringar Tema @@ -181,7 +182,7 @@ Schemalagt meddelande Skicka nu - Copy text + Kopiera text Radera Utseende @@ -192,7 +193,7 @@ Svart nattläge Starttid Sluttid - Automatic contact colors + Automatiska kontaktfärger Storlek på typsnitt Använd systemteckensnitt Automatisk uttryckssymbol @@ -203,7 +204,7 @@ Knapp 2 Knapp 3 Notifikations förhandsvisningar - Wake screen + Väck skärm Vibration Ljud Ingen @@ -221,7 +222,8 @@ Inget Arkivera Ta bort - Ring + Blockera + Ring upp Markera som läst Markera som oläst @@ -233,8 +235,18 @@ Ta bort accenter från tecken i utåtgående SMS-meddelanden Endast mobilnummer Visa endast mobilnummer när ett meddelanden skrivs + Radera gamla meddelanden automatiskt + Meddelanden kommer att raderas efter det angivna antalet dagar + Antal dagar + Aldrig + Radera gamla meddelanden automatiskt? + Om du fortsätter raderas %1$d meddelanden direkt + + After 1 day + Efter %d dagar + Skicka långa meddelanden som MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply + Om dina längre textmeddelanden inte skickas eller skickas i fel ordning kan du skicka dem som MMS-meddelanden istället. Ytterligare avgifter kan tillkomma Komprimera MMS-bilagor automatiskt Synkronisera meddelanden Synkronisera dina meddelanden med telefonens SMS-databas @@ -244,13 +256,13 @@ Felsökningsloggning inaktiverad Fyll i varaktighet (sekunder) Blockerar - Drop messages - Drop incoming messages from blocked senders instead of hiding them + Avvisa meddelanden + Avvisa inkommande meddelanden från blockerade avsändare istället för att dölja dem Blockerade konversationer Blockerings-hanterare QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Inbyggd blockeringsfunktion i QKSMS + Filtrera automatiskt dina samtal och meddelanden på ett bekvämt ställe! Med Community IQ™ kan du förhindra oönskade meddelanden från gemenskapens kända spammare Filtrera automatiskt meddelanden från oönskade nummer genom att använda appen \"Borde jag svara\" Kopiera blockerade nummer Fortsätt till %s och kopiera över dina befintliga blockerade nummer @@ -262,15 +274,15 @@ Blockera Blockerade meddelanden Dina blockerade meddelanden visas här - Block + Blockera Avblockera - Continue to %s and block this number - Continue to %s and block these numbers + Fortsätt till %s och blockera detta nummer + Fortsätt till %s och blockera dessa nummer - Continue to %s and allow this number - Continue to %s and allow these numbers + Fortsätt till %s och tillåt detta nummer + Fortsätt till %s och tillåt dessa nummer Om Version @@ -324,10 +336,12 @@ Verkställ Inget + Arkivera + Radera + Blockera + Ring upp Markera som läst Svara - Ring - Ta bort Ja Fortsätt @@ -351,7 +365,7 @@ System Inaktiverad Alltid på - Automatic + Automatisk Visa namn och meddelande @@ -366,9 +380,9 @@ Ingen fördröjning - Kort - Mellan - Lång + 3 sekunder + 5 sekunder + 10 sekunder Automatisk @@ -383,7 +397,7 @@ Okej Ett ögonblick - På väg + Jag är på väg Tack Låter bra Vad händer? diff --git a/presentation/src/main/res/values-th/strings.xml b/presentation/src/main/res/values-th/strings.xml index cb366da8d..016ea0038 100644 --- a/presentation/src/main/res/values-th/strings.xml +++ b/presentation/src/main/res/values-th/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s เลือก เปลี่ยน SIM การ์ด ส่งข้อความ กำลังส่ง... @@ -219,9 +220,10 @@ None Archive Delete + Block Call - Mark as read - Mark as unread + Mark read + Mark unread ยืนยันการส่ง ยืนยันว่า ส่งข้อความเรียบร้อยแล้ว @@ -231,6 +233,15 @@ ลบอักษรพิเศษ accents ออกจากข้อความ SMS ที่กำลังจะส่งออก Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply บีบอัดสิ่งที่แนบมาใน MMS อัตโนมัติ @@ -309,7 +320,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! กำลังโหลด… ดูการสนทนาเพิ่มเติม - ทำเครื่องหมายอ่าน + Mark read โทร Delete แสดงเพิ่มเติม @@ -320,10 +331,12 @@ ตกลง None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -361,9 +374,9 @@ ไม่ล่าช้า - สั้น - ปานกลาง - ยาว + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-tl/strings.xml b/presentation/src/main/res/values-tl/strings.xml index 187332d49..cb32cf1c1 100644 --- a/presentation/src/main/res/values-tl/strings.xml +++ b/presentation/src/main/res/values-tl/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selected, change SIM card Send message Sending… @@ -221,9 +222,10 @@ None Archive Delete + Block Call - Mark as read - Mark as unread + Mark read + Mark unread Delivery confirmations Confirm that messages were sent successfully @@ -233,6 +235,16 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -313,7 +325,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark as read + Mark read Call Delete Show more @@ -324,10 +336,12 @@ Apply None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -366,9 +380,9 @@ No delay - Short - Medium - Long + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-tr/strings.xml b/presentation/src/main/res/values-tr/strings.xml index 891dafb73..e1a4e017b 100644 --- a/presentation/src/main/res/values-tr/strings.xml +++ b/presentation/src/main/res/values-tr/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) seçili Seçili, %s SIM kartı değiştirin Mesaj gönder Gönderiliyor... @@ -221,6 +222,7 @@ Hiçbiri Arşivle Sil + Engelle Ara Okundu olarak işaretle Okunmadı olarak işaretle @@ -233,13 +235,23 @@ Giden SMS mesajlarından aksanlı karakterleri kaldırın Sadece cep telefonu numaraları Bir mesaj oluştururken, yalnızca cep telefonu numaralarını göster + Eski mesajları otomatik olarak sil + Belirtilen sayıda gün geçtikten sonra mesajlar silinecek + Gün sayısı + Asla + Eski mesajlar otomatik olarak silinsin mi? + Devam ederseniz, %1$d mesaj şimdi silinecek + + 1 gün sonra + %d gün sonra + Uzun mesajları MMS olarak gönder Uzun metin mesajlarınız gönderilemiyorsa veya yanlış sırada gönderiliyorsa, onları MMS mesajları olarak gönderebilirsiniz. Ek ücretler uygulanabilir Otomatik-MMS ekleri sıkıştır İletileri eşitle İletilerinizi Android SMS veritabanı ile yeniden senkronize edin QKSMS Hakkında - Sürüm ve Diğerleri(TR Muhammet Öztürk) + Sürüm %s Hata ayıklama günlüğü etkin Hata ayıklama günlüğü devre dışı Süre (saniye olarak) girin @@ -326,10 +338,12 @@ BAĞLAM İSTEĞİ
Uygula Hiçbiri - Okundu olarak işaretle - Yanıtla - Ara + Arşivle Sil + Engelle + Ara + Okundu olarak işaretle + Cevapla Evet Devam @@ -368,9 +382,9 @@ BAĞLAM İSTEĞİ
Gecikme yok - Kısa - Orta - Uzun + 3 saniye + 5 saniye + 10 saniye Otomatik diff --git a/presentation/src/main/res/values-uk/strings.xml b/presentation/src/main/res/values-uk/strings.xml index d586fc98e..45ed4b6e0 100644 --- a/presentation/src/main/res/values-uk/strings.xml +++ b/presentation/src/main/res/values-uk/strings.xml @@ -1,5 +1,4 @@ - + Обрана SIM-карта %1$d (%2$s) %s обрано, змініть SIM-картку Надіслати повідомлення Надсилання… @@ -225,6 +226,7 @@ Немає Перенести в архів Видалити + Заблокувати Виклик Позначити як прочитане Позначити як непрочитане @@ -237,6 +239,18 @@ Видалення акцентів з символів у вихідних SMS-повідомленнях Лише номери мобільних При написанні повідомлення показувати лише мобільні номери + Автоматично видаляти старі повідомлення + Повідомлення будуть видалені після вказаної кількості днів + Кількість днів + Ніколи + Автоматично видаляти старі повідомлення? + Якщо ви продовжите, %1$d повідомлень буде видалено + + Після 1 дня + Після %d днів + Після %d днів + Після %d днів + Надсилати довгі повідомлення як MMS Якщо частини довгого повідомлення не відправляються або роблять це в хибному порядку, ви можете відправити їх як MMS натомість. Оплата згідно тарифів вашого оператора Автоматичне стиснення MMS-вкладень @@ -332,10 +346,12 @@ Застосувати Немає + Архів + Видалити + Заблокувати + Виклик Позначити як прочитане Відповісти - Зателефонувати - Видалити Так Продовжити @@ -376,9 +392,9 @@ Без затримок - Коротка - Середня - Довга + 3 секунди + 5 секунд + 10 секунд Автоматично diff --git a/presentation/src/main/res/values-ur/strings.xml b/presentation/src/main/res/values-ur/strings.xml index 8833a810c..aa94dc935 100644 --- a/presentation/src/main/res/values-ur/strings.xml +++ b/presentation/src/main/res/values-ur/strings.xml @@ -1,5 +1,4 @@ - + SIM %1$d (%2$s) selected %s selected, change SIM card Send message Sending… @@ -221,9 +222,10 @@ None Archive Delete + Block Call - Mark as read - Mark as unread + Mark read + Mark unread Delivery confirmations Confirm that messages were sent successfully @@ -233,6 +235,16 @@ Remove accents from characters in outgoing SMS messages Mobile numbers only When composing a message, only show mobile numbers + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After 1 day + After %d days + Send long messages as MMS If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply Auto-compress MMS attachments @@ -313,7 +325,7 @@ QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark as read + Mark read Call Delete Show more @@ -324,10 +336,12 @@ Apply None - Mark as read - Reply - Call + Archive Delete + Block + Call + Mark read + Reply Yes Continue @@ -366,9 +380,9 @@ No delay - Short - Medium - Long + 3 seconds + 5 seconds + 10 seconds Automatic diff --git a/presentation/src/main/res/values-vi/strings.xml b/presentation/src/main/res/values-vi/strings.xml index 24b7ee63c..d051c7a87 100644 --- a/presentation/src/main/res/values-vi/strings.xml +++ b/presentation/src/main/res/values-vi/strings.xml @@ -1,5 +1,4 @@ - + Đã chọn SIM %1$d (%2$s) %s đã được chọn, hãy đổi thẻ SIM Gửi tin nhắn Đang gửi… @@ -186,8 +187,8 @@ Chung Trả lời nhanh Chủ đề - Chế độ ban đêm - Chế độ ban đêm tinh khiết + Giao diện tối + Giao diện tối AMOLED Thời gian bắt đầu Thời gian kết thúc Tự động hiện màu sắc của liên hệ @@ -219,9 +220,10 @@ Không dùng Lưu trữ Xoá - Gọi điện - Đánh dấu là đã đọc - Đánh dấu là chưa đọc + Chặn + Gọi + Đánh dấu đã đọc + Đánh dấu chưa đọc Xác nhận đã gửi tin Xác nhận rằng các tin nhắn được gửi thành công @@ -231,6 +233,15 @@ Bỏ dấu ký tự khi gửi SMS Lọc số di động Khi soạn tin mới, chỉ hiển thị liên hệ có số di động + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After %d days + Gửi đoạn tin nhắn dài như MMS Nếu đoạn tin nhắn dài bị lỗi khi gửi, hoặc gửi sai thứ tự, bạn có thể gửi chúng như tin nhắn MMS thay thế. Cước phí bổ sung có thể bị áp dụng Tự động nén các tệp đính kèm MMS @@ -297,8 +308,8 @@ Tin nhắn hẹn giờ sẽ được tự động gửi vào thời gian cụ thể Trì hoãn gửi tin nhắn Chờ một vài giây trước khi gửi tin nhắn để bạn có thể hoàn tác nó - Chế độ ban đêm tự động - Kích hoạt chế độ ban đêm dựa trên thời gian trong ngày + Giao diện tối tự động + Kích hoạt giao diện tối dựa trên thời gian trong ngày Chặn nâng cao Chặn tin nhắn có chứa các từ khóa hoặc phù hợp với các mẫu Tự động chuyển tiếp @@ -320,10 +331,12 @@ Áp dụng Không dùng - Đánh dấu là đã đọc - Trả lời - Gọi + Lưu trữ Xoá + Chặn + Gọi + Đánh dấu đã đọc + Trả lời Đồng ý Tiếp tục @@ -343,7 +356,7 @@ Các tin không gửi Các tin gửi đến %s không thành công - Hệ thống + Theo hệ thống Vô hiệu hoá Luôn bật Tự động @@ -361,9 +374,9 @@ Không trì hoãn - Ngắn - Vừa - Dài + 3 giây + 5 giây + 10 giây Tự động diff --git a/presentation/src/main/res/values-zh-rCN/strings.xml b/presentation/src/main/res/values-zh-rCN/strings.xml index a0e3ebece..88c705b0c 100644 --- a/presentation/src/main/res/values-zh-rCN/strings.xml +++ b/presentation/src/main/res/values-zh-rCN/strings.xml @@ -1,5 +1,4 @@ - + 已选择 SIM 卡 %1$d (%2$s) + %s 已选择,更改 SIM 卡 发送消息 正在发送... 已送达 %s @@ -145,7 +146,7 @@ 正在从备份还原 上次备份 正在加载… - 永不 + 从未 还原 选择备份 请解锁 QKSMS+ 以使用备份和还原 @@ -160,12 +161,12 @@ %d 条消息 - 目前, 只有 SMS 支持备份和恢复。彩信支持和定时备份将很快到来! + 目前只有短信支持备份和恢复。彩信支持和定时备份将很快到来! 立即备份 正在分析备份… %d/%d 消息 正在保存备份… - 正在同步消息… + 正在同步信息… 完成! 备份和还原 预约 @@ -219,6 +220,7 @@ 归档 删除 + 拦截 呼叫 标记为已读 标记为未读 @@ -227,10 +229,19 @@ 确认短信已成功送达 签名 在您的信息结尾添加签名 - 删除读音符号 - 删除短信中字母上的读音符号 + 删除重音符号 + 删除短信中字母上的重音符号 仅限手机号码 - 撰写邮件时, 只显示手机号码 + 撰写邮件时,只显示手机号码 + 自动删除旧消息 + 消息将在指定天数后删除 + 天数 + 从不 + 自动删除旧消息吗? + 如果您继续,现在将删除 %1$d 条消息 + + %d 天后 + 将长信息作为彩信发送 如果您无法发送较长的信息,或者是以错误的顺序发送,您可以将其作为彩信发送。可能会收取额外的费用 自动压缩彩信附件 @@ -238,7 +249,7 @@ 重新与安卓原生短信数据库进行同步 关于 QKSMS 版本 %s - 启用了调试日志记录 + 调试日志记录已启用 调试日志记录已禁用 输入时长 (秒) 屏蔽 @@ -247,19 +258,19 @@ 已屏蔽的会话 屏蔽管理器 QKSMS - QKSMS中内建的屏蔽功能 - 自动在适当的位置过滤您的来电和信息!Community IQ™允许您阻止社区中已知的垃圾发件人所发送的垃圾信息 - 使用\"Should I Answer\"应用过滤未知号码的消息 + QKSMS 中内建的屏蔽功能 + 自动在适当的位置过滤您的来电和信息!Community IQ™ 允许您阻止社区中已知的垃圾发件人所发送的垃圾信息 + 使用 \"Should I Answer\" 应用过滤未知号码的消息 复制屏蔽的号码 继续前往%s并复制到您现有的已屏蔽号码 已屏蔽的号码 您所屏蔽的号码将会在这里显示 屏蔽新的号码 - 屏蔽文本自 + 屏蔽短信自 电话号码 屏蔽 已屏蔽的信息 - 您所屏蔽的号码将会在这里显示 + 您所屏蔽的信息将会在这里显示 屏蔽 取消屏蔽 @@ -277,15 +288,15 @@ 许可证 版权 支持开发,解锁全部功能 - 你可以支付%s来拯救一个饥渴的程序猿 + 你可以支付 %s 来拯救一个饥渴的程序猿 终身升级只需 %1$s %2$s 解锁并捐赠只需 %1$s %2$s - 感谢你对QKSMS的支持! - 现在你可以使用QKSMS+的全部功能! - QKSMS+对F-Droid用户免费!如果您想支持开发,可以随意捐款。 - 使用PayPal捐赠 + 感谢你对 QKSMS 的支持! + 现在你可以使用 QKSMS+ 的全部功能! + QKSMS+ 对 F-Droid 用户免费!如果您想支持开发,可以随意捐款。 + 使用 PayPal 捐赠 即将推出 高级主题 解锁 \"Material Design\" 调色板中不可用的美丽彩色主题 @@ -306,7 +317,7 @@ 自动回复 使用预设文本自动回复短信 更多 - QKSMS积极更新中,您的购买将包含以后QKSMS+的所有功能 + QKSMS 积极更新中,您的购买将包含未来 QKSMS+ 的所有功能! 载入中... 查看更多对话 标记为已读 @@ -320,10 +331,12 @@ 应用 + 存档 + 删除 + 拦截 + 呼叫 标记为已读 回复 - 呼叫 - 删除 继续 @@ -336,7 +349,7 @@ 撤销 已复制 已存档的对话 - 您必须解锁QKSMS+才能使用此功能 + 您必须解锁 QKSMS+ 才能使用此功能 %s条新信息 @@ -361,9 +374,9 @@ 无延迟 - - - + 3 秒 + 5 秒 + 10 秒 自动 diff --git a/presentation/src/main/res/values-zh/strings.xml b/presentation/src/main/res/values-zh/strings.xml index dde414220..b590bb274 100644 --- a/presentation/src/main/res/values-zh/strings.xml +++ b/presentation/src/main/res/values-zh/strings.xml @@ -1,5 +1,4 @@ - + 已選取%1$d (%2$s) SIM卡 %s 已選擇,變更 SIM 卡 發送訊息 正在發送... 已傳遞 %s 發送失敗,點擊重試 詳細資訊 - Address copied + 已複製位址 對話標題 通知 主題 @@ -190,7 +191,7 @@ 純黑色夜間模式 開始時間 結束時間 - Automatic contact colors + 聯絡人自動上色 字體大小 使用系統字體 自動 emoji 表情 @@ -201,7 +202,7 @@ 按鈕2 按鈕3 通知預覽 - Wake screen + 喚醒畫面 震動 音效 @@ -219,7 +220,8 @@ 封存 刪除 - 呼叫 + 封鎖 + 通話 標示已讀 標示未讀 @@ -231,8 +233,17 @@ 刪除訊息中字母上的讀音符號 僅限手機號碼 撰寫訊息時,只顯示手機號碼 - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply + Delete old messages automatically + Messages will be deleted after the specified number of days + Number of days + Never + Delete old messages automatically? + If you proceed, %1$d messages will be deleted now + + After %d days + + 以MMS傳送長訊息 + 若您較長的訊息無法傳送或是以錯誤順序傳送,您可以改用MMS方式傳送訊息。(可能會有額外費用) 自動壓縮MMS附件 同步消息 重新與安卓原生訊息數據庫進行同步 @@ -309,7 +320,7 @@ QKSMS 正在積極開發中,您的購買將包含後續 QKSMS+ 的所有功能! 載入中... 查看更多對話 - 標記為已讀 + 標示已讀 致電 刪除 顯示更多 @@ -320,10 +331,12 @@ 應用 + 封存 + 刪除 + 封鎖 + 通話 標示已讀 回覆 - 呼叫 - 刪除 確定 繼續 @@ -361,19 +374,19 @@ 無延遲 - - - + 3 秒 + 5 秒 + 10 秒 - Automatic + 自動 100KB 200KB 300KB 600KB 1000KB 2000KB - No compression + 無壓縮 好的 -- GitLab From 4ba1ac1c5a451ea93fc75d9cf4e2f2a08656c110 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 2 Jan 2021 17:46:57 -0500 Subject: [PATCH 160/213] Add Crowdin badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a59f27141..b8476a568 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ # QKSMS [![Build Status](https://circleci.com/gh/moezbhatti/qksms/tree/master.svg?style=svg)](https://circleci.com/gh/moezbhatti/qksms/tree/master) +[![Crowdin](https://badges.crowdin.net/qksms/localized.svg)](https://crowdin.com/project/qksms) [![Liberapay donation](https://img.shields.io/badge/donate-liberapay-yellow.svg)](https://liberapay.com/moezbhatti/) [![Bitcoin donation](https://img.shields.io/badge/donate-bitcoin-yellow.svg)](https://qklabs.com/donate-btc/) [![PayPal donation](https://img.shields.io/badge/donate-paypal-yellow.svg)](https://qklabs.com/donate) -- GitLab From 8d9991fb491f34293249aa3b3e352d6a95430c98 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sat, 2 Jan 2021 17:48:59 -0500 Subject: [PATCH 161/213] Increment to v3.9.0 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 029e65a37..4cc58a6f3 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -30,8 +30,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2213 - versionName "3.8.1" + versionCode 2214 + versionName "3.9.0" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From bb8ad1ee579df9827a1049080f34423ff9ce113a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 17 Jan 2021 00:35:38 -0500 Subject: [PATCH 162/213] Support SMS Uri where no number is entered --- .../com/moez/QKSMS/feature/compose/ComposeActivityModule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index 780787cfc..260f66fba 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -20,7 +20,6 @@ package com.moez.QKSMS.feature.compose import android.content.Intent import android.net.Uri -import androidx.core.net.toFile import androidx.lifecycle.ViewModel import com.google.android.mms.ContentType import com.moez.QKSMS.injection.ViewModelKey @@ -50,8 +49,9 @@ class ComposeActivityModule { return activity.intent ?.decodedDataString() ?.substringAfter(':') // Remove scheme - ?.replaceAfter("?", "") // Remove query + ?.substringBeforeLast("?") // Remove query ?.split(",", ";") + ?.filter { number -> number.isNotEmpty() } ?: listOf() } -- GitLab From ff4170af324eff7446f6ae41800c245f9fef1e0d Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 1 Feb 2021 21:16:21 -0500 Subject: [PATCH 163/213] Don't include google billing or installReferrer in noAnalytics build --- data/build.gradle | 3 +- .../moez/QKSMS/manager/ReferralManagerImpl.kt | 33 +++++++++++++ .../moez/QKSMS/manager/ReferralManagerImpl.kt | 19 +++++++ .../com/moez/QKSMS/manager/BillingManager.kt | 43 ++++++++++++++++ presentation/build.gradle | 2 +- .../java/com/moez/QKSMS/common/Navigator.kt | 2 +- .../QKSMS/feature/backup/BackupPresenter.kt | 2 +- .../QKSMS/feature/compose/ComposeViewModel.kt | 2 +- .../moez/QKSMS/feature/main/MainViewModel.kt | 2 +- .../moez/QKSMS/feature/plus/PlusActivity.kt | 2 +- .../com/moez/QKSMS/feature/plus/PlusView.kt | 2 +- .../moez/QKSMS/feature/plus/PlusViewModel.kt | 4 +- .../feature/scheduled/ScheduledViewModel.kt | 4 +- .../feature/settings/SettingsPresenter.kt | 2 +- .../themepicker/ThemePickerPresenter.kt | 2 +- .../com/moez/QKSMS/injection/AppModule.kt | 5 ++ .../QKSMS/common/util/BillingManagerImpl.kt | 37 ++++++++++++++ .../QKSMS/common/util/BillingManagerImpl.kt} | 49 +++++++++---------- 18 files changed, 174 insertions(+), 41 deletions(-) create mode 100644 data/src/noAnalytics/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt rename data/src/{main => withAnalytics}/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt (73%) create mode 100644 domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt create mode 100644 presentation/src/noAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt rename presentation/src/{main/java/com/moez/QKSMS/common/util/BillingManager.kt => withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt} (73%) diff --git a/data/build.gradle b/data/build.gradle index ed2f1ceef..a76aff2b4 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -81,7 +81,6 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version" - implementation 'com.android.installreferrer:installreferrer:1.1' implementation 'com.callcontrol:datashare:1.2.0' implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version" implementation "com.jakewharton.timber:timber:$timber_version" @@ -92,6 +91,8 @@ dependencies { implementation project(":android-smsmms") implementation project(':common') implementation project(':domain') + + withAnalyticsImplementation 'com.android.installreferrer:installreferrer:1.1' withAnalyticsImplementation "com.amplitude:android-sdk:2.16.0" } diff --git a/data/src/noAnalytics/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt b/data/src/noAnalytics/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt new file mode 100644 index 000000000..0f51410f5 --- /dev/null +++ b/data/src/noAnalytics/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ + +package com.moez.QKSMS.manager + +import android.content.Context +import com.moez.QKSMS.util.Preferences +import kotlinx.coroutines.suspendCancellableCoroutine +import javax.inject.Inject +import kotlin.coroutines.resume + +class ReferralManagerImpl @Inject constructor() : ReferralManager { + + override suspend fun trackReferrer() { + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt b/data/src/withAnalytics/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt similarity index 73% rename from data/src/main/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt rename to data/src/withAnalytics/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt index 4446d6740..32a48cc0a 100644 --- a/data/src/main/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt +++ b/data/src/withAnalytics/java/com/moez/QKSMS/manager/ReferralManagerImpl.kt @@ -1,3 +1,22 @@ +/* + * Copyright (C) 2020 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ + package com.moez.QKSMS.manager import android.content.Context diff --git a/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt new file mode 100644 index 000000000..f02de472d --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ + +package com.moez.QKSMS.manager + +import android.app.Activity +import io.reactivex.Observable + +interface BillingManager { + + companion object { + const val SKU_PLUS = "remove_ads" + const val SKU_PLUS_DONATE = "qksms_plus_donate" + } + + data class Product( + val sku: String, + val price: String, + val priceCurrencyCode: String + ) + + val products: Observable> + val upgradeStatus: Observable + + fun initiatePurchaseFlow(activity: Activity, sku: String) + +} diff --git a/presentation/build.gradle b/presentation/build.gradle index 4cc58a6f3..0ba9a9f43 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -173,7 +173,6 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version" - implementation "com.android.billingclient:billing:1.0" implementation "com.github.chrisbanes:PhotoView:2.0.0" implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version" implementation "com.google.android:flexbox:0.3.1" @@ -187,6 +186,7 @@ dependencies { implementation project(':domain') withAnalyticsImplementation 'com.google.firebase:firebase-crashlytics:17.3.0' + withAnalyticsImplementation "com.android.billingclient:billing:1.0" noAnalyticsDebug project(path: ':data', configuration: 'noAnalyticsDebug') noAnalyticsRelease project(path: ':data', configuration: 'noAnalyticsRelease') diff --git a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt index 8ade172da..0a7cba9cb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt @@ -31,7 +31,6 @@ import android.provider.Telephony import android.webkit.MimeTypeMap import androidx.core.content.FileProvider import com.moez.QKSMS.BuildConfig -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.feature.backup.BackupActivity import com.moez.QKSMS.feature.blocking.BlockingActivity import com.moez.QKSMS.feature.compose.ComposeActivity @@ -42,6 +41,7 @@ import com.moez.QKSMS.feature.plus.PlusActivity import com.moez.QKSMS.feature.scheduled.ScheduledActivity import com.moez.QKSMS.feature.settings.SettingsActivity import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.manager.NotificationManager import com.moez.QKSMS.manager.PermissionManager import java.io.File diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt index 3f0174a1a..b547ec306 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt @@ -22,10 +22,10 @@ import android.content.Context import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.interactor.PerformBackup +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.repository.BackupRepository import com.uber.autodispose.android.lifecycle.scope diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index a2abfb2f8..ba601f2f7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -27,7 +27,6 @@ import androidx.core.content.getSystemService import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.ClipboardUtils import com.moez.QKSMS.common.util.MessageDetailsFormatter import com.moez.QKSMS.common.util.extensions.makeToast @@ -44,6 +43,7 @@ import com.moez.QKSMS.interactor.MarkRead import com.moez.QKSMS.interactor.RetrySending import com.moez.QKSMS.interactor.SendMessage import com.moez.QKSMS.manager.ActiveConversationManager +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Attachments diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index e1c2fc0e4..f79b19288 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -22,7 +22,6 @@ import androidx.recyclerview.widget.ItemTouchHelper import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.interactor.DeleteConversations import com.moez.QKSMS.interactor.MarkAllSeen @@ -36,6 +35,7 @@ import com.moez.QKSMS.interactor.MigratePreferences import com.moez.QKSMS.interactor.SyncContacts import com.moez.QKSMS.interactor.SyncMessages import com.moez.QKSMS.listener.ContactAddedListener +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.manager.ChangelogManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.manager.RatingManager 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 eaa01042e..b8a2ec9a6 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 @@ -27,7 +27,6 @@ import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.FontProvider import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint @@ -37,6 +36,7 @@ import com.moez.QKSMS.common.util.extensions.viewBinding import com.moez.QKSMS.common.widget.PreferenceView import com.moez.QKSMS.databinding.QksmsPlusActivityBinding import com.moez.QKSMS.feature.plus.experiment.UpgradeButtonExperiment +import com.moez.QKSMS.manager.BillingManager import dagger.android.AndroidInjection import javax.inject.Inject diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusView.kt index 8d7c67e84..261f9a4c9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusView.kt @@ -19,7 +19,7 @@ package com.moez.QKSMS.feature.plus import com.moez.QKSMS.common.base.QkView -import com.moez.QKSMS.common.util.BillingManager +import com.moez.QKSMS.manager.BillingManager import io.reactivex.Observable interface PlusView : QkView { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusViewModel.kt index a3475822d..a669dbeb8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusViewModel.kt @@ -20,8 +20,8 @@ package com.moez.QKSMS.feature.plus import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.manager.BillingManager import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable import io.reactivex.Observable @@ -84,4 +84,4 @@ class PlusViewModel @Inject constructor( .subscribe { navigator.showSettings() } } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt index 76d28f50d..a6b22fbdb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt @@ -22,11 +22,10 @@ import android.content.Context import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.ClipboardUtils import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.interactor.SendScheduledMessage -import com.moez.QKSMS.repository.MessageRepository +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.repository.ScheduledMessageRepository import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable @@ -37,7 +36,6 @@ import javax.inject.Inject class ScheduledViewModel @Inject constructor( billingManager: BillingManager, private val context: Context, - private val messageRepo: MessageRepository, private val navigator: Navigator, private val scheduledMessageRepo: ScheduledMessageRepository, private val sendScheduledMessage: SendScheduledMessage diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt index df35038f8..ba58580bc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt @@ -22,13 +22,13 @@ import android.content.Context import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.interactor.DeleteOldMessages import com.moez.QKSMS.interactor.SyncMessages import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.repository.SyncRepository import com.moez.QKSMS.service.AutoDeleteService diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt index 53168406a..a27315ec9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt @@ -21,8 +21,8 @@ package com.moez.QKSMS.feature.themepicker import com.f2prateek.rx.preferences2.Preference import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.manager.WidgetManager import com.moez.QKSMS.util.Preferences import com.uber.autodispose.android.lifecycle.scope diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index dc9adb0f8..afab6678c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -28,6 +28,7 @@ import com.f2prateek.rx.preferences2.RxSharedPreferences import com.moez.QKSMS.blocking.BlockingClient import com.moez.QKSMS.blocking.BlockingManager import com.moez.QKSMS.common.ViewModelFactory +import com.moez.QKSMS.common.util.BillingManagerImpl import com.moez.QKSMS.common.util.NotificationManagerImpl import com.moez.QKSMS.common.util.ShortcutManagerImpl import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoComponent @@ -40,6 +41,7 @@ import com.moez.QKSMS.manager.AlarmManager import com.moez.QKSMS.manager.AlarmManagerImpl import com.moez.QKSMS.manager.AnalyticsManager import com.moez.QKSMS.manager.AnalyticsManagerImpl +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.manager.ChangelogManager import com.moez.QKSMS.manager.ChangelogManagerImpl import com.moez.QKSMS.manager.KeyManager @@ -130,6 +132,9 @@ class AppModule(private var application: Application) { // Manager + @Provides + fun provideBillingManager(manager: BillingManagerImpl): BillingManager = manager + @Provides fun provideActiveConversationManager(manager: ActiveConversationManagerImpl): ActiveConversationManager = manager diff --git a/presentation/src/noAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt b/presentation/src/noAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt new file mode 100644 index 000000000..0507447c8 --- /dev/null +++ b/presentation/src/noAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.common.util + +import android.app.Activity +import com.moez.QKSMS.manager.BillingManager +import io.reactivex.Observable +import io.reactivex.subjects.BehaviorSubject +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class BillingManagerImpl @Inject constructor( +): BillingManager { + + override val products: Observable> = BehaviorSubject.createDefault(listOf()) + override val upgradeStatus: Observable = BehaviorSubject.createDefault(true) + + override fun initiatePurchaseFlow(activity: Activity, sku: String) = Unit + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt b/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt similarity index 73% rename from presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt rename to presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt index 1a2919d55..2f3b54dbd 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt +++ b/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2020 Moez Bhatti * * This file is part of QKSMS. * @@ -16,21 +16,20 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ + package com.moez.QKSMS.common.util import android.app.Activity import android.content.Context import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.BillingClient.BillingResponse -import com.android.billingclient.api.BillingClient.SkuType import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.SkuDetails import com.android.billingclient.api.SkuDetailsParams -import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.manager.BillingManager import io.reactivex.Flowable import io.reactivex.Observable import io.reactivex.schedulers.Schedulers @@ -41,18 +40,18 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class BillingManager @Inject constructor( +class BillingManagerImpl @Inject constructor( context: Context, private val analyticsManager: AnalyticsManager -) : PurchasesUpdatedListener { +) : BillingManager, PurchasesUpdatedListener { companion object { const val SKU_PLUS = "remove_ads" const val SKU_PLUS_DONATE = "qksms_plus_donate" } - val products: Observable> = BehaviorSubject.create() - val upgradeStatus: Observable + override val products: Observable> = BehaviorSubject.create() + override val upgradeStatus: Observable private val skus = listOf(SKU_PLUS, SKU_PLUS_DONATE) private val purchaseListObservable = BehaviorSubject.create>() @@ -66,23 +65,19 @@ class BillingManager @Inject constructor( querySkuDetailsAsync() } - upgradeStatus = when (BuildConfig.FLAVOR) { - "noAnalytics" -> BehaviorSubject.createDefault(true) - - else -> purchaseListObservable - .map { purchases -> purchases.any { it.sku == SKU_PLUS } || purchases.any { it.sku == SKU_PLUS_DONATE } } - .doOnNext { upgraded -> analyticsManager.setUserProperty("Upgraded", upgraded) } - } + upgradeStatus = purchaseListObservable + .map { purchases -> purchases.any { it.sku == SKU_PLUS } || purchases.any { it.sku == SKU_PLUS_DONATE } } + .doOnNext { upgraded -> analyticsManager.setUserProperty("Upgraded", upgraded) } } private fun queryPurchases() { executeServiceRequest { // Load the cached data - purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) + purchaseListObservable.onNext(billingClient.queryPurchases(BillingClient.SkuType.INAPP).purchasesList.orEmpty()) // On a fresh device, the purchase might not be cached, and so we'll need to force a refresh - billingClient.queryPurchaseHistoryAsync(SkuType.INAPP) { _, _ -> - purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) + billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP) { _, _ -> + purchaseListObservable.onNext(billingClient.queryPurchases(BillingClient.SkuType.INAPP).purchasesList.orEmpty()) } } } @@ -90,8 +85,8 @@ class BillingManager @Inject constructor( private fun startServiceConnection(onSuccess: () -> Unit) { val listener = object : BillingClientStateListener { - override fun onBillingSetupFinished(@BillingResponse billingResponseCode: Int) { - if (billingResponseCode == BillingResponse.OK) { + override fun onBillingSetupFinished(@BillingClient.BillingResponse billingResponseCode: Int) { + if (billingResponseCode == BillingClient.BillingResponse.OK) { isServiceConnected = true onSuccess() } else { @@ -114,16 +109,18 @@ class BillingManager @Inject constructor( executeServiceRequest { val subParams = SkuDetailsParams.newBuilder().setSkusList(skus).setType(BillingClient.SkuType.INAPP) billingClient.querySkuDetailsAsync(subParams.build()) { responseCode, skuDetailsList -> - if (responseCode == BillingResponse.OK) { - (products as Subject).onNext(skuDetailsList) + if (responseCode == BillingClient.BillingResponse.OK) { + (products as Subject).onNext(skuDetailsList.map { skuDetails -> + BillingManager.Product(skuDetails.sku, skuDetails.price, skuDetails.priceCurrencyCode) + }) } } } } - fun initiatePurchaseFlow(activity: Activity, sku: String) { + override fun initiatePurchaseFlow(activity: Activity, sku: String) { executeServiceRequest { - val params = BillingFlowParams.newBuilder().setSku(sku).setType(SkuType.INAPP) + val params = BillingFlowParams.newBuilder().setSku(sku).setType(BillingClient.SkuType.INAPP) billingClient.launchBillingFlow(activity, params.build()) } } @@ -136,9 +133,9 @@ class BillingManager @Inject constructor( } override fun onPurchasesUpdated(resultCode: Int, purchases: List?) { - if (resultCode == BillingResponse.OK) { + if (resultCode == BillingClient.BillingResponse.OK) { purchaseListObservable.onNext(purchases.orEmpty()) } } -} \ No newline at end of file +} -- GitLab From dfb44a589afee17011fc8e28080cde6d1112cf35 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 3 Feb 2021 18:11:00 -0500 Subject: [PATCH 164/213] Downgrade realm Closes #1712 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f56e5339a..c680e5c00 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { ext.mockito_version = '2.18.3' ext.moshi_version = '1.8.0' ext.okhttp3_version = '4.1.0' - ext.realm_version = '6.1.0' + ext.realm_version = '5.8.0' ext.realm_adapters_version = '3.1.0' ext.rxandroid_version = '2.0.1' ext.rxdogtag_version = '0.2.0' -- GitLab From 03606c053aa403cfd3b4f2ec3838085e4ee78e22 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 3 Feb 2021 23:34:57 -0500 Subject: [PATCH 165/213] Increment to v3.9.1 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 0ba9a9f43..ac61c22d9 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -30,8 +30,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2214 - versionName "3.9.0" + versionCode 2215 + versionName "3.9.1" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From 062adb76ac89d57bb73af75244f282a1a5dfa3d9 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 7 Feb 2021 14:30:40 -0500 Subject: [PATCH 166/213] Revert "Implement ViewBinding" This reverts commit 37d8c45379e351e4c556c72542fae67ac857ce1a. # Conflicts: # build.gradle # presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt # presentation/src/main/java/com/moez/QKSMS/common/widget/TextInputDialog.kt # presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt # presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt # presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt # presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt # presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt # presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- presentation/build.gradle | 9 +- .../com/moez/QKSMS/common/MenuItemAdapter.kt | 45 ++--- .../moez/QKSMS/common/base/FlowableAdapter.kt | 3 +- .../com/moez/QKSMS/common/base/QkActivity.kt | 12 +- .../com/moez/QKSMS/common/base/QkAdapter.kt | 3 +- .../moez/QKSMS/common/base/QkController.kt | 32 ++-- .../moez/QKSMS/common/base/QkRealmAdapter.kt | 4 +- .../QKSMS/common/base/QkThemedActivity.kt | 3 +- .../moez/QKSMS/common/base/QkViewHolder.kt | 14 +- .../util/extensions/ActivityExtensions.kt | 9 - .../common/util/extensions/ViewExtensions.kt | 6 - .../moez/QKSMS/common/widget/AvatarView.kt | 30 +-- .../QKSMS/common/widget/GroupAvatarView.kt | 19 +- .../QKSMS/common/widget/PagerTitleView.kt | 11 +- .../QKSMS/common/widget/PreferenceView.kt | 26 +-- .../com/moez/QKSMS/common/widget/QkDialog.kt | 34 ++-- .../common/widget/RadioPreferenceView.kt | 20 +- .../QKSMS/common/widget/TextInputDialog.kt | 13 +- .../QKSMS/feature/backup/BackupActivity.kt | 10 +- .../QKSMS/feature/backup/BackupAdapter.kt | 23 ++- .../QKSMS/feature/backup/BackupController.kt | 91 +++++---- .../feature/blocking/BlockingActivity.kt | 9 +- .../feature/blocking/BlockingController.kt | 25 ++- .../manager/BlockingManagerController.kt | 25 +-- .../messages/BlockedMessagesAdapter.kt | 51 ++--- .../messages/BlockedMessagesController.kt | 12 +- .../blocking/numbers/BlockedNumbersAdapter.kt | 18 +- .../numbers/BlockedNumbersController.kt | 28 +-- .../feature/changelog/ChangelogAdapter.kt | 18 +- .../feature/changelog/ChangelogDialog.kt | 15 +- .../feature/compose/AttachmentAdapter.kt | 54 +++--- .../QKSMS/feature/compose/ComposeActivity.kt | 138 +++++++------- .../QKSMS/feature/compose/MessagesAdapter.kt | 71 +++---- .../feature/compose/editing/ChipsAdapter.kt | 26 +-- .../compose/editing/ComposeItemAdapter.kt | 121 ++++++------ .../compose/editing/DetailedChipView.kt | 37 ++-- .../compose/editing/PhoneNumberAdapter.kt | 20 +- .../editing/PhoneNumberPickerAdapter.kt | 25 ++- .../QKSMS/feature/compose/part/FileBinder.kt | 42 ++--- .../QKSMS/feature/compose/part/MediaBinder.kt | 21 +-- .../QKSMS/feature/compose/part/PartBinder.kt | 31 +-- .../feature/compose/part/PartsAdapter.kt | 27 +-- .../QKSMS/feature/compose/part/VCardBinder.kt | 40 ++-- .../feature/contacts/ContactsActivity.kt | 26 ++- .../ConversationInfoActivity.kt | 9 +- .../ConversationInfoAdapter.kt | 115 ++++++----- .../ConversationInfoController.kt | 15 +- .../conversations/ConversationsAdapter.kt | 59 +++--- .../QKSMS/feature/gallery/GalleryActivity.kt | 20 +- .../feature/gallery/GalleryPagerAdapter.kt | 54 +++--- .../moez/QKSMS/feature/main/MainActivity.kt | 178 ++++++++++-------- .../moez/QKSMS/feature/main/SearchAdapter.kt | 31 +-- .../NotificationPrefsActivity.kt | 66 ++++--- .../moez/QKSMS/feature/plus/PlusActivity.kt | 70 +++---- .../QKSMS/feature/qkreply/QkReplyActivity.kt | 52 +++-- .../feature/scheduled/ScheduledActivity.kt | 37 ++-- .../scheduled/ScheduledMessageAdapter.kt | 33 ++-- .../ScheduledMessageAttachmentAdapter.kt | 20 +- .../feature/settings/SettingsActivity.kt | 9 +- .../feature/settings/SettingsController.kt | 75 ++++---- .../feature/settings/about/AboutController.kt | 13 +- .../settings/autodelete/AutoDeleteDialog.kt | 13 +- .../settings/swipe/SwipeActionsController.kt | 37 ++-- .../feature/themepicker/HSVPickerView.kt | 41 ++-- .../QKSMS/feature/themepicker/ThemeAdapter.kt | 35 ++-- .../themepicker/ThemePickerController.kt | 42 ++--- .../main/res/layout/collapsing_toolbar.xml | 1 + .../main/res/layout/contact_chip_detailed.xml | 18 +- .../src/main/res/layout/main_activity.xml | 14 +- .../main/res/layout/main_permission_hint.xml | 18 +- .../src/main/res/layout/main_syncing.xml | 4 +- .../main/res/layout/message_list_item_in.xml | 6 - .../main/res/layout/message_list_item_out.xml | 5 - .../main/res/layout/qksms_plus_activity.xml | 4 +- .../main/res/layout/scheduled_activity.xml | 4 +- .../main/res/layout/settings_theme_widget.xml | 24 ++- 78 files changed, 1208 insertions(+), 1217 deletions(-) diff --git a/build.gradle b/build.gradle index c680e5c00..14546f92e 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:3.5.4' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 303be1d37..4b9cec46b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Mar 03 19:56:50 EST 2020 +#Tue Dec 03 23:30:52 EST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/presentation/build.gradle b/presentation/build.gradle index ac61c22d9..457b9da17 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -19,6 +19,7 @@ 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 { @@ -69,10 +70,6 @@ android { noAnalytics { dimension "analytics" } } - viewBinding { - enabled = true - } - if (System.getenv("CI") == "true") { signingConfigs.release.storeFile = file("../keystore") signingConfigs.release.storePassword = System.getenv("keystore_password") @@ -81,6 +78,10 @@ android { } } +androidExtensions { + experimental = true +} + configurations { noAnalyticsDebug noAnalyticsRelease diff --git a/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt index eba4fed2a..656305482 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/MenuItemAdapter.kt @@ -20,26 +20,26 @@ package com.moez.QKSMS.common import android.content.Context import android.content.res.ColorStateList +import android.view.LayoutInflater import android.view.ViewGroup import androidx.annotation.ArrayRes import androidx.recyclerview.widget.RecyclerView +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.databinding.MenuListItemBinding import io.reactivex.disposables.CompositeDisposable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.menu_list_item.* +import kotlinx.android.synthetic.main.menu_list_item.view.* import javax.inject.Inject data class MenuItem(val title: String, val actionId: Int) -class MenuItemAdapter @Inject constructor( - private val context: Context, - private val colors: Colors -) : QkAdapter() { +class MenuItemAdapter @Inject constructor(private val context: Context, private val colors: Colors) : QkAdapter() { val menuItemClicks: Subject = PublishSubject.create() @@ -47,13 +47,13 @@ class MenuItemAdapter @Inject constructor( var selectedItem: Int? = null set(value) { - val old = data.map { it.actionId }.indexOfFirst { it == field }.takeIf { it != -1 } - val new = data.map { it.actionId }.indexOfFirst { it == value }.takeIf { it != -1 } + val old = data.map { it.actionId }.indexOfFirst { it == field } + val new = data.map { it.actionId }.indexOfFirst { it == value } field = value - old?.let(::notifyItemChanged) - new?.let(::notifyItemChanged) + old.let { notifyItemChanged(it) } + new.let { notifyItemChanged(it) } } fun setData(@ArrayRes titles: Int, @ArrayRes values: Int = -1) { @@ -63,28 +63,31 @@ class MenuItemAdapter @Inject constructor( .mapIndexed { index, title -> MenuItem(title, valueInts?.getOrNull(index) ?: index) } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, MenuListItemBinding::inflate).apply { - val states = arrayOf( - intArrayOf(android.R.attr.state_activated), - intArrayOf(-android.R.attr.state_activated)) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val view = layoutInflater.inflate(R.layout.menu_list_item, parent, false) - val text = parent.context.resolveThemeColor(android.R.attr.textColorTertiary) - binding.check.imageTintList = ColorStateList(states, intArrayOf(colors.theme().theme, text)) + val states = arrayOf( + intArrayOf(android.R.attr.state_activated), + intArrayOf(-android.R.attr.state_activated)) - binding.root.setOnClickListener { + val text = parent.context.resolveThemeColor(android.R.attr.textColorTertiary) + view.check.imageTintList = ColorStateList(states, intArrayOf(colors.theme().theme, text)) + + return QkViewHolder(view).apply { + view.setOnClickListener { val menuItem = getItem(adapterPosition) menuItemClicks.onNext(menuItem.actionId) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val menuItem = getItem(position) - holder.binding.title.text = menuItem.title - holder.binding.check.isActivated = (menuItem.actionId == selectedItem) - holder.binding.check.setVisible(selectedItem != null) + holder.title.text = menuItem.title + holder.check.isActivated = (menuItem.actionId == selectedItem) + holder.check.setVisible(selectedItem != null) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/FlowableAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/FlowableAdapter.kt index 7cd3f6156..1a71d5163 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/FlowableAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/FlowableAdapter.kt @@ -20,7 +20,6 @@ package com.moez.QKSMS.common.base import androidx.annotation.CallSuper import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding import io.reactivex.Flowable import io.reactivex.disposables.Disposable @@ -28,7 +27,7 @@ import io.reactivex.disposables.Disposable * Base RecyclerView.Adapter that provides some convenience when creating a new Adapter, such as * data list handing and item animations */ -abstract class FlowableAdapter : QkAdapter() { +abstract class FlowableAdapter : QkAdapter() { var flowable: Flowable>? = null set(value) { 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 8da859f06..379224fd7 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 @@ -22,20 +22,14 @@ import android.annotation.SuppressLint import android.os.Bundle import android.view.Menu import android.view.MenuItem -import android.view.View import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar -import com.moez.QKSMS.R -import com.moez.QKSMS.common.widget.QkTextView import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.toolbar.* abstract class QkActivity : AppCompatActivity() { - val toolbar by lazy { findViewById(R.id.toolbar)!! } - protected val menu: Subject = BehaviorSubject.create() - protected val toolbarTitle by lazy { findViewById(R.id.toolbarTitle) } @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { @@ -53,8 +47,8 @@ abstract class QkActivity : AppCompatActivity() { } } - override fun setContentView(view: View?) { - super.setContentView(view) + override fun setContentView(layoutResID: Int) { + super.setContentView(layoutResID) setSupportActionBar(toolbar) title = title // The title may have been set before layout inflation } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt index d55d8e721..0101f4351 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt @@ -22,7 +22,6 @@ import android.view.View import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding import com.moez.QKSMS.common.util.extensions.setVisible import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject @@ -31,7 +30,7 @@ import io.reactivex.subjects.Subject * Base RecyclerView.Adapter that provides some convenience when creating a new Adapter, such as * data list handing and item animations */ -abstract class QkAdapter : RecyclerView.Adapter>() { +abstract class QkAdapter : RecyclerView.Adapter() { var data: List = ArrayList() set(value) { 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 33d6b0c63..deefa8e21 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,17 +21,15 @@ package com.moez.QKSMS.common.base import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.LayoutRes import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar -import androidx.viewbinding.ViewBinding import com.bluelinelabs.conductor.archlifecycle.LifecycleController -import com.moez.QKSMS.R -import com.moez.QKSMS.common.widget.QkTextView +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.* +import kotlinx.android.synthetic.main.toolbar.view.* -abstract class QkController, State, Presenter : QkPresenter, Binding : ViewBinding>( - private val bindingInflater: (LayoutInflater, ViewGroup, Boolean) -> Binding -) : LifecycleController() { +abstract class QkController, State, Presenter : QkPresenter> : LifecycleController(), LayoutContainer { abstract var presenter: Presenter @@ -41,15 +39,16 @@ abstract class QkController, State, Present protected val themedActivity: QkThemedActivity? get() = activity as? QkThemedActivity - private val toolbar by lazy { view?.findViewById(R.id.toolbar) } - private val toolbarTitle by lazy { view?.findViewById(R.id.toolbarTitle) } + override var containerView: View? = null - lateinit var binding: Binding + @LayoutRes + var layoutRes: Int = 0 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = bindingInflater(inflater, container, false) - onViewCreated() - return binding.root + return inflater.inflate(layoutRes, container, false).also { + containerView = it + onViewCreated() + } } open fun onViewCreated() { @@ -61,13 +60,18 @@ abstract class QkController, State, Present fun setTitle(title: CharSequence?) { activity?.title = title - toolbarTitle?.text = title + view?.toolbarTitle?.text = title } fun showBackButton(show: Boolean) { appCompatActivity?.supportActionBar?.setDisplayHomeAsUpEnabled(show) } + override fun onDestroyView(view: View) { + containerView = null + clearFindViewByIdCache() + } + override fun onDestroy() { super.onDestroy() presenter.onCleared() diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt index 31fb5306d..7b1d8997e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt @@ -20,7 +20,6 @@ package com.moez.QKSMS.common.base import android.view.View import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding import com.moez.QKSMS.common.util.extensions.setVisible import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject @@ -31,8 +30,7 @@ import io.realm.RealmRecyclerViewAdapter import io.realm.RealmResults import timber.log.Timber -abstract class QkRealmAdapter - : RealmRecyclerViewAdapter>(null, true) { +abstract class QkRealmAdapter : RealmRecyclerViewAdapter(null, true) { /** * This view can be set, and the adapter will automatically control the visibility of this view 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 e6bfd358c..14b858b2c 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 @@ -44,6 +44,7 @@ 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 @@ -133,7 +134,7 @@ abstract class QkThemedActivity : QkActivity() { // Set the color for the overflow and navigation icon val textSecondary = resolveThemeColor(android.R.attr.textColorSecondary) - toolbar.overflowIcon = toolbar.overflowIcon?.apply { setTint(textSecondary) } + toolbar?.overflowIcon = toolbar?.overflowIcon?.apply { setTint(textSecondary) } // Update the colours of the menu items Observables.combineLatest(menu, theme) { menu, theme -> 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 b246c5b28..56e674349 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 @@ -18,16 +18,10 @@ */ package com.moez.QKSMS.common.base -import android.view.LayoutInflater -import android.view.ViewGroup +import android.view.View import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding - -class QkViewHolder(val binding: T) : RecyclerView.ViewHolder(binding.root) { - - constructor( - parent: ViewGroup, - bindingInflator: (LayoutInflater, ViewGroup, Boolean) -> T - ) : this(bindingInflator(LayoutInflater.from(parent.context), parent, false)) +import kotlinx.android.extensions.LayoutContainer +class QkViewHolder(view: View) : RecyclerView.ViewHolder(view), LayoutContainer { + override val containerView: View = view } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt index c99257466..3d4f59f59 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt @@ -20,10 +20,7 @@ package com.moez.QKSMS.common.util.extensions import android.app.Activity import android.content.Context -import android.view.LayoutInflater import android.view.inputmethod.InputMethodManager -import androidx.appcompat.app.AppCompatActivity -import androidx.viewbinding.ViewBinding fun Activity.dismissKeyboard() { window.currentFocus?.let { focus -> @@ -33,9 +30,3 @@ fun Activity.dismissKeyboard() { focus.clearFocus() } } - -inline fun AppCompatActivity.viewBinding( - crossinline bindingInflater: (LayoutInflater) -> T -): Lazy = lazy(LazyThreadSafetyMode.NONE) { - bindingInflater(layoutInflater) -} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt index eec96d543..f2bf92d17 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ViewExtensions.kt @@ -23,7 +23,6 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.PorterDuff import android.os.Build -import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -32,7 +31,6 @@ import android.widget.EditText import android.widget.ImageView import android.widget.ProgressBar import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding import androidx.viewpager.widget.ViewPager var ViewGroup.animateLayoutChanges: Boolean @@ -125,7 +123,3 @@ fun RecyclerView.scrapViews() { recycledViewPool.clear() adapter?.notifyDataSetChanged() } - -inline fun ViewGroup.viewBinding(crossinline bindingInflater: (LayoutInflater, ViewGroup) -> T): T { - return bindingInflater(LayoutInflater.from(context), this) -} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index b5a06769b..f6703ed8a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -20,17 +20,17 @@ package com.moez.QKSMS.common.widget import android.content.Context import android.util.AttributeSet +import android.view.View import android.widget.FrameLayout import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.AvatarViewBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.util.GlideApp +import kotlinx.android.synthetic.main.avatar_view.view.* import javax.inject.Inject class AvatarView @JvmOverloads constructor( @@ -39,21 +39,21 @@ class AvatarView @JvmOverloads constructor( @Inject lateinit var colors: Colors @Inject lateinit var navigator: Navigator - private lateinit var theme: Colors.Theme - - private val binding = viewBinding(AvatarViewBinding::inflate) private var lookupKey: String? = null private var fullName: String? = null private var photoUri: String? = null private var lastUpdated: Long? = null + private var theme: Colors.Theme init { if (!isInEditMode) { appComponent.inject(this) - theme = colors.theme() } + theme = colors.theme() + + View.inflate(context, R.layout.avatar_view, this) setBackgroundResource(R.drawable.circle) clipToOutline = true } @@ -81,8 +81,8 @@ class AvatarView @JvmOverloads constructor( private fun updateView() { // Apply theme setBackgroundTint(theme.theme) - binding.initial.setTextColor(theme.textPrimary) - binding.icon.setTint(theme.textPrimary) + initial.setTextColor(theme.textPrimary) + icon.setTint(theme.textPrimary) val initials = fullName ?.substringBefore(',') @@ -93,18 +93,18 @@ class AvatarView @JvmOverloads constructor( .map { initial -> initial.toString() } if (initials.isNotEmpty()) { - binding.initial.text = if (initials.size > 1) initials.first() + initials.last() else initials.first() - binding.icon.visibility = GONE + initial.text = if (initials.size > 1) initials.first() + initials.last() else initials.first() + icon.visibility = GONE } else { - binding.initial.text = null - binding.icon.visibility = VISIBLE + initial.text = null + icon.visibility = VISIBLE } - binding.photo.setImageDrawable(null) + photo.setImageDrawable(null) photoUri?.let { photoUri -> - GlideApp.with(binding.photo) + GlideApp.with(photo) .load(photoUri) - .into(binding.photo) + .into(photo) } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt index 3237773ca..85b240a15 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/GroupAvatarView.kt @@ -20,15 +20,16 @@ package com.moez.QKSMS.common.widget import android.content.Context import android.util.AttributeSet +import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams +import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.getColorCompat import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.GroupAvatarViewBinding import com.moez.QKSMS.model.Recipient +import kotlinx.android.synthetic.main.group_avatar_view.view.* class GroupAvatarView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -40,7 +41,9 @@ class GroupAvatarView @JvmOverloads constructor( updateView() } - private val binding = viewBinding(GroupAvatarViewBinding::inflate) + init { + View.inflate(context, R.layout.group_avatar_view, this) + } override fun onFinishInflate() { super.onFinishInflate() @@ -51,18 +54,18 @@ class GroupAvatarView @JvmOverloads constructor( } private fun updateView() { - binding.avatar1Frame.setBackgroundTint(when (recipients.size > 1) { + avatar1Frame.setBackgroundTint(when (recipients.size > 1) { true -> context.resolveThemeColor(android.R.attr.windowBackground) false -> context.getColorCompat(android.R.color.transparent) }) - binding.avatar1Frame.updateLayoutParams { + avatar1Frame.updateLayoutParams { matchConstraintPercentWidth = if (recipients.size > 1) 0.75f else 1.0f } - binding.avatar2.isVisible = recipients.size > 1 + avatar2.isVisible = recipients.size > 1 - recipients.getOrNull(0).run(binding.avatar1::setRecipient) - recipients.getOrNull(1).run(binding.avatar2::setRecipient) + recipients.getOrNull(0).run(avatar1::setRecipient) + recipients.getOrNull(1).run(avatar2::setRecipient) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt index ad38263a7..0d3d5db6b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt @@ -25,10 +25,10 @@ import android.view.LayoutInflater import android.widget.LinearLayout import android.widget.TextView import androidx.viewpager.widget.ViewPager +import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.forEach import com.moez.QKSMS.common.util.extensions.resolveThemeColor -import com.moez.QKSMS.databinding.TabViewBinding import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.repository.ConversationRepository @@ -36,6 +36,7 @@ import com.uber.autodispose.android.ViewScopeProvider import com.uber.autodispose.autoDisposable import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.tab_view.view.* import javax.inject.Inject class PagerTitleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { @@ -65,11 +66,11 @@ class PagerTitleView @JvmOverloads constructor(context: Context, attrs: Attribut removeAllViews() pager?.adapter?.count?.forEach { position -> - val binding = TabViewBinding.inflate(LayoutInflater.from(context), this, false) - binding.label.text = pager?.adapter?.getPageTitle(position) - binding.root.setOnClickListener { pager?.currentItem = position } + val view = LayoutInflater.from(context).inflate(R.layout.tab_view, this, false) + view.label.text = pager?.adapter?.getPageTitle(position) + view.setOnClickListener { pager?.currentItem = position } - addView(binding.root) + addView(view) } childCount.forEach { index -> 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 366f1408d..4e1a724c7 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 @@ -28,9 +28,8 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.resolveThemeAttribute import com.moez.QKSMS.common.util.extensions.resolveThemeColorStateList import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.PreferenceViewBinding import com.moez.QKSMS.injection.appComponent +import kotlinx.android.synthetic.main.preference_view.view.* class PreferenceView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -43,7 +42,7 @@ class PreferenceView @JvmOverloads constructor( if (isInEditMode) { findViewById(R.id.titleView).text = value } else { - binding.titleView.text = value + titleView.text = value } } @@ -58,23 +57,22 @@ class PreferenceView @JvmOverloads constructor( setVisible(value?.isNotEmpty() == true) } } else { - binding.summaryView.text = value - binding.summaryView.setVisible(value?.isNotEmpty() == true) + summaryView.text = value + summaryView.setVisible(value?.isNotEmpty() == true) } } - val binding: PreferenceViewBinding = viewBinding(PreferenceViewBinding::inflate) - init { if (!isInEditMode) { appComponent.inject(this) } + View.inflate(context, R.layout.preference_view, this) setBackgroundResource(context.resolveThemeAttribute(R.attr.selectableItemBackground)) orientation = HORIZONTAL gravity = Gravity.CENTER_VERTICAL - binding.icon.imageTintList = context.resolveThemeColorStateList(android.R.attr.textColorSecondary) + icon.imageTintList = context.resolveThemeColorStateList(android.R.attr.textColorSecondary) context.obtainStyledAttributes(attrs, R.styleable.PreferenceView).run { title = getString(R.styleable.PreferenceView_title) @@ -82,21 +80,17 @@ class PreferenceView @JvmOverloads constructor( // If there's a custom view used for the preference's widget, inflate it getResourceId(R.styleable.PreferenceView_widget, -1).takeIf { it != -1 }?.let { id -> - View.inflate(context, id, binding.widgetFrame) + View.inflate(context, id, widgetFrame) } // If an icon is being used, set up the icon view getResourceId(R.styleable.PreferenceView_icon, -1).takeIf { it != -1 }?.let { id -> - binding.icon.setVisible(true) - binding.icon.setImageResource(id) + icon.setVisible(true) + icon.setImageResource(id) } recycle() } } - fun widget(): T { - return binding.widgetFrame.getChildAt(0) as T - } - -} +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt index f38687b06..f275c8a0c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkDialog.kt @@ -19,15 +19,17 @@ package com.moez.QKSMS.common.widget import android.app.Activity +import android.view.LayoutInflater import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter -import com.moez.QKSMS.databinding.QkDialogBinding +import kotlinx.android.synthetic.main.qk_dialog.view.* class QkDialog(private val context: Activity) : AlertDialog(context) { - private val binding = QkDialogBinding.inflate(context.layoutInflater) + private val view = LayoutInflater.from(context).inflate(R.layout.qk_dialog, null) @StringRes var titleRes: Int? = null @@ -39,8 +41,8 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { var title: String? = null set(value) { field = value - binding.title.text = value - binding.title.isVisible = !value.isNullOrBlank() + view.title.text = value + view.title.isVisible = !value.isNullOrBlank() } @StringRes @@ -53,15 +55,15 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { var subtitle: String? = null set(value) { field = value - binding.subtitle.text = value - binding.subtitle.isVisible = !value.isNullOrBlank() + view.subtitle.text = value + view.subtitle.isVisible = !value.isNullOrBlank() } - var adapter: QkAdapter<*, *>? = null + var adapter: QkAdapter<*>? = null set(value) { field = value - binding.list.isVisible = value != null - binding.list.adapter = value + view.list.isVisible = value != null + view.list.adapter = value } var positiveButtonListener: (() -> Unit)? = null @@ -70,9 +72,9 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { var positiveButton: Int? = null set(value) { field = value - value?.run(binding.positiveButton::setText) - binding.positiveButton.isVisible = value != null - binding.positiveButton.setOnClickListener { + value?.run(view.positiveButton::setText) + view.positiveButton.isVisible = value != null + view.positiveButton.setOnClickListener { positiveButtonListener?.invoke() ?: dismiss() } } @@ -83,9 +85,9 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { var negativeButton: Int? = null set(value) { field = value - value?.run(binding.negativeButton::setText) - binding.negativeButton.isVisible = value != null - binding.negativeButton.setOnClickListener { + value?.run(view.negativeButton::setText) + view.negativeButton.isVisible = value != null + view.negativeButton.setOnClickListener { negativeButtonListener?.invoke() ?: dismiss() } } @@ -97,7 +99,7 @@ class QkDialog(private val context: Activity) : AlertDialog(context) { } init { - setView(binding.root) + setView(view) } } 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 abb7f3713..9f129c1af 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 @@ -30,9 +30,8 @@ import com.moez.QKSMS.common.util.extensions.forwardTouches import com.moez.QKSMS.common.util.extensions.resolveThemeAttribute import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.RadioPreferenceViewBinding import com.moez.QKSMS.injection.appComponent +import kotlinx.android.synthetic.main.radio_preference_view.view.* import javax.inject.Inject class RadioPreferenceView @JvmOverloads constructor( @@ -48,7 +47,7 @@ class RadioPreferenceView @JvmOverloads constructor( if (isInEditMode) { findViewById(R.id.titleView).text = value } else { - binding.titleView.text = value + titleView.text = value } } @@ -63,18 +62,17 @@ class RadioPreferenceView @JvmOverloads constructor( setVisible(value?.isNotEmpty() == true) } } else { - binding.summaryView.text = value - binding.summaryView.setVisible(value?.isNotEmpty() == true) + summaryView.text = value + summaryView.setVisible(value?.isNotEmpty() == true) } } - val binding: RadioPreferenceViewBinding = viewBinding(RadioPreferenceViewBinding::inflate) - init { if (!isInEditMode) { appComponent.inject(this) } + View.inflate(context, R.layout.radio_preference_view, this) setBackgroundResource(context.resolveThemeAttribute(R.attr.selectableItemBackground)) val states = arrayOf( @@ -86,8 +84,8 @@ class RadioPreferenceView @JvmOverloads constructor( false -> colors.theme().theme } val textSecondary = context.resolveThemeColor(android.R.attr.textColorTertiary) - binding.radioButton.buttonTintList = ColorStateList(states, intArrayOf(themeColor, textSecondary)) - binding.radioButton.forwardTouches(this) + radioButton.buttonTintList = ColorStateList(states, intArrayOf(themeColor, textSecondary)) + radioButton.forwardTouches(this) context.obtainStyledAttributes(attrs, R.styleable.RadioPreferenceView)?.run { title = getString(R.styleable.RadioPreferenceView_title) @@ -95,11 +93,11 @@ class RadioPreferenceView @JvmOverloads constructor( // If there's a custom view used for the preference's widget, inflate it getResourceId(R.styleable.RadioPreferenceView_widget, -1).takeIf { it != -1 }?.let { id -> - View.inflate(context, id, binding.widgetFrame) + View.inflate(context, id, widgetFrame) } recycle() } } -} +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/TextInputDialog.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/TextInputDialog.kt index 0c9ee56d8..1f639a018 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/TextInputDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/TextInputDialog.kt @@ -20,27 +20,28 @@ package com.moez.QKSMS.common.widget import android.app.Activity import android.content.DialogInterface +import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import com.moez.QKSMS.R -import com.moez.QKSMS.databinding.TextInputDialogBinding +import kotlinx.android.synthetic.main.text_input_dialog.view.* class TextInputDialog(context: Activity, hint: String, listener: (String) -> Unit) : AlertDialog(context) { - private val binding = TextInputDialogBinding.inflate(context.layoutInflater) + private val layout = LayoutInflater.from(context).inflate(R.layout.text_input_dialog, null) init { - binding.field.hint = hint + layout.field.hint = hint - setView(binding.root) + setView(layout) setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.button_cancel)) { _, _ -> } setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.button_delete)) { _, _ -> listener("") } setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.button_save)) { _, _ -> - listener(binding.field.text.toString()) + listener(layout.field.text.toString()) } } fun setText(text: String): TextInputDialog { - binding.field.setText(text) + layout.field.setText(text) return this } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupActivity.kt index 327fe4f13..65c52b3ce 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupActivity.kt @@ -22,22 +22,22 @@ import android.os.Bundle import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.ContainerActivityBinding import dagger.android.AndroidInjection +import kotlinx.android.synthetic.main.container_activity.* + class BackupActivity : QkThemedActivity() { - private val binding by viewBinding(ContainerActivityBinding::inflate) private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.container_activity) - router = Conductor.attachRouter(this, binding.container, savedInstanceState) + router = Conductor.attachRouter(this, container, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(BackupController())) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt index 57350e22c..038295dc6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt @@ -20,37 +20,42 @@ package com.moez.QKSMS.feature.backup import android.content.Context import android.text.format.Formatter +import android.view.LayoutInflater import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.FlowableAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.DateFormatter -import com.moez.QKSMS.databinding.BackupListItemBinding import com.moez.QKSMS.model.BackupFile import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.backup_list_item.* import javax.inject.Inject class BackupAdapter @Inject constructor( private val context: Context, private val dateFormatter: DateFormatter -) : FlowableAdapter() { +) : FlowableAdapter() { val backupSelected: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, BackupListItemBinding::inflate).apply { - binding.root.setOnClickListener { backupSelected.onNext(getItem(adapterPosition)) } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val view = layoutInflater.inflate(R.layout.backup_list_item, parent, false) + + return QkViewHolder(view).apply { + view.setOnClickListener { backupSelected.onNext(getItem(adapterPosition)) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val backup = getItem(position) + val count = backup.messages - holder.binding.title.text = dateFormatter.getDetailedTimestamp(backup.date) - holder.binding.messages.text = context.resources.getQuantityString(R.plurals.backup_message_count, count, count) - holder.binding.size.text = Formatter.formatFileSize(context, backup.size) + holder.title.text = dateFormatter.getDetailedTimestamp(backup.date) + holder.messages.text = context.resources.getQuantityString(R.plurals.backup_message_count, count, count) + holder.size.text = Formatter.formatFileSize(context, backup.size) } } \ No newline at end of file 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 dc0e691c8..1439f5f47 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,6 @@ import android.Manifest import android.app.Activity import android.content.res.ColorStateList import android.graphics.Typeface -import android.view.LayoutInflater import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat @@ -37,19 +36,18 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setPositiveButton import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.widget.PreferenceView -import com.moez.QKSMS.databinding.BackupControllerBinding -import com.moez.QKSMS.databinding.BackupListDialogBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.model.BackupFile import com.moez.QKSMS.repository.BackupRepository 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.backup_list_dialog.view.* +import kotlinx.android.synthetic.main.preference_view.view.* import javax.inject.Inject -class BackupController : QkController( - BackupControllerBinding::inflate -), BackupView { +class BackupController : QkController(), BackupView { @Inject lateinit var adapter: BackupAdapter @Inject lateinit var dateFormatter: DateFormatter @@ -60,11 +58,11 @@ class BackupController : QkController = PublishSubject.create() private val backupFilesDialog by lazy { - val binding = BackupListDialogBinding.inflate(LayoutInflater.from(activity)) + val view = View.inflate(activity, R.layout.backup_list_dialog, null) .apply { files.adapter = adapter.apply { emptyView = empty } } AlertDialog.Builder(activity!!) - .setView(binding.root) + .setView(view) .setCancelable(true) .create() } @@ -89,6 +87,7 @@ class BackupController : QkController - 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) + progressBar.indeterminateTintList = ColorStateList.valueOf(theme.theme) + progressBar.progressTintList = ColorStateList.valueOf(theme.theme) + fab.setBackgroundTint(theme.theme) + fabIcon.setTint(theme.textPrimary) + fabLabel.setTextColor(theme.textPrimary) } // Make the list titles bold - binding.linearLayout.children - .mapNotNull { view -> view as? PreferenceView } - .map { preferenceView -> preferenceView.binding.titleView } + linearLayout.children + .mapNotNull { it as? PreferenceView } + .map { it.titleView } .forEach { it.setTypeface(it.typeface, Typeface.BOLD) } } @@ -124,51 +123,51 @@ class BackupController : QkController { - 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 + 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 val running = (state.backupProgress as? BackupRepository.Progress.Running) - binding.progressBar.isVisible = state.backupProgress.indeterminate || running?.max ?: 0 > 0 - 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 + 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 } state.restoreProgress.running -> { - 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 + 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 val running = (state.restoreProgress as? BackupRepository.Progress.Running) - binding.progressBar.isVisible = state.restoreProgress.indeterminate || running?.max ?: 0 > 0 - 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 + 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 } else -> { - binding.progress.isVisible = false - binding.fab.isVisible = true + progress.isVisible = false + fab.isVisible = true } } - binding.backup.summary = state.lastBackup + backup.summary = state.lastBackup adapter.data = state.backups - binding.fabIcon.setImageResource(when (state.upgraded) { + fabIcon.setImageResource(when (state.upgraded) { true -> R.drawable.ic_file_upload_black_24dp false -> R.drawable.ic_star_black_24dp }) - binding.fabLabel.setText(when (state.upgraded) { + fabLabel.setText(when (state.upgraded) { true -> R.string.backup_now false -> R.string.title_qksms_plus }) @@ -176,18 +175,18 @@ class BackupController : QkController = activityVisibleSubject - override fun restoreClicks(): Observable<*> = binding.restore.clicks() + override fun restoreClicks(): Observable<*> = restore.clicks() override fun restoreFileSelected(): Observable = adapter.backupSelected .doOnNext { backupFilesDialog.dismiss() } override fun restoreConfirmed(): Observable<*> = confirmRestoreSubject - override fun stopRestoreClicks(): Observable<*> = binding.progressCancel.clicks() + override fun stopRestoreClicks(): Observable<*> = progressCancel.clicks() override fun stopRestoreConfirmed(): Observable<*> = stopRestoreSubject - override fun fabClicks(): Observable<*> = binding.fab.clicks() + override fun fabClicks(): Observable<*> = fab.clicks() override fun requestStoragePermission() { ActivityCompat.requestPermissions(activity!!, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 0) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt index 9b5d59a54..e028400fb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt @@ -22,22 +22,21 @@ import android.os.Bundle import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.ContainerActivityBinding import dagger.android.AndroidInjection +import kotlinx.android.synthetic.main.container_activity.* class BlockingActivity : QkThemedActivity() { - private val binding by viewBinding(ContainerActivityBinding::inflate) private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.container_activity) - router = Conductor.attachRouter(this, binding.container, savedInstanceState) + router = Conductor.attachRouter(this, container, savedInstanceState) if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(BlockingController())) } 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 c73e15670..1289b9c49 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 @@ -26,22 +26,20 @@ import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges -import com.moez.QKSMS.common.widget.QkSwitch -import com.moez.QKSMS.databinding.BlockingControllerBinding import com.moez.QKSMS.feature.blocking.manager.BlockingManagerController import com.moez.QKSMS.feature.blocking.messages.BlockedMessagesController import com.moez.QKSMS.feature.blocking.numbers.BlockedNumbersController import com.moez.QKSMS.injection.appComponent +import kotlinx.android.synthetic.main.blocking_controller.* +import kotlinx.android.synthetic.main.settings_switch_widget.view.* import javax.inject.Inject -class BlockingController : QkController( - BlockingControllerBinding::inflate -), BlockingView { +class BlockingController : QkController(), BlockingView { - override val blockingManagerIntent by lazy { binding.blockingManager.clicks() } - override val blockedNumbersIntent by lazy { binding.blockedNumbers.clicks() } - override val blockedMessagesIntent by lazy { binding.blockedMessages.clicks() } - override val dropClickedIntent by lazy { binding.drop.clicks() } + override val blockingManagerIntent by lazy { blockingManager.clicks() } + override val blockedNumbersIntent by lazy { blockedNumbers.clicks() } + override val blockedMessagesIntent by lazy { blockedMessages.clicks() } + override val dropClickedIntent by lazy { drop.clicks() } @Inject lateinit var colors: Colors @Inject override lateinit var presenter: BlockingPresenter @@ -49,11 +47,12 @@ class BlockingController : QkController().isChecked = state.dropEnabled - binding.blockedMessages.isEnabled = !state.dropEnabled + blockingManager.summary = state.blockingManager + drop.checkbox.isChecked = state.dropEnabled + blockedMessages.isEnabled = !state.dropEnabled } override fun openBlockedNumbers() { 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 400ac7202..ed64b1d5c 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 @@ -7,16 +7,18 @@ import androidx.core.view.isVisible import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkController -import com.moez.QKSMS.databinding.BlockingManagerControllerBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.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 kotlinx.android.synthetic.main.radio_preference_view.view.* import javax.inject.Inject -class BlockingManagerController : QkController(BlockingManagerControllerBinding::inflate), BlockingManagerView { +class BlockingManagerController : QkController(), + BlockingManagerView { @Inject override lateinit var presenter: BlockingManagerPresenter @@ -25,6 +27,7 @@ class BlockingManagerController : QkController = activityResumedSubject - override fun qksmsClicked(): Observable<*> = binding.qksms.clicks() - override fun callControlClicked(): Observable<*> = binding.callControl.clicks() - override fun siaClicked(): Observable<*> = binding.shouldIAnswer.clicks() + override fun qksmsClicked(): Observable<*> = qksms.clicks() + override fun callControlClicked(): Observable<*> = callControl.clicks() + override fun siaClicked(): Observable<*> = 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/messages/BlockedMessagesAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt index 7a48e2aa2..9a4c37e38 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 @@ -20,6 +20,7 @@ package com.moez.QKSMS.feature.blocking.messages import android.content.Context import android.graphics.Typeface +import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible import com.moez.QKSMS.R @@ -27,63 +28,65 @@ import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.resolveThemeColor -import com.moez.QKSMS.databinding.BlockedListItemBinding import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.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( private val context: Context, private val dateFormatter: DateFormatter -) : QkRealmAdapter() { +) : QkRealmAdapter() { val clicks: PublishSubject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, BlockedListItemBinding::inflate).apply { - if (viewType == 0) { - binding.title.setTypeface(binding.title.typeface, Typeface.BOLD) - binding.date.setTypeface(binding.date.typeface, Typeface.BOLD) - binding.date.setTextColor(parent.context.resolveThemeColor(android.R.attr.textColorPrimary)) - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.blocked_list_item, 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.root.setOnClickListener { + return QkViewHolder(view).apply { + view.setOnClickListener { val conversation = getItem(adapterPosition) ?: return@setOnClickListener when (toggleSelection(conversation.id, false)) { - true -> binding.root.isActivated = isSelected(conversation.id) + true -> view.isActivated = isSelected(conversation.id) false -> clicks.onNext(conversation.id) } } - - binding.root.setOnLongClickListener { + view.setOnLongClickListener { val conversation = getItem(adapterPosition) ?: return@setOnLongClickListener true toggleSelection(conversation.id) - binding.root.isActivated = isSelected(conversation.id) + view.isActivated = isSelected(conversation.id) true } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return - holder.binding.root.isActivated = isSelected(conversation.id) + holder.containerView.isActivated = isSelected(conversation.id) - holder.binding.avatars.recipients = conversation.recipients - holder.binding.title.collapseEnabled = conversation.recipients.size > 1 - holder.binding.title.text = conversation.getTitle() - holder.binding.date.text = dateFormatter.getConversationTimestamp(conversation.date) + holder.avatars.recipients = conversation.recipients + holder.title.collapseEnabled = conversation.recipients.size > 1 + holder.title.text = conversation.getTitle() + holder.date.text = dateFormatter.getConversationTimestamp(conversation.date) - holder.binding.blocker.text = when (conversation.blockingClient) { + holder.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.binding.reason.text = conversation.blockReason - holder.binding.blocker.isVisible = holder.binding.blocker.text.isNotEmpty() - holder.binding.reason.isVisible = holder.binding.blocker.text.isNotEmpty() + holder.reason.text = conversation.blockReason + holder.blocker.isVisible = holder.blocker.text.isNotEmpty() + holder.reason.isVisible = holder.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 eae709285..92dfcac39 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 @@ -27,15 +27,16 @@ import android.view.View import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.Colors -import com.moez.QKSMS.databinding.BlockedMessagesControllerBinding import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.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(BlockedMessagesControllerBinding::inflate), BlockedMessagesView { +class BlockedMessagesController : QkController(), + BlockedMessagesView { override val menuReadyIntent: Subject = PublishSubject.create() override val optionsItemIntent: Subject = PublishSubject.create() @@ -53,12 +54,13 @@ class BlockedMessagesController : QkController() { +class BlockedNumbersAdapter : QkRealmAdapter() { val unblockAddress: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, BlockedNumberListItemBinding::inflate).apply { - binding.unblock.setOnClickListener { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.blocked_number_list_item, parent, false) + return QkViewHolder(view).apply { + containerView.unblock.setOnClickListener { val number = getItem(adapterPosition) ?: return@setOnClickListener unblockAddress.onNext(number.id) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val item = getItem(position)!! - holder.binding.number.text = item.address + holder.number.text = item.address } } 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 3e167351d..8b0927eca 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 @@ -18,6 +18,7 @@ */ package com.moez.QKSMS.feature.blocking.numbers +import android.view.LayoutInflater import android.view.View import androidx.appcompat.app.AlertDialog import com.jakewharton.rxbinding2.view.clicks @@ -26,17 +27,17 @@ import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.databinding.BlockedNumbersAddDialogBinding -import com.moez.QKSMS.databinding.BlockedNumbersControllerBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.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 javax.inject.Inject -class BlockedNumbersController : QkController(BlockedNumbersControllerBinding::inflate), BlockedNumbersView { +class BlockedNumbersController : QkController(), + BlockedNumbersView { @Inject override lateinit var presenter: BlockedNumbersPresenter @Inject lateinit var colors: Colors @@ -48,6 +49,7 @@ class BlockedNumbersController : QkController = adapter.unblockAddress - override fun addAddress(): Observable<*> = binding.add.clicks() + override fun addAddress(): Observable<*> = add.clicks() override fun saveAddress(): Observable = saveAddressSubject override fun showAddDialog() { - val binding = BlockedNumbersAddDialogBinding.inflate(activity?.layoutInflater!!) - val textWatcher = BlockedNumberTextWatcher(binding.input, phoneNumberUtils) + val layout = LayoutInflater.from(activity).inflate(R.layout.blocked_numbers_add_dialog, null) + val textWatcher = BlockedNumberTextWatcher(layout.input, phoneNumberUtils) val dialog = AlertDialog.Builder(activity!!) - .setView(binding.root) + .setView(layout) .setPositiveButton(R.string.blocked_numbers_dialog_block) { _, _ -> - saveAddressSubject.onNext(binding.input.text.toString()) + saveAddressSubject.onNext(layout.input.text.toString()) } .setNegativeButton(R.string.button_cancel) { _, _ -> } .setOnDismissListener { textWatcher.dispose() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt index 98e954d74..43de03391 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt @@ -20,16 +20,15 @@ package com.moez.QKSMS.feature.changelog import android.content.Context import android.graphics.Typeface +import android.view.LayoutInflater import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.databinding.ChangelogListItemBinding import com.moez.QKSMS.manager.ChangelogManager +import kotlinx.android.synthetic.main.changelog_list_item.* -class ChangelogAdapter( - private val context: Context -) : QkAdapter() { +class ChangelogAdapter(private val context: Context) : QkAdapter() { data class ChangelogItem(val type: Int, val label: String) @@ -53,18 +52,19 @@ class ChangelogAdapter( data = changes } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, ChangelogListItemBinding::inflate).apply { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.changelog_list_item, parent, false) + return QkViewHolder(view).apply { if (viewType == 0) { - binding.changelogItem.setTypeface(binding.changelogItem.typeface, Typeface.BOLD) + changelogItem.setTypeface(changelogItem.typeface, Typeface.BOLD) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val item = getItem(position) - holder.binding.changelogItem.text = item.label + holder.changelogItem.text = item.label } override fun getItemViewType(position: Int): Int { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt index 7cf1c2a0b..04dc1d4ce 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt @@ -18,14 +18,15 @@ */ package com.moez.QKSMS.feature.changelog +import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R -import com.moez.QKSMS.databinding.ChangelogDialogBinding import com.moez.QKSMS.feature.main.MainActivity import com.moez.QKSMS.manager.ChangelogManager import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.changelog_dialog.view.* class ChangelogDialog(activity: MainActivity) { @@ -35,17 +36,17 @@ class ChangelogDialog(activity: MainActivity) { private val adapter = ChangelogAdapter(activity) init { - val binding = ChangelogDialogBinding.inflate(activity.layoutInflater) + val layout = LayoutInflater.from(activity).inflate(R.layout.changelog_dialog, null) dialog = AlertDialog.Builder(activity) .setCancelable(true) - .setView(binding.root) + .setView(layout) .create() - binding.version.text = activity.getString(R.string.changelog_version, BuildConfig.VERSION_NAME) - binding.changelog.adapter = adapter - binding.more.setOnClickListener { dialog.dismiss(); moreClicks.onNext(Unit) } - binding.dismiss.setOnClickListener { dialog.dismiss() } + layout.version.text = activity.getString(R.string.changelog_version, BuildConfig.VERSION_NAME) + layout.changelog.adapter = adapter + layout.more.setOnClickListener { dialog.dismiss(); moreClicks.onNext(Unit) } + layout.dismiss.setOnClickListener { dialog.dismiss() } } fun show(changelog: ChangelogManager.Changelog) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt index 7ef4c887e..3563798bc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt @@ -19,13 +19,12 @@ package com.moez.QKSMS.feature.compose import android.content.Context +import android.view.LayoutInflater import android.view.ViewGroup -import androidx.viewbinding.ViewBinding import com.bumptech.glide.Glide +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.databinding.AttachmentContactListItemBinding -import com.moez.QKSMS.databinding.AttachmentImageListItemBinding import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.model.Attachment import ezvcard.Ezvcard @@ -34,11 +33,14 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.attachment_contact_list_item.* +import kotlinx.android.synthetic.main.attachment_image_list_item.* +import kotlinx.android.synthetic.main.attachment_image_list_item.view.* import javax.inject.Inject class AttachmentAdapter @Inject constructor( private val context: Context -) : QkAdapter() { +) : QkAdapter() { companion object { private const val VIEW_TYPE_IMAGE = 0 @@ -47,42 +49,38 @@ class AttachmentAdapter @Inject constructor( val attachmentDeleted: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val holder: QkViewHolder = when (viewType) { - VIEW_TYPE_IMAGE -> QkViewHolder(parent, AttachmentImageListItemBinding::inflate) - VIEW_TYPE_CONTACT -> QkViewHolder(parent, AttachmentContactListItemBinding::inflate) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = when (viewType) { + VIEW_TYPE_IMAGE -> inflater.inflate(R.layout.attachment_image_list_item, parent, false) + .apply { thumbnailBounds.clipToOutline = true } + + VIEW_TYPE_CONTACT -> inflater.inflate(R.layout.attachment_contact_list_item, parent, false) + else -> null!! // Impossible } - return holder.apply { - if (binding is AttachmentImageListItemBinding) { - binding.thumbnailBounds.clipToOutline = true - } - - binding.root.setOnClickListener { + return QkViewHolder(view).apply { + view.setOnClickListener { val attachment = getItem(adapterPosition) attachmentDeleted.onNext(attachment) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val attachment = getItem(position) - when { - attachment is Attachment.Image && holder.binding is AttachmentImageListItemBinding -> { - Glide.with(context) - .load(attachment.getUri()) - .into(holder.binding.thumbnail) - } + when (attachment) { + is Attachment.Image -> Glide.with(context) + .load(attachment.getUri()) + .into(holder.thumbnail) - attachment is Attachment.Contact && holder.binding is AttachmentContactListItemBinding -> { - Observable.just(attachment.vCard) - .mapNotNull { vCard -> Ezvcard.parse(vCard).first() } - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { vcard -> holder.binding.name.text = vcard.formattedName.value } - } + is Attachment.Contact -> Observable.just(attachment.vCard) + .mapNotNull { vCard -> Ezvcard.parse(vCard).first() } + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { vcard -> holder.name?.text = vcard.formattedName.value } } } 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 8cad8642f..3b42535b6 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 @@ -55,8 +55,6 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.util.extensions.showKeyboard -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.ComposeActivityBinding import com.moez.QKSMS.feature.compose.editing.ChipsAdapter import com.moez.QKSMS.feature.contacts.ContactsActivity import com.moez.QKSMS.model.Attachment @@ -67,6 +65,7 @@ import dagger.android.AndroidInjection import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.compose_activity.* import java.text.SimpleDateFormat import java.util.* import javax.inject.Inject @@ -95,29 +94,28 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val chipDeletedIntent: Subject by lazy { chipsAdapter.chipDeleted } override val menuReadyIntent: Observable = menu.map { Unit } override val optionsItemIntent: Subject = PublishSubject.create() - override val sendAsGroupIntent by lazy { binding.sendAsGroupBackground.clicks() } + override val sendAsGroupIntent by lazy { sendAsGroupBackground.clicks() } override val messageClickIntent: Subject by lazy { messageAdapter.clicks } override val messagePartClickIntent: Subject by lazy { messageAdapter.partClicks } override val messagesSelectedIntent by lazy { messageAdapter.selectionChanges } override val cancelSendingIntent: Subject by lazy { messageAdapter.cancelSending } override val attachmentDeletedIntent: Subject by lazy { attachmentAdapter.attachmentDeleted } - override val textChangedIntent by lazy { binding.message.textChanges() } - override val attachIntent by lazy { Observable.merge(binding.attach.clicks(), binding.attachingBackground.clicks()) } - override val cameraIntent by lazy { Observable.merge(binding.camera.clicks(), binding.cameraLabel.clicks()) } - override val galleryIntent by lazy { Observable.merge(binding.gallery.clicks(), binding.galleryLabel.clicks()) } - override val scheduleIntent by lazy { Observable.merge(binding.schedule.clicks(), binding.scheduleLabel.clicks()) } - override val attachContactIntent by lazy { Observable.merge(binding.contact.clicks(), binding.contactLabel.clicks()) } + override val textChangedIntent by lazy { message.textChanges() } + override val attachIntent by lazy { Observable.merge(attach.clicks(), attachingBackground.clicks()) } + override val cameraIntent by lazy { Observable.merge(camera.clicks(), cameraLabel.clicks()) } + override val galleryIntent by lazy { Observable.merge(gallery.clicks(), galleryLabel.clicks()) } + override val scheduleIntent by lazy { Observable.merge(schedule.clicks(), scheduleLabel.clicks()) } + override val attachContactIntent by lazy { Observable.merge(contact.clicks(), contactLabel.clicks()) } override val attachmentSelectedIntent: Subject = PublishSubject.create() override val contactSelectedIntent: Subject = PublishSubject.create() - override val inputContentIntent by lazy { binding.message.inputContentSelected } + override val inputContentIntent by lazy { message.inputContentSelected } override val scheduleSelectedIntent: Subject = PublishSubject.create() - override val changeSimIntent by lazy { binding.sim.clicks() } - override val scheduleCancelIntent by lazy { binding.scheduledCancel.clicks() } - override val sendIntent by lazy { binding.send.clicks() } + override val changeSimIntent by lazy { sim.clicks() } + override val scheduleCancelIntent by lazy { scheduledCancel.clicks() } + override val sendIntent by lazy { send.clicks() } override val viewQksmsPlusIntent: Subject = PublishSubject.create() override val backPressedIntent: Subject = PublishSubject.create() - private val binding by viewBinding(ComposeActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ComposeViewModel::class.java] } private var cameraDestination: Uri? = null @@ -125,33 +123,33 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.compose_activity) showBackButton(true) viewModel.bindView(this) - binding.contentView.layoutTransition = LayoutTransition().apply { + contentView.layoutTransition = LayoutTransition().apply { disableTransitionType(LayoutTransition.CHANGING) } - chipsAdapter.view = binding.chips + chipsAdapter.view = chips - binding.chips.itemAnimator = null - binding.chips.layoutManager = FlexboxLayoutManager(this) + chips.itemAnimator = null + chips.layoutManager = FlexboxLayoutManager(this) - messageAdapter.autoScrollToStart(binding.messageList) - messageAdapter.emptyView = binding.messagesEmpty + messageAdapter.autoScrollToStart(messageList) + messageAdapter.emptyView = messagesEmpty - binding.messageList.setHasFixedSize(true) - binding.messageList.adapter = messageAdapter + messageList.setHasFixedSize(true) + messageList.adapter = messageAdapter - binding.attachments.adapter = attachmentAdapter + attachments.adapter = attachmentAdapter - binding.message.supportsInputContent = true + message.supportsInputContent = true theme - .doOnNext { binding.loading.setTint(it.theme) } - .doOnNext { binding.attach.setBackgroundTint(it.theme) } - .doOnNext { binding.attach.setTint(it.textPrimary) } + .doOnNext { loading.setTint(it.theme) } + .doOnNext { attach.setBackgroundTint(it.theme) } + .doOnNext { attach.setTint(it.textPrimary) } .doOnNext { messageAdapter.theme = it } .autoDisposable(scope()) .subscribe() @@ -160,7 +158,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - binding.messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) + messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) } } @@ -188,59 +186,59 @@ class ComposeActivity : QkThemedActivity(), ComposeView { else -> state.conversationtitle } - binding.toolbarSubtitle.setVisible(state.query.isNotEmpty()) - binding.toolbarSubtitle.text = getString(R.string.compose_subtitle_results, state.searchSelectionPosition, + toolbarSubtitle.setVisible(state.query.isNotEmpty()) + toolbarSubtitle.text = getString(R.string.compose_subtitle_results, state.searchSelectionPosition, state.searchResults) - binding.toolbarTitle.setVisible(!state.editingMode) - binding.chips.setVisible(state.editingMode) - binding.composeBar.setVisible(!state.loading) + toolbarTitle.setVisible(!state.editingMode) + chips.setVisible(state.editingMode) + composeBar.setVisible(!state.loading) // Don't set the adapters unless needed - if (state.editingMode && binding.chips.adapter == null) binding.chips.adapter = chipsAdapter + if (state.editingMode && chips.adapter == null) chips.adapter = chipsAdapter - binding.toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode - binding.toolbar.menu.findItem(R.id.call)?.isVisible = !state.editingMode && state.selectedMessages == 0 + toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode + toolbar.menu.findItem(R.id.call)?.isVisible = !state.editingMode && state.selectedMessages == 0 && state.query.isEmpty() - binding.toolbar.menu.findItem(R.id.info)?.isVisible = !state.editingMode && state.selectedMessages == 0 + toolbar.menu.findItem(R.id.info)?.isVisible = !state.editingMode && state.selectedMessages == 0 && state.query.isEmpty() - binding.toolbar.menu.findItem(R.id.copy)?.isVisible = !state.editingMode && state.selectedMessages > 0 - 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 - binding.toolbar.menu.findItem(R.id.forward)?.isVisible = !state.editingMode && state.selectedMessages == 1 - 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() + toolbar.menu.findItem(R.id.copy)?.isVisible = !state.editingMode && state.selectedMessages > 0 + toolbar.menu.findItem(R.id.details)?.isVisible = !state.editingMode && state.selectedMessages == 1 + toolbar.menu.findItem(R.id.delete)?.isVisible = !state.editingMode && state.selectedMessages > 0 + toolbar.menu.findItem(R.id.forward)?.isVisible = !state.editingMode && state.selectedMessages == 1 + 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() chipsAdapter.data = state.selectedChips - binding.loading.setVisible(state.loading) + loading.setVisible(state.loading) - binding.sendAsGroup.setVisible(state.editingMode && state.selectedChips.size >= 2) - binding.sendAsGroupSwitch.isChecked = state.sendAsGroup + sendAsGroup.setVisible(state.editingMode && state.selectedChips.size >= 2) + sendAsGroupSwitch.isChecked = state.sendAsGroup - binding.messageList.setVisible(!state.editingMode || state.sendAsGroup || state.selectedChips.size == 1) + messageList.setVisible(!state.editingMode || state.sendAsGroup || state.selectedChips.size == 1) messageAdapter.data = state.messages messageAdapter.highlight = state.searchSelectionId - binding.scheduledGroup.isVisible = state.scheduled != 0L - binding.scheduledTime.text = dateFormatter.getScheduledTimestamp(state.scheduled) + scheduledGroup.isVisible = state.scheduled != 0L + scheduledTime.text = dateFormatter.getScheduledTimestamp(state.scheduled) - binding.attachments.setVisible(state.attachments.isNotEmpty()) + attachments.setVisible(state.attachments.isNotEmpty()) attachmentAdapter.data = state.attachments - binding.attach.animate().rotation(if (state.attaching) 135f else 0f).start() - binding.attaching.isVisible = state.attaching + attach.animate().rotation(if (state.attaching) 135f else 0f).start() + attaching.isVisible = state.attaching - binding.counter.text = state.remaining - binding.counter.setVisible(binding.counter.text.isNotBlank()) + counter.text = state.remaining + counter.setVisible(counter.text.isNotBlank()) - 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() + 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.send.isEnabled = state.canSend - binding.send.imageAlpha = if (state.canSend) 255 else 128 + send.isEnabled = state.canSend + send.imageAlpha = if (state.canSend) 255 else 128 } override fun clearSelection() = messageAdapter.clearSelection() @@ -282,7 +280,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 - binding.message.hideKeyboard() + message.hideKeyboard() } override fun requestContact() { @@ -293,7 +291,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } override fun showContacts(sharing: Boolean, chips: List) { - binding.message.hideKeyboard() + message.hideKeyboard() val serialized = HashMap(chips.associate { chip -> chip.address to chip.contact?.lookupKey }) val intent = Intent(this, ContactsActivity::class.java) .putExtra(ContactsActivity.SharingKey, sharing) @@ -302,12 +300,12 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } override fun themeChanged() { - binding.messageList.scrapViews() + messageList.scrapViews() } override fun showKeyboard() { - binding.message.postDelayed({ - binding.message.showKeyboard() + message.postDelayed({ + message.showKeyboard() }, 200) } @@ -332,19 +330,19 @@ class ComposeActivity : QkThemedActivity(), ComposeView { } override fun setDraft(draft: String) { - binding.message.setText(draft) - binding.message.setSelection(draft.length) + message.setText(draft) + message.setSelection(draft.length) } override fun scrollToMessage(id: Long) { messageAdapter.data?.second ?.indexOfLast { message -> message.id == id } ?.takeIf { position -> position != -1 } - ?.let(binding.messageList::scrollToPosition) + ?.let(messageList::scrollToPosition) } override fun showQksmsPlusSnackbar(message: Int) { - Snackbar.make(binding.contentView, message, Snackbar.LENGTH_LONG).run { + Snackbar.make(contentView, message, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusIntent.onNext(Unit) } setActionTextColor(colors.theme().theme) show() 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 6515746c0..d892224b6 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 @@ -46,7 +46,6 @@ import com.moez.QKSMS.common.util.extensions.setPadding import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.compat.SubscriptionManagerCompat -import com.moez.QKSMS.databinding.MessageListItemInBinding import com.moez.QKSMS.extensions.isSmil import com.moez.QKSMS.extensions.isText import com.moez.QKSMS.feature.compose.BubbleUtils.canGroup @@ -60,6 +59,15 @@ import com.moez.QKSMS.util.Preferences 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.attachments +import kotlinx.android.synthetic.main.message_list_item_in.body +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 java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -74,7 +82,7 @@ class MessagesAdapter @Inject constructor( private val phoneNumberUtils: PhoneNumberUtils, private val prefs: Preferences, private val textViewStyler: TextViewStyler -) : QkRealmAdapter() { +) : QkRealmAdapter() { companion object { private const val VIEW_TYPE_MESSAGE_IN = 0 @@ -130,7 +138,7 @@ class MessagesAdapter @Inject constructor( * this a unique viewType even though it uses the same view, so that regular messages * don't need clipToOutline set to true, and they don't need to worry about images */ - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { // Use the parent's context to inflate the layout, otherwise link clicks will crash the app val layoutInflater = LayoutInflater.from(parent.context) @@ -144,26 +152,24 @@ class MessagesAdapter @Inject constructor( view = layoutInflater.inflate(R.layout.message_list_item_in, parent, false) } - val binding = MessageListItemInBinding.bind(view) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - binding.body.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE + view.body.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE } val partsAdapter = partsAdapterProvider.get() partsAdapter.clicks.subscribe(partClicks) - binding.attachments.adapter = partsAdapter - binding.attachments.setRecycledViewPool(partsViewPool) - binding.body.forwardTouches(view) + view.attachments.adapter = partsAdapter + view.attachments.setRecycledViewPool(partsViewPool) + view.body.forwardTouches(view) - return QkViewHolder(binding).apply { + return QkViewHolder(view).apply { view.setOnClickListener { val message = getItem(adapterPosition) ?: return@setOnClickListener when (toggleSelection(message.id, false)) { true -> view.isActivated = isSelected(message.id) false -> { clicks.onNext(message.id) - expanded[message.id] = binding.status.visibility != View.VISIBLE + expanded[message.id] = view.status.visibility != View.VISIBLE notifyItemChanged(adapterPosition) } } @@ -177,7 +183,7 @@ class MessagesAdapter @Inject constructor( } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val message = getItem(position) ?: return val previous = if (position == 0) null else getItem(position - 1) val next = if (position == itemCount - 1) null else getItem(position + 1) @@ -188,10 +194,10 @@ class MessagesAdapter @Inject constructor( } // Update the selected state - holder.binding.root.isActivated = isSelected(message.id) || highlight == message.id + holder.containerView.isActivated = isSelected(message.id) || highlight == message.id // Bind the cancel view - holder.binding.cancel.let { cancel -> + holder.cancel?.let { cancel -> val isCancellable = message.isSending() && message.date > System.currentTimeMillis() cancel.setVisible(isCancellable) cancel.clicks().subscribe { cancelSending.onNext(message.id) } @@ -219,25 +225,26 @@ class MessagesAdapter @Inject constructor( val timeSincePrevious = TimeUnit.MILLISECONDS.toMinutes(message.date - (previous?.date ?: 0)) val subscription = subs.find { sub -> sub.subscriptionId == message.subId } - holder.binding.timestamp.text = dateFormatter.getMessageTimestamp(message.date) - holder.binding.simIndex.text = subscription?.simSlotIndex?.plus(1)?.toString() + holder.timestamp.text = dateFormatter.getMessageTimestamp(message.date) + holder.simIndex.text = subscription?.simSlotIndex?.plus(1)?.toString() - holder.binding.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD + holder.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD || message.subId != previous?.subId && subscription != null) - holder.binding.sim.setVisible(message.subId != previous?.subId && subscription != null && subs.size > 1) - holder.binding.simIndex.setVisible(message.subId != previous?.subId && subscription != null && subs.size > 1) + + holder.sim.setVisible(message.subId != previous?.subId && subscription != null && subs.size > 1) + holder.simIndex.setVisible(message.subId != previous?.subId && subscription != null && subs.size > 1) // Bind the grouping val media = message.parts.filter { !it.isSmil() && !it.isText() } - holder.binding.root.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context)) + holder.containerView.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context)) // Bind the avatar and bubble colour if (!message.isMe()) { - holder.binding.avatar.setRecipient(contactCache[message.address]) - holder.binding.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) + holder.avatar.setRecipient(contactCache[message.address]) + holder.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) - holder.binding.body.setTextColor(theme.textPrimary) - holder.binding.body.setBackgroundTint(theme.theme) + holder.body.setTextColor(theme.textPrimary) + holder.body.setBackgroundTint(theme.theme) } // Bind the body text @@ -263,29 +270,29 @@ class MessagesAdapter @Inject constructor( } } val emojiOnly = messageText.isNotBlank() && messageText.matches(EMOJI_REGEX) - textViewStyler.setTextSize(holder.binding.body, when (emojiOnly) { + textViewStyler.setTextSize(holder.body, when (emojiOnly) { true -> TextViewStyler.SIZE_EMOJI false -> TextViewStyler.SIZE_PRIMARY }) - holder.binding.body.text = messageText - holder.binding.body.setVisible(message.isSms() || messageText.isNotBlank()) - holder.binding.body.setBackgroundResource(getBubble( + holder.body.text = messageText + holder.body.setVisible(message.isSms() || messageText.isNotBlank()) + holder.body.setBackgroundResource(getBubble( emojiOnly = emojiOnly, canGroupWithPrevious = canGroup(message, previous) || media.isNotEmpty(), canGroupWithNext = canGroup(message, next), isMe = message.isMe())) // Bind the attachments - val partsAdapter = holder.binding.attachments.adapter as PartsAdapter + val partsAdapter = holder.attachments.adapter as PartsAdapter partsAdapter.theme = theme partsAdapter.setData(message, previous, next, holder) } - private fun bindStatus(holder: QkViewHolder, message: Message, next: Message?) { + private fun bindStatus(holder: QkViewHolder, message: Message, next: Message?) { val age = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - message.date) - holder.binding.status.text = when { + holder.status.text = when { message.isSending() -> context.getString(R.string.message_status_sending) message.isDelivered() -> context.getString(R.string.message_status_delivered, dateFormatter.getTimestamp(message.dateSent)) @@ -299,7 +306,7 @@ class MessagesAdapter @Inject constructor( else -> dateFormatter.getTimestamp(message.date) } - holder.binding.status.setVisible(when { + holder.status.setVisible(when { expanded[message.id] == true -> true message.isSending() -> true message.isFailedMessage() -> true diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt index a6ebc5d01..b415d32f3 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/ChipsAdapter.kt @@ -20,6 +20,7 @@ package com.moez.QKSMS.feature.compose.editing import android.content.Context import android.os.Build +import android.view.LayoutInflater import android.view.ViewGroup import android.widget.RelativeLayout import androidx.recyclerview.widget.RecyclerView @@ -29,35 +30,37 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint -import com.moez.QKSMS.databinding.ContactChipBinding import com.moez.QKSMS.model.Recipient import io.reactivex.subjects.PublishSubject +import kotlinx.android.synthetic.main.contact_chip.* import javax.inject.Inject -class ChipsAdapter @Inject constructor() : QkAdapter() { +class ChipsAdapter @Inject constructor() : QkAdapter() { var view: RecyclerView? = null val chipDeleted: PublishSubject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, ContactChipBinding::inflate).apply { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.contact_chip, parent, false) + return QkViewHolder(view).apply { // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - binding.content.setBackgroundTint(parent.context.resolveThemeColor(R.attr.bubbleColor)) + content.setBackgroundTint(view.context.resolveThemeColor(R.attr.bubbleColor)) } - binding.root.setOnClickListener { + view.setOnClickListener { val chip = getItem(adapterPosition) - showDetailedChip(parent.context, chip) + showDetailedChip(view.context, chip) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val recipient = getItem(position) - holder.binding.avatar.setRecipient(recipient) - holder.binding.name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address + holder.avatar.setRecipient(recipient) + holder.name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address } /** @@ -73,9 +76,8 @@ class ChipsAdapter @Inject constructor() : QkAdapter() { +) : QkAdapter() { val clicks: Subject = PublishSubject.create() val longClicks: Subject = PublishSubject.create() @@ -57,19 +59,22 @@ class ComposeItemAdapter @Inject constructor( notifyDataSetChanged() } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, ContactListItemBinding::inflate).apply { - binding.icon.setTint(colors.theme().theme) + 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) - binding.numbers.setRecycledViewPool(numbersViewPool) - binding.numbers.adapter = PhoneNumberAdapter() - binding.numbers.forwardTouches(binding.root) + view.icon.setTint(colors.theme().theme) - binding.root.setOnClickListener { + view.numbers.setRecycledViewPool(numbersViewPool) + view.numbers.adapter = PhoneNumberAdapter() + view.numbers.forwardTouches(view) + + return QkViewHolder(view).apply { + view.setOnClickListener { val item = getItem(adapterPosition) clicks.onNext(item) } - binding.root.setOnLongClickListener { + view.setOnLongClickListener { val item = getItem(adapterPosition) longClicks.onNext(item) true @@ -77,7 +82,7 @@ class ComposeItemAdapter @Inject constructor( } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val prevItem = if (position > 0) getItem(position - 1) else null val item = getItem(position) @@ -90,92 +95,92 @@ class ComposeItemAdapter @Inject constructor( } } - private fun bindNew(holder: QkViewHolder, contact: Contact) { - holder.binding.index.isVisible = false + private fun bindNew(holder: QkViewHolder, contact: Contact) { + holder.index.isVisible = false - holder.binding.icon.isVisible = false + holder.icon.isVisible = false - holder.binding.avatar.recipients = listOf(createRecipient(contact)) + holder.avatar.recipients = listOf(createRecipient(contact)) - holder.binding.title.text = contact.numbers.joinToString { it.address } + holder.title.text = contact.numbers.joinToString { it.address } - holder.binding.subtitle.isVisible = false + holder.subtitle.isVisible = false - holder.binding.numbers.isVisible = false + holder.numbers.isVisible = false } - private fun bindRecent(holder: QkViewHolder, conversation: Conversation, prev: ComposeItem?) { - holder.binding.index.isVisible = false + private fun bindRecent(holder: QkViewHolder, conversation: Conversation, prev: ComposeItem?) { + holder.index.isVisible = false - holder.binding.icon.isVisible = prev !is ComposeItem.Recent - holder.binding.icon.setImageResource(R.drawable.ic_history_black_24dp) + holder.icon.isVisible = prev !is ComposeItem.Recent + holder.icon.setImageResource(R.drawable.ic_history_black_24dp) - holder.binding.avatar.recipients = conversation.recipients + holder.avatar.recipients = conversation.recipients - holder.binding.title.text = conversation.getTitle() + holder.title.text = conversation.getTitle() - holder.binding.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() - holder.binding.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> + holder.subtitle.isVisible = conversation.recipients.size > 1 && conversation.name.isBlank() + holder.subtitle.text = conversation.recipients.joinToString(", ") { recipient -> recipient.contact?.name ?: recipient.address } - holder.binding.subtitle.collapseEnabled = conversation.recipients.size > 1 + holder.subtitle.collapseEnabled = conversation.recipients.size > 1 - holder.binding.numbers.isVisible = conversation.recipients.size == 1 - (holder.binding.numbers.adapter as PhoneNumberAdapter).data = conversation.recipients + holder.numbers.isVisible = conversation.recipients.size == 1 + (holder.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.binding.index.isVisible = false + private fun bindStarred(holder: QkViewHolder, contact: Contact, prev: ComposeItem?) { + holder.index.isVisible = false - holder.binding.icon.isVisible = prev !is ComposeItem.Starred - holder.binding.icon.setImageResource(R.drawable.ic_star_black_24dp) + holder.icon.isVisible = prev !is ComposeItem.Starred + holder.icon.setImageResource(R.drawable.ic_star_black_24dp) - holder.binding.avatar.recipients = listOf(createRecipient(contact)) + holder.avatar.recipients = listOf(createRecipient(contact)) - holder.binding.title.text = contact.name + holder.title.text = contact.name - holder.binding.subtitle.isVisible = false + holder.subtitle.isVisible = false - holder.binding.numbers.isVisible = true - (holder.binding.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + holder.numbers.isVisible = true + (holder.numbers.adapter as PhoneNumberAdapter).data = contact.numbers } - private fun bindGroup(holder: QkViewHolder, group: ContactGroup, prev: ComposeItem?) { - holder.binding.index.isVisible = false + private fun bindGroup(holder: QkViewHolder, group: ContactGroup, prev: ComposeItem?) { + holder.index.isVisible = false - holder.binding.icon.isVisible = prev !is ComposeItem.Group - holder.binding.icon.setImageResource(R.drawable.ic_people_black_24dp) + holder.icon.isVisible = prev !is ComposeItem.Group + holder.icon.setImageResource(R.drawable.ic_people_black_24dp) - holder.binding.avatar.recipients = group.contacts.map(::createRecipient) + holder.avatar.recipients = group.contacts.map(::createRecipient) - holder.binding.title.text = group.title + holder.title.text = group.title - holder.binding.subtitle.isVisible = true - holder.binding.subtitle.text = group.contacts.joinToString(", ") { it.name } - holder.binding.subtitle.collapseEnabled = group.contacts.size > 1 + holder.subtitle.isVisible = true + holder.subtitle.text = group.contacts.joinToString(", ") { it.name } + holder.subtitle.collapseEnabled = group.contacts.size > 1 - holder.binding.numbers.isVisible = false + holder.numbers.isVisible = false } - private fun bindPerson(holder: QkViewHolder, contact: Contact, prev: ComposeItem?) { - holder.binding.index.isVisible = true - holder.binding.index.text = if (contact.name.getOrNull(0)?.isLetter() == true) contact.name[0].toString() else "#" - holder.binding.index.isVisible = prev !is ComposeItem.Person || + 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 || (contact.name[0].isLetter() && !contact.name[0].equals(prev.value.name[0], ignoreCase = true)) || (!contact.name[0].isLetter() && prev.value.name[0].isLetter()) - holder.binding.icon.isVisible = false + holder.icon.isVisible = false - holder.binding.avatar.recipients = listOf(createRecipient(contact)) + holder.avatar.recipients = listOf(createRecipient(contact)) - holder.binding.title.text = contact.name + holder.title.text = contact.name - holder.binding.subtitle.isVisible = false + holder.subtitle.isVisible = false - holder.binding.numbers.isVisible = true - (holder.binding.numbers.adapter as PhoneNumberAdapter).data = contact.numbers + holder.numbers.isVisible = true + (holder.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 0b9475df7..aa90cd994 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,53 +19,44 @@ package com.moez.QKSMS.feature.compose.editing import android.content.Context -import android.util.AttributeSet import android.view.View import android.view.animation.AlphaAnimation -import androidx.constraintlayout.widget.ConstraintLayout +import android.widget.RelativeLayout import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors -import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.ContactChipDetailedBinding import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.model.Recipient +import kotlinx.android.synthetic.main.contact_chip_detailed.view.* import javax.inject.Inject -class DetailedChipView(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { +class DetailedChipView(context: Context) : RelativeLayout(context) { @Inject lateinit var colors: Colors - private val binding = viewBinding(ContactChipDetailedBinding::inflate) - init { - if (!isInEditMode) { - appComponent.inject(this) - visibility = View.GONE - } + View.inflate(context, R.layout.contact_chip_detailed, this) + appComponent.inject(this) setOnClickListener { hide() } - setBackgroundResource(R.drawable.rounded_rectangle_2dp) - elevation = 8.dpToPx(context) .toFloat() -// updateLayoutParams { setMargins(8.dpToPx(context) ) } + visibility = View.GONE isFocusable = true isFocusableInTouchMode = true } fun setRecipient(recipient: Recipient) { - binding.avatar.setRecipient(recipient) - binding.name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address - binding.info.text = recipient.address + avatar.setRecipient(recipient) + name.text = recipient.contact?.name?.takeIf { it.isNotBlank() } ?: recipient.address + info.text = recipient.address colors.theme(recipient).let { theme -> - setBackgroundTint(theme.theme) - binding.name.setTextColor(theme.textPrimary) - binding.info.setTextColor(theme.textTertiary) - binding.delete.setTint(theme.textPrimary) + card.setBackgroundTint(theme.theme) + name.setTextColor(theme.textPrimary) + info.setTextColor(theme.textTertiary) + delete.setTint(theme.textPrimary) } } @@ -86,7 +77,7 @@ class DetailedChipView(context: Context, attrs: AttributeSet? = null) : Constrai } fun setOnDeleteListener(listener: (View) -> Unit) { - binding.delete.setOnClickListener(listener) + delete.setOnClickListener(listener) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt index 9a914e167..534f013e9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/editing/PhoneNumberAdapter.kt @@ -18,23 +18,27 @@ */ package com.moez.QKSMS.feature.compose.editing +import android.view.LayoutInflater import android.view.ViewGroup +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.databinding.ContactNumberListItemBinding import com.moez.QKSMS.model.PhoneNumber +import kotlinx.android.synthetic.main.contact_number_list_item.* -class PhoneNumberAdapter : QkAdapter() { +class PhoneNumberAdapter : QkAdapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, ContactNumberListItemBinding::inflate) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.contact_number_list_item, parent, false) + return QkViewHolder(view) } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val number = getItem(position) - holder.binding.address.text = number.address - holder.binding.type.text = number.type + holder.address.text = number.address + holder.type.text = number.type } override fun areItemsTheSame(old: PhoneNumber, new: PhoneNumber): Boolean { @@ -45,4 +49,4 @@ class PhoneNumberAdapter : QkAdapter( return old.type == new.type && old.address == new.address } -} +} \ No newline at end of file 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 74b5e4295..084e41a37 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 @@ -19,21 +19,24 @@ package com.moez.QKSMS.feature.compose.editing import android.content.Context +import android.view.LayoutInflater import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.extensions.forwardTouches -import com.moez.QKSMS.databinding.PhoneNumberListItemBinding import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.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( private val context: Context -) : QkAdapter() { +) : QkAdapter() { val selectedItemChanges: Subject> = BehaviorSubject.create() @@ -45,23 +48,25 @@ class PhoneNumberPickerAdapter @Inject constructor( selectedItemChanges.onNext(Optional(value)) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, PhoneNumberListItemBinding::inflate).apply { - binding.number.binding.radioButton.forwardTouches(itemView) + 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) - binding.root.setOnClickListener { + view.setOnClickListener { val phoneNumber = getItem(adapterPosition) selectedItem = phoneNumber.id } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val phoneNumber = getItem(position) - holder.binding.number.binding.radioButton.isChecked = phoneNumber.id == selectedItem - holder.binding.number.binding.titleView.text = phoneNumber.address - holder.binding.number.binding.summaryView.text = when (phoneNumber.isDefault) { + holder.number.radioButton.isChecked = phoneNumber.id == selectedItem + holder.number.titleView.text = phoneNumber.address + holder.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/FileBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt index cb055e40b..ff12a1bdd 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 @@ -28,37 +28,35 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.databinding.MmsFileListItemBinding import com.moez.QKSMS.feature.compose.BubbleUtils import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart 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(MmsFileListItemBinding::inflate) { +class FileBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { + override val partLayout = R.layout.mms_file_list_item override var theme = colors.theme() // This is the last binder we check. If we're here, we can bind the part override fun canBindPart(part: MmsPart) = true @SuppressLint("CheckResult") - override fun bindPartInternal( - holder: QkViewHolder, + override fun bindPart( + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) - .let(holder.binding.fileBackground::setBackgroundResource) + .let(holder.fileBackground::setBackgroundResource) - holder.binding.root.setOnClickListener { clicks.onNext(part.id) } + holder.containerView.setOnClickListener { clicks.onNext(part.id) } Observable.just(part.getUri()) .map(context.contentResolver::openInputStream) @@ -73,23 +71,23 @@ class FileBinder @Inject constructor( } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { size -> holder.binding.size.text = size } + .subscribe { size -> holder.size.text = size } - holder.binding.filename.text = part.name + holder.filename.text = part.name - val params = holder.binding.fileBackground.layoutParams as FrameLayout.LayoutParams + val params = holder.fileBackground.layoutParams as FrameLayout.LayoutParams if (!message.isMe()) { - holder.binding.fileBackground.layoutParams = params.apply { gravity = Gravity.START } - holder.binding.fileBackground.setBackgroundTint(theme.theme) - holder.binding.icon.setTint(theme.textPrimary) - holder.binding.filename.setTextColor(theme.textPrimary) - holder.binding.size.setTextColor(theme.textTertiary) + holder.fileBackground.layoutParams = params.apply { gravity = Gravity.START } + holder.fileBackground.setBackgroundTint(theme.theme) + holder.icon.setTint(theme.textPrimary) + holder.filename.setTextColor(theme.textPrimary) + holder.size.setTextColor(theme.textTertiary) } else { - holder.binding.fileBackground.layoutParams = params.apply { gravity = Gravity.END } - holder.binding.fileBackground.setBackgroundTint(holder.binding.root.context.resolveThemeColor(R.attr.bubbleColor)) - holder.binding.icon.setTint(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorSecondary)) - holder.binding.filename.setTextColor(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorPrimary)) - holder.binding.size.setTextColor(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorTertiary)) + holder.fileBackground.layoutParams = params.apply { gravity = Gravity.END } + 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)) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt index 78c5a3209..1ac437a5c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt @@ -19,45 +19,44 @@ package com.moez.QKSMS.feature.compose.part import android.content.Context +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.BubbleImageView -import com.moez.QKSMS.databinding.MmsPreviewListItemBinding import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.util.GlideApp +import kotlinx.android.synthetic.main.mms_preview_list_item.* import javax.inject.Inject -class MediaBinder @Inject constructor( - colors: Colors, - private val context: Context -) : PartBinder(MmsPreviewListItemBinding::inflate) { +class MediaBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { + override val partLayout = R.layout.mms_preview_list_item override var theme = colors.theme() override fun canBindPart(part: MmsPart) = part.isImage() || part.isVideo() - override fun bindPartInternal( - holder: QkViewHolder, + override fun bindPart( + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { - holder.binding.video.setVisible(part.isVideo()) - holder.binding.root.setOnClickListener { clicks.onNext(part.id) } + holder.video.setVisible(part.isVideo()) + holder.containerView.setOnClickListener { clicks.onNext(part.id) } - holder.binding.thumbnail.bubbleStyle = when { + holder.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 else -> BubbleImageView.Style.ONLY } - GlideApp.with(context).load(part.getUri()).fitCenter().into(holder.binding.thumbnail) + GlideApp.with(context).load(part.getUri()).fitCenter().into(holder.thumbnail) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt index c4e27ba27..d56f8547d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt @@ -18,9 +18,6 @@ */ package com.moez.QKSMS.feature.compose.part -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.viewbinding.ViewBinding import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.model.Message @@ -28,36 +25,18 @@ import com.moez.QKSMS.model.MmsPart import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject -abstract class PartBinder( - val bindingInflater: (LayoutInflater, ViewGroup, Boolean) -> Binding -) { +abstract class PartBinder { val clicks: Subject = PublishSubject.create() - abstract var theme: Colors.Theme + abstract val partLayout: Int - fun bindPart( - holder: QkViewHolder, - part: MmsPart, - message: Message, - canGroupWithPrevious: Boolean, - canGroupWithNext: Boolean - ): Boolean { - val castHolder = holder as? QkViewHolder - - if (!canBindPart(part) || castHolder == null) { - return false - } - - bindPartInternal(castHolder, part, message, canGroupWithPrevious, canGroupWithNext) - - return true - } + abstract var theme: Colors.Theme abstract fun canBindPart(part: MmsPart): Boolean - protected abstract fun bindPartInternal( - holder: QkViewHolder, + abstract fun bindPart( + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, 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 6f5437d2d..567f49268 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 @@ -18,20 +18,20 @@ */ package com.moez.QKSMS.feature.compose.part +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.viewbinding.ViewBinding import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.forwardTouches -import com.moez.QKSMS.databinding.MessageListItemInBinding import com.moez.QKSMS.extensions.isSmil import com.moez.QKSMS.extensions.isText import com.moez.QKSMS.feature.compose.BubbleUtils.canGroup import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import io.reactivex.Observable +import kotlinx.android.synthetic.main.message_list_item_in.* import javax.inject.Inject class PartsAdapter @Inject constructor( @@ -39,7 +39,7 @@ class PartsAdapter @Inject constructor( fileBinder: FileBinder, mediaBinder: MediaBinder, vCardBinder: VCardBinder -) : QkAdapter() { +) : QkAdapter() { private val partBinders = listOf(mediaBinder, vCardBinder, fileBinder) @@ -54,31 +54,34 @@ class PartsAdapter @Inject constructor( private lateinit var message: Message private var previous: Message? = null private var next: Message? = null - private var holder: QkViewHolder? = null + private var holder: QkViewHolder? = null private var bodyVisible: Boolean = true - fun setData(message: Message, previous: Message?, next: Message?, holder: QkViewHolder) { + fun setData(message: Message, previous: Message?, next: Message?, holder: QkViewHolder) { this.message = message this.previous = previous this.next = next this.holder = holder - this.bodyVisible = holder.binding.body.visibility == View.VISIBLE + this.bodyVisible = holder.body.visibility == View.VISIBLE this.data = message.parts.filter { !it.isSmil() && !it.isText() } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, partBinders[viewType].bindingInflater).apply { - holder?.binding?.root?.let(binding.root::forwardTouches) - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val layout = partBinders.getOrNull(viewType)?.partLayout ?: 0 + val view = LayoutInflater.from(parent.context).inflate(layout, parent, false) + holder?.containerView?.let(view::forwardTouches) + return QkViewHolder(view) } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val part = data[position] val canGroupWithPrevious = canGroup(message, previous) || position > 0 val canGroupWithNext = canGroup(message, next) || position < itemCount - 1 || bodyVisible - partBinders.find { binder -> binder.bindPart(holder, part, message, canGroupWithPrevious, canGroupWithNext) } + partBinders + .firstOrNull { it.canBindPart(part) } + ?.bindPart(holder, part, message, canGroupWithPrevious, canGroupWithNext) } override fun getItemViewType(position: Int): Int { 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 8c0f10508..9e068137d 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,7 +27,6 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.databinding.MmsVcardListItemBinding import com.moez.QKSMS.extensions.isVCard import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.feature.compose.BubbleUtils @@ -37,49 +36,48 @@ 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(MmsVcardListItemBinding::inflate) { +class VCardBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { + override val partLayout = R.layout.mms_vcard_list_item override var theme = colors.theme() override fun canBindPart(part: MmsPart) = part.isVCard() - override fun bindPartInternal( - holder: QkViewHolder, + override fun bindPart( + holder: QkViewHolder, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) - .let(holder.binding.vCardBackground::setBackgroundResource) + .let(holder.vCardBackground::setBackgroundResource) - holder.binding.root.setOnClickListener { clicks.onNext(part.id) } + holder.containerView.setOnClickListener { clicks.onNext(part.id) } Observable.just(part.getUri()) .map(context.contentResolver::openInputStream) .mapNotNull { inputStream -> inputStream.use { Ezvcard.parse(it).first() } } .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { vcard -> holder.binding.name?.text = vcard.formattedName.value } + .subscribe { vcard -> holder.name?.text = vcard.formattedName.value } - val params = holder.binding.vCardBackground.layoutParams as FrameLayout.LayoutParams + val params = holder.vCardBackground.layoutParams as FrameLayout.LayoutParams if (!message.isMe()) { - holder.binding.vCardBackground.layoutParams = params.apply { gravity = Gravity.START } - holder.binding.vCardBackground.setBackgroundTint(theme.theme) - holder.binding.vCardAvatar.setTint(theme.textPrimary) - holder.binding.name.setTextColor(theme.textPrimary) - holder.binding.label.setTextColor(theme.textTertiary) + holder.vCardBackground.layoutParams = params.apply { gravity = Gravity.START } + holder.vCardBackground.setBackgroundTint(theme.theme) + holder.vCardAvatar.setTint(theme.textPrimary) + holder.name.setTextColor(theme.textPrimary) + holder.label.setTextColor(theme.textTertiary) } else { - holder.binding.vCardBackground.layoutParams = params.apply { gravity = Gravity.END } - holder.binding.vCardBackground.setBackgroundTint(holder.binding.root.context.resolveThemeColor(R.attr.bubbleColor)) - holder.binding.vCardAvatar.setTint(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorSecondary)) - holder.binding.name.setTextColor(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorPrimary)) - holder.binding.label.setTextColor(holder.binding.root.context.resolveThemeColor(android.R.attr.textColorTertiary)) + holder.vCardBackground.layoutParams = params.apply { gravity = Gravity.END } + 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)) } } 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 af2904a82..f5ca9f71d 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 @@ -34,9 +34,7 @@ import com.moez.QKSMS.common.util.extensions.hideKeyboard import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.showKeyboard -import com.moez.QKSMS.common.util.extensions.viewBinding import com.moez.QKSMS.common.widget.QkDialog -import com.moez.QKSMS.databinding.ContactsActivityBinding import com.moez.QKSMS.extensions.Optional import com.moez.QKSMS.feature.compose.editing.ComposeItem import com.moez.QKSMS.feature.compose.editing.ComposeItemAdapter @@ -46,6 +44,7 @@ import dagger.android.AndroidInjection 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 { @@ -59,15 +58,14 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { @Inject lateinit var phoneNumberAdapter: PhoneNumberPickerAdapter @Inject lateinit var viewModelFactory: ViewModelFactory - 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 queryChangedIntent: Observable by lazy { search.textChanges() } + override val queryClearedIntent: Observable<*> by lazy { cancel.clicks() } + override val queryEditorActionIntent: Observable by lazy { 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 } override val phoneNumberActionIntent: Subject = PublishSubject.create() - private val binding by viewBinding(ContactsActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ContactsViewModel::class.java] } private val phoneNumberDialog by lazy { @@ -85,20 +83,20 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.contacts_activity) showBackButton(true) viewModel.bindView(this) - binding.contacts.adapter = contactsAdapter + contacts.adapter = contactsAdapter // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - binding.search.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) + search.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) } } override fun render(state: ContactsState) { - binding.cancel.isVisible = state.query.length > 1 + cancel.isVisible = state.query.length > 1 contactsAdapter.data = state.composeItems @@ -112,17 +110,17 @@ class ContactsActivity : QkThemedActivity(), ContactsContract { } override fun clearQuery() { - binding.search.text = null + search.text = null } override fun openKeyboard() { - binding.search.postDelayed({ - binding.search.showKeyboard() + search.postDelayed({ + search.showKeyboard() }, 200) } override fun finish(result: HashMap) { - binding.search.hideKeyboard() + search.hideKeyboard() val intent = Intent().putExtra(ChipsKey, 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 949bfa925..1b6e50378 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 @@ -22,22 +22,21 @@ import android.os.Bundle import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.ContainerActivityBinding import dagger.android.AndroidInjection +import kotlinx.android.synthetic.main.container_activity.* class ConversationInfoActivity : QkThemedActivity() { - private val binding by viewBinding(ContainerActivityBinding::inflate) private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.container_activity) - router = Conductor.attachRouter(this, binding.container, savedInstanceState) + router = Conductor.attachRouter(this, 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 04ec5798e..f75034171 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 @@ -1,9 +1,9 @@ package com.moez.QKSMS.feature.conversationinfo import android.content.Context +import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible -import androidx.viewbinding.ViewBinding import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter @@ -11,20 +11,20 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.databinding.ConversationInfoSettingsBinding -import com.moez.QKSMS.databinding.ConversationMediaListItemBinding -import com.moez.QKSMS.databinding.ConversationRecipientListItemBinding import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.feature.conversationinfo.ConversationInfoItem.* import com.moez.QKSMS.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( private val context: Context, private val colors: Colors -) : QkAdapter() { +) : QkAdapter() { val recipientClicks: Subject = PublishSubject.create() val recipientLongClicks: Subject = PublishSubject.create() @@ -36,97 +36,90 @@ class ConversationInfoAdapter @Inject constructor( val deleteClicks: Subject = PublishSubject.create() val mediaClicks: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val holder: QkViewHolder = when (viewType) { - 0 -> QkViewHolder(parent, ConversationRecipientListItemBinding::inflate) - 1 -> QkViewHolder(parent, ConversationInfoSettingsBinding::inflate) - 2 -> QkViewHolder(parent, ConversationMediaListItemBinding::inflate) - else -> throw IllegalStateException() - } + 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) + } - return holder.apply { - when (binding) { - is ConversationRecipientListItemBinding -> { - 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 - } - - binding.theme.setOnClickListener { - val item = getItem(adapterPosition) as? ConversationInfoRecipient - item?.value?.id?.run(themeClicks::onNext) - } + itemView.setOnLongClickListener { + val item = getItem(adapterPosition) as? ConversationInfoRecipient + item?.value?.id?.run(recipientLongClicks::onNext) + true } - is ConversationInfoSettingsBinding -> { - binding.groupName.clicks().subscribe(nameClicks) - binding.notifications.clicks().subscribe(notificationClicks) - binding.archive.clicks().subscribe(archiveClicks) - binding.block.clicks().subscribe(blockClicks) - binding.delete.clicks().subscribe(deleteClicks) + theme.setOnClickListener { + val item = getItem(adapterPosition) as? ConversationInfoRecipient + item?.value?.id?.run(themeClicks::onNext) } + } - is ConversationMediaListItemBinding -> { - itemView.setOnClickListener { - val item = getItem(adapterPosition) as? ConversationInfoMedia - item?.value?.id?.run(mediaClicks::onNext) - } + 1 -> QkViewHolder(inflater.inflate(R.layout.conversation_info_settings, parent, false)).apply { + groupName.clicks().subscribe(nameClicks) + notifications.clicks().subscribe(notificationClicks) + archive.clicks().subscribe(archiveClicks) + block.clicks().subscribe(blockClicks) + 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) } } + + else -> throw IllegalStateException() } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val item = getItem(position) - when { - item is ConversationInfoRecipient && holder.binding is ConversationRecipientListItemBinding -> { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + when (val item = getItem(position)) { + is ConversationInfoRecipient -> { val recipient = item.value - holder.binding.avatar.setRecipient(recipient) + holder.avatar.setRecipient(recipient) - holder.binding.name.text = recipient.contact?.name ?: recipient.address + holder.name.text = recipient.contact?.name ?: recipient.address - holder.binding.address.text = recipient.address - holder.binding.address.setVisible(recipient.contact != null) + holder.address.text = recipient.address + holder.address.setVisible(recipient.contact != null) - holder.binding.add.setVisible(recipient.contact == null) + holder.add.setVisible(recipient.contact == null) val theme = colors.theme(recipient) - holder.binding.theme.setTint(theme.theme) + holder.theme.setTint(theme.theme) } - item is ConversationInfoSettings && holder.binding is ConversationInfoSettingsBinding -> { - holder.binding.groupName.isVisible = item.recipients.size > 1 - holder.binding.groupName.summary = item.name + is ConversationInfoSettings -> { + holder.groupName.isVisible = item.recipients.size > 1 + holder.groupName.summary = item.name - holder.binding.notifications.isEnabled = !item.blocked + holder.notifications.isEnabled = !item.blocked - holder.binding.archive.isEnabled = !item.blocked - holder.binding.archive.title = context.getString(when (item.archived) { + holder.archive.isEnabled = !item.blocked + holder.archive.title = context.getString(when (item.archived) { true -> R.string.info_unarchive false -> R.string.info_archive }) - holder.binding.block.title = context.getString(when (item.blocked) { + holder.block.title = context.getString(when (item.blocked) { true -> R.string.info_unblock false -> R.string.info_block }) } - item is ConversationInfoMedia && holder.binding is ConversationMediaListItemBinding -> { + is ConversationInfoMedia -> { val part = item.value GlideApp.with(context) .load(part.getUri()) .fitCenter() - .into(holder.binding.thumbnail) + .into(holder.thumbnail) - holder.binding.video.isVisible = part.isVideo() + holder.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 07682cf98..c33408798 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 @@ -28,7 +28,6 @@ import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.extensions.scrapViews import com.moez.QKSMS.common.widget.TextInputDialog -import com.moez.QKSMS.databinding.ConversationInfoControllerBinding import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoModule import com.moez.QKSMS.feature.themepicker.ThemePickerController @@ -38,12 +37,12 @@ import com.uber.autodispose.autoDisposable 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(ConversationInfoControllerBinding::inflate), ConversationInfoView { +) : QkController(), ConversationInfoView { @Inject override lateinit var presenter: ConversationInfoPresenter @Inject lateinit var blockingDialog: BlockingDialog @@ -63,12 +62,14 @@ class ConversationInfoController( .conversationInfoModule(ConversationInfoModule(this)) .build() .inject(this) + + layoutRes = R.layout.conversation_info_controller } override fun onViewCreated() { - binding.recyclerView.adapter = adapter - binding.recyclerView.addItemDecoration(GridSpacingItemDecoration(adapter, activity!!)) - binding.recyclerView.layoutManager = GridLayoutManager(activity, 3).apply { + recyclerView.adapter = adapter + recyclerView.addItemDecoration(GridSpacingItemDecoration(adapter, activity!!)) + recyclerView.layoutManager = GridLayoutManager(activity, 3).apply { spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int = if (adapter.getItemViewType(position) == 2) 1 else 3 } @@ -76,7 +77,7 @@ class ConversationInfoController( themedActivity?.theme ?.autoDisposable(scope()) - ?.subscribe { binding.recyclerView.scrapViews() } + ?.subscribe { 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 370be6bce..ed1a3f11a 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 @@ -35,9 +35,10 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.databinding.ConversationListItemBinding import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.util.PhoneNumberUtils +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( @@ -46,48 +47,50 @@ class ConversationsAdapter @Inject constructor( private val dateFormatter: DateFormatter, private val navigator: Navigator, private val phoneNumberUtils: PhoneNumberUtils -) : QkRealmAdapter() { +) : QkRealmAdapter() { init { // This is how we access the threadId for the swipe actions setHasStableIds(true) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, ConversationListItemBinding::inflate).apply { - if (viewType == 1) { - val textColorPrimary = parent.context.resolveThemeColor(android.R.attr.textColorPrimary) + 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) - binding.title.setTypeface(binding.title.typeface, Typeface.BOLD) + if (viewType == 1) { + val textColorPrimary = parent.context.resolveThemeColor(android.R.attr.textColorPrimary) - binding.snippet.setTypeface(binding.snippet.typeface, Typeface.BOLD) - binding.snippet.setTextColor(textColorPrimary) - binding.snippet.maxLines = 5 + view.title.setTypeface(view.title.typeface, Typeface.BOLD) - binding.unread.isVisible = true + view.snippet.setTypeface(view.snippet.typeface, Typeface.BOLD) + view.snippet.setTextColor(textColorPrimary) + view.snippet.maxLines = 5 - binding.date.setTypeface(binding.date.typeface, Typeface.BOLD) - binding.date.setTextColor(textColorPrimary) - } + view.unread.isVisible = true + + view.date.setTypeface(view.date.typeface, Typeface.BOLD) + view.date.setTextColor(textColorPrimary) + } - binding.root.setOnClickListener { + return QkViewHolder(view).apply { + view.setOnClickListener { val conversation = getItem(adapterPosition) ?: return@setOnClickListener when (toggleSelection(conversation.id, false)) { - true -> binding.root.isActivated = isSelected(conversation.id) + true -> view.isActivated = isSelected(conversation.id) false -> navigator.showConversation(conversation.id) } } - - binding.root.setOnLongClickListener { + view.setOnLongClickListener { val conversation = getItem(adapterPosition) ?: return@setOnLongClickListener true toggleSelection(conversation.id) - binding.root.isActivated = isSelected(conversation.id) + view.isActivated = isSelected(conversation.id) true } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val conversation = getItem(position) ?: return // If the last message wasn't incoming, then the colour doesn't really matter anyway @@ -100,24 +103,24 @@ class ConversationsAdapter @Inject constructor( } val theme = colors.theme(recipient).theme - holder.binding.root.isActivated = isSelected(conversation.id) + holder.containerView.isActivated = isSelected(conversation.id) - holder.binding.avatars.recipients = conversation.recipients - holder.binding.title.collapseEnabled = conversation.recipients.size > 1 - holder.binding.title.text = buildSpannedString { + holder.avatars.recipients = conversation.recipients + holder.title.collapseEnabled = conversation.recipients.size > 1 + holder.title.text = buildSpannedString { append(conversation.getTitle()) if (conversation.draft.isNotEmpty()) { color(theme) { append(" " + context.getString(R.string.main_draft)) } } } - holder.binding.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) - holder.binding.snippet.text = when { + holder.date.text = conversation.date.takeIf { it > 0 }?.let(dateFormatter::getConversationTimestamp) + holder.snippet.text = when { conversation.draft.isNotEmpty() -> conversation.draft conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) else -> conversation.snippet } - holder.binding.pinned.isVisible = conversation.pinned - holder.binding.unread.setTint(theme) + holder.pinned.isVisible = conversation.pinned + holder.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 fe8e0fa2b..f2c261b68 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 @@ -33,13 +33,12 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkActivity import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.GalleryActivityBinding import com.moez.QKSMS.model.MmsPart import dagger.android.AndroidInjection 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 { @@ -50,21 +49,20 @@ class GalleryActivity : QkActivity(), GalleryView { val partId by lazy { intent.getLongExtra("partId", 0L) } - private val binding by viewBinding(GalleryActivityBinding::inflate) - private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[GalleryViewModel::class.java] } private val optionsItemSubject: Subject = PublishSubject.create() private val pageChangedSubject: Subject = PublishSubject.create() + private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[GalleryViewModel::class.java] } override fun onCreate(savedInstanceState: Bundle?) { delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.gallery_activity) showBackButton(true) viewModel.bindView(this) - binding.pager.adapter = pagerAdapter - binding.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + pager.adapter = pagerAdapter + pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { this@GalleryActivity.onPageSelected(position) } @@ -76,7 +74,7 @@ class GalleryActivity : QkActivity(), GalleryView { ?.indexOfFirst { part -> part.id == partId } ?.let { index -> onPageSelected(index) - binding.pager.setCurrentItem(index, false) + pager.setCurrentItem(index, false) pagerAdapter.unregisterAdapterDataObserver(this) } } @@ -84,15 +82,15 @@ class GalleryActivity : QkActivity(), GalleryView { } fun onPageSelected(position: Int) { - binding.toolbarSubtitle.text = pagerAdapter.getItem(position)?.messages?.firstOrNull()?.date + toolbarSubtitle.text = pagerAdapter.getItem(position)?.messages?.firstOrNull()?.date ?.let(dateFormatter::getDetailedTimestamp) - binding.toolbarSubtitle.isVisible = binding.toolbarTitle.text.isNotBlank() + toolbarSubtitle.isVisible = toolbarTitle.text.isNotBlank() pagerAdapter.getItem(position)?.run(pageChangedSubject::onNext) } override fun render(state: GalleryState) { - binding.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 96050060f..f3a4405bc 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 @@ -19,9 +19,9 @@ package com.moez.QKSMS.feature.gallery import android.content.Context +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.viewbinding.ViewBinding import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ExoPlayerFactory import com.google.android.exoplayer2.source.ExtractorMediaSource @@ -30,23 +30,22 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.util.Util import com.google.android.mms.ContentType +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.databinding.GalleryImagePageBinding -import com.moez.QKSMS.databinding.GalleryInvalidPageBinding -import com.moez.QKSMS.databinding.GalleryVideoPageBinding import com.moez.QKSMS.extensions.isImage import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.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 -class GalleryPagerAdapter @Inject constructor( - private val context: Context -) : QkRealmAdapter() { +class GalleryPagerAdapter @Inject constructor(private val context: Context) : QkRealmAdapter() { companion object { private const val VIEW_TYPE_INVALID = 0 @@ -59,61 +58,60 @@ class GalleryPagerAdapter @Inject constructor( private val contentResolver = context.contentResolver private val exoPlayers = Collections.newSetFromMap(WeakHashMap()) - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val holder: QkViewHolder = when (viewType) { - VIEW_TYPE_IMAGE -> QkViewHolder(parent, GalleryImagePageBinding::inflate) - VIEW_TYPE_VIDEO -> QkViewHolder(parent, GalleryVideoPageBinding::inflate) - else -> QkViewHolder(parent, GalleryInvalidPageBinding::inflate) - } + 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 { - return holder.apply { - if (binding is GalleryImagePageBinding) { // 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! - binding.image.attacher.run { + image.attacher.run { javaClass.getDeclaredField("mMinScale").run { isAccessible = true - setFloat(binding.image.attacher, 1f) + setFloat(image.attacher, 1f) } javaClass.getDeclaredField("mMidScale").run { isAccessible = true - setFloat(binding.image.attacher, 1f) + setFloat(image.attacher, 1f) } javaClass.getDeclaredField("mMaxScale").run { isAccessible = true - setFloat(binding.image.attacher, 3f) + setFloat(image.attacher, 3f) } } } - binding.root.setOnClickListener(clicks::onNext) - } + VIEW_TYPE_VIDEO -> inflater.inflate(R.layout.gallery_video_page, parent, false) + + else -> inflater.inflate(R.layout.gallery_invalid_page, parent, false) + + }.apply { setOnClickListener(clicks::onNext) }) } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val part = getItem(position) ?: return - when { - getItemViewType(position) == VIEW_TYPE_IMAGE && holder.binding is GalleryImagePageBinding -> { + when (getItemViewType(position)) { + VIEW_TYPE_IMAGE -> { // 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.binding.image) + .into(holder.image) else -> GlideApp.with(context) .asBitmap() .load(part.getUri()) - .into(holder.binding.image) + .into(holder.image) } } - getItemViewType(position) == VIEW_TYPE_VIDEO && holder.binding is GalleryVideoPageBinding -> { + VIEW_TYPE_VIDEO -> { val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(null) val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory) val exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector) - holder.binding.video.player = exoPlayer + holder.video.player = exoPlayer exoPlayers.add(exoPlayer) val dataSourceFactory = DefaultDataSourceFactory(context, Util.getUserAgent(context, "QKSMS")) 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 088a8179f..068bc4bec 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 @@ -29,10 +29,12 @@ import android.view.Gravity import android.view.Menu import android.view.MenuItem import android.view.View +import android.view.ViewStub import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.app.ActivityCompat import androidx.core.view.GravityCompat import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.ItemTouchHelper @@ -50,8 +52,6 @@ import com.moez.QKSMS.common.util.extensions.scrapViews import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.MainActivityBinding import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.feature.changelog.ChangelogDialog import com.moez.QKSMS.feature.conversations.ConversationItemTouchCallback @@ -65,6 +65,10 @@ 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 { @@ -80,10 +84,10 @@ class MainActivity : QkThemedActivity(), MainView { override val onNewIntentIntent: Subject = PublishSubject.create() override val activityResumedIntent: Subject = PublishSubject.create() - override val queryChangedIntent by lazy { binding.toolbarSearch.textChanges() } - override val composeIntent by lazy { binding.compose.clicks() } + override val queryChangedIntent by lazy { toolbarSearch.textChanges() } + override val composeIntent by lazy { compose.clicks() } override val drawerOpenIntent: Observable by lazy { - binding.drawerLayout + drawerLayout .drawerOpen(Gravity.START) .doOnNext { dismissKeyboard() } } @@ -91,32 +95,31 @@ class MainActivity : QkThemedActivity(), MainView { override val navigationIntent: Observable by lazy { Observable.merge(listOf( backPressedSubject, - 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.settings.clicks().map { NavItem.SETTINGS }, - binding.drawer.plus.clicks().map { NavItem.PLUS }, - binding.drawer.help.clicks().map { NavItem.HELP }, - binding.drawer.invite.clicks().map { NavItem.INVITE })) + 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 }, + settings.clicks().map { NavItem.SETTINGS }, + plus.clicks().map { NavItem.PLUS }, + help.clicks().map { NavItem.HELP }, + invite.clicks().map { NavItem.INVITE })) } override val optionsItemIntent: Subject = PublishSubject.create() - override val plusBannerIntent by lazy { binding.drawer.plusBanner.clicks() } - override val dismissRatingIntent by lazy { binding.drawer.rateDismiss.clicks() } - override val rateIntent by lazy { binding.drawer.rateOkay.clicks() } + override val plusBannerIntent by lazy { plusBanner.clicks() } + override val dismissRatingIntent by lazy { rateDismiss.clicks() } + override val rateIntent by lazy { rateOkay.clicks() } override val conversationsSelectedIntent by lazy { conversationsAdapter.selectionChanges } override val confirmDeleteIntent: Subject> = PublishSubject.create() override val swipeConversationIntent by lazy { itemTouchCallback.swipes } override val changelogMoreIntent by lazy { changelogDialog.moreClicks } override val undoArchiveIntent: Subject = PublishSubject.create() - override val snackbarButtonIntent by lazy { binding.snackbar.button.clicks() } + override val snackbarButtonIntent: Subject = PublishSubject.create() - private val binding by viewBinding(MainActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[MainViewModel::class.java] } - private val toggle by lazy { ActionBarDrawerToggle(this, binding.drawerLayout, binding.toolbar, R.string.main_drawer_open_cd, 0) } + private val toggle by lazy { ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.main_drawer_open_cd, 0) } private val itemTouchHelper by lazy { ItemTouchHelper(itemTouchCallback) } - private val progressAnimator by lazy { ObjectAnimator.ofInt(binding.syncing.progress, "progress", 0, 0) } + private val progressAnimator by lazy { ObjectAnimator.ofInt(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) } @@ -125,21 +128,32 @@ class MainActivity : QkThemedActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.main_activity) viewModel.bindView(this) onNewIntentIntent.onNext(intent) + (snackbar as? ViewStub)?.setOnInflateListener { _, _ -> + snackbarButton.clicks() + .autoDisposable(scope(Lifecycle.Event.ON_DESTROY)) + .subscribe(snackbarButtonIntent) + } + + (syncing as? ViewStub)?.setOnInflateListener { _, _ -> + syncingProgress?.progressTintList = ColorStateList.valueOf(theme.blockingFirst().theme) + syncingProgress?.indeterminateTintList = ColorStateList.valueOf(theme.blockingFirst().theme) + } + toggle.syncState() - binding.toolbar.setNavigationOnClickListener { + toolbar.setNavigationOnClickListener { dismissKeyboard() homeIntent.onNext(Unit) } itemTouchCallback.adapter = conversationsAdapter - conversationsAdapter.autoScrollToStart(binding.recyclerView) + conversationsAdapter.autoScrollToStart(recyclerView) // Don't allow clicks to pass through the drawer layout - binding.drawer.root.clicks().autoDisposable(scope()).subscribe() + drawer.clicks().autoDisposable(scope()).subscribe() // Set the theme color tint to the recyclerView, progressbar, and FAB theme @@ -153,28 +167,28 @@ class MainActivity : QkThemedActivity(), MainView { resolveThemeColor(android.R.attr.textColorSecondary) .let { textSecondary -> ColorStateList(states, intArrayOf(theme.theme, textSecondary)) } .let { tintList -> - binding.drawer.inboxIcon.imageTintList = tintList - binding.drawer.archivedIcon.imageTintList = tintList + inboxIcon.imageTintList = tintList + archivedIcon.imageTintList = tintList } // Miscellaneous views - listOf(binding.drawer.plusBadge1, binding.drawer.plusBadge2).forEach { badge -> + listOf(plusBadge1, plusBadge2).forEach { badge -> badge.setBackgroundTint(theme.theme) badge.setTextColor(theme.textPrimary) } - binding.syncing.progress.progressTintList = ColorStateList.valueOf(theme.theme) - binding.syncing.progress.indeterminateTintList = ColorStateList.valueOf(theme.theme) - binding.drawer.plusIcon.setTint(theme.theme) - binding.drawer.rateIcon.setTint(theme.theme) - binding.compose.setBackgroundTint(theme.theme) + syncingProgress?.progressTintList = ColorStateList.valueOf(theme.theme) + syncingProgress?.indeterminateTintList = ColorStateList.valueOf(theme.theme) + plusIcon.setTint(theme.theme) + rateIcon.setTint(theme.theme) + compose.setBackgroundTint(theme.theme) // Set the FAB compose icon color - binding.compose.setTint(theme.textPrimary) + compose.setTint(theme.textPrimary) } // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - binding.toolbarSearch.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) + toolbarSearch.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) } } @@ -213,46 +227,46 @@ class MainActivity : QkThemedActivity(), MainView { else -> 0 } - binding.toolbarSearch.setVisible(state.page is Inbox && state.page.selected == 0 || state.page is Searching) - binding.toolbarTitle.setVisible(binding.toolbarSearch.visibility != View.VISIBLE) + toolbarSearch.setVisible(state.page is Inbox && state.page.selected == 0 || state.page is Searching) + toolbarTitle.setVisible(toolbarSearch.visibility != View.VISIBLE) - binding.toolbar.menu.findItem(R.id.archive)?.isVisible = state.page is Inbox && selectedConversations != 0 - binding.toolbar.menu.findItem(R.id.unarchive)?.isVisible = state.page is Archived && selectedConversations != 0 - binding.toolbar.menu.findItem(R.id.delete)?.isVisible = selectedConversations != 0 - binding.toolbar.menu.findItem(R.id.add)?.isVisible = addContact && selectedConversations != 0 - binding.toolbar.menu.findItem(R.id.pin)?.isVisible = markPinned && selectedConversations != 0 - binding.toolbar.menu.findItem(R.id.unpin)?.isVisible = !markPinned && selectedConversations != 0 - binding.toolbar.menu.findItem(R.id.read)?.isVisible = markRead && selectedConversations != 0 - binding.toolbar.menu.findItem(R.id.unread)?.isVisible = !markRead && selectedConversations != 0 - binding.toolbar.menu.findItem(R.id.block)?.isVisible = selectedConversations != 0 + toolbar.menu.findItem(R.id.archive)?.isVisible = state.page is Inbox && selectedConversations != 0 + toolbar.menu.findItem(R.id.unarchive)?.isVisible = state.page is Archived && selectedConversations != 0 + toolbar.menu.findItem(R.id.delete)?.isVisible = selectedConversations != 0 + toolbar.menu.findItem(R.id.add)?.isVisible = addContact && selectedConversations != 0 + toolbar.menu.findItem(R.id.pin)?.isVisible = markPinned && selectedConversations != 0 + toolbar.menu.findItem(R.id.unpin)?.isVisible = !markPinned && selectedConversations != 0 + toolbar.menu.findItem(R.id.read)?.isVisible = markRead && selectedConversations != 0 + toolbar.menu.findItem(R.id.unread)?.isVisible = !markRead && selectedConversations != 0 + toolbar.menu.findItem(R.id.block)?.isVisible = selectedConversations != 0 - listOf(binding.drawer.plusBadge1, binding.drawer.plusBadge2).forEach { badge -> + listOf(plusBadge1, plusBadge2).forEach { badge -> badge.isVisible = drawerBadgesExperiment.variant && !state.upgraded } - binding.drawer.plus.isVisible = state.upgraded - binding.drawer.plusBanner.isVisible = !state.upgraded - binding.drawer.rateLayout.setVisible(state.showRating) + plus.isVisible = state.upgraded + plusBanner.isVisible = !state.upgraded + rateLayout.setVisible(state.showRating) - 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 = binding.empty.takeIf { state.page is Searching } + compose.setVisible(state.page is Inbox || state.page is Archived) + conversationsAdapter.emptyView = empty.takeIf { state.page is Inbox || state.page is Archived } + searchAdapter.emptyView = 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 (binding.recyclerView.adapter !== conversationsAdapter) binding.recyclerView.adapter = conversationsAdapter + if (recyclerView.adapter !== conversationsAdapter) recyclerView.adapter = conversationsAdapter conversationsAdapter.updateData(state.page.data) - itemTouchHelper.attachToRecyclerView(binding.recyclerView) - binding.empty.setText(R.string.inbox_empty_text) + itemTouchHelper.attachToRecyclerView(recyclerView) + empty.setText(R.string.inbox_empty_text) } is Searching -> { showBackButton(true) - if (binding.recyclerView.adapter !== searchAdapter) binding.recyclerView.adapter = searchAdapter + if (recyclerView.adapter !== searchAdapter) recyclerView.adapter = searchAdapter searchAdapter.data = state.page.data ?: listOf() itemTouchHelper.attachToRecyclerView(null) - binding.empty.setText(R.string.inbox_search_empty_text) + empty.setText(R.string.inbox_search_empty_text) } is Archived -> { @@ -261,20 +275,20 @@ class MainActivity : QkThemedActivity(), MainView { true -> getString(R.string.main_title_selected, state.page.selected) false -> getString(R.string.title_archived) } - if (binding.recyclerView.adapter !== conversationsAdapter) binding.recyclerView.adapter = conversationsAdapter + if (recyclerView.adapter !== conversationsAdapter) recyclerView.adapter = conversationsAdapter conversationsAdapter.updateData(state.page.data) itemTouchHelper.attachToRecyclerView(null) - binding.empty.setText(R.string.archived_empty_text) + empty.setText(R.string.archived_empty_text) } } - binding.drawer.inbox.isActivated = state.page is Inbox - binding.drawer.archived.isActivated = state.page is Archived + inbox.isActivated = state.page is Inbox + archived.isActivated = state.page is Archived - 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) + if (drawerLayout.isDrawerOpen(GravityCompat.START) && !state.drawerOpen) { + drawerLayout.closeDrawer(GravityCompat.START) + } else if (!drawerLayout.isDrawerVisible(GravityCompat.START) && state.drawerOpen) { + drawerLayout.openDrawer(GravityCompat.START) } when (state.syncing) { @@ -285,30 +299,30 @@ class MainActivity : QkThemedActivity(), MainView { is SyncRepository.SyncProgress.Running -> { syncing.isVisible = true - binding.syncing.progress.max = state.syncing.max - progressAnimator.apply { setIntValues(binding.syncing.progress.progress, state.syncing.progress) }.start() - binding.syncing.progress.isIndeterminate = state.syncing.indeterminate + syncingProgress.max = state.syncing.max + progressAnimator.apply { setIntValues(syncingProgress.progress, state.syncing.progress) }.start() + syncingProgress.isIndeterminate = state.syncing.indeterminate snackbar.isVisible = false } } when { !state.defaultSms -> { - binding.snackbar.title.setText(R.string.main_default_sms_title) - binding.snackbar.message.setText(R.string.main_default_sms_message) - binding.snackbar.button.setText(R.string.main_default_sms_change) + snackbarTitle?.setText(R.string.main_default_sms_title) + snackbarMessage?.setText(R.string.main_default_sms_message) + snackbarButton?.setText(R.string.main_default_sms_change) } !state.smsPermission -> { - binding.snackbar.title.setText(R.string.main_permission_required) - binding.snackbar.message.setText(R.string.main_permission_sms) - binding.snackbar.button.setText(R.string.main_permission_allow) + snackbarTitle?.setText(R.string.main_permission_required) + snackbarMessage?.setText(R.string.main_permission_sms) + snackbarButton?.setText(R.string.main_permission_allow) } !state.contactPermission -> { - binding.snackbar.title.setText(R.string.main_permission_required) - binding.snackbar.message.setText(R.string.main_permission_contacts) - binding.snackbar.button.setText(R.string.main_permission_allow) + snackbarTitle?.setText(R.string.main_permission_required) + snackbarMessage?.setText(R.string.main_permission_contacts) + snackbarButton?.setText(R.string.main_permission_allow) } } } @@ -329,7 +343,7 @@ class MainActivity : QkThemedActivity(), MainView { } override fun showBackButton(show: Boolean) { - toggle.onDrawerSlide(binding.drawer.root, if (show) 1f else 0f) + toggle.onDrawerSlide(drawer, if (show) 1f else 0f) toggle.drawerArrowDrawable.color = when (show) { true -> resolveThemeColor(android.R.attr.textColorSecondary) false -> resolveThemeColor(android.R.attr.textColorPrimary) @@ -349,7 +363,7 @@ class MainActivity : QkThemedActivity(), MainView { override fun clearSearch() { dismissKeyboard() - binding.toolbarSearch.text = null + toolbarSearch.text = null } override fun clearSelection() { @@ -357,7 +371,7 @@ class MainActivity : QkThemedActivity(), MainView { } override fun themeChanged() { - binding.recyclerView.scrapViews() + recyclerView.scrapViews() } override fun showBlockingDialog(conversations: List, block: Boolean) { @@ -379,7 +393,7 @@ class MainActivity : QkThemedActivity(), MainView { } override fun showArchivedSnackbar() { - Snackbar.make(binding.drawerLayout, R.string.toast_archived, Snackbar.LENGTH_LONG).apply { + Snackbar.make(drawerLayout, R.string.toast_archived, Snackbar.LENGTH_LONG).apply { setAction(R.string.button_undo) { undoArchiveIntent.onNext(Unit) } setActionTextColor(colors.theme().theme) show() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt index e59dde89e..9a964a208 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt @@ -31,9 +31,10 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.databinding.SearchListItemBinding import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.model.SearchResult +import kotlinx.android.synthetic.main.search_list_item.* +import kotlinx.android.synthetic.main.search_list_item.view.* import javax.inject.Inject class SearchAdapter @Inject constructor( @@ -41,24 +42,26 @@ class SearchAdapter @Inject constructor( private val context: Context, private val dateFormatter: DateFormatter, private val navigator: Navigator -) : QkAdapter() { +) : QkAdapter() { private val highlightColor: Int by lazy { colors.theme().highlight } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, SearchListItemBinding::inflate).apply { - binding.root.setOnClickListener { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val view = layoutInflater.inflate(R.layout.search_list_item, parent, false) + return QkViewHolder(view).apply { + view.setOnClickListener { val result = getItem(adapterPosition) navigator.showConversation(result.conversation.id, result.query.takeIf { result.messages > 0 }) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val previous = data.getOrNull(position - 1) val result = getItem(position) - holder.binding.resultsHeader.setVisible(result.messages > 0 && previous?.messages == 0) + holder.resultsHeader.setVisible(result.messages > 0 && previous?.messages == 0) val query = result.query val title = SpannableString(result.conversation.getTitle()) @@ -68,23 +71,23 @@ class SearchAdapter @Inject constructor( title.setSpan(BackgroundColorSpan(highlightColor), index, index + query.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) index = title.indexOf(query, index + query.length, true) } - holder.binding.title.text = title + holder.title.text = title - holder.binding.avatars.recipients = result.conversation.recipients + holder.avatars.recipients = result.conversation.recipients when (result.messages == 0) { true -> { - holder.binding.date.setVisible(true) - holder.binding.date.text = dateFormatter.getConversationTimestamp(result.conversation.date) - holder.binding.snippet.text = when (result.conversation.me) { + holder.date.setVisible(true) + holder.date.text = dateFormatter.getConversationTimestamp(result.conversation.date) + holder.snippet.text = when (result.conversation.me) { true -> context.getString(R.string.main_sender_you, result.conversation.snippet) false -> result.conversation.snippet } } false -> { - holder.binding.date.setVisible(false) - holder.binding.snippet.text = context.getString(R.string.main_message_results, result.messages) + holder.date.setVisible(false) + holder.snippet.text = context.getString(R.string.main_message_results, result.messages) } } } 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 570ca299d..210b5d3fe 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 @@ -33,16 +33,15 @@ import com.moez.QKSMS.common.QkDialog import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.util.extensions.viewBinding import com.moez.QKSMS.common.widget.PreferenceView -import com.moez.QKSMS.common.widget.QkSwitch -import com.moez.QKSMS.databinding.NotificationPrefsActivityBinding import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable import dagger.android.AndroidInjection import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.notification_prefs_activity.* +import kotlinx.android.synthetic.main.settings_switch_widget.view.* import javax.inject.Inject class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { @@ -56,7 +55,6 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { override val ringtoneSelectedIntent: Subject = PublishSubject.create() override val actionsSelectedIntent by lazy { actionsDialog.adapter.menuItemClicks } - private val binding by viewBinding(NotificationPrefsActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[NotificationPrefsViewModel::class.java] } @@ -64,27 +62,27 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.notification_prefs_activity) setTitle(R.string.title_notification_prefs) showBackButton(true) viewModel.bindView(this) - binding.preferences.postDelayed({ binding.preferences.animateLayoutChanges = true }, 100) + preferences.postDelayed({ preferences?.animateLayoutChanges = true }, 100) val hasOreo = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - binding.notificationsO.setVisible(hasOreo) - binding.notifications.setVisible(!hasOreo) - binding.vibration.setVisible(!hasOreo) - binding.ringtone.setVisible(!hasOreo) + notificationsO.setVisible(hasOreo) + notifications.setVisible(!hasOreo) + vibration.setVisible(!hasOreo) + 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 binding.preferences.childCount) - .map { index -> binding.preferences.getChildAt(index) } + (0 until preferences.childCount) + .map { index -> preferences.getChildAt(index) } .mapNotNull { view -> view as? PreferenceView } .map { preference -> preference.clicks().map { preference } } .let { Observable.merge(it) } @@ -97,29 +95,29 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { title = state.conversationTitle } - binding.notifications.widget().isChecked = state.notificationsEnabled - binding.previews.summary = state.previewSummary + notifications.checkbox.isChecked = state.notificationsEnabled + previews.summary = state.previewSummary previewModeDialog.adapter.selectedItem = state.previewId - binding.wake.widget().isChecked = state.wakeEnabled - binding.vibration.widget().isChecked = state.vibrationEnabled - binding.ringtone.summary = state.ringtoneName - - 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 - - binding.qkreplyDivider.isVisible = state.threadId == 0L - binding.qkreplyTitle.isVisible = state.threadId == 0L - binding.qkreply.widget().isChecked = state.qkReplyEnabled - binding.qkreply.isVisible = state.threadId == 0L - binding.qkreplyTapDismiss.isVisible = state.threadId == 0L - binding.qkreplyTapDismiss.isEnabled = state.qkReplyEnabled - binding.qkreplyTapDismiss.widget().isChecked = state.qkReplyTapDismiss + wake.checkbox.isChecked = state.wakeEnabled + vibration.checkbox.isChecked = state.vibrationEnabled + 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 + + 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 } 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 b8a2ec9a6..eb589d764 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 @@ -24,6 +24,7 @@ import androidx.core.view.children import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import com.jakewharton.rxbinding2.view.clicks +import com.jakewharton.rxbinding2.view.enabled import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity @@ -32,12 +33,14 @@ import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.util.extensions.viewBinding import com.moez.QKSMS.common.widget.PreferenceView -import com.moez.QKSMS.databinding.QksmsPlusActivityBinding import com.moez.QKSMS.feature.plus.experiment.UpgradeButtonExperiment import com.moez.QKSMS.manager.BillingManager import dagger.android.AndroidInjection +import io.reactivex.Observable +import kotlinx.android.synthetic.main.collapsing_toolbar.* +import kotlinx.android.synthetic.main.preference_view.view.* +import kotlinx.android.synthetic.main.qksms_plus_activity.* import javax.inject.Inject class PlusActivity : QkThemedActivity(), PlusView { @@ -46,68 +49,67 @@ class PlusActivity : QkThemedActivity(), PlusView { @Inject lateinit var upgradeButtonExperiment: UpgradeButtonExperiment @Inject lateinit var viewModelFactory: ViewModelProvider.Factory - private val binding by viewBinding(QksmsPlusActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[PlusViewModel::class.java] } - override val upgradeIntent by lazy { binding.upgrade.clicks() } - override val upgradeDonateIntent by lazy { binding.upgradeDonate.clicks() } - override val donateIntent by lazy { binding.donate.clicks() } - override val themeClicks by lazy { binding.themes.clicks() } - override val scheduleClicks by lazy { binding.schedule.clicks() } - override val backupClicks by lazy { binding.backup.clicks() } - override val delayedClicks by lazy { binding.delayed.clicks() } - override val nightClicks by lazy { binding.night.clicks() } + override val upgradeIntent by lazy { upgrade.clicks() } + override val upgradeDonateIntent by lazy { upgradeDonate.clicks() } + override val donateIntent by lazy { donate.clicks() } + override val themeClicks by lazy { themes.clicks() } + override val scheduleClicks by lazy { schedule.clicks() } + override val backupClicks by lazy { backup.clicks() } + override val delayedClicks by lazy { delayed.clicks() } + override val nightClicks by lazy { night.clicks() } override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.qksms_plus_activity) setTitle(R.string.title_qksms_plus) showBackButton(true) viewModel.bindView(this) - binding.free.setVisible(false) + free.setVisible(false) if (!prefs.systemFont.get()) { fontProvider.getLato { lato -> val typeface = Typeface.create(lato, Typeface.BOLD) - binding.appBarLayout.collapsingToolbar.setCollapsedTitleTypeface(typeface) - binding.appBarLayout.collapsingToolbar.setExpandedTitleTypeface(typeface) + collapsingToolbar.setCollapsedTitleTypeface(typeface) + collapsingToolbar.setExpandedTitleTypeface(typeface) } } // Make the list titles bold - binding.linearLayout.children - .mapNotNull { view -> view as? PreferenceView } - .map { preferenceView -> preferenceView.binding.titleView } + linearLayout.children + .mapNotNull { it as? PreferenceView } + .map { it.titleView } .forEach { it.setTypeface(it.typeface, Typeface.BOLD) } val textPrimary = resolveThemeColor(android.R.attr.textColorPrimary) - binding.appBarLayout.collapsingToolbar.setCollapsedTitleTextColor(textPrimary) - binding.appBarLayout.collapsingToolbar.setExpandedTitleColor(textPrimary) + collapsingToolbar.setCollapsedTitleTextColor(textPrimary) + collapsingToolbar.setExpandedTitleColor(textPrimary) val theme = colors.theme().theme - binding.donate.setBackgroundTint(theme) - binding.upgrade.setBackgroundTint(theme) - binding.thanksIcon.setTint(theme) + donate.setBackgroundTint(theme) + upgrade.setBackgroundTint(theme) + thanksIcon.setTint(theme) } override fun render(state: PlusState) { - binding.description.text = getString(R.string.qksms_plus_description_summary, state.upgradePrice) - binding.upgrade.text = getString(upgradeButtonExperiment.variant, state.upgradePrice, state.currency) - binding.upgradeDonate.text = getString(R.string.qksms_plus_upgrade_donate, state.upgradeDonatePrice, state.currency) + description.text = getString(R.string.qksms_plus_description_summary, state.upgradePrice) + upgrade.text = getString(upgradeButtonExperiment.variant, state.upgradePrice, state.currency) + upgradeDonate.text = getString(R.string.qksms_plus_upgrade_donate, state.upgradeDonatePrice, state.currency) val fdroid = BuildConfig.FLAVOR == "noAnalytics" - binding.free.setVisible(fdroid) - binding.toUpgrade.setVisible(!fdroid && !state.upgraded) - binding.upgraded.setVisible(!fdroid && state.upgraded) + free.setVisible(fdroid) + toUpgrade.setVisible(!fdroid && !state.upgraded) + upgraded.setVisible(!fdroid && state.upgraded) - binding.themes.isEnabled = state.upgraded - binding.schedule.isEnabled = state.upgraded - binding.backup.isEnabled = state.upgraded - binding.delayed.isEnabled = state.upgraded - binding.night.isEnabled = state.upgraded + themes.isEnabled = state.upgraded + schedule.isEnabled = state.upgraded + backup.isEnabled = state.upgraded + delayed.isEnabled = state.upgraded + night.isEnabled = state.upgraded } override fun initiatePurchaseFlow(billingManager: BillingManager, sku: String) { 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 c7237397c..392196d41 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 @@ -36,12 +36,11 @@ import com.moez.QKSMS.common.util.extensions.autoScrollToStart import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.QkreplyActivityBinding import com.moez.QKSMS.feature.compose.MessagesAdapter import dagger.android.AndroidInjection import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.qkreply_activity.* import javax.inject.Inject class QkReplyActivity : QkThemedActivity(), QkReplyView { @@ -50,11 +49,10 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override val menuItemIntent: Subject = PublishSubject.create() - override val textChangedIntent by lazy { binding.message.textChanges() } - override val changeSimIntent by lazy { binding.sim.clicks() } - override val sendIntent by lazy { binding.send.clicks() } + override val textChangedIntent by lazy { message.textChanges() } + override val changeSimIntent by lazy { sim.clicks() } + override val sendIntent by lazy { send.clicks() } - private val binding by viewBinding(QkreplyActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[QkReplyViewModel::class.java] } override fun onCreate(savedInstanceState: Bundle?) { @@ -63,27 +61,27 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { super.onCreate(savedInstanceState) setFinishOnTouchOutside(prefs.qkreplyTapDismiss.get()) - setContentView(binding.root) + setContentView(R.layout.qkreply_activity) window.setBackgroundDrawable(null) window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) viewModel.bindView(this) - binding.toolbar.clipToOutline = true + toolbar.clipToOutline = true - 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) + messages.adapter = adapter + messages.adapter?.autoScrollToStart(messages) + messages.adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onChanged() = messages.scrollToPosition(adapter.itemCount - 1) }) // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { - binding.toolbar.setBackgroundTint(resolveThemeColor(R.attr.colorPrimary)) - binding.background.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) - binding.messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) - binding.composeBackgroundGradient.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) - binding.composeBackgroundSolid.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) + toolbar.setBackgroundTint(resolveThemeColor(R.attr.colorPrimary)) + background.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) + messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) + composeBackgroundGradient.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) + composeBackgroundSolid.setBackgroundTint(resolveThemeColor(android.R.attr.windowBackground)) } } @@ -96,24 +94,24 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { title = state.title - binding.toolbar.menu.findItem(R.id.expand)?.isVisible = !state.expanded - binding.toolbar.menu.findItem(R.id.collapse)?.isVisible = state.expanded + toolbar.menu.findItem(R.id.expand)?.isVisible = !state.expanded + toolbar.menu.findItem(R.id.collapse)?.isVisible = state.expanded adapter.data = state.data - binding.counter.text = state.remaining - binding.counter.setVisible(binding.counter.text.isNotBlank()) + counter.text = state.remaining + counter.setVisible(counter.text.isNotBlank()) - 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)}" + sim.setVisible(state.subscription != null) + sim.contentDescription = getString(R.string.compose_sim_cd, state.subscription?.displayName) + simIndex.text = "${state.subscription?.simSlotIndex?.plus(1)}" - binding.send.isEnabled = state.canSend - binding.send.imageAlpha = if (state.canSend) 255 else 128 + send.isEnabled = state.canSend + send.imageAlpha = if (state.canSend) 255 else 128 } override fun setDraft(draft: String) { - binding.message.setText(draft) + 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 669d8f9c5..9bcc3af5f 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 @@ -30,9 +30,9 @@ import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.FontProvider import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.ScheduledActivityBinding import dagger.android.AndroidInjection +import kotlinx.android.synthetic.main.collapsing_toolbar.* +import kotlinx.android.synthetic.main.scheduled_activity.* import javax.inject.Inject @@ -45,16 +45,15 @@ class ScheduledActivity : QkThemedActivity(), ScheduledView { override val messageClickIntent by lazy { messageAdapter.clicks } override val messageMenuIntent by lazy { dialog.adapter.menuItemClicks } - override val composeIntent by lazy { binding.compose.clicks() } - override val upgradeIntent by lazy { binding.upgrade.clicks() } + override val composeIntent by lazy { compose.clicks() } + override val upgradeIntent by lazy { upgrade.clicks() } - private val binding by viewBinding(ScheduledActivityBinding::inflate) private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ScheduledViewModel::class.java] } override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.scheduled_activity) setTitle(R.string.scheduled_title) showBackButton(true) viewModel.bindView(this) @@ -62,33 +61,33 @@ class ScheduledActivity : QkThemedActivity(), ScheduledView { if (!prefs.systemFont.get()) { fontProvider.getLato { lato -> val typeface = Typeface.create(lato, Typeface.BOLD) - binding.appBarLayout.collapsingToolbar.setCollapsedTitleTypeface(typeface) - binding.appBarLayout.collapsingToolbar.setExpandedTitleTypeface(typeface) + collapsingToolbar.setCollapsedTitleTypeface(typeface) + collapsingToolbar.setExpandedTitleTypeface(typeface) } } dialog.title = getString(R.string.scheduled_options_title) dialog.adapter.setData(R.array.scheduled_options) - messageAdapter.emptyView = binding.empty - binding.messages.adapter = messageAdapter + messageAdapter.emptyView = empty + messages.adapter = messageAdapter colors.theme().let { theme -> - 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) + 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) } } override fun render(state: ScheduledState) { messageAdapter.updateData(state.scheduledMessages) - binding.compose.isVisible = state.upgraded - binding.upgrade.isVisible = !state.upgraded + compose.isVisible = state.upgraded + upgrade.isVisible = !state.upgraded } override fun showMessageOptions() { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt index 2a66db1bb..23c09227c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt @@ -20,13 +20,14 @@ package com.moez.QKSMS.feature.scheduled import android.content.Context import android.net.Uri +import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.DateFormatter -import com.moez.QKSMS.databinding.ScheduledMessageListItemBinding import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.ScheduledMessage @@ -34,6 +35,8 @@ import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.util.PhoneNumberUtils import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.scheduled_message_list_item.* +import kotlinx.android.synthetic.main.scheduled_message_list_item.view.* import javax.inject.Inject class ScheduledMessageAdapter @Inject constructor( @@ -41,7 +44,7 @@ class ScheduledMessageAdapter @Inject constructor( private val contactRepo: ContactRepository, private val dateFormatter: DateFormatter, private val phoneNumberUtils: PhoneNumberUtils -) : QkRealmAdapter() { +) : QkRealmAdapter() { private val contacts by lazy { contactRepo.getContacts() } private val contactCache = ContactCache() @@ -49,34 +52,36 @@ class ScheduledMessageAdapter @Inject constructor( val clicks: Subject = PublishSubject.create() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, ScheduledMessageListItemBinding::inflate).apply { - binding.attachments.adapter = ScheduledMessageAttachmentAdapter(context) - binding.attachments.setRecycledViewPool(imagesViewPool) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.scheduled_message_list_item, parent, false) - binding.root.setOnClickListener { + view.attachments.adapter = ScheduledMessageAttachmentAdapter(context) + view.attachments.setRecycledViewPool(imagesViewPool) + + return QkViewHolder(view).apply { + view.setOnClickListener { val message = getItem(adapterPosition) ?: return@setOnClickListener clicks.onNext(message.id) } } } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val message = getItem(position) ?: return // GroupAvatarView only accepts recipients, so map the phone numbers to recipients - holder.binding.avatars.recipients = message.recipients.map { address -> Recipient(address = address) } + holder.avatars.recipients = message.recipients.map { address -> Recipient(address = address) } - holder.binding.recipients.text = message.recipients.joinToString(",") { address -> + holder.recipients.text = message.recipients.joinToString(",") { address -> contactCache[address]?.name?.takeIf { it.isNotBlank() } ?: address } - holder.binding.date.text = dateFormatter.getScheduledTimestamp(message.date) - holder.binding.body.text = message.body + holder.date.text = dateFormatter.getScheduledTimestamp(message.date) + holder.body.text = message.body - val adapter = holder.binding.attachments.adapter as ScheduledMessageAttachmentAdapter + val adapter = holder.attachments.adapter as ScheduledMessageAttachmentAdapter adapter.data = message.attachments.map(Uri::parse) - holder.binding.attachments.isVisible = message.attachments.isNotEmpty() + holder.attachments.isVisible = message.attachments.isNotEmpty() } /** diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt index 80b7b9a4b..558c8af7a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAttachmentAdapter.kt @@ -20,27 +20,31 @@ package com.moez.QKSMS.feature.scheduled import android.content.Context import android.net.Uri +import android.view.LayoutInflater import android.view.ViewGroup +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.databinding.ScheduledMessageImageListItemBinding import com.moez.QKSMS.util.GlideApp +import kotlinx.android.synthetic.main.attachment_image_list_item.view.* +import kotlinx.android.synthetic.main.scheduled_message_image_list_item.* import javax.inject.Inject class ScheduledMessageAttachmentAdapter @Inject constructor( private val context: Context -) : QkAdapter() { +) : QkAdapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, ScheduledMessageImageListItemBinding::inflate).apply { - binding.thumbnail.clipToOutline = true - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.scheduled_message_image_list_item, parent, false) + view.thumbnail.clipToOutline = true + + return QkViewHolder(view) } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val attachment = getItem(position) - GlideApp.with(context).load(attachment).into(holder.binding.thumbnail) + GlideApp.with(context).load(attachment).into(holder.thumbnail) } } 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 95276b1b5..0d2bd59ee 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 @@ -22,22 +22,21 @@ import android.os.Bundle import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity -import com.moez.QKSMS.common.util.extensions.viewBinding -import com.moez.QKSMS.databinding.ContainerActivityBinding import dagger.android.AndroidInjection +import kotlinx.android.synthetic.main.container_activity.* class SettingsActivity : QkThemedActivity() { - private val binding by viewBinding(ContainerActivityBinding::inflate) private lateinit var router: Router override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) - setContentView(binding.root) + setContentView(R.layout.container_activity) - router = Conductor.attachRouter(this, binding.container, savedInstanceState) + router = Conductor.attachRouter(this, 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 5159f07e1..f09adc3e9 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 @@ -43,7 +43,6 @@ import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.PreferenceView import com.moez.QKSMS.common.widget.QkSwitch import com.moez.QKSMS.common.widget.TextInputDialog -import com.moez.QKSMS.databinding.SettingsControllerBinding import com.moez.QKSMS.feature.settings.about.AboutController import com.moez.QKSMS.feature.settings.autodelete.AutoDeleteDialog import com.moez.QKSMS.feature.settings.swipe.SwipeActionsController @@ -59,12 +58,14 @@ import io.reactivex.subjects.Subject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext +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 javax.inject.Inject import kotlin.coroutines.resume -class SettingsController : QkController( - SettingsControllerBinding::inflate -), SettingsView { +class SettingsController : QkController(), SettingsView { @Inject lateinit var context: Context @Inject lateinit var colors: Colors @@ -88,11 +89,12 @@ class SettingsController : QkController = PublishSubject.create() private val autoDeleteSubject: Subject = PublishSubject.create() - private val progressAnimator by lazy { ObjectAnimator.ofInt(binding.syncingProgress, "progress", 0, 0) } + private val progressAnimator by lazy { ObjectAnimator.ofInt(syncingProgress, "progress", 0, 0) } init { appComponent.inject(this) retainViewMode = RetainViewMode.RETAIN_DETACH + layoutRes = R.layout.settings_controller colors.themeObservable() .autoDisposable(scope()) @@ -100,7 +102,7 @@ class SettingsController : QkController= 29) { true -> nightModeDialog.adapter.setData(R.array.night_modes) @@ -112,7 +114,7 @@ class SettingsController : QkController = (0 until binding.preferences.childCount) - .map { index -> binding.preferences.getChildAt(index) } + override fun preferenceClicks(): Observable = (0 until preferences.childCount) + .map { index -> preferences.getChildAt(index) } .mapNotNull { view -> view as? PreferenceView } .map { preference -> preference.clicks().map { preference } } .let { preferences -> Observable.merge(preferences) } - override fun aboutLongClicks(): Observable<*> = binding.about.longClicks() + override fun aboutLongClicks(): Observable<*> = about.longClicks() override fun viewQksmsPlusClicks(): Observable<*> = viewQksmsPlusSubject @@ -149,64 +151,63 @@ class SettingsController : QkController = mmsSizeDialog.adapter.menuItemClicks override fun render(state: SettingsState) { - binding.theme.widget().setBackgroundTint(state.theme) - binding.night.summary = state.nightModeSummary + themePreview.setBackgroundTint(state.theme) + night.summary = state.nightModeSummary nightModeDialog.adapter.selectedItem = state.nightModeId - 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 + 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.black.setVisible(state.nightModeId != Preferences.NIGHT_MODE_OFF) - binding.black.widget().isChecked = state.black + black.setVisible(state.nightModeId != Preferences.NIGHT_MODE_OFF) + black.checkbox.isChecked = state.black - binding.autoEmoji.widget().isChecked = state.autoEmojiEnabled + autoEmoji.checkbox.isChecked = state.autoEmojiEnabled - binding.delayed.summary = state.sendDelaySummary + delayed.summary = state.sendDelaySummary sendDelayDialog.adapter.selectedItem = state.sendDelayId - binding.delivery.widget().isChecked = state.deliveryEnabled + delivery.checkbox.isChecked = state.deliveryEnabled - binding.signature.summary = state.signature.takeIf { it.isNotBlank() } + signature.summary = state.signature.takeIf { it.isNotBlank() } ?: context.getString(R.string.settings_signature_summary) - binding.textSize.summary = state.textSizeSummary + textSize.summary = state.textSizeSummary textSizeDialog.adapter.selectedItem = state.textSizeId - binding.autoColor.widget().isChecked = state.autoColor + autoColor.checkbox.isChecked = state.autoColor - binding.systemFont.widget().isChecked = state.systemFontEnabled + systemFont.checkbox.isChecked = state.systemFontEnabled - binding.unicode.widget().isChecked = state.stripUnicodeEnabled - binding.mobileOnly.widget().isChecked = state.mobileOnly + unicode.checkbox.isChecked = state.stripUnicodeEnabled + mobileOnly.checkbox.isChecked = state.mobileOnly - binding.autoDelete.summary = when (state.autoDelete) { + 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) } - binding.longAsMms.widget().isChecked = state.longAsMms + longAsMms.checkbox.isChecked = state.longAsMms - binding.mmsSize.summary = state.maxMmsSizeSummary + mmsSize.summary = state.maxMmsSizeSummary mmsSizeDialog.adapter.selectedItem = state.maxMmsSizeId when (state.syncProgress) { - is SyncRepository.SyncProgress.Idle -> binding.syncingProgress.isVisible = false + is SyncRepository.SyncProgress.Idle -> syncingProgress.isVisible = false is SyncRepository.SyncProgress.Running -> { - 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 + syncingProgress.isVisible = true + syncingProgress.max = state.syncProgress.max + progressAnimator.apply { setIntValues(syncingProgress.progress, state.syncProgress.progress) }.start() + syncingProgress.isIndeterminate = state.syncProgress.indeterminate } } } override fun showQksmsPlusSnackbar() { view?.run { - Snackbar.make(binding.root, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { + Snackbar.make(contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusSubject.onNext(Unit) } setActionTextColor(colors.theme().theme) show() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt index ec81ea094..7385a29c9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt @@ -24,23 +24,22 @@ import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.widget.PreferenceView -import com.moez.QKSMS.databinding.AboutControllerBinding import com.moez.QKSMS.injection.appComponent import io.reactivex.Observable +import kotlinx.android.synthetic.main.about_controller.* import javax.inject.Inject -class AboutController : QkController( - AboutControllerBinding::inflate -), AboutView { +class AboutController : QkController(), AboutView { @Inject override lateinit var presenter: AboutPresenter init { appComponent.inject(this) + layoutRes = R.layout.about_controller } override fun onViewCreated() { - binding.version.summary = BuildConfig.VERSION_NAME + version.summary = BuildConfig.VERSION_NAME } override fun onAttach(view: View) { @@ -50,8 +49,8 @@ class AboutController : QkController = (0 until binding.preferences.childCount) - .map { index -> binding.preferences.getChildAt(index) } + override fun preferenceClicks(): Observable = (0 until preferences.childCount) + .map { index -> 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/autodelete/AutoDeleteDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/autodelete/AutoDeleteDialog.kt index 260b0f4fe..8f55c1413 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/autodelete/AutoDeleteDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/autodelete/AutoDeleteDialog.kt @@ -20,29 +20,30 @@ package com.moez.QKSMS.feature.settings.autodelete import android.app.Activity import android.content.DialogInterface +import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import com.moez.QKSMS.R -import com.moez.QKSMS.databinding.SettingsAutoDeleteDialogBinding +import kotlinx.android.synthetic.main.settings_auto_delete_dialog.view.* class AutoDeleteDialog(context: Activity, listener: (Int) -> Unit) : AlertDialog(context) { - private val binding = SettingsAutoDeleteDialogBinding.inflate(context.layoutInflater) + private val layout = LayoutInflater.from(context).inflate(R.layout.settings_auto_delete_dialog, null) init { - setView(binding.root) + setView(layout) setTitle(R.string.settings_auto_delete) setMessage(context.getString(R.string.settings_auto_delete_dialog_message)) setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.button_cancel)) { _, _ -> } setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.settings_auto_delete_never)) { _, _ -> listener(0) } setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.button_save)) { _, _ -> - listener(binding.field.text.toString().toIntOrNull() ?: 0) + listener(layout.field.text.toString().toIntOrNull() ?: 0) } } fun setExpiry(days: Int): AutoDeleteDialog { when (days) { - 0 -> binding.field.text = null - else -> binding.field.setText(days.toString()) + 0 -> layout.field.text = null + else -> layout.field.setText(days.toString()) } return this } 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 2d53d7c30..8121ad443 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 @@ -28,20 +28,16 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.databinding.SwipeActionsControllerBinding import com.moez.QKSMS.injection.appComponent -import com.uber.autodispose.android.autoDisposable -import com.uber.autodispose.android.lifecycle.autoDisposable import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable 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( - SwipeActionsControllerBinding::inflate), SwipeActionsView { +class SwipeActionsController : QkController(), SwipeActionsView { @Inject override lateinit var presenter: SwipeActionsPresenter @Inject lateinit var actionsDialog: QkDialog @@ -54,24 +50,25 @@ class SwipeActionsController : init { appComponent.inject(this) + layoutRes = R.layout.swipe_actions_controller actionsDialog.adapter.setData(R.array.settings_swipe_actions) } override fun onViewCreated() { colors.theme().let { theme -> - binding.rightIcon.setBackgroundTint(theme.theme) - binding.rightIcon.setTint(theme.textPrimary) - binding.leftIcon.setBackgroundTint(theme.theme) - binding.leftIcon.setTint(theme.textPrimary) + rightIcon.setBackgroundTint(theme.theme) + rightIcon.setTint(theme.textPrimary) + leftIcon.setBackgroundTint(theme.theme) + leftIcon.setTint(theme.textPrimary) } - binding.right.postDelayed({ binding.right.animateLayoutChanges = true }, 100) - binding.left.postDelayed({ binding.left.animateLayoutChanges = true }, 100) + right.postDelayed({ right?.animateLayoutChanges = true }, 100) + left.postDelayed({ left?.animateLayoutChanges = true }, 100) Observable.merge( - binding.right.clicks().map { SwipeActionsView.Action.RIGHT }, - binding.left.clicks().map { SwipeActionsView.Action.LEFT }) + right.clicks().map { SwipeActionsView.Action.RIGHT }, + left.clicks().map { SwipeActionsView.Action.LEFT }) .autoDisposable(scope()) .subscribe(actionClicks) } @@ -93,13 +90,13 @@ class SwipeActionsController : } override fun render(state: SwipeActionsState) { - binding.rightIcon.isVisible = state.rightIcon != 0 - binding.rightIcon.setImageResource(state.rightIcon) - binding.rightLabel.text = state.rightLabel + rightIcon.isVisible = state.rightIcon != 0 + rightIcon.setImageResource(state.rightIcon) + rightLabel.text = state.rightLabel - binding.leftIcon.isVisible = state.leftIcon != 0 - binding.leftIcon.setImageResource(state.leftIcon) - binding.leftLabel.text = state.leftLabel + leftIcon.isVisible = state.leftIcon != 0 + leftIcon.setImageResource(state.leftIcon) + leftLabel.text = state.leftLabel } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt index fdc0af525..155224433 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/HSVPickerView.kt @@ -23,14 +23,15 @@ import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.util.AttributeSet import android.view.MotionEvent +import android.view.View import androidx.constraintlayout.widget.ConstraintLayout +import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint -import com.moez.QKSMS.common.util.extensions.viewBinding import com.moez.QKSMS.common.util.extensions.within -import com.moez.QKSMS.databinding.HsvPickerViewBinding import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.hsv_picker_view.view.* class HSVPickerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -38,8 +39,6 @@ class HSVPickerView @JvmOverloads constructor( val selectedColor: Subject = BehaviorSubject.create() - private val binding = viewBinding(HsvPickerViewBinding::inflate) - private val hues = arrayOf(0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000) .map { it.toInt() }.toIntArray() @@ -53,10 +52,12 @@ class HSVPickerView @JvmOverloads constructor( } init { + View.inflate(context, R.layout.hsv_picker_view, this) + var swatchX = 0f var swatchY = 0f - binding.saturation.setOnTouchListener { _, event -> + saturation.setOnTouchListener { _, event -> setupBounds() when (event.action) { MotionEvent.ACTION_DOWN -> { @@ -67,8 +68,8 @@ class HSVPickerView @JvmOverloads constructor( MotionEvent.ACTION_MOVE -> { // Calculate the new x/y position - binding.swatch.x = (event.rawX + swatchX + min).within(min, max) - binding.swatch.y = (event.rawY + swatchY + min).within(min, max) + swatch.x = (event.rawX + swatchX + min).within(min, max) + swatch.y = (event.rawY + swatchY + min).within(min, max) updateSelectedColor() } @@ -84,7 +85,7 @@ class HSVPickerView @JvmOverloads constructor( var hueThumbX = 0f - binding.hueGroup.setOnTouchListener { _, event -> + hueGroup.setOnTouchListener { _, event -> setupBounds() when (event.action) { MotionEvent.ACTION_DOWN -> { @@ -95,8 +96,8 @@ class HSVPickerView @JvmOverloads constructor( MotionEvent.ACTION_MOVE -> { val x = (event.rawX + hueThumbX + min).within(min, max) - binding.hueThumb.x = x - hue = (binding.hueThumb.x - min) / (max - min) * 360 + hueThumb.x = x + hue = (hueThumb.x - min) / (max - min) * 360 updateSelectedColor() } @@ -110,14 +111,14 @@ class HSVPickerView @JvmOverloads constructor( true } - binding.hueTrack.clipToOutline = true - binding.hueTrack.setImageDrawable(GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, hues)) + hueTrack.clipToOutline = true + hueTrack.setImageDrawable(GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, hues)) } private fun setupBounds() { if (min == 0f || max == 0f) { - min = binding.saturation.x - binding.swatch.width / 2 - max = min + binding.saturation.width + min = saturation.x - swatch.width / 2 + max = min + saturation.width } } @@ -125,10 +126,10 @@ class HSVPickerView @JvmOverloads constructor( setupBounds() val range = max - min - val hsv = floatArrayOf(hue, (binding.swatch.x - min) / range, 1 - (binding.swatch.y - min) / range) + val hsv = floatArrayOf(hue, (swatch.x - min) / range, 1 - (swatch.y - min) / range) val color = Color.HSVToColor(hsv) - binding.swatch.setTint(color) + swatch.setTint(color) selectedColor.onNext(color) } @@ -144,9 +145,9 @@ class HSVPickerView @JvmOverloads constructor( setupBounds() val range = max - min - binding.hueThumb.x = range * hsv[0] / 360 + min - binding.swatch.x = range * hsv[1] + min - binding.swatch.y = range * (1 - hsv[2]) + min + hueThumb.x = range * hsv[0] / 360 + min + swatch.x = range * hsv[1] + min + swatch.y = range * (1 - hsv[2]) + min updateSelectedColor() } @@ -155,7 +156,7 @@ class HSVPickerView @JvmOverloads constructor( private fun updateHue() { val hsv = floatArrayOf(hue, 1f, 1f) val tint = Color.HSVToColor(hsv) - binding.saturation.setBackgroundTint(tint) + 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 3993551b4..cf99614e7 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,6 +25,7 @@ import android.view.ViewGroup import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayout +import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors @@ -32,16 +33,17 @@ import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.databinding.ThemeListItemBinding -import com.moez.QKSMS.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( private val context: Context, private val colors: Colors -) : QkAdapter, ThemePaletteListItemBinding>() { +) : QkAdapter>() { val colorSelected: Subject = PublishSubject.create() @@ -59,14 +61,15 @@ class ThemeAdapter @Inject constructor( private var iconTint = 0 - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - return QkViewHolder(parent, ThemePaletteListItemBinding::inflate).apply { - binding.palette.flexWrap = FlexWrap.WRAP - binding.palette.flexDirection = FlexDirection.ROW - } + 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 + + return QkViewHolder(view) } - override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val palette = getItem(position) val screenWidth = Resources.getSystem().displayMetrics.widthPixels @@ -78,15 +81,15 @@ class ThemeAdapter @Inject constructor( } val swatchPadding = (screenWidth - size * 5) / 12 - holder.binding.palette.removeAllViews() - holder.binding.palette.setPadding(swatchPadding, swatchPadding, swatchPadding, swatchPadding) + holder.palette.removeAllViews() + holder.palette.setPadding(swatchPadding, swatchPadding, swatchPadding, swatchPadding) (palette.subList(0, 5) + palette.subList(5, 10).reversed()) .mapIndexed { index, color -> - ThemeListItemBinding.inflate(LayoutInflater.from(context), holder.binding.palette, false).apply { + LayoutInflater.from(context).inflate(R.layout.theme_list_item, holder.palette, false).apply { // Send clicks to the selected subject - root.setOnClickListener { colorSelected.onNext(color) } + setOnClickListener { colorSelected.onNext(color) } // Apply the color to the view theme.setBackgroundTint(color) @@ -96,15 +99,15 @@ class ThemeAdapter @Inject constructor( check.setTint(iconTint) // Update the size so that the spacing is perfectly even - root.layoutParams = (root.layoutParams as FlexboxLayout.LayoutParams).apply { + layoutParams = (layoutParams as FlexboxLayout.LayoutParams).apply { height = size width = size isWrapBefore = index % 5 == 0 setMargins(swatchPadding, swatchPadding, swatchPadding, swatchPadding) } - }.root + } } - .forEach { theme -> holder.binding.palette.addView(theme) } + .forEach { theme -> holder.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 7c6301406..ae4dc56f2 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 @@ -29,19 +29,18 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.databinding.ThemePickerControllerBinding import com.moez.QKSMS.feature.themepicker.injection.ThemePickerModule import com.moez.QKSMS.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( - ThemePickerControllerBinding::inflate -), ThemePickerView { +) : QkController(), ThemePickerView { @Inject override lateinit var presenter: ThemePickerPresenter @@ -57,17 +56,19 @@ class ThemePickerController( .themePickerModule(ThemePickerModule(this)) .build() .inject(this) + + layoutRes = R.layout.theme_picker_controller } override fun onViewCreated() { - binding.pager.offscreenPageLimit = 1 - binding.pager.adapter = themePagerAdapter - binding.tabs.pager = binding.pager + pager.offscreenPageLimit = 1 + pager.adapter = themePagerAdapter + tabs.pager = pager themeAdapter.data = colors.materialColors - binding.materialColors.layoutManager = LinearLayoutManager(activity) - binding.materialColors.adapter = themeAdapter + materialColors.layoutManager = LinearLayoutManager(activity) + materialColors.adapter = themeAdapter } override fun onAttach(view: View) { @@ -85,13 +86,12 @@ class ThemePickerController( super.onDetach(view) themedActivity?.supportActionBar?.let { toolbar -> - ObjectAnimator.ofFloat(toolbar, "elevation", toolbar.elevation, 8.dpToPx(toolbar.themedContext).toFloat()) - .start() + ObjectAnimator.ofFloat(toolbar, "elevation", toolbar.elevation, 8.dpToPx(toolbar.themedContext).toFloat()).start() } } override fun showQksmsPlusSnackbar() { - Snackbar.make(binding.contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { + Snackbar.make(contentView, R.string.toast_qksms_plus, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusSubject.onNext(Unit) } setActionTextColor(colors.theme().theme) show() @@ -100,26 +100,26 @@ class ThemePickerController( override fun themeSelected(): Observable = themeAdapter.colorSelected - override fun hsvThemeSelected(): Observable = binding.hsvPicker.picker.selectedColor + override fun hsvThemeSelected(): Observable = picker.selectedColor - override fun clearHsvThemeClicks(): Observable<*> = binding.hsvPicker.clear.clicks() + override fun clearHsvThemeClicks(): Observable<*> = clear.clicks() - override fun applyHsvThemeClicks(): Observable<*> = binding.hsvPicker.apply.clicks() + override fun applyHsvThemeClicks(): Observable<*> = apply.clicks() override fun viewQksmsPlusClicks(): Observable<*> = viewQksmsPlusSubject override fun render(state: ThemePickerState) { - binding.tabs.setRecipientId(state.recipientId) + tabs.setRecipientId(state.recipientId) - binding.hsvPicker.hex.setText(Integer.toHexString(state.newColor).takeLast(6)) + hex.setText(Integer.toHexString(state.newColor).takeLast(6)) - binding.hsvPicker.applyGroup.setVisible(state.applyThemeVisible) - binding.hsvPicker.apply.setBackgroundTint(state.newColor) - binding.hsvPicker.apply.setTextColor(state.newTextColor) + applyGroup.setVisible(state.applyThemeVisible) + apply.setBackgroundTint(state.newColor) + apply.setTextColor(state.newTextColor) } override fun setCurrentTheme(color: Int) { - binding.hsvPicker.picker.setColor(color) + picker.setColor(color) themeAdapter.selectedColor = color } diff --git a/presentation/src/main/res/layout/collapsing_toolbar.xml b/presentation/src/main/res/layout/collapsing_toolbar.xml index 5f2ad571f..1994eb710 100644 --- a/presentation/src/main/res/layout/collapsing_toolbar.xml +++ b/presentation/src/main/res/layout/collapsing_toolbar.xml @@ -20,6 +20,7 @@ diff --git a/presentation/src/main/res/layout/contact_chip_detailed.xml b/presentation/src/main/res/layout/contact_chip_detailed.xml index 64ab9a592..786e160fd 100755 --- a/presentation/src/main/res/layout/contact_chip_detailed.xml +++ b/presentation/src/main/res/layout/contact_chip_detailed.xml @@ -17,21 +17,25 @@ ~ You should have received a copy of the GNU General Public License ~ along with QKSMS. If not, see . --> - + android:layout_margin="8dp" + android:background="@drawable/rounded_rectangle_2dp" + android:elevation="8dp" + android:gravity="center_vertical" + android:paddingStart="16dp" + android:paddingTop="8dp" + android:paddingBottom="8dp" + tools:backgroundTint="@color/tools_theme"> @@ -80,4 +84,4 @@ app:layout_constraintEnd_toEndOf="parent" tools:tint="@color/white" /> - \ No newline at end of file + \ No newline at end of file diff --git a/presentation/src/main/res/layout/main_activity.xml b/presentation/src/main/res/layout/main_activity.xml index 38de075e9..4b9bebf2a 100644 --- a/presentation/src/main/res/layout/main_activity.xml +++ b/presentation/src/main/res/layout/main_activity.xml @@ -121,20 +121,20 @@ android:background="?android:attr/divider" app:layout_constraintBottom_toTopOf="@id/snackbar" /> - - @@ -146,4 +146,4 @@ android:layout_height="match_parent" android:layout_gravity="start" /> - + \ No newline at end of file diff --git a/presentation/src/main/res/layout/main_permission_hint.xml b/presentation/src/main/res/layout/main_permission_hint.xml index c7e9bd540..932134456 100644 --- a/presentation/src/main/res/layout/main_permission_hint.xml +++ b/presentation/src/main/res/layout/main_permission_hint.xml @@ -24,22 +24,22 @@ android:layout_height="wrap_content"> - + \ No newline at end of file diff --git a/presentation/src/main/res/layout/main_syncing.xml b/presentation/src/main/res/layout/main_syncing.xml index 8f18bc38a..d3b17e59c 100644 --- a/presentation/src/main/res/layout/main_syncing.xml +++ b/presentation/src/main/res/layout/main_syncing.xml @@ -23,7 +23,7 @@ android:orientation="vertical"> - - - - - + - + . --> - + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:paddingStart="8dp" + android:paddingEnd="8dp"> + + + + \ No newline at end of file -- GitLab From 13b9e00923c1c16f6cc6762c1d6e23cc58d6103c Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 7 Feb 2021 15:19:53 -0500 Subject: [PATCH 167/213] Increment to v3.9.2 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 457b9da17..178c460b9 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2215 - versionName "3.9.1" + versionCode 2216 + versionName "3.9.2" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From bc6c19e0800b17a28105276ccb9eabb1a82cea68 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Fri, 12 Feb 2021 23:24:58 -0500 Subject: [PATCH 168/213] Replace pin icon with material version --- .../{ic_pin_black_24dp.xml => ic_baseline_push_pin_24.xml} | 5 +++-- presentation/src/main/res/layout/conversation_list_item.xml | 2 +- presentation/src/main/res/menu/main.xml | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) rename presentation/src/main/res/drawable/{ic_pin_black_24dp.xml => ic_baseline_push_pin_24.xml} (76%) diff --git a/presentation/src/main/res/drawable/ic_pin_black_24dp.xml b/presentation/src/main/res/drawable/ic_baseline_push_pin_24.xml similarity index 76% rename from presentation/src/main/res/drawable/ic_pin_black_24dp.xml rename to presentation/src/main/res/drawable/ic_baseline_push_pin_24.xml index 3602d9d2a..e799af2a8 100644 --- a/presentation/src/main/res/drawable/ic_pin_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_baseline_push_pin_24.xml @@ -1,5 +1,5 @@ + + android:pathData="M16,9V4l1,0c0.55,0 1,-0.45 1,-1v0c0,-0.55 -0.45,-1 -1,-1H7C6.45,2 6,2.45 6,3v0c0,0.55 0.45,1 1,1l1,0v5c0,1.66 -1.34,3 -3,3h0v2h5.97v7l1,1l1,-1v-7H19v-2h0C17.34,12 16,10.66 16,9z" /> diff --git a/presentation/src/main/res/layout/conversation_list_item.xml b/presentation/src/main/res/layout/conversation_list_item.xml index 516d4d558..70e1a2526 100644 --- a/presentation/src/main/res/layout/conversation_list_item.xml +++ b/presentation/src/main/res/layout/conversation_list_item.xml @@ -78,7 +78,7 @@ android:layout_height="16dp" android:layout_marginTop="2dp" android:paddingStart="4dp" - android:src="@drawable/ic_pin_black_24dp" + android:src="@drawable/ic_baseline_push_pin_24" android:tint="?android:attr/textColorTertiary" android:visibility="gone" app:layout_constraintEnd_toStartOf="@id/unread" diff --git a/presentation/src/main/res/menu/main.xml b/presentation/src/main/res/menu/main.xml index 579d60abc..b8f22b0f3 100644 --- a/presentation/src/main/res/menu/main.xml +++ b/presentation/src/main/res/menu/main.xml @@ -50,14 +50,14 @@ -- GitLab From ca1e7b7c7a5514cb1e9e824584a8088cfc47eaba Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 14 Feb 2021 00:19:30 -0500 Subject: [PATCH 169/213] Improve message sync speed & reliability --- .../moez/QKSMS/mapper/CursorToMessageImpl.kt | 2 - .../com/moez/QKSMS/mapper/CursorToPartImpl.kt | 8 +- .../moez/QKSMS/migration/QkRealmMigration.kt | 13 ++- .../QKSMS/repository/SyncRepositoryImpl.kt | 95 ++++++++++++------- .../com/moez/QKSMS/mapper/CursorToPart.kt | 4 +- .../main/java/com/moez/QKSMS/mapper/Mapper.kt | 2 +- .../main/java/com/moez/QKSMS/model/MmsPart.kt | 2 + 7 files changed, 81 insertions(+), 45 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToMessageImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToMessageImpl.kt index 08bfb8f37..03cbe2503 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToMessageImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToMessageImpl.kt @@ -124,8 +124,6 @@ class CursorToMessageImpl @Inject constructor( ?.let { EncodedStringValue(subjectCharset, it).string } ?: "" textContentType = "" attachmentType = Message.AttachmentType.NOT_LOADED - - parts.addAll(cursorToPart.getPartsCursor(contentId)?.map { cursorToPart.map(it) } ?: listOf()) } } } diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt index 7624f4cbf..c057c1400 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt @@ -35,6 +35,7 @@ class CursorToPartImpl @Inject constructor(private val context: Context) : Curso override fun map(from: Cursor) = MmsPart().apply { id = from.getLong(from.getColumnIndexOrThrow(Telephony.Mms.Part._ID)) + messageId = from.getLong(from.getColumnIndexOrThrow(Telephony.Mms.Part.MSG_ID)) type = from.getStringOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.CONTENT_TYPE)) ?: "*/*" seq = from.getIntOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.SEQ)) ?: -1 name = from.getStringOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.NAME)) @@ -43,9 +44,8 @@ class CursorToPartImpl @Inject constructor(private val context: Context) : Curso text = from.getStringOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.TEXT)) } - override fun getPartsCursor(messageId: Long): Cursor? { - return context.contentResolver.query(CONTENT_URI, null, - "${Telephony.Mms.Part.MSG_ID} = ?", arrayOf(messageId.toString()), null) + override fun getPartsCursor(): Cursor? { + return context.contentResolver.query(CONTENT_URI, null, null, null, null) } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index c8dc96b97..a006f83b4 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -36,7 +36,7 @@ class QkRealmMigration @Inject constructor( ) : RealmMigration { companion object { - const val SchemaVersion: Long = 10 + const val SchemaVersion: Long = 11 } @SuppressLint("ApplySharedPref") @@ -223,6 +223,17 @@ class QkRealmMigration @Inject constructor( version++ } + if (version == 10L) { + realm.schema.get("MmsPart") + ?.addField("messageId", Long::class.java, FieldAttribute.INDEXED, FieldAttribute.REQUIRED) + ?.transform { part -> + val messageId = part.linkingObjects("Message", "parts").firstOrNull()?.getLong("contentId") ?: 0 + part.setLong("messageId", messageId) + } + + version++ + } + check(version >= newVersion) { "Migration missing from v$oldVersion to v$newVersion" } } diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index eefe1b833..4f03110be 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -23,6 +23,7 @@ import android.content.ContentUris import android.net.Uri import android.provider.Telephony import com.f2prateek.rx.preferences2.RxSharedPreferences +import com.moez.QKSMS.extensions.forEach import com.moez.QKSMS.extensions.insertOrUpdate import com.moez.QKSMS.extensions.map import com.moez.QKSMS.manager.KeyManager @@ -31,6 +32,7 @@ import com.moez.QKSMS.mapper.CursorToContactGroup import com.moez.QKSMS.mapper.CursorToContactGroupMember import com.moez.QKSMS.mapper.CursorToConversation import com.moez.QKSMS.mapper.CursorToMessage +import com.moez.QKSMS.mapper.CursorToPart import com.moez.QKSMS.mapper.CursorToRecipient import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.ContactGroup @@ -45,6 +47,7 @@ import com.moez.QKSMS.util.tryOrNull import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject import io.realm.Realm +import io.realm.RealmList import io.realm.Sort import javax.inject.Inject import javax.inject.Singleton @@ -55,6 +58,7 @@ class SyncRepositoryImpl @Inject constructor( private val conversationRepo: ConversationRepository, private val cursorToConversation: CursorToConversation, private val cursorToMessage: CursorToMessage, + private val cursorToPart: CursorToPart, private val cursorToRecipient: CursorToRecipient, private val cursorToContact: CursorToContact, private val cursorToContactGroup: CursorToContactGroup, @@ -103,25 +107,48 @@ class SyncRepositoryImpl @Inject constructor( keys.reset() + val partsCursor = cursorToPart.getPartsCursor() val messageCursor = cursorToMessage.getMessagesCursor() val conversationCursor = cursorToConversation.getConversationsCursor() val recipientCursor = cursorToRecipient.getRecipientCursor() - val max = (messageCursor?.count ?: 0) + + val max = (partsCursor?.count ?: 0) + + (messageCursor?.count ?: 0) + (conversationCursor?.count ?: 0) + (recipientCursor?.count ?: 0) var progress = 0 + // Sync message parts + partsCursor?.use { + partsCursor.forEach { + tryOrNull { + progress++ + val part = cursorToPart.map(partsCursor) + realm.insertOrUpdate(part) + } + } + } + // Sync messages messageCursor?.use { val messageColumns = CursorToMessage.MessageColumns(messageCursor) - val messages = messageCursor.map { cursor -> - progress++ - syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) - cursorToMessage.map(Pair(cursor, messageColumns)) + messageCursor.forEach { cursor -> + tryOrNull { + progress++ + syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) + val message = cursorToMessage.map(Pair(cursor, messageColumns)).apply { + if (isMms()) { + parts = RealmList().apply { + addAll(realm.where(MmsPart::class.java) + .equalTo("messageId", contentId) + .findAll()) + } + } + } + realm.insertOrUpdate(message) + } } - realm.insertOrUpdate(messages) } // Migrate blocked conversations from 2.7.3 @@ -133,46 +160,44 @@ class SyncRepositoryImpl @Inject constructor( // Sync conversations conversationCursor?.use { - val conversations = conversationCursor.map { cursor -> - progress++ - syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) - cursorToConversation.map(cursor).apply { - persistedData[id]?.let { persistedConversation -> - archived = persistedConversation.archived - blocked = persistedConversation.blocked - pinned = persistedConversation.pinned - name = persistedConversation.name - blockingClient = persistedConversation.blockingClient - blockReason = persistedConversation.blockReason + conversationCursor.forEach { cursor -> + tryOrNull { + progress++ + syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) + val conversation = cursorToConversation.map(cursor).apply { + persistedData[id]?.let { persistedConversation -> + archived = persistedConversation.archived + blocked = persistedConversation.blocked + pinned = persistedConversation.pinned + name = persistedConversation.name + blockingClient = persistedConversation.blockingClient + blockReason = persistedConversation.blockReason + } + lastMessage = realm.where(Message::class.java) + .sort("date", Sort.DESCENDING) + .equalTo("threadId", id) + .findFirst() } + realm.insertOrUpdate(conversation) } } - - realm.where(Message::class.java) - .sort("date", Sort.DESCENDING) - .distinct("threadId") - .findAll() - .forEach { message -> - val conversation = conversations.find { conversation -> conversation.id == message.threadId } - conversation?.lastMessage = message - } - - realm.insertOrUpdate(conversations) } // Sync recipients recipientCursor?.use { val contacts = realm.copyToRealmOrUpdate(getContacts()) - val recipients = recipientCursor.map { cursor -> - progress++ - syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) - cursorToRecipient.map(cursor).apply { - contact = contacts.firstOrNull { contact -> - contact.numbers.any { phoneNumberUtils.compare(address, it.address) } + recipientCursor.forEach { cursor -> + tryOrNull { + progress++ + syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) + val recipient = cursorToRecipient.map(cursor).apply { + contact = contacts.firstOrNull { contact -> + contact.numbers.any { phoneNumberUtils.compare(address, it.address) } + } } + realm.insertOrUpdate(recipient) } } - realm.insertOrUpdate(recipients) } syncProgress.onNext(SyncRepository.SyncProgress.Running(0, 0, true)) diff --git a/domain/src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt index bb3922033..69fdc619a 100644 --- a/domain/src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt +++ b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt @@ -23,6 +23,6 @@ import com.moez.QKSMS.model.MmsPart interface CursorToPart : Mapper { - fun getPartsCursor(messageId: Long): Cursor? + fun getPartsCursor(): Cursor? -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/mapper/Mapper.kt b/domain/src/main/java/com/moez/QKSMS/mapper/Mapper.kt index 7f726e8c8..29430f634 100644 --- a/domain/src/main/java/com/moez/QKSMS/mapper/Mapper.kt +++ b/domain/src/main/java/com/moez/QKSMS/mapper/Mapper.kt @@ -22,4 +22,4 @@ interface Mapper { fun map(from: From): To -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt b/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt index d1afb35d5..aa3fa9082 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt @@ -21,12 +21,14 @@ package com.moez.QKSMS.model import androidx.core.net.toUri import io.realm.RealmObject import io.realm.RealmResults +import io.realm.annotations.Index import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey open class MmsPart : RealmObject() { @PrimaryKey var id: Long = 0 + @Index var messageId: Long = 0 var type: String = "" var seq: Int = -1 var name: String? = null -- GitLab From 3d84ccceea09825a4a3e912f4e7439fac99f1a21 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 16 Feb 2021 23:39:55 -0500 Subject: [PATCH 170/213] Update to billing client v3 --- build.gradle | 1 + .../com/moez/QKSMS/manager/BillingManager.kt | 6 +- presentation/build.gradle | 4 +- .../com/moez/QKSMS/common/QKApplication.kt | 4 + .../moez/QKSMS/feature/plus/PlusActivity.kt | 16 +- presentation/src/main/res/values/strings.xml | 1 + .../QKSMS/common/util/BillingManagerImpl.kt | 4 +- .../QKSMS/common/util/BillingManagerImpl.kt | 159 +++++++++++------- 8 files changed, 125 insertions(+), 70 deletions(-) diff --git a/build.gradle b/build.gradle index 14546f92e..ffbc91873 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ buildscript { ext.androidx_testrunner_version = '1.1.0-alpha3' ext.androidx_viewpager_version = '1.0.0-beta05' ext.autodispose_version = '1.3.0' + ext.billing_version = '3.0.2' ext.conductor_version = '2.1.5' ext.coroutines_version = '1.2.2' ext.dagger_version = "2.16" diff --git a/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt index f02de472d..e92e763ac 100644 --- a/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt +++ b/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt @@ -38,6 +38,10 @@ interface BillingManager { val products: Observable> val upgradeStatus: Observable - fun initiatePurchaseFlow(activity: Activity, sku: String) + suspend fun checkForPurchases() + + suspend fun queryProducts() + + suspend fun initiatePurchaseFlow(activity: Activity, sku: String) } diff --git a/presentation/build.gradle b/presentation/build.gradle index 178c460b9..0d3804af2 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -187,8 +187,8 @@ dependencies { implementation project(':domain') withAnalyticsImplementation 'com.google.firebase:firebase-crashlytics:17.3.0' - withAnalyticsImplementation "com.android.billingclient:billing:1.0" - + withAnalyticsImplementation "com.android.billingclient:billing:$billing_version" + withAnalyticsImplementation "com.android.billingclient:billing-ktx:$billing_version" noAnalyticsDebug project(path: ':data', configuration: 'noAnalyticsDebug') noAnalyticsRelease project(path: ':data', configuration: 'noAnalyticsRelease') withAnalyticsDebug project(path: ':data', configuration: 'withAnalyticsDebug') diff --git a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt index b227d1b82..12ececf6d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt @@ -31,6 +31,7 @@ import com.moez.QKSMS.common.util.FileLoggingTree import com.moez.QKSMS.injection.AppComponentManager import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.manager.ReferralManager import com.moez.QKSMS.migration.QkMigration import com.moez.QKSMS.migration.QkRealmMigration @@ -60,6 +61,7 @@ class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverIn @Suppress("unused") @Inject lateinit var qkMigration: QkMigration + @Inject lateinit var billingManager: BillingManager @Inject lateinit var dispatchingActivityInjector: DispatchingAndroidInjector @Inject lateinit var dispatchingBroadcastReceiverInjector: DispatchingAndroidInjector @Inject lateinit var dispatchingServiceInjector: DispatchingAndroidInjector @@ -85,6 +87,8 @@ class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverIn GlobalScope.launch(Dispatchers.IO) { referralManager.trackReferrer() + billingManager.checkForPurchases() + billingManager.queryProducts() } nightModeManager.updateCurrentTheme() 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 eb589d764..90fc5ec1f 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 @@ -24,11 +24,11 @@ import androidx.core.view.children import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import com.jakewharton.rxbinding2.view.clicks -import com.jakewharton.rxbinding2.view.enabled import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.FontProvider +import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint @@ -37,10 +37,13 @@ import com.moez.QKSMS.common.widget.PreferenceView import com.moez.QKSMS.feature.plus.experiment.UpgradeButtonExperiment import com.moez.QKSMS.manager.BillingManager import dagger.android.AndroidInjection -import io.reactivex.Observable import kotlinx.android.synthetic.main.collapsing_toolbar.* import kotlinx.android.synthetic.main.preference_view.view.* import kotlinx.android.synthetic.main.qksms_plus_activity.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject class PlusActivity : QkThemedActivity(), PlusView { @@ -113,7 +116,14 @@ class PlusActivity : QkThemedActivity(), PlusView { } override fun initiatePurchaseFlow(billingManager: BillingManager, sku: String) { - billingManager.initiatePurchaseFlow(this, sku) + GlobalScope.launch(Dispatchers.Main) { + try { + billingManager.initiatePurchaseFlow(this@PlusActivity, sku) + } catch (e: Exception) { + Timber.w(e) + makeToast(R.string.qksms_plus_error) + } + } } } \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 1261abf01..2c7e1b838 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -337,6 +337,7 @@ Unlock + donate for %1$s %2$s Thank you for supporting QKSMS! You now have access to all QKSMS+ features + An error has occurred, please try again QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. Donate via PayPal Coming soon diff --git a/presentation/src/noAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt b/presentation/src/noAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt index 0507447c8..428caaa13 100644 --- a/presentation/src/noAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt +++ b/presentation/src/noAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt @@ -32,6 +32,8 @@ class BillingManagerImpl @Inject constructor( override val products: Observable> = BehaviorSubject.createDefault(listOf()) override val upgradeStatus: Observable = BehaviorSubject.createDefault(true) - override fun initiatePurchaseFlow(activity: Activity, sku: String) = Unit + override suspend fun checkForPurchases() = Unit + override suspend fun queryProducts() = Unit + override suspend fun initiatePurchaseFlow(activity: Activity, sku: String) = Unit } diff --git a/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt b/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt index 2f3b54dbd..a0c53380b 100644 --- a/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt +++ b/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt @@ -21,23 +21,32 @@ package com.moez.QKSMS.common.util import android.app.Activity import android.content.Context +import com.android.billingclient.api.AcknowledgePurchaseParams import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingFlowParams +import com.android.billingclient.api.BillingResult import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.SkuDetails import com.android.billingclient.api.SkuDetailsParams +import com.android.billingclient.api.acknowledgePurchase +import com.android.billingclient.api.queryPurchaseHistory +import com.android.billingclient.api.querySkuDetails import com.moez.QKSMS.manager.AnalyticsManager import com.moez.QKSMS.manager.BillingManager -import io.reactivex.Flowable import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine @Singleton class BillingManagerImpl @Inject constructor( @@ -45,96 +54,120 @@ class BillingManagerImpl @Inject constructor( private val analyticsManager: AnalyticsManager ) : BillingManager, PurchasesUpdatedListener { - companion object { - const val SKU_PLUS = "remove_ads" - const val SKU_PLUS_DONATE = "qksms_plus_donate" - } + private val productsSubject: Subject> = BehaviorSubject.create() + override val products: Observable> = productsSubject + .map { skuDetailsList -> + skuDetailsList.map { skuDetails -> + BillingManager.Product(skuDetails.sku, skuDetails.price, skuDetails.priceCurrencyCode) + } + } - override val products: Observable> = BehaviorSubject.create() - override val upgradeStatus: Observable + private val purchaseListSubject = BehaviorSubject.create>() + override val upgradeStatus: Observable = purchaseListSubject + .map { purchases -> + purchases + .filter { it.purchaseState == Purchase.PurchaseState.PURCHASED } + .any { it.sku in skus } + } + .distinctUntilChanged() + .doOnNext { upgraded -> analyticsManager.setUserProperty("Upgraded", upgraded) } - private val skus = listOf(SKU_PLUS, SKU_PLUS_DONATE) - private val purchaseListObservable = BehaviorSubject.create>() + private val skus = listOf(BillingManager.SKU_PLUS, BillingManager.SKU_PLUS_DONATE) + private val billingClient: BillingClient = BillingClient.newBuilder(context) + .setListener(this) + .enablePendingPurchases() + .build() - private val billingClient: BillingClient = BillingClient.newBuilder(context).setListener(this).build() private var isServiceConnected = false - init { - startServiceConnection { - queryPurchases() - querySkuDetailsAsync() - } + override suspend fun checkForPurchases() = executeServiceRequest { + // Load the cached data + queryPurchases() - upgradeStatus = purchaseListObservable - .map { purchases -> purchases.any { it.sku == SKU_PLUS } || purchases.any { it.sku == SKU_PLUS_DONATE } } - .doOnNext { upgraded -> analyticsManager.setUserProperty("Upgraded", upgraded) } + // On a fresh device, the purchase might not be cached, and so we'll need to force a refresh + billingClient.queryPurchaseHistory(BillingClient.SkuType.INAPP) + queryPurchases() } - private fun queryPurchases() { - executeServiceRequest { - // Load the cached data - purchaseListObservable.onNext(billingClient.queryPurchases(BillingClient.SkuType.INAPP).purchasesList.orEmpty()) + override suspend fun queryProducts() = executeServiceRequest { + val params = SkuDetailsParams.newBuilder() + .setSkusList(skus) + .setType(BillingClient.SkuType.INAPP) - // On a fresh device, the purchase might not be cached, and so we'll need to force a refresh - billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP) { _, _ -> - purchaseListObservable.onNext(billingClient.queryPurchases(BillingClient.SkuType.INAPP).purchasesList.orEmpty()) - } + val (billingResult, skuDetailsList) = billingClient.querySkuDetails(params.build()) + if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { + productsSubject.onNext(skuDetailsList.orEmpty()) } } + override suspend fun initiatePurchaseFlow(activity: Activity, sku: String) = executeServiceRequest { + val skuDetails = withContext(Dispatchers.IO) { + val params = SkuDetailsParams.newBuilder() + .setType(BillingClient.SkuType.INAPP) + .setSkusList(listOf(sku)) + .build() - private fun startServiceConnection(onSuccess: () -> Unit) { - val listener = object : BillingClientStateListener { - override fun onBillingSetupFinished(@BillingClient.BillingResponse billingResponseCode: Int) { - if (billingResponseCode == BillingClient.BillingResponse.OK) { - isServiceConnected = true - onSuccess() - } else { - Timber.w("Billing response: $billingResponseCode") - purchaseListObservable.onNext(listOf()) - } - } - - override fun onBillingServiceDisconnected() { - isServiceConnected = false - } + billingClient.querySkuDetails(params).skuDetailsList?.firstOrNull()!! } - Flowable.fromCallable { billingClient.startConnection(listener) } - .subscribeOn(Schedulers.io()) - .subscribe() + val params = BillingFlowParams.newBuilder().setSkuDetails(skuDetails) + billingClient.launchBillingFlow(activity, params.build()) } - private fun querySkuDetailsAsync() { - executeServiceRequest { - val subParams = SkuDetailsParams.newBuilder().setSkusList(skus).setType(BillingClient.SkuType.INAPP) - billingClient.querySkuDetailsAsync(subParams.build()) { responseCode, skuDetailsList -> - if (responseCode == BillingClient.BillingResponse.OK) { - (products as Subject).onNext(skuDetailsList.map { skuDetails -> - BillingManager.Product(skuDetails.sku, skuDetails.price, skuDetails.priceCurrencyCode) - }) - } + override fun onPurchasesUpdated(result: BillingResult, purchases: MutableList?) { + if (result.responseCode == BillingClient.BillingResponseCode.OK) { + GlobalScope.launch(Dispatchers.IO) { + handlePurchases(purchases.orEmpty()) } } } - override fun initiatePurchaseFlow(activity: Activity, sku: String) { - executeServiceRequest { - val params = BillingFlowParams.newBuilder().setSku(sku).setType(BillingClient.SkuType.INAPP) - billingClient.launchBillingFlow(activity, params.build()) + private suspend fun queryPurchases() { + val result = billingClient.queryPurchases(BillingClient.SkuType.INAPP) + if (result.responseCode == BillingClient.BillingResponseCode.OK) { + handlePurchases(result.purchasesList.orEmpty()) } } - private fun executeServiceRequest(runnable: () -> Unit) { - when (isServiceConnected) { + private suspend fun handlePurchases(purchases: List) = executeServiceRequest { + purchases.forEach { purchase -> + if (!purchase.isAcknowledged) { + val params = AcknowledgePurchaseParams.newBuilder() + .setPurchaseToken(purchase.purchaseToken) + .build() + + Timber.i("Acknowledging purchase ${purchase.orderId}") + val result = billingClient.acknowledgePurchase(params) + Timber.i("Acknowledgement result: ${result.responseCode}, ${result.debugMessage}") + } + } + + purchaseListSubject.onNext(purchases) + } + + private suspend fun executeServiceRequest(runnable: suspend () -> Unit) { + when (billingClient.isReady) { true -> runnable() false -> startServiceConnection(runnable) } } - override fun onPurchasesUpdated(resultCode: Int, purchases: List?) { - if (resultCode == BillingClient.BillingResponse.OK) { - purchaseListObservable.onNext(purchases.orEmpty()) + private suspend fun startServiceConnection(onSuccess: suspend () -> Unit) = withContext(Dispatchers.IO) { + val result = suspendCoroutine { cont -> + val listener = object : BillingClientStateListener { + override fun onBillingSetupFinished(result: BillingResult) = cont.resume(result) + override fun onBillingServiceDisconnected() = Timber.i("Billing service disconnected") + } + + Timber.i("Starting billing service") + billingClient.startConnection(listener) + } + + if (result.responseCode == BillingClient.BillingResponseCode.OK) { + Timber.i("Billing service connected") + onSuccess() + } else { + Timber.w("Billing response: ${result.responseCode}") } } -- GitLab From 178cccd93fe1979488c5223d94b0b916767a1344 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 9 Feb 2021 23:09:05 -0500 Subject: [PATCH 171/213] Add support for Call Blocker --- .../moez/QKSMS/blocking/BlockingManager.kt | 2 + .../blocking/CallBlockerBlockingClient.kt | 84 +++++++++++++++++++ .../java/com/moez/QKSMS/util/Preferences.kt | 1 + .../java/com/moez/QKSMS/common/Navigator.kt | 9 ++ .../manager/BlockingManagerController.kt | 4 + .../manager/BlockingManagerPresenter.kt | 26 ++++++ .../blocking/manager/BlockingManagerState.kt | 1 + .../blocking/manager/BlockingManagerView.kt | 1 + .../layout/blocking_manager_controller.xml | 8 ++ presentation/src/main/res/values/strings.xml | 2 + 10 files changed, 138 insertions(+) create mode 100644 data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt diff --git a/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt b/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt index 23d9e7586..633a22f31 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt @@ -12,6 +12,7 @@ import javax.inject.Singleton @Singleton class BlockingManager @Inject constructor( private val prefs: Preferences, + private val callBlockerBlockingClient: CallBlockerBlockingClient, private val callControlBlockingClient: CallControlBlockingClient, private val qksmsBlockingClient: QksmsBlockingClient, private val shouldIAnswerBlockingClient: ShouldIAnswerBlockingClient @@ -19,6 +20,7 @@ class BlockingManager @Inject constructor( private val client: BlockingClient get() = when (prefs.blockingManager.get()) { + Preferences.BLOCKING_MANAGER_CB -> callBlockerBlockingClient Preferences.BLOCKING_MANAGER_SIA -> shouldIAnswerBlockingClient Preferences.BLOCKING_MANAGER_CC -> callControlBlockingClient else -> qksmsBlockingClient diff --git a/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt new file mode 100644 index 000000000..8cb4ada1f --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.blocking + +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.net.Uri +import androidx.core.database.getStringOrNull +import com.moez.QKSMS.common.util.extensions.isInstalled +import com.moez.QKSMS.extensions.map +import com.moez.QKSMS.util.tryOrNull +import io.reactivex.Completable +import io.reactivex.Single +import java.util.ArrayList +import javax.inject.Inject + +class CallBlockerBlockingClient @Inject constructor( + private val context: Context +) : BlockingClient { + + class LookupResult(cursor: Cursor) { + val blockReason: String? = cursor.getStringOrNull(0) + } + + override fun isAvailable(): Boolean = context.isInstalled("com.cuiet.blockCalls") + + override fun getClientCapability() = BlockingClient.Capability.BLOCK_WITH_PERMISSION + + override fun getAction(address: String): Single = Single.fromCallable { + val uri = Uri.parse("content://com.cuiet.blockCalls.ContProvBlockCalls/lookup/is.blocked.lookup") + val blockReason = tryOrNull { + context.contentResolver.query(uri, arrayOf("result"), "incomingNumber", + arrayOf(address), null) + ?.use { cursor -> cursor.map(::LookupResult) } + ?.find { result -> result.blockReason != null } + }?.blockReason + + when (blockReason) { + "true" -> BlockingClient.Action.Block() + else -> BlockingClient.Action.Unblock + } + } + + override fun block(addresses: List): Completable = Completable.fromCallable { + val arrayList = ArrayList() + arrayList.addAll(addresses) + val intent = Intent("com.cuiet.blockCalls.ADD_NUMBERS") + intent.putStringArrayListExtra("addresses", arrayList) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } + + override fun unblock(addresses: List): Completable = Completable.fromCallable { + val arrayList = ArrayList() + arrayList.addAll(addresses) + val intent = Intent("com.cuiet.blockCalls.REMOVE_NUMBERS") + intent.putStringArrayListExtra("addresses", arrayList) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } + + override fun openSettings() { + val intent = Intent("com.cuiet.blockCalls.OPEN_SETTINGS") + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } +} diff --git a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index 919fab278..d7711f3be 100644 --- a/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -75,6 +75,7 @@ class Preferences @Inject constructor( const val BLOCKING_MANAGER_QKSMS = 0 const val BLOCKING_MANAGER_CC = 1 const val BLOCKING_MANAGER_SIA = 2 + const val BLOCKING_MANAGER_CB = 3 } // Internal diff --git a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt index 0a7cba9cb..b0659c991 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt @@ -191,6 +191,15 @@ class Navigator @Inject constructor( } } + /** + * Launch the Play Store and display the Call Blocker listing + */ + fun installCallBlocker() { + val url = "https://play.google.com/store/apps/details?id=com.cuiet.blockCalls" + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivityExternal(intent) + } + /** * Launch the Play Store and display the Call Control listing */ 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 ed64b1d5c..7c0b392c3 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 @@ -44,6 +44,9 @@ 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() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt index 1ef5d91fc..09b748bc0 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt @@ -3,6 +3,7 @@ package com.moez.QKSMS.feature.blocking.manager import android.content.Context import com.moez.QKSMS.R import com.moez.QKSMS.blocking.BlockingClient +import com.moez.QKSMS.blocking.CallBlockerBlockingClient import com.moez.QKSMS.blocking.CallControlBlockingClient import com.moez.QKSMS.blocking.QksmsBlockingClient import com.moez.QKSMS.blocking.ShouldIAnswerBlockingClient @@ -21,6 +22,7 @@ import javax.inject.Inject class BlockingManagerPresenter @Inject constructor( private val analytics: AnalyticsManager, + private val callBlocker: CallBlockerBlockingClient, private val callControl: CallControlBlockingClient, private val context: Context, private val conversationRepo: ConversationRepository, @@ -30,6 +32,7 @@ class BlockingManagerPresenter @Inject constructor( private val shouldIAnswer: ShouldIAnswerBlockingClient ) : QkPresenter(BlockingManagerState( blockingManager = prefs.blockingManager.get(), + callBlockerInstalled = callBlocker.isAvailable(), callControlInstalled = callControl.isAvailable(), siaInstalled = shouldIAnswer.isAvailable() )) { @@ -42,6 +45,12 @@ class BlockingManagerPresenter @Inject constructor( override fun bindIntents(view: BlockingManagerView) { super.bindIntents(view) + view.activityResumed() + .map { callBlocker.isAvailable() } + .distinctUntilChanged() + .autoDisposable(view.scope()) + .subscribe { available -> newState { copy(callBlockerInstalled = available) } } + view.activityResumed() .map { callControl.isAvailable() } .distinctUntilChanged() @@ -64,6 +73,23 @@ class BlockingManagerPresenter @Inject constructor( prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_QKSMS) } + view.callBlockerClicked() + .filter { + val installed = callBlocker.isAvailable() + if (!installed) { + analytics.track("Install Call Blocker") + navigator.installCallBlocker() + } + + val enabled = prefs.blockingManager.get() == Preferences.BLOCKING_MANAGER_CB + installed && !enabled + } + .autoDisposable(view.scope()) + .subscribe { + analytics.setUserProperty("Blocking Manager", "Call Blocker") + prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_CB) + } + view.callControlClicked() .filter { val installed = callControl.isAvailable() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt index 2663caf06..169add4b2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt @@ -2,6 +2,7 @@ package com.moez.QKSMS.feature.blocking.manager data class BlockingManagerState( val blockingManager: Int = 0, + val callBlockerInstalled: Boolean = false, val callControlInstalled: Boolean = false, val siaInstalled: Boolean = false ) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt index 7318e53a2..dd871e17d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt @@ -8,6 +8,7 @@ interface BlockingManagerView : QkViewContract { fun activityResumed(): Observable<*> fun qksmsClicked(): Observable<*> + fun callBlockerClicked(): Observable<*> fun callControlClicked(): Observable<*> fun siaClicked(): Observable<*> diff --git a/presentation/src/main/res/layout/blocking_manager_controller.xml b/presentation/src/main/res/layout/blocking_manager_controller.xml index 03575c7b3..b85e28771 100644 --- a/presentation/src/main/res/layout/blocking_manager_controller.xml +++ b/presentation/src/main/res/layout/blocking_manager_controller.xml @@ -37,6 +37,14 @@ app:summary="@string/blocking_manager_qksms_summary" app:title="@string/blocking_manager_qksms_title" /> + + Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Call Blocker + Call Blocker is a program that blocks unwanted calls, call centers, spam, robocalls, telemarketing, private calls, hidden calls, anonymous calls, strangers, etc. Call Control Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Should I Answer? -- GitLab From 2bcb05945b95e0621d13ef4671a9529e8d7f65f7 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 10 Feb 2021 21:27:06 -0500 Subject: [PATCH 172/213] Add ability to check blacklist in spite of blacklist schedule --- .../moez/QKSMS/blocking/BlockingManager.kt | 4 +++- .../blocking/CallBlockerBlockingClient.kt | 19 ++++++++++++++++--- .../blocking/CallControlBlockingClient.kt | 4 +++- .../QKSMS/blocking/QksmsBlockingClient.kt | 4 +++- .../blocking/ShouldIAnswerBlockingClient.kt | 4 +++- .../com/moez/QKSMS/blocking/BlockingClient.kt | 9 ++++++++- .../com/moez/QKSMS/interactor/ReceiveMms.kt | 2 +- .../com/moez/QKSMS/interactor/ReceiveSms.kt | 2 +- .../QKSMS/feature/blocking/BlockingDialog.kt | 5 +++-- .../feature/blocking/BlockingPresenter.kt | 3 ++- .../manager/BlockingManagerPresenter.kt | 4 ++-- 11 files changed, 45 insertions(+), 15 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt b/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt index 633a22f31..a9f4114d7 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt @@ -30,7 +30,9 @@ class BlockingManager @Inject constructor( override fun getClientCapability(): BlockingClient.Capability = client.getClientCapability() - override fun getAction(address: String): Single = client.getAction(address) + override fun shouldBlock(address: String): Single = client.shouldBlock(address) + + override fun isBlacklisted(address: String): Single = client.isBlacklisted(address) override fun block(addresses: List): Completable = client.block(addresses) diff --git a/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt index 8cb4ada1f..ca57497dc 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt @@ -43,11 +43,24 @@ class CallBlockerBlockingClient @Inject constructor( override fun getClientCapability() = BlockingClient.Capability.BLOCK_WITH_PERMISSION - override fun getAction(address: String): Single = Single.fromCallable { + override fun shouldBlock(address: String): Single = Single.fromCallable { val uri = Uri.parse("content://com.cuiet.blockCalls.ContProvBlockCalls/lookup/is.blocked.lookup") val blockReason = tryOrNull { - context.contentResolver.query(uri, arrayOf("result"), "incomingNumber", - arrayOf(address), null) + context.contentResolver.query(uri, arrayOf("result"), "incomingNumber", arrayOf(address), null) + ?.use { cursor -> cursor.map(::LookupResult) } + ?.find { result -> result.blockReason != null } + }?.blockReason + + when (blockReason) { + "true" -> BlockingClient.Action.Block() + else -> BlockingClient.Action.Unblock + } + } + + override fun isBlacklisted(address: String): Single = Single.fromCallable { + val uri = Uri.parse("content://com.cuiet.blockCalls.ContProvBlockCalls/lookup/is.blocked.lookup") + val blockReason = tryOrNull { + context.contentResolver.query(uri, arrayOf("result"), "blacklistLookup", arrayOf(address), null) ?.use { cursor -> cursor.map(::LookupResult) } ?.find { result -> result.blockReason != null } }?.blockReason diff --git a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt index ada74816c..35b231f39 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt @@ -48,7 +48,9 @@ class CallControlBlockingClient @Inject constructor( override fun getClientCapability() = BlockingClient.Capability.BLOCK_WITH_PERMISSION - override fun getAction(address: String): Single = Single.fromCallable { + override fun shouldBlock(address: String): Single = isBlacklisted(address) + + override fun isBlacklisted(address: String): Single = Single.fromCallable { val uri = Uri.withAppendedPath(CallControl.LOOKUP_TEXT_URI, address) val blockReason = tryOrNull { context.contentResolver.query(uri, projection, null, null, null) // Query URI diff --git a/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt index 33be2b182..6ea3f9b94 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt @@ -31,7 +31,9 @@ class QksmsBlockingClient @Inject constructor( override fun getClientCapability() = BlockingClient.Capability.BLOCK_WITHOUT_PERMISSION - override fun getAction(address: String): Single = Single.fromCallable { + override fun shouldBlock(address: String): Single = isBlacklisted(address) + + override fun isBlacklisted(address: String): Single = Single.fromCallable { when (blockingRepo.isBlocked(address)) { true -> BlockingClient.Action.Block() false -> BlockingClient.Action.Unblock diff --git a/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt index bfbc65a0c..84c927e5d 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt @@ -55,7 +55,9 @@ class ShouldIAnswerBlockingClient @Inject constructor( override fun getClientCapability() = BlockingClient.Capability.CANT_BLOCK - override fun getAction(address: String): Single { + override fun shouldBlock(address: String): Single = isBlacklisted(address) + + override fun isBlacklisted(address: String): Single { return Binder(context, address).isBlocked() .map { blocked -> when (blocked) { diff --git a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt index 97534f8f8..6f9a2fe40 100644 --- a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt +++ b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt @@ -52,7 +52,14 @@ interface BlockingClient { /** * Returns the recommendation action to perform given a message from the [address] */ - fun getAction(address: String): Single + fun shouldBlock(address: String): Single + + /** + * Returns whether or not the [address] is in the blocking manager's blacklist + * In most cases this will return the same result as [shouldBlock], but it's possible for an app's blacklist + * to be temporarily deactivated, in which case the results will differ + */ + fun isBlacklisted(address: String): Single /** * Blocks the numbers or opens the manager diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt index 0d1ff0838..e67c7a798 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt @@ -57,7 +57,7 @@ class ReceiveMms @Inject constructor( // to check if it should be blocked after we've pulled it into realm. If it // turns out that it should be dropped, then delete it // TODO Don't store blocked messages in the first place - val action = blockingClient.getAction(message.address).blockingGet() + val action = blockingClient.shouldBlock(message.address).blockingGet() val shouldDrop = prefs.drop.get() Timber.v("block=$action, drop=$shouldDrop") diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt index f2b4c6d34..c8fbeddbb 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt @@ -49,7 +49,7 @@ class ReceiveSms @Inject constructor( // Don't continue if the sender is blocked val messages = it.messages val address = messages[0].displayOriginatingAddress - val action = blockingClient.getAction(address).blockingGet() + val action = blockingClient.shouldBlock(address).blockingGet() val shouldDrop = prefs.drop.get() Timber.v("block=$action, drop=$shouldDrop") diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt index 04d6daa59..ea56b9609 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt @@ -77,7 +77,7 @@ class BlockingDialog @Inject constructor( } private fun allBlocked(addresses: List): Boolean = addresses.all { address -> - blockingManager.getAction(address).blockingGet() is BlockingClient.Action.Block + blockingManager.isBlacklisted(address).blockingGet() is BlockingClient.Action.Block } private suspend fun showDialog( @@ -92,8 +92,9 @@ class BlockingDialog @Inject constructor( } val manager = context.getString(when (prefs.blockingManager.get()) { - Preferences.BLOCKING_MANAGER_SIA -> R.string.blocking_manager_sia_title + Preferences.BLOCKING_MANAGER_CB -> R.string.blocking_manager_call_blocker_title Preferences.BLOCKING_MANAGER_CC -> R.string.blocking_manager_call_control_title + Preferences.BLOCKING_MANAGER_SIA -> R.string.blocking_manager_sia_title else -> R.string.blocking_manager_qksms_title }) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingPresenter.kt index 749ae6718..4e37d238c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingPresenter.kt @@ -38,8 +38,9 @@ class BlockingPresenter @Inject constructor( disposables += prefs.blockingManager.asObservable() .map { client -> when (client) { - Preferences.BLOCKING_MANAGER_SIA -> R.string.blocking_manager_sia_title + Preferences.BLOCKING_MANAGER_CB -> R.string.blocking_manager_call_blocker_title Preferences.BLOCKING_MANAGER_CC -> R.string.blocking_manager_call_control_title + Preferences.BLOCKING_MANAGER_SIA -> R.string.blocking_manager_sia_title else -> R.string.blocking_manager_qksms_title } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt index 09b748bc0..71e66aa21 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt @@ -118,7 +118,7 @@ class BlockingManagerPresenter @Inject constructor( .switchMap { numbers -> callControl.block(numbers).andThen(Observable.just(Unit)) } // Hack .autoDisposable(view.scope()) .subscribe { - callControl.getAction("callcontrol").blockingGet() + callControl.shouldBlock("callcontrol").blockingGet() analytics.setUserProperty("Blocking Manager", "Call Control") prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_CC) } @@ -143,6 +143,6 @@ class BlockingManagerPresenter @Inject constructor( private fun getAddressesToBlock(client: BlockingClient) = conversationRepo.getBlockedConversations() .fold(listOf(), { numbers, conversation -> numbers + conversation.recipients.map { it.address } }) - .filter { number -> client.getAction(number).blockingGet() !is BlockingClient.Action.Block } + .filter { number -> client.isBlacklisted(number).blockingGet() !is BlockingClient.Action.Block } } -- GitLab From 6b9469757e4b2dee31cd7721303a1cac3288d31b Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 10 Feb 2021 21:33:38 -0500 Subject: [PATCH 173/213] If we can't access the blocking manager, don't unblock the conversation --- .../blocking/CallBlockerBlockingClient.kt | 36 ++++++++----------- .../blocking/CallControlBlockingClient.kt | 18 ++++++---- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt index ca57497dc..965658a99 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/CallBlockerBlockingClient.kt @@ -25,10 +25,10 @@ import android.net.Uri import androidx.core.database.getStringOrNull import com.moez.QKSMS.common.util.extensions.isInstalled import com.moez.QKSMS.extensions.map -import com.moez.QKSMS.util.tryOrNull import io.reactivex.Completable import io.reactivex.Single -import java.util.ArrayList +import timber.log.Timber +import java.util.* import javax.inject.Inject class CallBlockerBlockingClient @Inject constructor( @@ -43,31 +43,25 @@ class CallBlockerBlockingClient @Inject constructor( override fun getClientCapability() = BlockingClient.Capability.BLOCK_WITH_PERMISSION - override fun shouldBlock(address: String): Single = Single.fromCallable { - val uri = Uri.parse("content://com.cuiet.blockCalls.ContProvBlockCalls/lookup/is.blocked.lookup") - val blockReason = tryOrNull { - context.contentResolver.query(uri, arrayOf("result"), "incomingNumber", arrayOf(address), null) - ?.use { cursor -> cursor.map(::LookupResult) } - ?.find { result -> result.blockReason != null } - }?.blockReason + override fun shouldBlock(address: String): Single = lookup(address, "incomingNumber") - when (blockReason) { - "true" -> BlockingClient.Action.Block() - else -> BlockingClient.Action.Unblock - } - } + override fun isBlacklisted(address: String): Single = lookup(address, "blacklistLookup") - override fun isBlacklisted(address: String): Single = Single.fromCallable { + private fun lookup(address: String, reason: String): Single = Single.fromCallable { val uri = Uri.parse("content://com.cuiet.blockCalls.ContProvBlockCalls/lookup/is.blocked.lookup") - val blockReason = tryOrNull { - context.contentResolver.query(uri, arrayOf("result"), "blacklistLookup", arrayOf(address), null) + return@fromCallable try { + val blockReason = context.contentResolver.query(uri, arrayOf("result"), reason, arrayOf(address), null) ?.use { cursor -> cursor.map(::LookupResult) } ?.find { result -> result.blockReason != null } - }?.blockReason + ?.blockReason - when (blockReason) { - "true" -> BlockingClient.Action.Block() - else -> BlockingClient.Action.Unblock + when (blockReason) { + "true" -> BlockingClient.Action.Block() + else -> BlockingClient.Action.Unblock + } + } catch (e: Exception) { + Timber.w(e) + BlockingClient.Action.DoNothing } } diff --git a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt index 35b231f39..a22632fea 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt @@ -26,9 +26,9 @@ import androidx.core.database.getStringOrNull import com.callcontrol.datashare.CallControl import com.moez.QKSMS.common.util.extensions.isInstalled import com.moez.QKSMS.extensions.map -import com.moez.QKSMS.util.tryOrNull import io.reactivex.Completable import io.reactivex.Single +import timber.log.Timber import javax.inject.Inject class CallControlBlockingClient @Inject constructor( @@ -52,15 +52,19 @@ class CallControlBlockingClient @Inject constructor( override fun isBlacklisted(address: String): Single = Single.fromCallable { val uri = Uri.withAppendedPath(CallControl.LOOKUP_TEXT_URI, address) - val blockReason = tryOrNull { - context.contentResolver.query(uri, projection, null, null, null) // Query URI + return@fromCallable try { + val blockReason = context.contentResolver.query(uri, projection, null, null, null) // Query URI ?.use { cursor -> cursor.map(::LookupResult) } // Map to Result object ?.find { result -> result.blockReason != null } // Check if any are blocked - }?.blockReason // If none are blocked or we errored at some point, return false + ?.blockReason // If none are blocked or we errored at some point, return false - when (blockReason) { - null -> BlockingClient.Action.Unblock - else -> BlockingClient.Action.Block(blockReason) + when (blockReason) { + null -> BlockingClient.Action.Unblock + else -> BlockingClient.Action.Block(blockReason) + } + } catch (e: Exception) { + Timber.w(e) + BlockingClient.Action.DoNothing } } -- GitLab From 1e69a6a000d701d31c5155206649572deefac116 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 23 Feb 2021 23:04:19 -0500 Subject: [PATCH 174/213] Show app icons for blocking manager preference --- .../manager/BlockingManagerController.kt | 46 +++++++-- .../manager/BlockingManagerPreferenceView.kt | 93 ++++++++++++++++++ .../ic_blocking_manager_call_blocker.png | Bin 0 -> 1994 bytes .../ic_blocking_manager_call_control.png | Bin 0 -> 5429 bytes .../ic_blocking_manager_qksms.png | Bin 0 -> 6517 bytes .../ic_blocking_manager_sia.png | Bin 0 -> 4733 bytes .../layout/blocking_manager_controller.xml | 15 ++- .../layout/blocking_manager_list_option.xml | 8 +- .../blocking_manager_preference_view.xml | 76 ++++++++++++++ .../main/res/layout/container_activity.xml | 2 +- presentation/src/main/res/values/attrs.xml | 12 ++- presentation/src/main/res/values/strings.xml | 4 +- 12 files changed, 233 insertions(+), 23 deletions(-) create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPreferenceView.kt create mode 100644 presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_call_blocker.png create mode 100644 presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_call_control.png create mode 100644 presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_qksms.png create mode 100644 presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_sia.png create mode 100644 presentation/src/main/res/layout/blocking_manager_preference_view.xml 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 7c0b392c3..da9783e07 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 @@ -2,11 +2,14 @@ package com.moez.QKSMS.feature.blocking.manager import android.app.Activity import android.app.AlertDialog +import android.content.res.ColorStateList import android.view.View -import androidx.core.view.isVisible +import androidx.core.view.isInvisible import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkController +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.util.Preferences import io.reactivex.Observable @@ -14,12 +17,12 @@ 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 kotlinx.android.synthetic.main.radio_preference_view.view.* import javax.inject.Inject class BlockingManagerController : QkController(), BlockingManagerView { + @Inject lateinit var colors: Colors @Inject override lateinit var presenter: BlockingManagerPresenter private val activityResumedSubject: PublishSubject = PublishSubject.create() @@ -35,6 +38,18 @@ class BlockingManagerController : QkController R.drawable.ic_chevron_right_black_24dp + else -> R.drawable.ic_check_white_24dp } override fun activityResumed(): Observable<*> = activityResumedSubject 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 new file mode 100644 index 000000000..8abce241a --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPreferenceView.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ + +package com.moez.QKSMS.feature.blocking.manager + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import com.moez.QKSMS.R +import com.moez.QKSMS.common.util.extensions.animateLayoutChanges +import com.moez.QKSMS.common.util.extensions.resolveThemeAttribute +import com.moez.QKSMS.common.util.extensions.setVisible +import kotlinx.android.synthetic.main.blocking_manager_preference_view.view.* + +class BlockingManagerPreferenceView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : ConstraintLayout(context, attrs) { + + var icon: Drawable? = null + set(value) { + field = value + + if (isInEditMode) { + findViewById(R.id.iconView).setImageDrawable(value) + } else { + iconView.setImageDrawable(value) + } + } + + var title: String? = null + set(value) { + field = value + + if (isInEditMode) { + findViewById(R.id.titleView).text = value + } else { + titleView.text = value + } + } + + var summary: String? = null + set(value) { + field = value + + if (isInEditMode) { + findViewById(R.id.summaryView).run { + text = value + setVisible(value?.isNotEmpty() == true) + } + } else { + summaryView.text = value + summaryView.setVisible(value?.isNotEmpty() == true) + } + } + + init { + View.inflate(context, R.layout.blocking_manager_preference_view, this) + setBackgroundResource(context.resolveThemeAttribute(R.attr.selectableItemBackground)) + + context.obtainStyledAttributes(attrs, R.styleable.BlockingManagerPreferenceView).run { + icon = getDrawable(R.styleable.BlockingManagerPreferenceView_icon) + title = getString(R.styleable.BlockingManagerPreferenceView_title) + summary = getString(R.styleable.BlockingManagerPreferenceView_summary) + + // If there's a custom view used for the preference's widget, inflate it + getResourceId(R.styleable.BlockingManagerPreferenceView_widget, -1).takeIf { it != -1 }?.let { id -> + View.inflate(context, id, widgetFrame) + } + + recycle() + } + } +} diff --git a/presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_call_blocker.png b/presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_call_blocker.png new file mode 100644 index 0000000000000000000000000000000000000000..0ebdcfa680b296a210ca38698571cea1a3ba60a4 GIT binary patch literal 1994 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$b_Mu^xc=S83`Kwc{r&&{ z?=FVFTN(cT{_yu8|KBYPfA=%}-NW#A6T{!l41b@_`}=Is-|Y;4pUnPy!RPNjhQB)* z{_bG-`(nl4bqs%BZ~MEC^Y54Ie-F$5{r>3hS@*w(Rsa6^{P#i6-+T3cFNOa-sr~oE z@xSj5{JmN9_e%WVN7Mh_sr-9P_wRn;zyE&!{rT$ejoiN%0{;H__V?SJzn?Dsy_Wj- zoa5h)q zw!NM%jv*Dd-rmU$eq< z%Vb8efGrUY=TN1Ws0bXI+M~0D;U(AJeNwDe3d#XYD%Z~)j%0t3XB2uURYj4d;Hp_H|@&2wECC&YaIEfx0v=!KVYT*rADSsqkh{Us1>-^b9r&A>oXgW>(^e`me9 zPx~uf2`d%-@upz{L(JQ|JsW@JbFO_D(y^CQgyCdp#l?PI`+wPUuY2#ko}$3`W1iO8 z$*0+xzy-TAYok{%4}n%Bx-ew{b9{1Kz{ z{j61cdlow|PG}VCb`1KtwJf$U&Z_nOv(L#pR~q@S2{p)vd4G0vdVgoyZKvXsLW~yk zPjl<18ckrR@i%?GU+9$mTeGXrzJ0c4=)2mKeQuHyHOhKHt9lKjt9u$ZA&)K>8Q}eYO7a|!fa(D61E0b(v zc~HH}E?;%azsFZYxaCiu=Q+@-?PACKAops%>KDIylcV=K*_Q~PP=j;!jl?fa4WHzigdVl@YkK4bAI9M%ImpEpzfXDE9PQ}WP?|vj4U^Prw{!#)WChDCEZmETx0HN2jx$?!1G87#CC$>T7BGVythQ|#BwYyn)3gp!ockD@&5x6Ut zxG3ZP#-m?mRx;H2H&oZy3MQn=JYeJf^WWZ&QOO}-R%2qrj^lh*$EOQ3#{4^Na6o+P z3y}l~AqL^(!wn0zxEzo_XyDS2%`ll^rCUV(|I2GNcn|Q->t(7~qMo=yvW?@wm8qT$ zS1ROLE_{hlabYOTEn)eRFVkS%|6%WCiT8YG7#B@verA2!fYo6YcQHe}`?U+#&h5UD z;nwh3Qi91t-HIXB_0Jy$zvN(s@GA;W`L#qEHe6v;W?1=;f$7w(bZbRM4^uPd2XRlH z+2k>FFr3-FhgFzC_NA-Dk*oeJ6Rv7AGrXLl#LJQ`Xx!S#ptC~%VFCj``$R?!Z834B zhLFk4jGL?)U;H-}vg%+vP;sN-Fhh{d-yViZ^@0l+J?`4^C^!qoHb&VL3Y&2sSivR3 z^k9)t?t<#MHqDs~K_>Z$^$qcJ9asX2W2Q+g5}dGVyZwRCy%~QuSDo4{BEs;@ius0k z!`J=nRa#5ygc^938{go55H3FdmHl-cRTT!cQ0WBzhW}50nKxZ=?_4==<@$9DL5!JS z%a=91nEC5V!SYGs3U^;U|MgJmGRp$S6At#ydJQ~tu0H?uJan4!F@_z|HcosEB8&d4 ze7!(vQ|>_*2DPhCHJ31LsMVWqAOF8gc#>StM7foL6PPBUM(=eDp3a9e~k@t-}+z~dS)MR=as>)(-U7>>PK_** zOZivaS75k4<6^E%Vj27Hr-zG!#a~U>P~Nz@aB>$5L)rc>T6{a|jrk9ly-KrMw)S#Z z`&N6whLa9kerK{YT>pBIeal1!v(vQ(FKgTxUc_shclnvp?ix1x`2-h+3|`x};@>x% z;BR__WOc@xMeg*i1xH2#>Fckl1DEiM( z3Px4`8Or}NWdDZ>mj7qS|Id*3pP>vaQu3dnNXIKb!fA@co?bE@on);t%%72Dxu-_+w zEo}jNyzW0k<$s3S{|u|aK~eahAqVV*wO}V@{b$(npJCH~hRq-czyJQ9VZ(ohjsF?e z|Nr~;yp%m4Gw`G5c8f1}O+k3aw4apHge;r|TV{s*l2|K`L0 z3(x-_eDr_K{r`PO|Cb;6AF}zs;o|=c(f?n4{XhNO|LPO}D-Qfm*!jP5`~Rd(|J4@! zm!0!pWa58;;{U&X{=fC^|C2ZWFTMVM_xb;m_y6y>^MCH0|7&mlUv~X}*SY@_PW@ke z^1sur{~XKzdo2DhHS@pQ#Q!oA{|op2H}C$h+4^6({{N2W|0mu4zvue@`B(n0x$r;r z(EkYs{zvcs-?sOE&hGyVyZ;w#`R};)zsB1CIZOXbFZ~}g_rJ=_|Gv}zbGHA_-1uK& z!+*2&{}tB$=bZUppz=RM=zs2&I=>hg7|Kh6{DK*p1wXx#7vtP8fz?{-_qCl1nlpSH zba)T!-M+LvLH6OgRa5KgssaPuwKY{fynp-V?D0iKNv6gJ_iic94fJqwlKArZ>EpwD zHZ5N?XU^1~&WIo1Zy%aIttrCBMD^F#SI@3qJhFe=mhM=?zdznSy?y({p-sytbk&vw zIEA`-M>8-87JIrlhE&{oGwb<#??8#;{d<$&@BQAl_xrtS7Ds{3j+>7bg}*M+&%Jxk z@9Ltmq+?>r-#84D_9*Q6>{X$>=fkm`OXbeJEIhZd)Bg|S{QUW)x8%Rfub*wi{OhSp zy|HP?R<>W&nx|p!XX8L*2{&pi>shtK|ECs)(n`V97{$}x<&r+}Sj2dn~{#YXW)7(Yl?)r(| z%b9O*nyd}nzwt%?vV@bL9Bo&m^EX63yJMfv2-dB4q^539N9HSmikUcEGRbJeyNKW>L*AN*B%d;8hF2Tcw?{1DF6U@v%} zd-aB_r>DO9R&9L|p)dby>6DYfD_piNb2iKAaQnD_J)6NMmJhcX_q=3S9i|(-z3A?) z(8;SFiKJfeS5JSrvHO{Lc64fniqOpuj8+YL!VKyW+wyLnn(7_CUiyA=*8GX8$FBSi z>|Yk;x>|h2jK|8N+#&kY_UCSX=W2FbdEtq+UTI^_MP&@aOe{MY7c?6&7POSUE@<1j zdiuK9O-Y%PEiW(oDrvZ(qOFnHohQCw$;s)r7jf1e_?FJjP(P93v=L(g6OZ4*V^22gE7ph;c7|4l;zuN*eCvb!=Pq(*xv4dUh<+G{;QmZUk=9CwHg1r#8|dV zsC0sisFT=>!wiR6img2K7%Z7m8tzstnvudD${Bg!`7am7LvQ#V&1d|O&gPN7C?cpc zeB$ecVG9Ko^8cH|6rgXhj)@8NkJhLQ=2CWq%Rq#at-nxesV zX^GuMhdE3h=MCmCa!j}*=NI9+bl#7J*O}!w4hzNIF!VB-&gE4@n? zD=(z~Jk0mBd_qaX+BVq(tGZoI3ZCS9ars{f;|*mV}{F!qwgl;k@?QK8&ubx|M}-7Q^E|^AGd<1aBZ*=I?iae-Lc6;Vpk*M zkHbt#QXRJ)KQWfAFFPVTBc^hK#N0*ko7>OF?_-!XKW5Pz1_LI|J+8Um^u%gUe*ALt zUlZfCVs{3nlx-Smib9KFOu+jQ!xd(Eru7e)8kyj>r`e$JZH zZ)Khz;||#^waZ^`W>_jX|J>x#qiu@(zgp^FY?=IQP15hntPSY`uQ?8IBZDsq|0N;jvK1c@4qZOYz(NtY+!cOp1;}0MHu2;?VKW_ef!+4I3 zv)+&WVt7ugG9P10owSsINS54(;|CaoW%%pfo!4Zz>NsQVKV7~9tNMPJJipmo$M*T9 zT^d7gj>1Q#gl)@3Bp46A{~B5vJt>5}h|aEIRD{(v0v)X%d8Q;KOYlT zXx_$pIbFhUV*~@k9?#kj;fLAh@Z?qAEjZL6trpZU*ZYz+M~1F=gD(HC8NB(CjCt%f zF3b~I6J$1?5p8at+;Bxr!xjaK>=VhN7?}mN@yRx8o(?w?( zmS1_V;h-}uYUYhUzKlOsG$gvjZ(_O=Zu*Q(DW9SCNRlSAm4_C?eYr1&iD?bbR~`HE z-2cG4rv)r3+X5;Dt1ld%5gy5?x6AJHbiD&%@{tUyS0pg(vw0z%_tZlBfOhz?`F9=8 zFsV#Vlt}D!4l{l1-evsdZr)Rw!~dJSVi-PUGyJ%$t+6dvwr9hc;6?LKv46>(l3l&Y zR{5KJsBN4ZvthUf%Z+{BH=ihM_|FvqPqFUe5R zCS)zL&zmWX@ksiFgWK6_rLXr2$Nz{go7uHaN>1MNz#q;AKChP_CQSR~6nyx>?&f{H ztK|MY%1$}*cDv%Ahb-Mb1v%HbG#FS;D2hB_USgTZUt#l%S$)I*nG0*bd_Tk_bjLv; zVRkn6+xrR8#eCB|L3(eK!NQ`it#xtVIc_pDe(kp1^?sM*8U5u9S6FoJ z@46J{X!5sMiP7uFkv9+5i|2iv^0`~6V*hdZ{Y^n?dJJcuP4j5ECAZJ!#qEq|i}m-N ziZ4I>Ph5gw7sG3F%S}P-od(C=K3v^w`|bLrU>=)G8*P82zpwws^4>2`W!}P~`X{F^ zUsZN_FnRy7x}S4ye>}qcR`~^gKF4qN%&53W_0>=2tnECNEnmp~PCzw6^))~5lvAHN zCj4?X*V^xOLhjGsS^o}rPHD)izdhfcVWUc&TXZC|OO>+WFSa9A>dba~4xT=zU$Mf- zuKt5M8$;fP{)-Ne{->^0EwAy|$Kwt5ISTK^ekkee zY-xzITjSoj`HkVduWUb=-%EXoIVM3rt=cViGYensEq~DuccmAY4vy9b? z>EHTQw>vVQJZ}(i_Ayi@4*lIlcqKuPbLNF>F;^aDbOXt z|3vQT!O4@hZ#GW;cK*iJ+kLj|mj#TikI#$Uez(+GY5J9k9y?XG2b}sl>rPOI(f_v} zF6W>5Z@<}i-`w`kKW6?sU;pp_&AT(Fl}|l!?(=4|yGNWSEZ+6w&taCZ@0Pn0IX_?9 zD$NStiFz;x|Y&o<% z+1A@%*0Z8b;dS+|gX?#hE6g}x*IFZ(cSx*GYKgjbc_JFeRgw>t1JLA|y&7#j2zg)n+f8CR~2U{D0YJM`y|B3!~ zfLG&^8^^3Ano{PhFC$Afe|Ot*^Zl2@tRW#eAKa_O8g}(BR~^6m*2 zc$df6yl6b1^{=hH;ne=WMzy8SzT7O+{d>ILNcu!RpW4G{m6uL?`@XCZ`N#Qwbn{}&t)O+w0~8{-vzJS<6Y{f z)#@a$pK-osTc(HG_wK5XANIzcu09`i;7{b-pVqs-I)u$lGT!yyy+-PoZ{SLMIj6eq zXI~sl$gehDDrNou!o%NN_DEifuM5k+6kwdX?oRc$*GngdSRd}WuyFU=`1=obU*4Q+ zuWLNvzJ!ZZ_Ew${M!6q(o^OTgcG*-IJbZa)%VydavH} z<<>eA$)x{Z@|GNYc8baJMdNv|?FURAl>RNe+ELHt>^(g-H8Jvoahyi;NBHm*gX6?~a{j%Mn~ebKqF`HTPYBuS>jZ+w+ZVxHbG zl>4Le^zc2G2D`9c3-e1mCJMDB+gx+@`mMO}|AWV~61cvoPmEGkG&@xNBS7th;GNZ5 zKK=d6(P_cr>2kd6!sC#ou@$1WBEF4*DoMt&XO2~@X+Q4vQP5_6WX=C5p{*0Mj6~#q zd{zEYFwdSLRQVH+zIsY1q_cocmF>Ix%GZ zox}gOZ|yq4AMfp!yY&{se}{{K67wU^M18$@di8?)zkhjNJytiza^8_^HKr>2_v~M? zEoZCb{&^RpR@(ji^X=!?;N^LH_I&y2zV%v4$NZMZ*-mPQH*RKlU$$V2{#@hl26h!u z-%CoatkDkVn%cLj_s7Tc33u)=|KGwa_rouG_Nukp_r&kqk#Tz4+on{5y&APdG>72=jGSq zt97Q8UG%k%sm&I#*i$W69((N#ljX7LUVcHhH<)gc(_5gY@=a~^?mcDSH)(FQoU|=7 zdNuE_6LrZZKl%S=y|0U!&2h+}VgIb$GtbV8?K-KdGGS8IODB&7FBg8j(yhMm!NJw( z2Y-8CS)lNPJ>*FH^eK6pj%{1=cg8x8n=FfSC(XPY`Qp8>jj@ff_4KE;HFmYPS*B0E zJYU@L{n_6I&)pACT(4C7W%DGypXvuI1z09u-n_Z{Wa|F!A5VYZe*6*t@xThx3nwRw zWuN~o|7-8`^>+2O-(P;+e*fa(-u^?Yo5JRr+_aFJ(_t^w-+owQSJ5WNTQ}-v%$8g; zF=vun?$3i&p0i6n>1+twU~_4eLBiT4KC|93ui@Y3vHR@J;0cSEYnC!zb14#O-`%%C ztYDJ}_j=ZA47&YEo2E0ao_=%jjS~KYT@N>S8{QI2TWW|NvZGDsEmi*ZFMF`-SN_C{Tzvm- z%N<{Ex_;lExtHv#TY@fc`T6DZmFv&fGens0{CoM1_<{Q$Wv5?ytiXKc*m;?NRViq>{an^LB{Ts5&uq%| literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_qksms.png b/presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_qksms.png new file mode 100644 index 0000000000000000000000000000000000000000..cb53b2df9c27a1d7aab23967b874b46606c1a058 GIT binary patch literal 6517 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@xMeg*i1xH2#>FfcJOu&^*N zGc)k;GH`G*@bZBW8yf>JKf|=845DHT{DKTZA|S-e&mbVkFm(xom;}R;tqdaK3{#dc zOkBv&KcAtnmchn}L0F8TXEwv+MGS3I8A=-&aw`}XZUV`6Ok-HNn__XrIQ=Jc%K%lA&n=Lt-w&n!OC2GeGL6E@7Cp3S{8Y?F{8DAa+(MLqH_R7s0U% z0bvXs(?E(BZe(ceW5_IKn7;h$*RLQiPh7+>c`?Y|zPTX9QBo;BA3l6wsBB}X>0&6V|MTMqV{YZ2zkeADY92gzc=hU4hPms0eEatH z&6{7pe*OCK{qyI~w{G3KeCg7sPoEeTY-E_TmLa?R)ytO`&Y%DP`}fD6zph`ue&xc2 zlV{HEJ8^R5fkV&Ve>ng0?XF|Tw;Vb4@7K?_-@YF?b^6GY7b`AYaof08W#T-Bj8ew5 zqU&$pKYIP^Xl^UmzcHz!=UHtpEysoQo1En9#6`ODR}AKbfk_12Xubtf;( zK6Sq2$eGri`+B$Rh*`J2Y}LlVxyusgF7}u_OSo$qS6%nkE7z-b9#C7n(Rj%Q=E;lA zrp%S^oXXiW;rxS#$FAMjaq_hP+HL%;lO-n2*O5B29F!SKg8YIRnAutRxs-%><$j1t z*r`cde$xGC^ychtJ2TB?mnVdKe&3Y(=h=f>>$)4_!u;*NyqQsw{_(^0W2@)X738Vi zyLRE|!N^y~mQG0W)A(_H|Bg9r^=@ZRZ(TNf>g1-zigZsCS<9;nCm3DMzaG!PAbQl( z#WAGf)|HvQ9wLbn$8VSJ&k|qibo<5Oew{nsnXOJ9u2q^=At5do)c0FRMEawx$S+|qRysu2ih;!veK(~ zyU@rzH8b!hlMe5e1$$O(xfEE&;iKcW)mCBm&9L2PH?H4x?!Wbfln)}MSAWag&dN=@ zs#C&t`&+K4^=pMKMGtn(6}`RfwvOd=1E(KBc>*?)+iSKV+Ji zud=^owJf|KdV@cYQ$ds?hmn10`N>Ut*U0VL>-AvXuT5{g`VJ=82Rw3L_2_=VfxG~X zeLHt7V?WDiYg)Eh=&bI#mxnU^4#b)sjnu!?$$U41@1wR>6Ev0raKDz9~5|K<_5cm7CWpS##=a^_L2)kdZ@u2F4bs~b!E)3_BL+)w@+UblCfc*o-E z@O$?Q!Kl)QX?Ry=^wLKm$6yJ`eV{M5AJ85HDNew9y0Om6$Z}rCSAPB@)r~(66x8c{{Py&Oz~exVMJa)^ zymx3dY-;R{aki?PH(9FNlOMlti;-iMVG8{f$+WDhXH#=t3ID!c-j>!K4-2f_ zRSx~RU&#J{G|9F}(%NKy)I5f_aY%=Uew2(?h@4ti1jqP4ee{)yIFnr8u*1cmKQTTE=G0txT8r1KD~v zeE7LC=VRNUH#Z)bzhtx3R6Q{{j`7()Ym1savLV+>3VbCNUKVNG!}82a$MQeR^!Y(7 z`Vut`LA(V^EuV@_nDc+>hJVGG6N-eNTvzmTsLT5+sv?wPZy;11Fx~#{LxxFA&G$b3 z6gAuNcuJZ;gw|)N#dnpvp0{gle#74{!kn=&?wWUBnd(i2C*ijg4pr?p^ON?dJb%+` zW6Pg20drdaoDxpD-{ZCOV!_I{-xasSc>TM`BE7K4$+~)Tr{fcy|Ft|b%@m3lzqkDO zssG<(Z*^4DLAJ?HoINxg`aU%~8hl#QI<1E1>DnVle!jezI89;ypT#deeovca!WXA$ zqt+Apgz4P$oF&btWCg#yYgG$kj!6}XbJ8{_Y@XkCI7IIwTXp`{iBb_In_6uImIoaqbORy-*(%2#hgomvenm~S$7(A9uS(nB%6Wlg8Y)A zUh^M+_M9*0O8nG)AjD(Vf4^$=sl|&HR@>c=RBCpb{9%TgY=d_z;|Jv(53H+~F$zm? z@V`1dqky3~dF~XhFJJAL8;zGA;WhYgpC40((CSvs5roT76?jr_TXn&VGx~9a6t){=J!b`4L;iWI-04-3Fd@w{FTW4N;o4 zgMnGV{J?GpK^Tgv5woY+5@U>(?e^Bp>56d?z9FW{nVI=(^$nU_m z7MBHyhpJnHde8khZsy1!a^S)I^_I88d^R|qJ^x$d>xa0#m5eOQPGmG}FJfYKWns$m z_i<%h$nuD7$BKq}Uttc5{e0y?T9>{*o1CvzpPlW-d}jah=j#u$h)Q(EFAZ7p`IzlO zmbD5DY`vMhAEe?EOvUQi=g!&lXlclkS6k0dKkra;LjQrIMel(uzXKtD3Ky)_C9$@M z#W!{L^v}P^FnzW~qf;ScxvkEF8QE!2@|h;eo4?Oq1r2DVK)|}b?9H1;h504YLedgP1{;qDco`Kv=yHJY@q zC#g;LOir8Rkv7SqO+sm7h)0hJze(!~5z&bp%ko5`6g$>7xo%j{;o5cn)uHW8>F?$~ zv2dFsU3hH2@Gjf?d!O6)E54N}X851*zkWxf!Cpq5{f-TqOBgL880N+QzRwUR>nO4} z=C<0iz1!#A;5GQvFyjV?1E_<&l*4c-dNyNB|0Ah}_(dNde{}vcg@I9Q7V{6K@Ty$a6I0b5 z88N&#-v3kIDJ1uq!dn3ixKQfDby8Gnr7 z!TP#wrE^}(db>$8RQ!{$6WzbF`Q=fuJ(8k_;@_A!YJ7bWVWQ*N#-;LtMQ-ka=MTS~ zyUeg{BZCWDjv-@}#H|LEMCQXkq#rgjFvP!`wI`B?|60s7?&~wNw(~I9&)?s0d_8l) zoppYH4$btGoFo^s&9J>nPEFME27`d3E?dKo+B;Fa2X;bX~0tmFCR zCtGB4JQYkD15&1^=7&D#wa zN*X!urGCD~^g!n0yp##@ybbSEKNedZn0AxFpqQ1L;Yq;PKbq5ZPxT%Zo#m($%TSW| z%Y-593`;|%He-WoyAX5hp3`h!r?ovw5m@w?pRd5As_KtO!c!B58M`DHBDgYl)UC^L z=lj~{Q|7)^kL@%6H2DgJnjaTFFK6gp?#HNWd4TBvw`1y+K5dSrYJHiKUp_NE$Yfw( z7O?Z?)xS-SAg<_rm)9&v}%HTDYk&Yo|#YwKkgpTpGx>nk zDJF->8XxW0{+>}{`oJI(xVL$$h=NU>+`OlLvI*bZ7!v%HG$++q-b(}gJel^c52MH;YPZM5dqZe(FG zm+$}o+xx`bm6N0od=PAyV|!BJ--Szknkyuu3lvgymMs$LJGOqm)~c-3AM4q>gMNvG z?waBxUO#mYOM^3ukAlF@yROq1O%9%9&J`+P5|G(nRsCg+l=hU{?i@?~c+Vz&0 zIDJQV`R5ePeexo#U-D1?oZN1|D3W1oiuN;s)stE46(-pfKWba`O_cko+3l#6F{j0n zwN~keoe9gDS!5-;YdP=YV?TujOS+u9w?s-^{n@;=I5Ss?>u|xM3_+KLqAp7nb_nq7 zI_0+~*X4r!1Ad8~XIYCB&#vV(l<;x#;BC4l#hht0!NH7kOWvg3Zn34(ijh~R2CRzZ zURQJ8a`Mkfp2k~^?PUFxV!za$FHiLQ#XGm(+fAmWsMRlj_aU8{+OOx+&rV;zZPjUk z*zB#Zdeb)FT(LcB?W}^p*xx^%Zoa?LmVx7@NYjHqCqLS^rfGY&mmT0%4Am(&OZ~Pv zUVm@pfB#qZdVBjq#j?MCeV6usa>9k%uk-3xA7=gb=qta(44VeVqs(!S&CbqGwJttf zox!e=y8M5nMZCs#^C!2GbhiD;8;?%g^L^zEcKx;ocN3Ds z*KHRI|NkJ(FM5C0x8lDCf2Zp_o-@ziqrI%br0UZZ`Px7Y1+Lp)qj*BDZ@YKkh__y3 zW4?{>{;grLeMhyn{_lq!`79YV7V^?tgvBBb#3q?DO zCb^wk&V4dTP>k(v{vGB|KV+l0s+24K%W=N+c zjZcbpmRNC1pY-|m{rh>lt!x^_Gd?}s-=fxh;EnEjLw3INlO4Xf%E{fDdvD#HjL@*; zJ8ao~GGDwr*jG;t3%>VuYWD>Gj}!WihgnYaP?2gC-r@25!@+6YJOw|K7E0|i&Aw># zS>U_#F&&#vKZ;NC=q!-1`Wt_{Xmi}^HBB>^?rqwWdCJ=U_gkllxph{eeUBoyH~eaG zX6WCmTOj_UO`gprWWN`WL{hK)oQtovN3DyS(qkbISkSupBo9Ax(aO8`U%y*(+US?^ z@|#nhex3X7*n-%$DmDA#+v0kT>})uHT>s3=Lo6Ht(xn9^rZRgfM4PtG*)d@PPc(I1NWCO1ROC| z*~PE4ChYc{6dv=#&B;Jjb6o?~DJ#2)Bpw>m|hd6#G-8JHU>ml=IZ@~GW`l7mq`Sq4l{wk}k zDZ4aPTcpA8oV~}4q7N1e%3N+Zn6Ol^zZC2cf2CM|>rc;t&F>i<);Lr({NC|jo=Kqo zkIoi>Lq}^LJkn_pIAHL%K;)KbZk=0k(-ZOBeMt|MSRGDxo>nd9YpPQ-Y0>|8iqpX| zpi1q^>oez9tT?$ikpI#2m8_W?qTa<$K4Y~&U4ixA!it=~jh~H|)L%Lz@Y|MS?z4nv z@8;ZI!>kt9u}s|~<6m%oU0c8uZSDOc?0r*r8wYkd@{P_z%yMx--PH`%ecJVvjrLyaby$sLf z!O z3O%ko?Ra{pchc=S%=s70|NXgDQ1`Flr{u3c)BF;4XfSk2KUWBle;v5`>^y_l2i`9J zBbjgi<@fiKKP6wyf7QGHk%Pj`+zIKH7Y=TGeci{JW7juh2JXeb{UuFy{y)S#x2f@V z-CqvThB(Flxz}pygxD8Z_$^Tl#;4&xeEA3LIan-L)G3 z?w;$ouqt+PtlJlLx%wlYjWZhW%iT-+SGtwq&+Rsmvf1%}T=ubjw*lGe@IbHOLgmt( z+y;_P9GnX{HklrH-Tw4{{E_3&kFLM6pO0b51Wm5MIf;ksr^LGU)Gz)iX=C@__mO!U zUxLUcH`OEi{ytk?#3mDOW6xaq^XKHB|JM3;L`~>$>X5wi&O0>mkYC=TpOP%k^`|^! zS9>@=KFE=q?S23A&2LP1B>#EzbLLW8pUeHdjtdm`t!cV0Rr~F-%4)_d4A0Y#{hvSo z;O}B7`#Jqla*v)bTp+&I@y6Wrk_8R@uj|kLIg`YHIWvl%JuJ}uPmD;&qU>#^J8b6L zpEL5fVEWm3NA*qVrJlbSU*z(B{2ZdND{=SQ7s1~Z??yG<<9wE`(R=xq>y@Hh<`ld~F@M`d@vs&sttDMJ6zCzi(Np>O9d3N|w7 zom^bvSysuPcF6lsQq#FvGY*_Nv_jH+!v2*Srvt+mB@{jV$NV#p_4LWNvx`CVC!Vf; JF6*2UngEN@KM?=` literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_sia.png b/presentation/src/main/res/drawable-xxxhdpi/ic_blocking_manager_sia.png new file mode 100644 index 0000000000000000000000000000000000000000..e922a9569a34a9af79f4ff53bbefb3ac2a4e4712 GIT binary patch literal 4733 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@xMeg*i1xH2#>FfN?#x#u9$ z{3-fp*5!o0?SCL^``l~_c*thb@qgG|Bsw2=4RjCQSp3F zVdMwK8GQv2AM%1f6h*venBL1Ye+uV{na&rMl}CN!Sv5=b*jj;Q(->z?us%GmFya&A z^j@BMGb>^~S{_;MdU9#ny;Y?#pIXyDG0d6be0rJd$wle@9~h@~X>6M(wt6c2^qyF^ z&uRW2lJ2b1-Zh(X$t?XnbNx;)D~tY=erJ8+^*zCzXBg*CN%DB_dUA=?p*c+RrZ7zD zNWQz0ec|-nP7lhtitGRtvitl@$vx~*n&DPmDQ*Ogdo(26=o8RWge~7-e)MW3R zgj>s`*Gwyl`pB|iiv5v!ad(!Kd4J2ivsP>SoMf9XnfJFVY?xc(`7`D2%IF)*JWnrD zUN_C<%#zN$cYJfZqYi9W**G;e_md}4{o{)LKL=9(Q`XmMzf^459so9AlmnD2FVsnNcL`g;~|ESFKZASq^q`sx-a9s{EM024 zWrgnE`C*e!CCxqSv1pIivDIE@7x2%YnFFbUq88T*QQOHpKcymvwXpPE2B^EoQ>iPR0R8M+Wv7G(7Pxh~wZ)5|eicOJGvfi89&~;|P zI$<@9IAcf4sHItt;~(>AsCBMy|KT4Iu$q5Mm$~et|0mc>XGHq*f86uZH=y<5Zo`MJ zPcj#@sOH@ zQwO~7Icb`0pC9cVt)cp)>}=pP9f#-JLe28M#SY}I_OaABp1Lj6Y`%M7$(5{yD@-<9 z-YRfgeDQ-u7mK%oHh<2Y>Th2@Hs76m?*3}zXTK-$%$9?9}_ep=6^S&oc+kf|-zm|k+*Bj>Y z?Ad$cCfRE~DF0XY`|!WnC!gncTg}Yjo9<)&ZvTtDe<%Nc^ZRD@`A_?+{=R(O6z-78 zcmBWl=4WD;QY4OjoSyi+*d_2n2WKp2W2^}$d+9_I69W#NC9~fg>ML2$y+C;C)-_MR zJmA0y72e6)+gNiTD|Z2 zU;FdFb&ZQxt!DkNed9mdw|1sqD>NkxGo6^;82H}0waZH5L6`-@_ah7q`_Dd;UL^7% z;ZloBNB*^4S#R{^6h8eq%zh%RzqVyv+*~Vl#c$K|T$lV8wPV;*Z*b#n7*FBhj+>K( zo#g+#e)2DcL9jk}!TP;{7weDBVX^p~IO*xXf`1Dc?%hx0V%fd7i+9FsjrR$&N=tVz zJmP1az%YT~HgjX6*!wzFp=YcK%y$1%7#x|8FiPb)7zoH@bWePc`G{%eq)B{B*2pyf zW1oHfN+)v&4+E#c^Uq&P&8w4q7Wn@7kt$W5bKYzUW80gUwRN9uMA9b}DgAkWg6Ylo zuYYsCAB#Gauck0JaCY{Ff@wyK=aO!$i|>5o6=nKfpLfCOrvCx{i;wTNXPZ}h>x~ij zpUwkJKX`A6h}drmOQ^rYu&JSy@yJi>TeBVP+5G&s%ePE@z!UW?c7l`w|A$<|1N+|G z_1)sVV)^zn>sOpF6bs%K*Zj!Y&oqyrvi0CU^N#(t*0pwD`Ch&J`Bwc{*OS))HW52R z?<9QSDx1x+fM-e0(W))?Jare;zF-z-p541yx8GanQVOG3Jfn_Tb(YYrD?Zu_HZFa9 zxS){J`Zw34%hPhE<_RP*6fiTnZH+hmV8-*~@5O7MxXhH7y(s#?5?ax^@#fo=Cppf} z^3^_Y^Tyln+Fji`=~g|Hl+9Y>%3?Qj3sdFG4$+g^OBlbHePTO! z(`VVEkeZD$mZCN*cUxpE3O@5r;GnACM{ABZ@vc@4$KEe*UvPeTSi|9th>eWq4t5SF z8MVqnBh?ofaJvVb5Abf!pHXP|Q=njJnxfl_Z6BFm zB89rHYfjwx_ATtl^p1)D(q&YC+?O@qANiDr)kn=WclrnCP`jkIyu(r%2YhexGS$R9 zc&~B(u&B?!6-8}K6E89RoU&rospOq!w*)di`@4|w>D1LmZU-fAIrSIj8$?C)UCnAW zKGLUZuKdUHT%Pz}?W}CQcV|3K&UzoZb0XUTORuPNlD0|iw^W556fh((Ce&-!{o9)M zd1v?1-D2-ctuhVsBsfkSWv%V}C)**bnZ4w_!UlU5iQnN@8vd`mbou-}+wTGEg>7pU zvvbZd{N)y~o>Z*wdPm{SvW)+mkN4GBA8jhW0Q@IF4|OXM^jV1AO}!AH-LEQxW;_HgNAL6<*%ratoPD{W;txPVH^{ zo4dze(&0iwH^2A8KQcC-etz~gH$65lF56>5XhSJuMteo`1f~QvMhnh=?R>ViSD%Gd zn(|pD1qTJ~6f{&Z{=TlDu8sYNf=I!a(--}}UJ*0-+U68^$w)>=JTmN6>c4u%Hr@r3 zkNkIec=;e(75A+lVpB9WYq4&0{{MVdMct}33k2jGo^7yanejusZ13{pHzM!L9^Nan z*}eV7Lf-rY!vm^+*z8JIP5B{j6&O;nWA*D)+rQH5w)LIMDC|6SrLsfP^L8umo1&Ba z0w3+8LP~x(IbL6FHdXA)r$=5P2C`03e1DGehAHtg*!*8~h?|vl?eYDqwmv9aerK1^ zKQ0|t9z(Imyl3p|Z!m0PWck;AUR=ih!mm{`_w~-Vf43%N0n>^Y6JbM_$?_uWE<9mN zR5RzWJ8Ja6jPR!nf=06xR!% z_X@X6U9q-yt$U8AQC7JAg=Y`L8>BweYw!e|l;>LD8{~glU-f^~p;a?AKU<$*V9Y?J!nZSe~vh2Ty!i8VzJB+k)WX>N3^xw%oHFOY&Fpu@3pRW_D?0P6_JbuWzAk^{;d|?U z{o2-w0_ht}(`CF(&W`6|L2EpVEVCGQL%+Wxz4oi|Y={g5o{%6awcZa%umyxJ#xUHakp zAAysyem(Pe`kFoWxmZ==+-H{#EzbID_j$VRMd_It1?h`|^e0uTZ)E>=>g>J^`$JZ1 ztUDr;$XGFLIrrD=4apVkwt5VC=U?ugI;+pzNH2J$y<2_C&7gv|gWk^yeZ0R&!T$!+i{lL$pQ%Aqf<<-2-Ey2*WK_Ex%pm4cx z{n;aF`Xa&d`q8rw$3LCzGd=pg;#U{Lx8~cfE&Q{(i_swAs>}w#`yCtYQfD21&fwhT z9@dn*Mpp4q%WDrw^(8(6Z+_qUmT>04%pIqFKIfEr=+{ach+LG;`RXzA&zDVyb>Czk z5DSY}UpM7T{jy4<_3vX0OLsCfB?|vHNo@0ZZ5`$lyKnQyA1f2q1pmDK>AlL!#5A1^ z8)j7fO}yz*dev~>TA8RBo|EO$+7j;0U8z0uT93)ae2#Ns?BlYyr@MF$1l*6=FyVppBfqbLVhwpl4g%LASMx7?s3Tb* zoEJN@i)CKfn|PawB8D}pKi1FuebQH&V==?OgatQrXGBdnqSB}1{q>N7U?h{6F1Pm< zA#0_^O(z+vTD~#Nzi}?Kh(SzwHW^+mM720+edRtxBh<{E-!5!a9XBA^?s|-mJi2EfB(zeYQB&0 zs*P8xU0=Jn+wZ!)UdQYI%{uNIvvuc#IVnCL=Ns-?wXSRDsjL_!&)^&*sk2|79jg5O zmbb_-P37d_ZL|C1<^Jn=PM`mIcjxK(6PE~R9&_Ctx4!@0{@V6GJ}f6AXIy9!EtoXv z%YybemFo@uCO=tT@Uxs0h|NgS^6*M<imbF*BHUTY&$?^&g_98(iw(B2&UJhf5-?$w z_wxg4c72Po8HF5IH!EjEH&}Gk|5i95Y`@U%4I1Y~SI)k0vrueH&66Wu9*pO_9yW5#@=0m$oTD{^JIM7+(al>< zey2Ki1|Rdgb!JNBkpt~XWiDQ4ZGADuY~^3Nk2|P~`{?BQdOZ)_c`c?IR~{{y zbGy+h%wd(L9g{Ri(E2M!wX5dz%lWsciLB&t^|Vcjla#DuI_M%^_VmAb00RR9gQu&X J%Q~loCIAZnBLV;b literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/layout/blocking_manager_controller.xml b/presentation/src/main/res/layout/blocking_manager_controller.xml index b85e28771..37d90fd15 100644 --- a/presentation/src/main/res/layout/blocking_manager_controller.xml +++ b/presentation/src/main/res/layout/blocking_manager_controller.xml @@ -30,33 +30,38 @@ android:paddingTop="8dp" android:paddingBottom="8dp"> - + app:title="@string/blocking_manager_qksms_title" + app:widget="@layout/blocking_manager_list_option" /> - - - diff --git a/presentation/src/main/res/layout/blocking_manager_list_option.xml b/presentation/src/main/res/layout/blocking_manager_list_option.xml index 06d4f8274..079757a17 100644 --- a/presentation/src/main/res/layout/blocking_manager_list_option.xml +++ b/presentation/src/main/res/layout/blocking_manager_list_option.xml @@ -18,11 +18,11 @@ ~ along with QKSMS. If not, see . --> + tools:src="@drawable/ic_chevron_right_black_24dp" + tools:tint="?android:attr/textColorTertiary" /> diff --git a/presentation/src/main/res/layout/blocking_manager_preference_view.xml b/presentation/src/main/res/layout/blocking_manager_preference_view.xml new file mode 100644 index 000000000..503f5672f --- /dev/null +++ b/presentation/src/main/res/layout/blocking_manager_preference_view.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/container_activity.xml b/presentation/src/main/res/layout/container_activity.xml index 1f4c959c3..10c439adf 100644 --- a/presentation/src/main/res/layout/container_activity.xml +++ b/presentation/src/main/res/layout/container_activity.xml @@ -22,7 +22,7 @@ android:layout_height="match_parent" android:orientation="vertical"> - + @@ -53,7 +54,7 @@ - + @@ -62,4 +63,11 @@ - \ No newline at end of file + + + + + + + + diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index c93ecf5ef..2fe3e3301 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -289,8 +289,8 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS - Call Blocker - Call Blocker is a program that blocks unwanted calls, call centers, spam, robocalls, telemarketing, private calls, hidden calls, anonymous calls, strangers, etc. + Call Blocker - Incoming/Outgoing + Block spam messages, numbers & unknown calls with blacklist & Schedule Call Control Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Should I Answer? -- GitLab From c940ea337c807e97b0ce4af7d6ba78bd6b112e7b Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 23 Feb 2021 23:06:14 -0500 Subject: [PATCH 175/213] Update translations --- .../src/main/res/values-ar/strings.xml | 58 +- .../src/main/res/values-bn/strings.xml | 2 + .../src/main/res/values-cs/strings.xml | 2 + .../src/main/res/values-da/strings.xml | 2 + .../src/main/res/values-de/strings.xml | 2 + .../src/main/res/values-el/strings.xml | 2 + .../src/main/res/values-es/strings.xml | 2 + .../src/main/res/values-fa/strings.xml | 22 +- .../src/main/res/values-fi/strings.xml | 2 + .../src/main/res/values-fr/strings.xml | 18 +- .../src/main/res/values-hi/strings.xml | 2 + .../src/main/res/values-hr/strings.xml | 2 + .../src/main/res/values-hu/strings.xml | 2 + .../src/main/res/values-in/strings.xml | 2 + .../src/main/res/values-it/strings.xml | 2 + .../src/main/res/values-iw/strings.xml | 166 ++--- .../src/main/res/values-ja/strings.xml | 2 + .../src/main/res/values-ko/strings.xml | 16 +- .../src/main/res/values-lt/strings.xml | 2 + .../src/main/res/values-nb/strings.xml | 2 + .../src/main/res/values-ne/strings.xml | 2 + .../src/main/res/values-nl/strings.xml | 2 + .../src/main/res/values-pl/strings.xml | 30 +- .../src/main/res/values-pt-rBR/strings.xml | 2 + .../src/main/res/values-pt/strings.xml | 2 + .../src/main/res/values-ro/strings.xml | 2 + .../src/main/res/values-ru/strings.xml | 2 + .../src/main/res/values-sk/strings.xml | 2 + .../src/main/res/values-sl/strings.xml | 2 + .../src/main/res/values-sr/strings.xml | 690 +++++++++--------- .../src/main/res/values-sv/strings.xml | 4 +- .../src/main/res/values-th/strings.xml | 2 + .../src/main/res/values-tl/strings.xml | 2 + .../src/main/res/values-tr/strings.xml | 2 + .../src/main/res/values-uk/strings.xml | 2 + .../src/main/res/values-ur/strings.xml | 2 + .../src/main/res/values-vi/strings.xml | 2 + .../src/main/res/values-zh-rCN/strings.xml | 2 + .../src/main/res/values-zh/strings.xml | 2 + 39 files changed, 572 insertions(+), 494 deletions(-) diff --git a/presentation/src/main/res/values-ar/strings.xml b/presentation/src/main/res/values-ar/strings.xml index d545c94d3..0f0f03da9 100644 --- a/presentation/src/main/res/values-ar/strings.xml +++ b/presentation/src/main/res/values-ar/strings.xml @@ -43,8 +43,8 @@ اضافة الى القائمة التثبيت فوق إلغاء التثبيت - Mark read - Mark unread + مقروء + غير مقروء حظر مزامنة الرسائل… أنت: %s @@ -90,7 +90,7 @@ حذف اختر رقم الهاتف - %s ∙ Default + الوضع الافتراضي فقط واحد دائما %d مختارة @@ -128,7 +128,7 @@ إرفاق جهة اتصال خطأ في قراءة جهة الاتصال - SIM %1$d (%2$s) selected + تحديد الشريحة %s محددة، غيّر شريحة SIM إرسال الرسالة الإرسال جارٍ… @@ -201,7 +201,7 @@ وضع ليلي بسوادٍ حالك وقت البدء وقت الانتهاء - Automatic contact colors + تلوين جهات الاتصال تلقائيا حجم الخط استخدم خط النظام إيموجي تلقائية @@ -230,34 +230,34 @@ لاشيء أرشفة حذف - Block - Call - Mark read - Mark unread + حظر + اتصل + مقروء + غير مقروء تأكيدات الإستلام تأكيد أنه تم إرسال الرسائل بنجاح توقيع - Add a signature to the end of your messages + اضافة توقيع الى نهاية الرسائل نزع حركات التشكيل حذف حركات التشكيل من المحارف في الرسائل النصية الصادرة أرقام الهواتف النقالة فقط عند إنشاء رسالة، أظهر فقط أرقام الهواتف النقالة - Delete old messages automatically - Messages will be deleted after the specified number of days - Number of days - Never - Delete old messages automatically? + مسح الرسائل القدينة تلقائيا + مسح الرسائل القديمة تلقائيا بعد عدة ايام محددة + عدد الايام + لا نهائيا + مسح الرسائل القديمة تلقائيا؟ If you proceed, %1$d messages will be deleted now - After %d days + بعد يوم واحد After 1 day After %d days After %d days After %d days After %d days - Send long messages as MMS + ارسل الرسائل الطويلة كرسائل الوسائط المتعددة If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply الضغط التلقائي لمرفقات رسائل الوسائط مزامنة الرسائل @@ -274,6 +274,7 @@ مدير المحظورات QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers الترشيح التلقائي للرسائل من الأرقام غير المرغوبة باستخدام تطبيق \"Should I Answer\" نسخ الارقام المحظورة @@ -320,6 +321,7 @@ افتح + التبرع ل %1$s%2$s شكراً لكم على دعم QKSMS! جميع ميزات QKSMS+ متاحة لك الآن + An error has occurred, please try again QKSMS+ مجاني لمستخدمي متجر ف-درويد! لا تتردد في التبرع إن كنت ترغب دعم تطوير هذا التطبيق. التبرع عن طريق بي بال قريباً @@ -346,7 +348,7 @@ QKSMS قيد التطوير النشط ، مشتراك سيتضمن كل ميزات QKSMS+ المستقبلية! تحميل… عرض المزيد من المحادثات - Mark read + مقروء مكالمة حذف إظهار المزيد @@ -357,12 +359,12 @@ تطبيق لا شيء - Archive - Delete - Block - Call - Mark read - Reply + ارشيف + مسح + منع + اتصال + مقروء + اجابه نعم الاستمرار @@ -405,9 +407,9 @@ دون تأخير - 3 seconds - 5 seconds - 10 seconds + ثلاث ثوان + خمس ثوان + عشر ثوان تلقائي @@ -417,7 +419,7 @@ 600كيلوبايت 1000كيلوبايت 2000كيلوبايت - No compression + بدون ضغط حسنًا diff --git a/presentation/src/main/res/values-bn/strings.xml b/presentation/src/main/res/values-bn/strings.xml index 93a7c2860..6ec138164 100644 --- a/presentation/src/main/res/values-bn/strings.xml +++ b/presentation/src/main/res/values-bn/strings.xml @@ -262,6 +262,7 @@ অবরুদ্ধকরণ ব্যবস্থাপক QKSMS QKSMS-এর অন্তর্ভুক্ত অবরোধী সক্ষমতা(blocking) + Block spam messages, numbers & unknown calls with blacklist & Schedule তোমার কল আর বার্তা একটাই সুবিধাজনক জায়গা থেকে ফিল্টার করো বা ছাঁকো! Community IQ™ দিয়ে অবাঞ্ছিত বার্তা আটকানো যায় সমাজে পরিচিত স্প্যামারদের থেকে স্বয়ংক্রিয়ভাবে \"Should I Answer\"(আমার কি উত্তর দেয়া উচিত?) অ্যাপ্লিকেশন ব্যবহার করে অযাচিত নম্বর-এর বার্তাগুলি ফিল্টার করো অবরুদ্ধ নম্বরগুলো অনুলিপি করো @@ -300,6 +301,7 @@ %1$s %2$s দান করুন, + আনলক করুন QKSMS সমর্থনের জন্য আপনাকে ধন্যবাদ! আপনি এখন QKSMS+-এর সব বৈশিষ্ট্যেের সুবিধা পাচ্ছেন + An error has occurred, please try again F-Droid ব্যবহারকারীরা QKSMS+ বিনামূল্যে ব্যবহার করতে পারবেন! যদি আপনি উন্নয়ন সমর্থন করতে মুক্ত মনে অনুদান করুন। পেপাল এর মাধ্যমে দান করুন শীঘ্রই আসছে diff --git a/presentation/src/main/res/values-cs/strings.xml b/presentation/src/main/res/values-cs/strings.xml index 301b4bf52..44b236787 100644 --- a/presentation/src/main/res/values-cs/strings.xml +++ b/presentation/src/main/res/values-cs/strings.xml @@ -268,6 +268,7 @@ Správce blokování QKSMS Vestavěné blokování v QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatické filtrování hovorů a zpráv na jednom místě. Community IQ™ poskytuje ochranu před nevyžádanými zprávami od komunitou označených spammerů. Automaticky filtrovat zprávy od nevyžádaných čísel pomocí aplikace „Můžu to zvednout?“ Kopírovat blokovaná čísla @@ -310,6 +311,7 @@ Odemknout + darovat %1$s %2$s Děkujeme za podporu QKSMS! Nyní máte přístup ke všem funkcím QKSMS+ + An error has occurred, please try again QKSMS + je zdarma pro uživatele F-Droid! Pokud byste chtěli podpořit vývoj, neváhejte přispět. Darovat pomocí PayPal Připravujeme diff --git a/presentation/src/main/res/values-da/strings.xml b/presentation/src/main/res/values-da/strings.xml index f7f468573..94272bea9 100644 --- a/presentation/src/main/res/values-da/strings.xml +++ b/presentation/src/main/res/values-da/strings.xml @@ -262,6 +262,7 @@ Blokeringshåndtering QKSMS Indbygget blokeringsfunktionalitet i QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Filtrér automatisk dine opkald og beskeder på ét praktisk sted! Community IQ ™ giver dig mulighed for at forhindre uønskede beskeder fra spammere kendt af fællesskabet Filtrér automatisk beskeder fra uopfordrede numre vha. \"Bør jeg besvare\"-app\'en Kopiér blokerede numre @@ -300,6 +301,7 @@ Oplås + donér for %1$s %2$s Tak for din støtte til QKSMS! Du har nu adgang til alle QKSMS+-funktioner + An error has occurred, please try again QKSMS+ er gratis for F-Droid-brugere! Ønsker du at støtte udviklingen, er du velkommen til at donere. Donér via PayPal Kommer snart diff --git a/presentation/src/main/res/values-de/strings.xml b/presentation/src/main/res/values-de/strings.xml index e06f954ca..f24e229e2 100644 --- a/presentation/src/main/res/values-de/strings.xml +++ b/presentation/src/main/res/values-de/strings.xml @@ -262,6 +262,7 @@ Blockiermanager QKSMS In QKSMS integrierte Blockierfunktion + Spam-Nachrichten und Nummern & unbekannte Anrufe mit Sperrliste & Zeitplan blockieren Filtern Sie automatisch Ihre Anrufe und Nachrichten in einer geeigneten Umgeben! Community IQ™ erlaubt es Ihnen unerwünschte Nachrichten von bekannten Spamnern zu unterbinden Nachrichten von unerwünschten Nummern automatisch mit Hilfe der App \"Should I Answer?\" filtern blockierte Nummer(n) kopieren @@ -300,6 +301,7 @@ Entsperren & Spenden für %1$s %2$s Vielen Dank für die Unterstützung von QKSMS! Du kannst jetzt alle QKSMS+ Funktionen verwenden + Ein Fehler ist aufgetreten, bitte versuchen Sie es erneut QKSMS+ ist für Nutzer von F-Droid kostenfrei! Spende gerne um die Entwicklung zu Unterstützen. Spenden per PayPal Demnächst verfügbar diff --git a/presentation/src/main/res/values-el/strings.xml b/presentation/src/main/res/values-el/strings.xml index a2884a225..71b1bc8c7 100644 --- a/presentation/src/main/res/values-el/strings.xml +++ b/presentation/src/main/res/values-el/strings.xml @@ -262,6 +262,7 @@ Διαχειριστής φραγής QKSMS Ενσωματωμένη λειτουργία φραγής του QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Αντιγραφή αριθμών με φραγή @@ -300,6 +301,7 @@ Unlock + donate for %1$s %2$s Thank you for supporting QKSMS! You now have access to all QKSMS+ features + An error has occurred, please try again QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. Δωρεά μέσω PayPal Έρχεται σύντομα diff --git a/presentation/src/main/res/values-es/strings.xml b/presentation/src/main/res/values-es/strings.xml index 1471c4bf8..781d25108 100644 --- a/presentation/src/main/res/values-es/strings.xml +++ b/presentation/src/main/res/values-es/strings.xml @@ -262,6 +262,7 @@ Gestor de bloqueo QKSMS Función de bloqueo integrado en QKSMS + Bloquee mensajes de spam, números y llamadas desconocidas con lista negra y programación ¡Filtrre automáticamente sus llamadas y mensajes en un lugar conveniente! La comunidad IQ™ te permite prevenir mensajes no deseados de spammers conocidos por la comunidad Filtra automáticamente los mensajes de números no solicitados mediante la aplicación \"¿Debo responder?\" Copiar números bloqueados @@ -300,6 +301,7 @@ Desbloquear + donación %1$s %2$s Gracias por apoyar a QKSMS! Ahora tienes acceso a todas las funciones de QKSMS + + An error has occurred, please try again QKSMS+ es gratis para los usuarios de F-Droid! Si desea apoyar el desarrollo, no dude en hacer una donación. Donar con PayPal Próximamente diff --git a/presentation/src/main/res/values-fa/strings.xml b/presentation/src/main/res/values-fa/strings.xml index ab04c012a..8cd0b8e70 100644 --- a/presentation/src/main/res/values-fa/strings.xml +++ b/presentation/src/main/res/values-fa/strings.xml @@ -78,7 +78,7 @@ حذف آیا مطمئن هستید که می خواهید این گفتگو را حذف کنید؟ - آیا مطمئن هستید که می خواهید این %d گفتگو را حذف کنید؟ + آیا مطمئن هستید که می خواهید %d گفتگو را حذف کنید؟ کپی متن @@ -181,7 +181,7 @@ یک پیام را برنامه‌ریزی کنید پیام برنامه‌ریزی شده - هم‌اکنون ارسال کن + هم اکنون بفرست کپی متن حذف @@ -235,15 +235,15 @@ حذف کاراکترهای اضافه پیام ارسالی فقط شماره های تلفن وقتی در حال ارسال پیام هستید فقط می توانید شماره تلفن را ببنید - Delete old messages automatically - Messages will be deleted after the specified number of days - Number of days - Never - Delete old messages automatically? - If you proceed, %1$d messages will be deleted now + حذف خودکار پیام های قدیمی + پیام ها پس از تعداد روز تعیین شده حذف می شوند + تعداد روزها + هرگز + پیام های قدیمی به طور خودکار حذف شوند؟ + اگر ادامه دهید، اکنون %1$d پیام حذف خواهد شد - After 1 day - After %d days + بعد از ۱ روز + بعد از %d روز پیام های طولانی را به عنوان MMS ارسال کن اگر پیام های متنی طولانی شما نتوانسته اند ارسال شوند یا به ترتیب اشتباه ارسال شوند ، می توانید به جای آنها پیام های MMS ارسال کنید. هزینه های اضافی ممکن است اعمال شود @@ -262,6 +262,7 @@ مدیریت انسداد QKSMS عملکرد مسدود کننده داخلی در QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule تماس ها و پیام های خود را بطور خودکار فیلتر کنید! IQ Community به شما اجازه می دهد تا از پیام های ناخواسته توسط اسپمرهای شناخته شده در جامعه جلوگیری کنید با استفاده از برنامه «آیا باید جواب بدم»، پیام‌ها را از شماره های ناخواسته به طور خودکار فیلتر کنید کپی شماره های مسدود شده @@ -300,6 +301,7 @@ نسخه پیشرفته و حمایت به‌ازای %1$s %2$s سپاس از شما برای حمایت از QKSMS! حال شما به همه امکانات QKSMS+ دسترسی دارید + An error has occurred, please try again QKSMS+ برای کاربران F-Droid رایگان است! اگر می‌خواهید از توسعه حمایت کنید، از کمک مالی بسیار قدردانی می‌شود. از طریق PayPal کمک مالی کنید به زودی diff --git a/presentation/src/main/res/values-fi/strings.xml b/presentation/src/main/res/values-fi/strings.xml index f26dd1083..feb834ae5 100644 --- a/presentation/src/main/res/values-fi/strings.xml +++ b/presentation/src/main/res/values-fi/strings.xml @@ -262,6 +262,7 @@ Estojen hallinta QKSMS QKSMS:ään sisäänrakennettu esto-toiminto + Block spam messages, numbers & unknown calls with blacklist & Schedule Suodata puhelusi ja soittosi automaattisesti, yhdessä kätevässä paikassa! Community IQ™ antaa sinun estää haluamattomia viestejä, yhteisölle tutuilta roskapostin lähettäjiltä Suodata haluamattomien numeroin lähettämät viestit automaattisesti, \"Should I Answer\" -sovellusta käyttämällä Kopioi estetyt numerot @@ -300,6 +301,7 @@ Ominaisuuksien avaus + lahjoitus hintaan %1$s %2$s Kiitos, kun tuit QKSMS:ää! Olet nyt oikeutettu käyttämään kaikkia QKSMS+ -ominaisuuksia + An error has occurred, please try again QKSMS+ on ilmainen F-Droidin käyttäjille! Jos haluaisit tukea kehitystyötä tulevaisuudessa, arvostaisimme lahjoitustasi todella paljon. Lahjoita PayPalilla Tulossa pian diff --git a/presentation/src/main/res/values-fr/strings.xml b/presentation/src/main/res/values-fr/strings.xml index 27ae8db75..a6e0db82e 100644 --- a/presentation/src/main/res/values-fr/strings.xml +++ b/presentation/src/main/res/values-fr/strings.xml @@ -235,15 +235,15 @@ Supprimer les accents des caractères lors de l\'envoi de messages SMS Numéros de mobile Lorsque vous composez un message, afficher uniquement les numéros de mobile - Delete old messages automatically - Messages will be deleted after the specified number of days - Number of days - Never - Delete old messages automatically? - If you proceed, %1$d messages will be deleted now + Supprimer les anciens messages automatiquement + Les messages seront supprimés après le nombre de jours spécifié + Nombre de jours + Jamais + Supprimer les anciens messages automatiquement ? + Si vous continuez, %1$d messages seront supprimés maintenant - After 1 day - After %d days + Après 1 jour + Après %d jours Envoyer les messages longs en MMS Si vos messages texte plus longs ne parviennent pas à être envoyés, ou s\'ils sont envoyés dans le désordre, vous pouvez les envoyer sous forme de messages MMS à la place. Des frais supplémentaires peuvent s\'appliquer @@ -262,6 +262,7 @@ Gestionnaire de blocage QKSMS Fonction de blocage intégrée dans QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Filtrez automatiquement vos appels et vos messages dans un seul endroit pratique ! La communauté IQ™ vous permet d\'éviter les messages indésirables des spammeurs connus de celle-ci Filtrer automatiquement les messages de numéros non sollicités à l’aide de l’application « Devrais-je répondre » Copier les numéros bloqués @@ -300,6 +301,7 @@ Déverrouiller + faire un don pour %1$s %2$s Merci de soutenir QKSMS ! Vous avez maintenant accès à toutes les fonctionnalités de QKSMS+ + An error has occurred, please try again QKSMS + est gratuit pour les utilisateurs de F-Droid ! Si vous souhaitez soutenir le développement, n’hésitez pas à faire un don. Faire un don avec PayPal Prochainement diff --git a/presentation/src/main/res/values-hi/strings.xml b/presentation/src/main/res/values-hi/strings.xml index 2116b8ecf..3ffa4570c 100644 --- a/presentation/src/main/res/values-hi/strings.xml +++ b/presentation/src/main/res/values-hi/strings.xml @@ -262,6 +262,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Copy blocked numbers @@ -300,6 +301,7 @@ Unlock + donate for %1$s %2$s Thank you for supporting QKSMS! You now have access to all QKSMS+ features + An error has occurred, please try again QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. Donate via PayPal जल्द आ रहा है diff --git a/presentation/src/main/res/values-hr/strings.xml b/presentation/src/main/res/values-hr/strings.xml index c58b8e68a..d00984050 100644 --- a/presentation/src/main/res/values-hr/strings.xml +++ b/presentation/src/main/res/values-hr/strings.xml @@ -265,6 +265,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Copy blocked numbers @@ -305,6 +306,7 @@ Otključajte + za %1$s %2$s Hvala vam za podržavanje QKSMS! Sada imate pristup svim QKSMS+ značajkama + An error has occurred, please try again QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. Donirajte putem PayPala Uskoro dolazi diff --git a/presentation/src/main/res/values-hu/strings.xml b/presentation/src/main/res/values-hu/strings.xml index c5c5bd6d0..99c29c249 100644 --- a/presentation/src/main/res/values-hu/strings.xml +++ b/presentation/src/main/res/values-hu/strings.xml @@ -262,6 +262,7 @@ Tiltáskezelő QKSMS A QKSMS beépített tiltási funkciója + Block spam messages, numbers & unknown calls with blacklist & Schedule Egy kényelmesen kezelhető helyen a hívások és üzenetek szűrése! A Community IQ™ megakadályozza a közösség által ismert spammerek üzeneteit Automatikusan szűrheti az üzeneteket a nem kívánt számok alapján a \"Should I Answer\" alkalmazás használatával Tiltott számok másolása @@ -300,6 +301,7 @@ Minden funkció engedélyezése + adomány %1$s %2$s Köszönjük, hogy támogatot a QKSMS-t! MOst hozzáférsz minden QKSMS+ funkcióhoz + An error has occurred, please try again A QKSMS+ minden F-Droid felhasználónak ingyenes. Ha támogatni szeretnéd a fejlesztést az alábbi módon megteheted. Támogatás PayPalon keresztül Hamarosan diff --git a/presentation/src/main/res/values-in/strings.xml b/presentation/src/main/res/values-in/strings.xml index 11a69da83..6bab78459 100644 --- a/presentation/src/main/res/values-in/strings.xml +++ b/presentation/src/main/res/values-in/strings.xml @@ -259,6 +259,7 @@ Pengelola Pemblokiran QKSMS Fitur pemblokiran pesan di dalam QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Otomatis menyaring panggilan dan pesan di dalam satu tempat! Community IQ™ memungkinkan anda memblokir pesan yang tidak diinginkan dari daftar spammer Secara otomatis menyaring pesan dari nomor yang tidak diinginkan menggunakan aplikasi \"Should I Answer\" Salin nomor yang diblokir @@ -295,6 +296,7 @@ Unlock + donate for %1$s %2$s Terima kasih telah mendukung QKSMS! Anda sekarang bisa mengakses semua fitur QKSMS+ + An error has occurred, please try again QKSMS+ selalu gratis untuk pengguna F-Droid! Jika anda ingin mendukung pengembangan, jangan ragu untuk donasi. Donasi via PayPal Segera hadir diff --git a/presentation/src/main/res/values-it/strings.xml b/presentation/src/main/res/values-it/strings.xml index cee6c8e57..14fd3ba3e 100644 --- a/presentation/src/main/res/values-it/strings.xml +++ b/presentation/src/main/res/values-it/strings.xml @@ -262,6 +262,7 @@ Gestore blocchi QKSMS Funzionalità di blocco integrata in QKSMS + Blocca i messaggi di spam, i numeri e le chiamate sconosciute con la blacklist e con lo Scheduler Filtra automaticamente le tue chiamate e i tuoi messaggi in un unico comodo posto! Community IQ™ ti consente di prevenire messaggi indesiderati dagli spammer noti alla comunità Filtrare automaticamente i messaggi provenienti da numeri indesiderati utilizzando l\'app \"Dovrei rispondere?\" Copia i numeri bloccati @@ -300,6 +301,7 @@ Sblocca & dona per %1$s %2$s Grazie per il supporto a QKSMS! È ora possibile accedere a tutte le funzioni QKSMS+ + Si è verificato un errore, si prega di riprovare QKSMS+ è gratuito per gli utenti di F-Droid! Se desideri sostenere il progetto, una tua donazione sarebbe molto apprezzata. Fai una donazione tramite PayPal Prossimamente diff --git a/presentation/src/main/res/values-iw/strings.xml b/presentation/src/main/res/values-iw/strings.xml index 9d91c28ec..ad00c6106 100644 --- a/presentation/src/main/res/values-iw/strings.xml +++ b/presentation/src/main/res/values-iw/strings.xml @@ -19,35 +19,35 @@ --> התכתבות חדשה - כתיבה + כתוב הודעה קיצור הדרך הושבת בארכיון הגדרות התראות - ערכת עיצוב - חיפוש בדואר הנכנס… + ערכת נושא + חיפוש בהודעות נכנסות… נא להקליד שם או מספר - דילוג + דלג המשך - להוסיף מישהו + הוסף איש קשר התקשרות פרטים - שמירה לגלריה + שמור לגלריה שיתוף - פתיחת מגירת ניווט + פתח מגירת ניווט %d נבחרו - פינוי + נקה לארכיון שליפה מהארכיון - מחיקה - הוספה לאנשי קשר - הצמדה למעלה - שחרור הצמדה - סימון כהודעה שנקראה - סימון כהודעה שלא נקראה - חסימה - ההודעות מסונכרנות… - אני: %s + מחק + הוסף לאנשי קשר + הצמד למעלה + בטל הצמדה + סמן כהודעה שנקראה + סמן כהודעה שלא נקראה + חסום + מסנכרן הודעות… + אתה: %s טיוטה תוצאות בהודעות %d הודעות @@ -55,16 +55,16 @@ אין תוצאות ההתכתבויות שהעברת לארכיון תופענה כאן התחלת התכתבות חדשה - להתאהב בהתמסררות - הגדרת QKSMS ליישומון המסרונים כבררת מחדל - החלפה + להתאהב מחדש בכתיבת הודעות + הגדרת QKSMS כיישומון בררת מחדל למסרונים + החלף נדרשת הרשאה ל־QKSMS נדרשות הרשאות כדי לשלוח ולצפות במסרונים ל־QKSMS נדרשות הרשאות כדי לצפות באנשי הקשר שלך - לאפשר + אפשר דואר נכנס בארכיון - בתזמון + מתוזמן חסימה עוד הגדרות @@ -75,20 +75,20 @@ נשמח לקבל ממך דירוג ב־Google Play! כמובן! התעלמות - מחיקה + מחק - למחוק את ההתכתבות הזאת? + אתה בטוח שברצונך למחוק ההתכתבות זאת? למחוק את %d ההתכתבויות האלה? למחוק את %d ההתכתבויות האלה? למחוק את %d ההתכתבויות האלה? - העתקת טקסט - העברה - מחיקה + העתק טקסט + העבר + מחק נא לבחור מספר טלפון - %s ∙ בררת מחדל + %s ∙ ברירת מחדל פעם אחד בלבד תמיד %d נבחרו @@ -101,13 +101,13 @@ הזמן הנבחר חייב להיות בעתיד! עליך לשחרר את QKSMS+‎ כדי להשתמש בהודעות מתוזמנות הוספה להודעות המתוזמנות - כתיבת הודעה… - העתקת טקסט - העברה - מחיקה - הקודמת - הבאה - פינוי + כתוב הודעה… + העתק טקסט + העבר + מחק + הקודם + הבא + נקה פרטי הודעה סוג: %s מאת: %s @@ -115,68 +115,68 @@ נושא: %s עדיפות: %s גודל: %s - שליחה: %s - קבלה: %s - מסירה: %s + נשלח: %s + התקבל: %s + נמסר: %s קוד שגיאה: %d - הוספת קובץ מצורף - צירוף תמונה - צילום תמונה + הוסף קובץ מצורף + צרף תמונה + צלם תמונה תזמון הודעה - צירוף איש קשר + צרף איש קשר שגיאה בקריאת איש קשר נבחר SIM מס׳ %1$d ‏(%2$s) - %s נבחרו, החלפת כרטיס SIM - שליחת הודעה - מתבצעת שליחה… + %s נבחר, החלף כרטיס SIM + שלח הודעה + שולח… %s נמסרה - השליחה נכשלה. נא לגעת כדי לנסות שוב + השליחה נכשלה. הקש כדי לנסות שוב פרטים הכתובת הועתקה כותרת התכתבות התראות - ערכת עיצוב - העברה לארכיון + ערכת נושא + ארכיון הוצאה מארכיון - חסימה - שחרור חסימה - מחיקת התכתבות + חסום + בטל חסימה + מחק התכתבות לא ניתן לטעון מדיה נשמר לגלריה גיבוי ושחזור - ההודעות מגובות - מתבצע שחזור מגיבוי + מגבה הודעות + משחזר מגיבוי גיבוי אחרון - בטעינה… - מעולם לא + טוען… + לעולם לא שחזור - בחירת גיבוי + בחר גיבוי נא לשחרר את QKSMS+‎ כדי להשתמש בגיבוי ובשחזור מתבצע גיבוי… מתבצע שחזור… - שחזור מגיבוי - לשחזר את ההודעות שלך מהגיבוי הזה? - עצירת השחזור + שחזר מגיבוי + אתה בטוח שברצונך לשחזר את ההודעות שלך מגיבוי זה? + עצור שחזור הודעות שכבר שוחזרו יישארו במכשיר שלך גיבויים לא נמצאו גיבויים - הודעה אחת - שתי הודעות + הודעה %d + %d הודעות %d הודעות %d הודעות נכון לעכשיו, קיימת תמיכה בהודעות מסוג SMS בתכונת הגיבוי והשחזור. תמיכה ב־MMS וגיבויים מתוזמנים יגיעו בעתיד הקרוב! - לגבות כעת + גבה כעת הגיבוי מפוענח… %d/%d הודעות - הגיבוי נשמר… - ההודעות מסונכרנות… + שומר גיבוי… + מסנכרן הודעות… הסתיים! גיבוי ושחזור - מתוזמנת - שליחת הודעה אוטומטית, ברגע המדויק שמתאים לך + מתוזמן + שלח הודעה אוטומטית, ברגע המדויק שמתאים לך היי! מתי יום ההולדת שלך שוב? ב־23 בדצמבר מזל טוב! תראה איך החברות בינינו פורחת, זכרתי את יום ההולדת שלך @@ -185,14 +185,14 @@ תזמון הודעה הודעה מתוזמנת - לשלוח כע - העתקת טקסט - מחיקה + שלח כעת + העתק טקסט + מחק מראה כללי תגובה מהירה - ערכת עיצוב + ערכת נושא מצב לילה מצב לילה שחור טהור זמן התחלה @@ -202,7 +202,7 @@ שימוש בגופן המערכת אימוג׳י אוטומטי התראות - יש לגעת כדי להתאים אישית + הקש כדי להתאים אישית פעולות כפתור 1 כפתור 2 @@ -214,22 +214,22 @@ ללא תגובה מהירה חלונית קופצת להודעות חדשות - יש לגעת כדי להתעלם - יש לגעת מחוץ לחלונית הקופצת כדי לסגור אותה + הקש כדי להתעלם + הקש מחוץ לחלונית הקופצת כדי לסגור אותה שליחה בעיכוב - פעולות גרירה - להגדיר פעולות גרירה להתכתבויות - גרירה ימינה - גרירה שמאלה + פעולות ניגוב + הגדר פעולות ניגוב להתכתבויות + ניגוב ימינה + ניגוב שמאלה החלפה ללא לארכיון - מחיקה - חסימה - להתקשר - סימון כהודעה שנקראה - סימון כהודעה שלא נקראה + מחק + חסום + התקשר + סמן כהודעה שנקראה + סמן כהודעה שלא נקראה אישור מסירה אישור שההודעות נשלחו בהצלחה @@ -268,6 +268,7 @@ מנהל חסימות QKSMS תכונת חסימה מובנית בתוך QKSMS + חסימת הודעות ספאם, מספרים ושיחות לא מזוהות עם רשימת חסימה ותזמון לסנן אוטומטית את השיחות וההודעות שלך במקום אחיד ונוח! Community IQ™‎ מאפשר לך להימנע מהודעות בלתי רצויות בזכות דיווחים על מפיצי ספאם מצד הקהילה לסנן אוטומטית הודעות ממספרים בלתי רצויים על ידי שימוש ביישומון „Should I Answer” (שאענה) העתקת מספרים חסומים @@ -310,6 +311,7 @@ שחרור ותרומה תמורת %1$s %2$s תודה לך על תמיכתך ב־QKSMS! כעת יש לך גישה לכל התכונות של QKSMS+‎ + אירעה שגיאה, נא לנסות שוב התכנית QKSMS+‎ מוגשת בחינם למשתמשי F-Droid! אם מעניין אותך לתמוך בפיתוח, אפשר להגיש תרומה. תרומה באמצעות PayPal בקרוב diff --git a/presentation/src/main/res/values-ja/strings.xml b/presentation/src/main/res/values-ja/strings.xml index b277ee049..0bd3df743 100644 --- a/presentation/src/main/res/values-ja/strings.xml +++ b/presentation/src/main/res/values-ja/strings.xml @@ -259,6 +259,7 @@ ブロッキングマネージャー QKSMS QKSMS の内蔵ブロック機能 + ブラックリストとスケジュールにより迷惑メッセージ、番号、不明な通話をブロック 一か所で便利に通話とメッセージを自動的にフィルタリングします! Community IQ™を使用すると、コミュニティで知られているスパマーからの不要なメッセージを防ぐことができます \"Should I Answer\" アプリを使用して、迷惑な番号からのメッセージを自動的にフィルタします ブロックした番号をコピー @@ -295,6 +296,7 @@ %1$s %2$s のロック解除と寄付 QKSMS を支援いただきありがとうございます! QKSMS+ のすべての機能にアクセスできます + エラーが発生しました。もう一度やり直してください QKSMS+ は F-Droid ユーザーは無料です! 開発をサポートしたい場合は、気軽に寄付をしてください。 PayPal で寄付する 近日登場 diff --git a/presentation/src/main/res/values-ko/strings.xml b/presentation/src/main/res/values-ko/strings.xml index 174e6f286..0a70241ef 100644 --- a/presentation/src/main/res/values-ko/strings.xml +++ b/presentation/src/main/res/values-ko/strings.xml @@ -233,14 +233,14 @@ SMS를 주고받을 때, 대문자 없애기 전화번호만 보이기 메시지를 쓸 때, 전화번호만 보이게 하기 - Delete old messages automatically - Messages will be deleted after the specified number of days - Number of days - Never - Delete old messages automatically? - If you proceed, %1$d messages will be deleted now + 자동으로 오래된 메시지 제거 + 지정된 일 수 후에 메시지를 삭제합니다 + 일 수 + 사용 안 함 + 자동으로 오래된 메시지를 제거하시겠습니까? + 이대로 진행하면, %1$d 개의 메시지가 지금 삭제됩니다 - After %d days + %d 일 후 장문 메시지를 MMS로 보내기 만약 장문의 메시지가 보내지지 않거나, 잘못된 순서로 보내진다면, MMS 메시지로 보낼 수 있습니다. (MMS로 인한 추가요금이 발생할 수 있습니다) @@ -259,6 +259,7 @@ 차단 메니저 QKSMS 빌트인 차단기능이 있는 기본 필터. + Block spam messages, numbers & unknown calls with blacklist & Schedule 자동으오 전화와 메시지를 한 곳에서 필터링하세요! Community IQ™는 커뮤니티 기반으로 원하지 않는 스팸 메시지를 차단해줍니다. Should I Answer 앱을 이용해 필요없는 번호에서 보낸 메시지를 자동으로 필터링하기 차단된 번호 복사하기 @@ -295,6 +296,7 @@ %1$s %2$s 으로 프리미엄 기능 잠금 해제 및 기부 QKSMS를 지원해 주셔서 감사합니다! 이제 모든 QKSMS+ 기능에 액세스할 수 있습니다 + An error has occurred, please try again QKSMS+는 F-Droid 사용자들에게 무료입니다! 만약 여러분이 개발을 지원하고 싶으시다면, 기부해 주시기 바랍니다. 페이팔을 통해 기부 출시 예정 diff --git a/presentation/src/main/res/values-lt/strings.xml b/presentation/src/main/res/values-lt/strings.xml index f527eecf7..f739bb27e 100644 --- a/presentation/src/main/res/values-lt/strings.xml +++ b/presentation/src/main/res/values-lt/strings.xml @@ -268,6 +268,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatiškai filtruoti žinutes nuo nežinomų numerių su „Ar reikia atsakyti“ programėle Copy blocked numbers @@ -310,6 +311,7 @@ Atrakinti + aukoti %1$s %2$s Ačiū už paramą! Dabar jūs turite prieigą prie visoms QKSMS+ funkcijoms + An error has occurred, please try again QKSMS+ yra nemokama F-Droid vartotojams! Taip pat galite paremti programos kūrėją aukodami. Paaukoti per PayPal Jau greitai diff --git a/presentation/src/main/res/values-nb/strings.xml b/presentation/src/main/res/values-nb/strings.xml index 47ecbf767..5725c06eb 100644 --- a/presentation/src/main/res/values-nb/strings.xml +++ b/presentation/src/main/res/values-nb/strings.xml @@ -262,6 +262,7 @@ Blokkeringer QKSMS Innebygd blokkeringsfunksjon i QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Filtrer anrop og meldinger automatisk. Fellesskapets intelligens™ hjelper med å forhindre uønskede henvendelser fra plageånder Filtrer meldinger fra ukjente numre automatisk med \"Skal jeg svare\"-appen Kopier blokkerte numre @@ -300,6 +301,7 @@ Aktiver + donér for %1$s %2$s Takk for at du støtter QKSMS! Du har nå tilgang til alle funksjoner i QKSMS+ + An error has occurred, please try again QKSMS+ er gratis for F-Droid-brukere. Om du ønsker å støtte utviklingen, kan du gjerne donere. Donér med PayPal Kommer snart diff --git a/presentation/src/main/res/values-ne/strings.xml b/presentation/src/main/res/values-ne/strings.xml index cf88bc382..98a76c069 100644 --- a/presentation/src/main/res/values-ne/strings.xml +++ b/presentation/src/main/res/values-ne/strings.xml @@ -262,6 +262,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Copy blocked numbers @@ -300,6 +301,7 @@ Unlock + donate for %1$s %2$s Thank you for supporting QKSMS! You now have access to all QKSMS+ features + An error has occurred, please try again QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. Donate via PayPal Coming soon diff --git a/presentation/src/main/res/values-nl/strings.xml b/presentation/src/main/res/values-nl/strings.xml index acaf0a76b..e7f3bc758 100644 --- a/presentation/src/main/res/values-nl/strings.xml +++ b/presentation/src/main/res/values-nl/strings.xml @@ -262,6 +262,7 @@ Blokkeermanager QKSMS Ingebouwde blokkeerfunctie in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Filter je oproepen en berichten automatisch op één handige plaats! Community IQ™ stelt je in staat om ongewenste berichten van de bij community bekende spammers te blokkeren Automatisch filteren van berichten van ongevraagde nummers met behulp van de app \"Should I Answer\" Kopieer geblokkeerde nummers @@ -300,6 +301,7 @@ Ontgrendel en doneer voor %1$s %2$s Bedankt voor het onderhouden van QKSMS! Je hebt nu toegang tot alle QKSMS+ functies + An error has occurred, please try again QKSMS+ is gratis voor F-Droid gebruikers. Als je verdere ontwikkelingen wilt helpen, kun je gerust een donatie overwegen. Doneren via PayPal Binnenkort diff --git a/presentation/src/main/res/values-pl/strings.xml b/presentation/src/main/res/values-pl/strings.xml index bdf2dc19d..f8af2c1f3 100644 --- a/presentation/src/main/res/values-pl/strings.xml +++ b/presentation/src/main/res/values-pl/strings.xml @@ -51,9 +51,9 @@ Szkic Wyniki w wiadomościach %d wiadomości - Twoje rozmowy będą wyświetlane tutaj + Miejsce dla twoich bieżących rozmów Brak wyników - Twoje zarchiwizowane rozmowy będą wyświetlane tutaj + Miejsce dla twoich zarchiwizowanych rozmów Rozpocznij nową rozmowę Pokochaj SMSowanie na nowo Ustaw QKSMS jako domyślną aplikację do osbługi SMSów i MMSów @@ -218,7 +218,7 @@ Dotknij gdziekolwiek poza okienkiem, aby zamknąć Opóźnione wysyłanie Akcje po przeciągnięciu - Ustaw akcje po przeciągnięciu dla rozmów + Ustaw akcje dla rozmów po przeciągnięciu Przeciągnięcie w prawo Przeciągnięcie w lewo ZMIEŃ @@ -239,17 +239,17 @@ Usuwaj ogonki ze znaków w wiadomościach Tylko numery komórkowe Pokazuj kontakty tylko z numerami komórkowymi - Delete old messages automatically - Messages will be deleted after the specified number of days - Number of days - Never - Delete old messages automatically? - If you proceed, %1$d messages will be deleted now + Automatycznie usuwaj wiadomości + Stare wiadomości będą usuwane po określonej liczbie dni + Liczba dni + Nigdy + Usunąć automatycznie stare wiadomości? + Jeśli przejdziesz dalej, %1$d wiadomość(-ci) zostanie usuniętych - After 1 day - After %d days - After %d days - After %d days + Po 1 dniu + Po %d dniach + Po %d dniach + Po %d dniach Wysyłaj długie wiadomości jako MMS Jeśli dłuższe wiadomości tekstowe nie są wysyłane lub wysyłane są w złej kolejności, możesz wysyłać je jako wiadomości MMS. Mogą zostać naliczone dodatkowe opłaty @@ -268,6 +268,7 @@ Menadżer Blokowania QKSMS Wbudowana funkcja blokowania w QKSMS + Blokuj spam, numery i połączenia od nieznajomych za pomocą czarnej listy i harmonogramu. Automatycznie filtruj twoje rozmowy i wiadomości tekstowe w jednym wygodnym miejscu! Community IQ™ pozwala zapobiegać niechcianym wiadomościom od znanych spamerów Automatycznie filtruj wiadomości od niechcianych numerów za pomocą aplikacji \"Should I Answer?\" Skopiuj zablokowane numery @@ -279,7 +280,7 @@ Numer telefonu Blokuj Zablokowane wiadomości - Tutaj pojawią się twoje zablokowane wiadomości + Miejsce dla twoich zablokowanych wiadomości Blokuj Odblokuj @@ -310,6 +311,7 @@ Odblokuj i wesprzyj (%1$s %2$s) Dziękujemy za wsparcie aplikacji QKSMS! Masz teraz dostęp do wszystkich funkcji QKSMS+ + Wystąpił błąd. Spróbuj ponownie. Aplikacja QKSMS+ jest bezpłatna dla użytkowników F-Droid! Jeśli chcesz wesprzeć jej rozwój, rzuć pieniążka. Wsparcie za pośrednictwem serwisu PayPal Już wkrótce diff --git a/presentation/src/main/res/values-pt-rBR/strings.xml b/presentation/src/main/res/values-pt-rBR/strings.xml index d9e86e707..941eb6a2a 100644 --- a/presentation/src/main/res/values-pt-rBR/strings.xml +++ b/presentation/src/main/res/values-pt-rBR/strings.xml @@ -262,6 +262,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automaticamente filtrar mensagens não solicitadas usando o aplicativo \"Should I Answer\" Copy blocked numbers @@ -300,6 +301,7 @@ Desbloqueie e doar por %1$s %2$s Obrigado por apoiar o QKSMS! Agora você tem acesso a todos os recursos do QKSMS+ + An error has occurred, please try again QKSMS+ é gratuito para usuários do F-Droid! Se você gostaria de apoiar o desenvolvimento, sinta-se livre para fazer uma doação. Doar via PayPal Em Breve diff --git a/presentation/src/main/res/values-pt/strings.xml b/presentation/src/main/res/values-pt/strings.xml index 9459bfe18..f4a4270d9 100644 --- a/presentation/src/main/res/values-pt/strings.xml +++ b/presentation/src/main/res/values-pt/strings.xml @@ -262,6 +262,7 @@ Gestor de bloqueios QKSMS Funcionalidade de bloqueio, nativa no QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Filtrar automaticamente as chamadas e as mensagens com a aplicação Community IQ™, com a qual pode descartar as mensagens que forem enviadas pelo \'spammers\' reconhecidos na comunidade Filtrar automaticamente as mensagens de números utilizando a aplicação \"Should I Answer\" Copiar números bloqueados @@ -300,6 +301,7 @@ Desbloquear + com uma doação de %1$s %2$s Obrigado por apoiar o QKSMS! Agora, tem acesso a todas as funções do QKSMS+ + Ocorreu um erro. Por favor tente novamente. O QKSMS+ é gratuito para os utilizadores F-Droid! Se quiser ajudar no desenvolvimento, pode efetuar um donativo. Donativos por PayPal Brevemente diff --git a/presentation/src/main/res/values-ro/strings.xml b/presentation/src/main/res/values-ro/strings.xml index 0637d1473..e4f3446d5 100644 --- a/presentation/src/main/res/values-ro/strings.xml +++ b/presentation/src/main/res/values-ro/strings.xml @@ -265,6 +265,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Copy blocked numbers @@ -305,6 +306,7 @@ Unlock + donate for %1$s %2$s Thank you for supporting QKSMS! You now have access to all QKSMS+ features + An error has occurred, please try again QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. Donate via PayPal Coming soon diff --git a/presentation/src/main/res/values-ru/strings.xml b/presentation/src/main/res/values-ru/strings.xml index 70f8a15ff..38ff9e648 100644 --- a/presentation/src/main/res/values-ru/strings.xml +++ b/presentation/src/main/res/values-ru/strings.xml @@ -268,6 +268,7 @@ Управление блокировкой QKSMS Встроенная функция блокировки QKSMS + Чёрный список с возможность работы по расписанию для блокировки сообщений со спамом, звонков с определённых и неизвестных номеров Автоматическая фильтрация звонков и сообщений в одном удобном месте! Community IQ™ позволяет предотвратить получение нежелательных сообщений от известных сообществу спаммеров Автоматически фильтровать сообщения от нежелательных номеров, используя приложение «Should I Answer» Скопировать заблокированные номера @@ -310,6 +311,7 @@ Разблокировать + пожертвовать %1$s %2$s Спасибо за поддержку QKSMS! Теперь у вас есть доступ ко всем функциям QKSMS+ + Произошла ошибка. Попробуйте ещё раз. QKSMS+ является бесплатным для пользователей F-Droid! Если вы хотите поддержать развитие приложения, сделайте пожертвование. Пожертвовать через PayPal Скоро diff --git a/presentation/src/main/res/values-sk/strings.xml b/presentation/src/main/res/values-sk/strings.xml index 955be4fbb..1a936dc78 100644 --- a/presentation/src/main/res/values-sk/strings.xml +++ b/presentation/src/main/res/values-sk/strings.xml @@ -268,6 +268,7 @@ Správca blokovaní QKSMS Vstavané blokovanie v QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automaticky filtrujte svoje hovory a správy na jednom mieste! Community IQ™ vám poskytuje ochranu pred spamom od komunitou označených spammerov. Automaticky filtrovať správy od nevyžiadaných čísiel pomocou aplikácie \"Should I Answer?\" Kopírovať blokované čísla @@ -310,6 +311,7 @@ Odomknúť + darovať za %1$s %2$s Ďakujeme, že ste podporili QKSMS! Odteraz máte prístup ku všetkým QKSMS+ funkciám + An error has occurred, please try again QKSMS+ je zdarma pre používateľov F-Droid! Ak si prajete podporiť vývoj, neváhajte prispieť. Prispieť cez PayPal Už čoskoro diff --git a/presentation/src/main/res/values-sl/strings.xml b/presentation/src/main/res/values-sl/strings.xml index 593a3ac58..feec57f00 100644 --- a/presentation/src/main/res/values-sl/strings.xml +++ b/presentation/src/main/res/values-sl/strings.xml @@ -268,6 +268,7 @@ Upravljalnik Blokiranja QKSMS Vgrajena sposobnost blokiranja v QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Samodejno razvrstite vaše klice in sporočila v eno priročno mesto! Skupnostni IQ™ omogoča, da preprečite prejemanje sporočil pošiljateljev neželenih sporočil, kateri so skupnosti poznani Samodejno razvrstite sporočila neželenih številk tako, da uporabiš \"Should I Answer\" aplikacijo Kopiraj blokirane številke @@ -310,6 +311,7 @@ Odkleni + doniraj za %1$s %2$s Hvala da podpirate QKSMS! Zdaj imate dostop do vseh QKSMS+ funkcij + An error has occurred, please try again QKSMS+ je brezplačen za F-Droid uporabnike! Če želite podpirati razvoj, so donacije zelo dobrodošle. Doniraj preko PayPal Prihaja kmalu diff --git a/presentation/src/main/res/values-sr/strings.xml b/presentation/src/main/res/values-sr/strings.xml index 14a2fc5a4..71abcce51 100644 --- a/presentation/src/main/res/values-sr/strings.xml +++ b/presentation/src/main/res/values-sr/strings.xml @@ -18,400 +18,402 @@ ~ along with QKSMS. If not, see . --> - New conversation - Compose - Shortcut disabled - Archived + Нови разговор + Нова порука + Пречица је онемогућена + Архива Подешавања - Notifications - Theme - Search inbox… + Обавештења + Тема + Претражи примљене поруке Састављање - Skip - Continue - Add person + Прескочи + Настави + Додај контакт Позови - Details - Save to gallery - Share - Open navigation drawer - %d selected - Clear - Archive - Unarchive - Delete - Add to contacts - Pin to top - Unpin - Mark read - Mark unread - Block - Syncing messages… - You: %s - Draft - Results in messages - %d messages - Your conversations will appear here - No results - Your archived conversations will appear here - Start new conversation - Love texting again - Make QKSMS your default SMS app - Change - Permission required - QKSMS needs permission to send and view SMS messages - QKSMS needs permission to view your contacts - Allow - Inbox - Archived - Scheduled - Blocking - More - Settings - Help & feedback - Invite friends - Unlock amazing new features, and support development - Enjoying QKSMS? - Share some love and rate us on Google Play! - OKAY! - DISMISS - Delete + Детаљи + Сачувај у галерију + Подели + Отвори панел за навигацију + %d обележено + Обриши + Сачувај + Врати у примљене + Обриши + Додај у контакте + Закачи на врх + Откачи + Означи као прочитано + Означи као непрочитано + Блокирај + Синхронизујем поруке… + Ја: %s + Нацрт + Пронађено у порукама + %d поруке + Овде ће се појавити твоји разговори + Нема резултата + Овде ће се појавити твоји сачувани разговори + Започни нов разговор + Поново заволите СМС. + Постави QKSMS као подразумевану СМС апликацију + Промени + Потребна је дозвола + QKSMS тражи дозволу да шаље и прима СМС поруке + QKSMS тражи приступ твојим контактима + Дозволи + Примљено + Сачувано + Заказане поруке + Блокирање + Још + Подешавања + Помоћ и повратне информације + Позови пријатеље + Откључај нове могућности и подржи развој апликације + Да ли уживаш уз QKSMS? + Покажи мало љубави и оцени нас на Google Play! + ВАЖИ! + ОТКАЖИ + Обриши - Are you sure you would like to delete this conversation? - Are you sure you would like to delete %d conversations? - Are you sure you would like to delete %d conversations? + Да ли стварно желите избришете овај разговор? + Да ли стварно желите избришете ове разговоре? + Да ли стварно желите избришете %d разговоре? - Copy text - Forward - Delete + Копирај текст + Проследи + Обриши - Choose a phone number - %s ∙ Default - Just once - Always - %d selected - %1$d of %2$d results - Send as group message - Recipients and replies will be visible to everyone - This is the start of your conversation. Say something nice! - Contact card - Scheduled for - Selected time must be in the future! - You must unlock QKSMS+ to use scheduled messaging - Added to scheduled messages - Write a message… - Copy text - Forward - Delete - Previous - Next - Clear - Message details - Type: %s - From: %s - To: %s - Subject: %s - Priority: %s - Size: %s - Sent: %s - Received: %s - Delivered: %s - Error code: %d - Add an attachment - Attach a photo - Take a photo - Schedule message - Attach a contact - Error reading contact + Изабери број телефона + %s ∙ Подразумевано + Само једном + Увек + Изабрано: %d + %1$d од %2$d резултата + Пошаљи као групну поруку + Примаоци и одговори ће бити свима видљиви + Овде почиње ваш разговор. Реци нешто лепо за почетак! + Контакт картица + Заказано за + Изабрано време мора бити у будућности! + Да би заказивао поруке мораш откључати QKSMS+ + Додато у заказане поруке + Напиши поруку… + Копирај текст + Проследи + Обриши + Претходно + Следеће + Обриши + Детаљи поруке + Тип: %s + Шаље: %s + Прима: %s + Наслов: %s + Важност: %s + Величина: %s + Послато: %s + Примљено: %s + Испоручено: %s + Кôд грешке: %d + Додај прилог + Приложи фотографију + Сними фотографију + Планирај поруку + Приложи контакт + Грешка при учитавању контакта - SIM %1$d (%2$s) selected - %s selected, change SIM card - Send message - Sending… - Delivered %s - Failed to send. Tap to try again - Details - Address copied - Conversation title - Notifications - Theme - Archive - Unarchive - Block - Unblock - Delete conversation - Couldn\'t load media - Saved to gallery - Backup and restore - Backing up messages - Restoring from backup - Last backup - Loading… - Never - Restore - Select a backup - Please unlock QKSMS+ to use backup and restore - Backup in progress… - Restore in progress… - Restore from backup - Are you sure you would like to restore your messages from this backup? - Stop restore - Messages that have already been restored will remain on your device - Backups - No backups found + Изабрана SIM картица %1$d (%2$s) + Изабрано: %s; промените SIM картицу + Пошаљи поруку + Шаљем… + Испоручено %s + Слање неуспешно. Додирните да покушате поново. + Детаљи + Адреса је копирана + Наслов + Обавештења + Тема + Сачувај + Врати у примљене + Блокирај + Одблокирај + Обриши разговор + Неуспешно учитавање + Сачувано у галерију + Резервнe копијe + Правим копију + Враћање резервне копије + Последња копија + Учитавање… + Никад + Поврати копију + Изабери резервну копију + Молимо откључајте QKSMS+ да бисте користили опцију за прављење резервне копије + Правим резервну копију… + Враћам резервну копију… + Поврати резервну копију + Да ли сте сигурни да желите да вратите своје поруке из резервне копије? + Заустави повраћај + Поруке које су већ повраћене ће остати сачуване на Вашем уређају + Резервне копије + Нема резервних копија - %d message - %d messages - %d messages + %d порука + %d порука + %d порука - Currently, only SMS is supported by Backup and Restore. MMS support and scheduled backups will be coming soon! - Backup now - Parsing backup… - %d/%d messages - Saving backup… - Syncing messages… - Finished! - Backup and restore - Scheduled - Automatically send a message, at the exact moment you\'d like - Hey! When was your birthday again? - It\'s on December 23rd - Happy birthday! Look at what a great friend I am, remembering your birthday + Тренутно су подржане резервне копије само SMS порука. Ускоро очекујте подршку за MMS поруке и заказано прављење резервних копија! + Одмах направите копију + Обрађујем копију… + %d/%d порука + Сачувавам резерну копију… + Синхронизујем поруке… + Завршено! + Резервне копије + Заказано + Аутоматски пошаљи поруку у тачно одређеном тренутку + Хеј! Кад ти оно беше рођендан? + 23. децембра + Срећан рођендан! Видиш како сам добар друг, сетио сам се твог рођедана! - Sending on December 23rd  - Schedule a message - Scheduled message + Шаље се 23. децембра. + Закажи слање + Заказана порука - Send now - Copy text - Delete + Пошаљи одмах + Копирај текст + Обриши - Appearance - General - QK Reply - Theme - Night mode - Pure black night mode - Start time - End time - Automatic contact colors - Font size - Use system font - Automatic emoji - Notifications - Tap to customize - Actions - Button 1 - Button 2 - Button 3 - Notification previews - Wake screen - Vibration - Sound - None - QK Reply - Popup for new messages - Tap to dismiss - Tap outside of the popup to close it - Delayed sending - Swipe actions - Configure swipe actions for conversations - Right swipe - Left swipe - CHANGE + Уреди изглед + Опште + Брзи одговор + Тема + Ноћни мод + Потпуно црни ноћни мод + Време почетка + Време завршетка + Аутоматске боје за контакте + Величина слова + Користи системски фонт + Аутоматски емотикони + Обавештења + Додирни за уређивање + Радње + Опција 1 + Опција 2 + Опција 3 + Преглед обавештења + Укључи екран + Вибрација + Звук + Ниједна + Брзи одговор + Искачући прозор за нове поруке + Додирни да уклониш + Додирни екран изван искачућег прозора да га уклониш + Одложено слање + Радње превлачењем + Подеси радње превлачењем у оквиру разговора + Превлачење удесно + Превлачење улево + ПРОМЕНИ - None - Archive - Delete - Block - Call - Mark read - Mark unread + Ништа + Сачувај + Обриши + Блокирај + Позови + Означи као прочитано + Означи као непрочитано - Delivery confirmations - Confirm that messages were sent successfully - Signature - Add a signature to the end of your messages - Strip accents - Remove accents from characters in outgoing SMS messages - Mobile numbers only - When composing a message, only show mobile numbers - Delete old messages automatically - Messages will be deleted after the specified number of days - Number of days - Never - Delete old messages automatically? - If you proceed, %1$d messages will be deleted now + Потврде о испоруци + Потврди да су поруке успешно послате + Потпис + Додај потпис на крај сваке поруке + Уклони акценте + Уклони акценте са слова у одлазним порукама + Само бројеви из мобилних мрежа + При састављању порука прикажи само бројеве из мобилних мрежа + Аутоматско брисање старих порука + Поруке ће бити обрисане после наведеног броја дана + Број дана + Никада + Аутоматски обрисати старе поруке? + Ако наставите, %1$d порука ће одмах бити обрисано. - After 1 day - After %d days - After %d days + Након 1 дана + Након %d дана + Након %d дана - Send long messages as MMS - If your longer text messages are failing to send, or sending in the wrong order, you can send them as MMS messages instead. Additional charges may apply - Auto-compress MMS attachments - Sync messages - Re-sync your messages with the native Android SMS database - About QKSMS - Version %s - Debug logging enabled - Debug logging disabled - Enter duration (seconds) - Blocking - Drop messages - Drop incoming messages from blocked senders instead of hiding them - Blocked conversations - Blocking Manager + Пошаљи дугачке поруке као MMS + Ако дугачке поруке не могу да се пошаљу или се шаљу погрешним редоследом, можете их послати као MMS, што може имати додатне трошкове. + Аутоматска оптимизација величине MMS прилога. + Синхронизуј поруке + Поново синхронизуј поруке са изворном Андроид SMS базом података. + О апликацији + Верзија %s + Записи за отклањање грешака су омогућени + Записи за отклањање грешака су онемогућени + Унеси трајање (у секундама) + Блокирање + Одбиј поруке + Одбиј поруке које стижу са блокираних бројева уместо да их сакријеш + Блокирани разговори + Апликација за блокирање QKSMS - Built-in blocking functionality in QKSMS - Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app - Copy blocked numbers - Continue to %s and copy over your existing blocked numbers - Blocked numbers - Your blocked numbers will appear here - Block a new number - Block texts from - Phone number - Block - Blocked messages - Your blocked messages will appear here - Block - Unblock + Уграђена могућност блокирања у QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule + Аутоматски филтрирај позиве и поруке на једном месту! Community IQ™ ти омогућује да блокираш нежељене поруке од \"спамера\" познатих заједници + Аутоматски фитрирај поруке са непожељних бројева користећи \"Should I Answer\" апликацију + Копирај блокиране бројеве + Отвори %s и пренеси бројеве које си већ блокирао + Блокирани бројеви + Овде ће се појавити бројеви које блокираш + Блокирај нови број + Блокирај поруке са броја + Број телефона + Блокирај + Блокиране поруке + Овде ће се појавити поруке које блокираш + Блокирај + Одблокирај - Continue to %s and block this number - Continue to %s and block these numbers - Continue to %s and block these numbers + Отвори %s и блокирај овај број + Отвори %s и блокирај ове бројеве + Отвори %s и блокирај ове бројеве - Continue to %s and allow this number - Continue to %s and allow these numbers - Continue to %s and allow these numbers + Отвори %s и дозволи овај број + Отвори %s и дозволи ове бројеве + Отвори %s и дозволи ове бројеве - About - Version - Developer - Source code - Changelog - Contact - License - Copyright - Support development, unlock everything - You can save a starving developer for just %s + О апликацији + Верзија + Програмер + Изворни код + Историја измена + Kontakt + Лиценца + Ауторска права + Подржи развој ове апликације и откључај све опције + За само %s можеш спасити програмера од глади - Lifetime upgrade for %1$s %2$s + Трајна надоградња за само %1$s %2$s - Unlock + donate for %1$s %2$s - Thank you for supporting QKSMS! - You now have access to all QKSMS+ features - QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. - Donate via PayPal - Coming soon - Premium themes - Unlock beautiful theme colors not available on the Material Design palette - Custom auto-emoji - Create custom auto-emoji shortcuts - Message backup - Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost - Scheduled messages - Schedule messages to automatically be sent at a specific time and date - Delayed sending - Wait a few seconds before sending your message - Automatic night mode - Enable night mode based on the time of day - Advanced blocking - Block messages that contain keywords or match patterns - Auto-forward - Automatically forward messages from certain senders - Auto-respond - Automatically respond to incoming messages with a preset response - More - QKSMS is under active development, and your purchase will include all future QKSMS+ features! - Loading… - View more conversations - Mark read - Call - Delete - Show more - Show less - Open conversation + Откључавање + добровољни прилог за %1$s %2$s + Хвала што подржавате QKSMS! + Сада имате приступ свим QKSMS+ могућностима + An error has occurred, please try again + QKSMS+ је бесплатан за кориснике F-Droid-а! Ако желиш да подржиш развој апликације, били бисмо ти захвални на добровољном прилогу. + Добровољни прилог путем PayPal-а + Ускоро + Премијум теме + Откључај прелепе боје које нису доступне у Material Design палети + Уређивање аутоматских емотикона + Направите своје аутоматске пречице за емотиконе + Резервне копије порука + Аутоматски сачувајте своје поруке. Више не морате да бринете да ћете изгубити историју разговора у случају да промените или изгубите телефон! + Заказане поруке + Закажите аутоматско слање порука у тачно одређено време + Одложено слање + Сачекајте неколико секунди пре него што пошаљете поруку + Аутоматски ноћни мод + Укључи ноћни мод према тренутном времену + Напредно блокирање + Блокирај поруке које садрже одређене кључне речи или одговарају шаблону + Аутоматски проследи + Аутоматски проследи поруке од одређених пошиљалаца + Аутоматски одговори + Предефинисани аутоматски одговор на долазне поруке + Још + QKSMS се активно развија и једнократним плаћањем добијаш и све будуће QKSMS+ могућности! + Учитавање… + Прикажи више разговора + Означи као прочитано + Позови + Обриши + Прикажи више + Прикажи мање + Прикажи разговор Material HEX - Apply + Примени - None - Archive - Delete - Block - Call - Mark read - Reply + Ниједна + Сачувај + Обриши + Блокирај + Позови + Означи као прочитано + Одговори - Yes - Continue - Cancel - Delete - Save - Stop - More - Set - Undo - Copied - Archived conversation - You must unlock QKSMS+ to use this + Да + Настави + Откажи + Обриши + Сачувај + Заустави + Још + Постави + Опозови + Копирано + Сачувани разговори + Откључај QKSMS+ за приступ овој опцији - New message - %s new messages - %s new messages + Нова порука + %s нове/нових порука + %s нове/нових порука - Message not sent - The message to %s failed to send + Порука није послата + Порука за %s није послата - System - Disabled - Always on - Automatic + Систем + Онемогућено + Увек укључено + Аутоматски - Show name and message - Show name - Hide contents + Прикажи име и поруку + Прикажи име + Сакриј садржај - Small - Normal - Large - Larger + Мала + Средња + Велика + Највећа - No delay - 3 seconds - 5 seconds - 10 seconds + Без одлагања + 3 секунде + 5 секунди + 10 секунди - Automatic - 100KB - 200KB - 300KB - 600KB - 1000KB - 2000KB - No compression + Аутоматско + 100kB + 200kB + 300kB + 600kB + 1000kB + 2000kB + Без оптимизације У реду Сачекај секунд Стижем Хвала - Не звучи лоше + Добро изгледа Шта има? Договорено Не Волим те Извини - Значи, ЛОЛ + LOL Добро онда diff --git a/presentation/src/main/res/values-sv/strings.xml b/presentation/src/main/res/values-sv/strings.xml index 48b4c6fdc..7fdcff5ff 100644 --- a/presentation/src/main/res/values-sv/strings.xml +++ b/presentation/src/main/res/values-sv/strings.xml @@ -242,7 +242,7 @@ Radera gamla meddelanden automatiskt? Om du fortsätter raderas %1$d meddelanden direkt - After 1 day + Efter 1 dag Efter %d dagar Skicka långa meddelanden som MMS @@ -262,6 +262,7 @@ Blockerings-hanterare QKSMS Inbyggd blockeringsfunktion i QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Filtrera automatiskt dina samtal och meddelanden på ett bekvämt ställe! Med Community IQ™ kan du förhindra oönskade meddelanden från gemenskapens kända spammare Filtrera automatiskt meddelanden från oönskade nummer genom att använda appen \"Borde jag svara\" Kopiera blockerade nummer @@ -300,6 +301,7 @@ Lås upp + donera för %1$s %2$s Tack för att du stöttar QKSMS! Du har nu tillgång till alla funktioner i QKSMS+ + An error has occurred, please try again QKSMS + är gratis för F-Droid användare! Om du vill stödja utvecklingen, Så får du gärna donationera. Donera via PayPal Kommer snart diff --git a/presentation/src/main/res/values-th/strings.xml b/presentation/src/main/res/values-th/strings.xml index 016ea0038..0ef07860f 100644 --- a/presentation/src/main/res/values-th/strings.xml +++ b/presentation/src/main/res/values-th/strings.xml @@ -259,6 +259,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers กรองข้อความจากหมายเลขที่ไม่พึงประสงค์ โดยใช้แอพควรตอบกลับหรือไม่ Copy blocked numbers @@ -295,6 +296,7 @@ ปลดล็อค + บริจาค %1$s %2$s ขอบคุณสำหรับการสนับสนุน QKSMS ตอนนี้คุณสามารถเข้าถึงคุณสมบัติทั้งหมดของ QKSMS+ ได้แล้ว + An error has occurred, please try again QKSMS+ เป็นบริการฟรีสำหรับผู้ใช้งาน F-Droid หากคุณต้องการสนับสนุนการพัฒนา สามารถบริจาคได้ บริจาคผ่าน PayPal เร็วๆ นี้ diff --git a/presentation/src/main/res/values-tl/strings.xml b/presentation/src/main/res/values-tl/strings.xml index cb32cf1c1..3d9821a6b 100644 --- a/presentation/src/main/res/values-tl/strings.xml +++ b/presentation/src/main/res/values-tl/strings.xml @@ -262,6 +262,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Copy blocked numbers @@ -300,6 +301,7 @@ Unlock + donate for %1$s %2$s Thank you for supporting QKSMS! You now have access to all QKSMS+ features + An error has occurred, please try again QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. Donate via PayPal Coming soon diff --git a/presentation/src/main/res/values-tr/strings.xml b/presentation/src/main/res/values-tr/strings.xml index e1a4e017b..803093652 100644 --- a/presentation/src/main/res/values-tr/strings.xml +++ b/presentation/src/main/res/values-tr/strings.xml @@ -262,6 +262,7 @@ Engelleme Yöneticisi QKSMS QKSMS\'de yerleşik engelleme işlevi + Block spam messages, numbers & unknown calls with blacklist & Schedule Aramalarınızı ve mesajlarınızı uygun bir yerde otomatik olarak filtreleyin! Topluluk IQ™ bilinen spam istenmeyen mesajları önlemek için izin verir BAĞLAM İSTEĞİ @@ -302,6 +303,7 @@ BAĞLAM İSTEĞİ Kilidi aç ve Satın Al %1$s %2$s QKSMS desteği için teşekkür ederiz!! Şimdi tüm QKSMS+ özellikleri açıldı! + An error has occurred, please try again QKSMS+ F-Droid kullanıcıları için ücretsizdir! Eğer gelişmeyi desteklemek istiyorsanız, bağış yapmakta özgürsünüz. Paypal ile satın al Yakında diff --git a/presentation/src/main/res/values-uk/strings.xml b/presentation/src/main/res/values-uk/strings.xml index 45ed4b6e0..9403551a9 100644 --- a/presentation/src/main/res/values-uk/strings.xml +++ b/presentation/src/main/res/values-uk/strings.xml @@ -268,6 +268,7 @@ Керування блокуванням QKSMS Вбудована функція блокування QKSMS + Блокуйте спам-повідомлення, невідомі номери та дзвінки за допомогою чорного списку та за розкладом Автоматична фільтрація дзвінків та повідомлень в одному зручному місці! Community IQ™ дозволить запобігти отримуванню небажаних повідомлень від відомих спільноті спамерів Автоматично фільтрувати повідомлення від небажаних номерів, використовуючи додаток «Should I Answer» Скопіювати заблоковані номери @@ -310,6 +311,7 @@ Розблокувати + пожертвувати %1$s %2$s Дякуємо вам за підтримку QKSMS! Тепер у вас є доступ до всіх функцій QKSMS+ + Сталася помилка, спробуйте ще раз QKSMS+ є безкоштовним для користувачів F-Droid! Якщо ви хочете підтримати подальший розвиток програми, зробіть пожертву. Пожертвування через PayPal Незабаром diff --git a/presentation/src/main/res/values-ur/strings.xml b/presentation/src/main/res/values-ur/strings.xml index aa94dc935..15c5a2e7b 100644 --- a/presentation/src/main/res/values-ur/strings.xml +++ b/presentation/src/main/res/values-ur/strings.xml @@ -262,6 +262,7 @@ Blocking Manager QKSMS Built-in blocking functionality in QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Copy blocked numbers @@ -300,6 +301,7 @@ Unlock + donate for %1$s %2$s Thank you for supporting QKSMS! You now have access to all QKSMS+ features + An error has occurred, please try again QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. Donate via PayPal Coming soon diff --git a/presentation/src/main/res/values-vi/strings.xml b/presentation/src/main/res/values-vi/strings.xml index d051c7a87..2c98a28d9 100644 --- a/presentation/src/main/res/values-vi/strings.xml +++ b/presentation/src/main/res/values-vi/strings.xml @@ -259,6 +259,7 @@ Quản lý danh sách chặn QKSMS Chức năng chặn tích hợp sẵn trong QKSMS + Block spam messages, numbers & unknown calls with blacklist & Schedule Tự động lọc các cuộc gọi và tin nhắn của bạn ở một nơi thuận tiện! Cộng đồng IQ™ cho phép bạn ngăn chặn các tin nhắn từ những kẻ gửi tin rác được đóng góp bởi cộng đồng Tự động lọc tin nhắn từ số điện thoại không mong muốn bằng cách sử dụng ứng dụng \"Should I Answer\" Sao chép số đã chặn @@ -295,6 +296,7 @@ Mở khoá và tài trợ với %1$s %2$s Cảm ơn bạn đã ủng hộ QKSMS! Bây giờ bạn có thể sử dụng tất cả các chức năng của QKSMS+ + An error has occurred, please try again QKSMS+ miễn phí cho người sử dụng F-Droid! Bạn có thể trả phí nếu muốn hỗ trợ phát triển. Tài trợ bằng PayPal Sắp có diff --git a/presentation/src/main/res/values-zh-rCN/strings.xml b/presentation/src/main/res/values-zh-rCN/strings.xml index 88c705b0c..0bf3954fd 100644 --- a/presentation/src/main/res/values-zh-rCN/strings.xml +++ b/presentation/src/main/res/values-zh-rCN/strings.xml @@ -259,6 +259,7 @@ 屏蔽管理器 QKSMS QKSMS 中内建的屏蔽功能 + Block spam messages, numbers & unknown calls with blacklist & Schedule 自动在适当的位置过滤您的来电和信息!Community IQ™ 允许您阻止社区中已知的垃圾发件人所发送的垃圾信息 使用 \"Should I Answer\" 应用过滤未知号码的消息 复制屏蔽的号码 @@ -295,6 +296,7 @@ 解锁并捐赠只需 %1$s %2$s 感谢你对 QKSMS 的支持! 现在你可以使用 QKSMS+ 的全部功能! + 发生错误,请重试 QKSMS+ 对 F-Droid 用户免费!如果您想支持开发,可以随意捐款。 使用 PayPal 捐赠 即将推出 diff --git a/presentation/src/main/res/values-zh/strings.xml b/presentation/src/main/res/values-zh/strings.xml index b590bb274..ffcd5c23e 100644 --- a/presentation/src/main/res/values-zh/strings.xml +++ b/presentation/src/main/res/values-zh/strings.xml @@ -259,6 +259,7 @@ 封鎖管理器 QKSMS QKSMS內建封鎖功能 + Block spam messages, numbers & unknown calls with blacklist & Schedule 自動過濾來電與簡訊!群眾智慧Community IQ™能讓你遠離不想要的垃圾簡訊 使用\"Should I Answer\"應用過濾未知號碼的消息 複製封鎖號碼 @@ -295,6 +296,7 @@ 解鎖並捐贈只需 %1$s %2$s 感謝您對 QKSMS 的支持! 現在您可以使用 QKSMS+ 的全部功能! + An error has occurred, please try again QKSMS+對F-Droid用戶免費!如果您想支持開發,可以隨意捐款。 使用PayPal捐贈 即將推出 -- GitLab From 84923d1deabd956561e90ac22574e43bc863bfbc Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 23 Feb 2021 23:50:27 -0500 Subject: [PATCH 176/213] Fix single MMS sync --- .../main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt | 8 ++++++-- .../java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt | 6 ++++++ .../src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt index c057c1400..23a2c6a95 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt @@ -44,8 +44,12 @@ class CursorToPartImpl @Inject constructor(private val context: Context) : Curso text = from.getStringOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.TEXT)) } - override fun getPartsCursor(): Cursor? { - return context.contentResolver.query(CONTENT_URI, null, null, null, null) + override fun getPartsCursor(messageId: Long?): Cursor? { + return when (messageId) { + null -> context.contentResolver.query(CONTENT_URI, null, null, null, null) + else -> context.contentResolver.query(CONTENT_URI, null, + "${Telephony.Mms.Part.MSG_ID} = ?", arrayOf(messageId.toString()), null) + } } } diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index 4f03110be..1990adf10 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -251,6 +251,12 @@ class SyncRepositoryImpl @Inject constructor( cursorToMessage.map(Pair(cursor, columnsMap)).apply { existingId?.let { this.id = it } + if (isMms()) { + parts = RealmList().apply { + addAll(cursorToPart.getPartsCursor(contentId)?.map { cursorToPart.map(it) }.orEmpty()) + } + } + conversationRepo.getOrCreateConversation(threadId) insertOrUpdate() } diff --git a/domain/src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt index 69fdc619a..e4ae1acc6 100644 --- a/domain/src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt +++ b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToPart.kt @@ -23,6 +23,6 @@ import com.moez.QKSMS.model.MmsPart interface CursorToPart : Mapper { - fun getPartsCursor(): Cursor? + fun getPartsCursor(messageId: Long? = null): Cursor? } -- GitLab From 796e9c7747eb41d54bfd872ef2c4f2fe25e74fef Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 23 Feb 2021 23:50:36 -0500 Subject: [PATCH 177/213] Increment to v3.9.3 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 0d3804af2..6b0fdfd81 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2216 - versionName "3.9.2" + versionCode 2217 + versionName "3.9.3" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From d1883ad941290200ff8ba01f862c7a3fe5259aa8 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 24 Feb 2021 09:42:28 -0500 Subject: [PATCH 178/213] Fix incorrect handling of shared text that contains a link with query params --- .../com/moez/QKSMS/feature/compose/ComposeActivityModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index 260f66fba..9b1b908c6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -49,7 +49,7 @@ class ComposeActivityModule { return activity.intent ?.decodedDataString() ?.substringAfter(':') // Remove scheme - ?.substringBeforeLast("?") // Remove query + ?.substringBefore("?") // Remove query ?.split(",", ";") ?.filter { number -> number.isNotEmpty() } ?: listOf() -- GitLab From 2bce0126041e7f3420edc8c38076e1ff03170fb7 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 24 Feb 2021 09:43:10 -0500 Subject: [PATCH 179/213] Increment to v3.9.4 --- presentation/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 6b0fdfd81..7dee8d3c8 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.moez.QKSMS" minSdkVersion 21 targetSdkVersion 29 - versionCode 2217 - versionName "3.9.3" + versionCode 2218 + versionName "3.9.4" setProperty("archivesBaseName", "QKSMS-v${versionName}") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" -- GitLab From 48a0803bc4e4429df14e74df2d0641173335ec34 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Mon, 1 Mar 2021 00:35:54 -0500 Subject: [PATCH 180/213] Fix incorrect handling of shared text with commas --- .../com/moez/QKSMS/feature/compose/ComposeActivityModule.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index 9b1b908c6..8368dfa81 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -67,8 +67,7 @@ class ComposeActivityModule { ?: activity.intent.extras?.getString("sms_body") ?: activity.intent?.decodedDataString() ?.substringAfter('?') // Query string - ?.split(',') - ?.firstOrNull { param -> param.startsWith("body") } + ?.takeIf { it.startsWith("body") } ?.substringAfter('=') ?: "") } -- GitLab From f766745e127b97509d7af610399c5515021c1264 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 14 Mar 2021 15:31:23 -0400 Subject: [PATCH 181/213] Store changelog locally #1708 --- data/src/main/assets/changelog.json | 272 ++++++++++++++++++ .../QKSMS/manager/ChangelogManagerImpl.kt | 110 ++----- .../moez/QKSMS/manager/ChangelogManager.kt | 6 +- .../feature/changelog/ChangelogAdapter.kt | 8 +- .../feature/changelog/ChangelogDialog.kt | 2 +- .../moez/QKSMS/feature/main/MainActivity.kt | 2 +- .../com/moez/QKSMS/feature/main/MainView.kt | 2 +- .../moez/QKSMS/feature/main/MainViewModel.kt | 14 +- 8 files changed, 314 insertions(+), 102 deletions(-) create mode 100644 data/src/main/assets/changelog.json diff --git a/data/src/main/assets/changelog.json b/data/src/main/assets/changelog.json new file mode 100644 index 000000000..c4d2e917e --- /dev/null +++ b/data/src/main/assets/changelog.json @@ -0,0 +1,272 @@ +[ + { + "versionCode": "2218", + "versionName": "3.9.4", + "fixed": [ + "Incorrect handling of shared text that contains a question mark" + ] + }, + { + "versionCode": "2217", + "versionName": "3.9.3", + "improved": [ + "Message sync is now 2x faster" + ] + }, + { + "versionCode": "2216", + "versionName": "3.9.2", + "fixed": [ + "Crash when launching app in 3.9.1" + ] + }, + { + "versionCode": "2215", + "versionName": "3.9.1", + "improved": [ + "Support SMS URIs that only have a body" + ], + "fixed": [ + "Slowness introduced in 3.9.0" + ] + }, + { + "versionCode": "2214", + "versionName": "3.9.0", + "added": [ + "Auto-delete old messages", + "Block/archive messages from notification", + "Swipe to block conversation", + "Share vCard from other apps" + ], + "improved": [ + "Better visibility for conversations with drafts", + "Remember send-as-group preference", + "Shorten \"mark read\" string for notification actions", + "Clarify delayed message durations", + "Trim whitespace when searching", + "Line count limit for contact groups in compose screen", + "Buzz and show warning when SIM is changed", + "Removed Mixpanel" + ], + "fixed": [ + "Incorrect selection state when picking contact phone number", + "Max height for QK dialog", + "Duplicate phone numbers when multiple contacts apps are installed", + "Swipe state gets stuck when marking read conversation as read again", + "Titles and special characters included in contact's initials", + "Subject text not included when sharing", + "Message history not appearing", + "Dismiss notification for failed message not working", + "Incorrect padding for SIM selector", + "Incorrect padding for contact search", + "Incorrect empty state for messasge search", + "Pressing enter creates line break when searching", + "Crash when composing message", + "Scheduled message date picker can be blocked by keyboard", + "Cursor set to wrong position when cancelling message", + "Can't share to group via Simple Contacts", + "\"View more conversations\" button on widget broken" + ] + }, + { + "versionCode": "2213", + "versionName": "3.8.1", + "fixed": [ + "MMS not sending", + "Crash when deleting conversation", + "Crash on startup when upgrading from old version", + "Contact Groups not showing up in some cases" + ] + }, + { + "versionCode": "2212", + "versionName": "3.8.0", + "improved": [ + "Updated translations" + ] + }, + { + "versionCode": "2211", + "versionName": "3.8.0-beta2", + "fixed": [ + "Crash when starting new conversation", + "Swipe actions not working" + ] + }, + { + "versionCode": "2210", + "versionName": "3.8.0-beta1", + "added": [ + "Support for Contact Groups and Starred Contacts", + "Wake screen setting", + "Per-contact colours", + "Automatic colours", + "Option to send long messages as MMS", + "Long-press recipient to copy phone number", + "Copy text from multiple messages", + "Share photos externally" + ], + "improved": [ + "Redesigned compose message screen", + "Redesigned avatars", + "More reliable contact photo loading", + "Custom colours for contacts will appear everywhere, not just inside the conversation", + "More intuitive unicode stripping", + "Make drafts easier to find", + "Faster scrolling performance everywhere", + "Increased max signature length" + ], + "fixed": [ + "Sent attachments are always low quality", + "Not receiving all MMS", + "Contact name/photo might be out of sync with contacts app", + "MMS might appear in the wrong thread", + "Duplicate phone numbers appear when Whatsapp is installed", + "Can't share to multiple people via another app", + "Body not parsed correctly in SMS Uri", + "Notification sounds not working on Android 7", + "Sometimes can't add number to contacts", + "Default SMS \"CHANGE\" button sometimes doesn't work", + "24h timestamps don't work correctly when language set to Japanese", + "Conversation details page slow when there are lots of attachments" + ] + }, + { + "versionCode": "2209", + "versionName": "3.7.10", + "improved": [ + "Include archived conversations in search results", + "Allow manually blocking non-numeric addresses" + ], + "fixed": [ + "MMS appear to have sent, but they haven't", + "New conversations sometimes don't appear" + ] + }, + { + "versionCode": "2208", + "versionName": "3.7.9", + "improved": [ + "Better reliability for sending MMS" + ], + "fixed": [ + "Fixed delayed messages getting stuck \"Sending...\"" + ] + }, + { + "versionCode": "2207", + "versionName": "3.7.8", + "fixed": [ + "Contact not always linked to conversation", + "Wrong colour for disabled preference icons", + "Multiple crashes" + ] + }, + { + "versionCode": "2206", + "versionName": "3.7.7", + "fixed": [ + "New conversations may not appear in inbox" + ] + }, + { + "versionCode": "2204", + "versionName": "3.7.5", + "added": [ + "Added ability to retry sending failed MMS" + ], + "improved": [ + "Show hint for signature setting", + "Keep widget more up-to-date", + "Improve MMS support for Dual-SIM phones", + "Display sent time for incoming MMS", + "Improve MMS sending reliability" + ], + "fixed": [ + "Contacts sometimes not linked to conversation correctly", + "Sometimes can't save multiple attachments to phone" + ] + }, + { + "versionCode": "2203", + "versionName": "3.7.4", + "added": [ + "Support for Android Q Roles" + ], + "improved": [ + "Use theme color for text cursor", + "Better matching between phone number and conversation" + ], + "fixed": [ + "Messages often stuck \"Sending...\"", + "Short codes won't show up as valid number when composing new message" + ] + }, + { + "versionCode": "2202", + "versionName": "3.7.3", + "added": [ + "You can now view attachments of any filetype" + ], + "improved": [ + "Improve search for contacts with accents in their names", + "Custom labels for phone numbers will show the custom text", + "Include MMS in search results", + "Made it easier to access blocked numbers list", + "Added option to add number to contacts from main conversation list", + "Conversations with drafts will be indicated appropriately in conversation list", + "You can now copy text from a scheduled message", + "Show blocking reason for numbers blocked via Call Control" + ], + "fixed": [ + "Can't open vCard attachments", + "Phone number gets blocked when it shouldn't", + "Icon gets stretched when swiping conversation", + "Can't forward text in MMS message" + ] + }, + { + "versionCode": "2201", + "versionName": "3.7.2", + "fixed": [ + "Various crashes from 3.7.1" + ] + }, + { + "versionCode": "2200", + "versionName": "3.7.1", + "fixed": [ + "Various crashes from 3.7.0" + ] + }, + { + "versionCode": "2198", + "versionName": "3.6.8", + "fixed": [ + "Messages can't be sent or received on older devices", + "Norweigan translations not working" + ] + }, + { + "versionCode": "2197", + "versionName": "3.6.7", + "added": [ + "Signature support", + "System dark mode support for Android Q" + ], + "improved": [ + "You'll be asked for storage permission before trying to save a photo if necessary", + "Smaller download size", + "Load high resolution contact photos" + ], + "fixed": [ + "Contact photos never update", + "Can't create new conversation on Huawei devices", + "Can't receive MMS on Xiaomi devices", + "Visual glitch on compose bar in night mode", + "Theme doesn't update in preview after going back to settings", + "Search hint gets cut off after rotating screen" + ] + } +] diff --git a/data/src/main/java/com/moez/QKSMS/manager/ChangelogManagerImpl.kt b/data/src/main/java/com/moez/QKSMS/manager/ChangelogManagerImpl.kt index 3b09f6d25..6d414de04 100644 --- a/data/src/main/java/com/moez/QKSMS/manager/ChangelogManagerImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/manager/ChangelogManagerImpl.kt @@ -24,14 +24,14 @@ import com.moez.QKSMS.util.Preferences import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import com.squareup.moshi.Moshi +import com.squareup.moshi.Types import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import okhttp3.Call import okhttp3.Callback -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.OkHttpClient -import okhttp3.Request import okhttp3.Response import java.io.IOException import javax.inject.Inject @@ -44,54 +44,24 @@ class ChangelogManagerImpl @Inject constructor( override fun didUpdate(): Boolean = prefs.changelogVersion.get() != context.versionCode - override fun getChangelog(): Single { - val url = "https://firestore.googleapis.com/v1/projects/qksms-app/databases/(default)/documents/changelog" - val request = url.toHttpUrlOrNull()?.let { Request.Builder().url(it).build() } - val call = request?.let { OkHttpClient().newCall(it) } - val adapter = moshi.adapter(ChangelogResponse::class.java) + override suspend fun getChangelog(): ChangelogManager.CumulativeChangelog { + val listType = Types.newParameterizedType(List::class.java, Changeset::class.java) + val adapter = moshi.adapter>(listType) - return Single - .create { emitter -> - call?.enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - if (!emitter.isDisposed) { - emitter.onSuccess(response) - } - } - - override fun onFailure(call: Call, e: IOException) { - if (!emitter.isDisposed) { - emitter.onError(e) - } - } - }) - emitter.setCancellable { - call?.cancel() - } - } - .map { response -> response.body?.string()?.let(adapter::fromJson) } - .map { response -> - response.documents - .sortedBy { document -> document.fields.versionCode.value } - .filter { document -> - val range = (prefs.changelogVersion.get() + 1)..context.versionCode - document.fields.versionCode.value.toInt() in range - } - } - .map { documents -> - val added = documents.fold(listOf()) { acc, document -> - acc + document.fields.added?.value?.values?.map { value -> value.value }.orEmpty() - } - val improved = documents.fold(listOf()) { acc, document -> - acc + document.fields.improved?.value?.values?.map { value -> value.value }.orEmpty() + return withContext(Dispatchers.IO) { + val changelogs = context.assets.open("changelog.json").bufferedReader().use { it.readText() } + .let(adapter::fromJson) + .orEmpty() + .sortedBy { changelog -> changelog.versionCode } + .filter { changelog -> + changelog.versionCode in prefs.changelogVersion.get().inc()..context.versionCode } - val fixed = documents.fold(listOf()) { acc, document -> - acc + document.fields.fixed?.value?.values?.map { value -> value.value }.orEmpty() - } - ChangelogManager.Changelog(added, improved, fixed) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) + + ChangelogManager.CumulativeChangelog( + added = changelogs.fold(listOf()) { acc, changelog -> acc + changelog.added.orEmpty()}, + improved = changelogs.fold(listOf()) { acc, changelog -> acc + changelog.improved.orEmpty()}, + fixed = changelogs.fold(listOf()) { acc, changelog -> acc + changelog.fixed.orEmpty()}) + } } override fun markChangelogSeen() { @@ -99,42 +69,12 @@ class ChangelogManagerImpl @Inject constructor( } @JsonClass(generateAdapter = true) - data class ChangelogResponse( - @Json(name = "documents") val documents: List - ) - - @JsonClass(generateAdapter = true) - data class Document( - @Json(name = "fields") val fields: Changelog - ) - - @JsonClass(generateAdapter = true) - data class Changelog( - @Json(name = "added") val added: ArrayField?, - @Json(name = "improved") val improved: ArrayField?, - @Json(name = "fixed") val fixed: ArrayField?, - @Json(name = "versionName") val versionName: StringField, - @Json(name = "versionCode") val versionCode: IntegerField - ) - - @JsonClass(generateAdapter = true) - data class ArrayField( - @Json(name = "arrayValue") val value: ArrayValues - ) - - @JsonClass(generateAdapter = true) - data class ArrayValues( - @Json(name = "values") val values: List - ) - - @JsonClass(generateAdapter = true) - data class StringField( - @Json(name = "stringValue") val value: String - ) - - @JsonClass(generateAdapter = true) - data class IntegerField( - @Json(name = "integerValue") val value: String + data class Changeset( + @Json(name = "added") val added: List?, + @Json(name = "improved") val improved: List?, + @Json(name = "fixed") val fixed: List?, + @Json(name = "versionName") val versionName: String, + @Json(name = "versionCode") val versionCode: Int ) } diff --git a/domain/src/main/java/com/moez/QKSMS/manager/ChangelogManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/ChangelogManager.kt index 37f714b78..4ba22f8b8 100644 --- a/domain/src/main/java/com/moez/QKSMS/manager/ChangelogManager.kt +++ b/domain/src/main/java/com/moez/QKSMS/manager/ChangelogManager.kt @@ -18,11 +18,9 @@ */ package com.moez.QKSMS.manager -import io.reactivex.Single - interface ChangelogManager { - data class Changelog( + data class CumulativeChangelog( val added: List, val improved: List, val fixed: List @@ -33,7 +31,7 @@ interface ChangelogManager { */ fun didUpdate(): Boolean - fun getChangelog(): Single + suspend fun getChangelog(): CumulativeChangelog fun markChangelogSeen() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt index 43de03391..efba6cfc2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt @@ -32,21 +32,21 @@ class ChangelogAdapter(private val context: Context) : QkAdapter() if (changelog.added.isNotEmpty()) { changes += ChangelogItem(0, context.getString(R.string.changelog_added)) - changes += changelog.added.map { change -> ChangelogItem(1, change) } + changes += changelog.added.map { change -> ChangelogItem(1, "• $change") } changes += ChangelogItem(0, "") } if (changelog.improved.isNotEmpty()) { changes += ChangelogItem(0, context.getString(R.string.changelog_improved)) - changes += changelog.improved.map { change -> ChangelogItem(1, change) } + changes += changelog.improved.map { change -> ChangelogItem(1, "• $change") } changes += ChangelogItem(0, "") } if (changelog.fixed.isNotEmpty()) { changes += ChangelogItem(0, context.getString(R.string.changelog_fixed)) - changes += changelog.fixed.map { change -> ChangelogItem(1, change) } + changes += changelog.fixed.map { change -> ChangelogItem(1, "• $change") } changes += ChangelogItem(0, "") } data = changes diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt index 04dc1d4ce..9d0deba6f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt @@ -49,7 +49,7 @@ class ChangelogDialog(activity: MainActivity) { layout.dismiss.setOnClickListener { dialog.dismiss() } } - fun show(changelog: ChangelogManager.Changelog) { + fun show(changelog: ChangelogManager.CumulativeChangelog) { adapter.setChangelog(changelog) dialog.show() } 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 068bc4bec..362f66a6d 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 @@ -388,7 +388,7 @@ class MainActivity : QkThemedActivity(), MainView { .show() } - override fun showChangelog(changelog: ChangelogManager.Changelog) { + override fun showChangelog(changelog: ChangelogManager.CumulativeChangelog) { changelogDialog.show(changelog) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt index 47511e09f..70bbf36bc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt @@ -50,7 +50,7 @@ interface MainView : QkView { fun themeChanged() fun showBlockingDialog(conversations: List, block: Boolean) fun showDeleteDialog(conversations: List) - fun showChangelog(changelog: ChangelogManager.Changelog) + fun showChangelog(changelog: ChangelogManager.CumulativeChangelog) fun showArchivedSnackbar() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index f79b19288..e4fec32bd 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -50,6 +50,9 @@ import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.schedulers.Schedulers import io.realm.Realm +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -168,12 +171,11 @@ class MainViewModel @Inject constructor( // Show changelog if (changelogManager.didUpdate()) { if (Locale.getDefault().language.startsWith("en")) { - disposables += changelogManager.getChangelog() - .timeout(3, TimeUnit.SECONDS) // If it takes long than 3s, we'll just try again next time - .subscribe({ changelog -> - changelogManager.markChangelogSeen() - view.showChangelog(changelog) - }, {}) // Ignore error + GlobalScope.launch(Dispatchers.Main) { + val changelog = changelogManager.getChangelog() + changelogManager.markChangelogSeen() + view.showChangelog(changelog) + } } else { changelogManager.markChangelogSeen() } -- GitLab From 45736566861c80d25b95470a9617ed991ada6926 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 14 Mar 2021 18:48:47 -0400 Subject: [PATCH 182/213] Fix crash in billingmanager when service is disconnected --- build.gradle | 2 +- .../QKSMS/common/util/BillingManagerImpl.kt | 48 ++++++++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/build.gradle b/build.gradle index ffbc91873..f7a1e2c8f 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { ext.autodispose_version = '1.3.0' ext.billing_version = '3.0.2' ext.conductor_version = '2.1.5' - ext.coroutines_version = '1.2.2' + ext.coroutines_version = '1.4.3' ext.dagger_version = "2.16" ext.espresso_version = '3.1.0-alpha3' ext.exoplayer_version = "2.8.1" diff --git a/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt b/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt index a0c53380b..c735c8d32 100644 --- a/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt +++ b/presentation/src/withAnalytics/java/com/moez/QKSMS/common/util/BillingManagerImpl.kt @@ -40,19 +40,20 @@ import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine @Singleton class BillingManagerImpl @Inject constructor( context: Context, private val analyticsManager: AnalyticsManager -) : BillingManager, PurchasesUpdatedListener { +) : BillingManager, BillingClientStateListener, PurchasesUpdatedListener { private val productsSubject: Subject> = BehaviorSubject.create() override val products: Observable> = productsSubject @@ -78,7 +79,14 @@ class BillingManagerImpl @Inject constructor( .enablePendingPurchases() .build() - private var isServiceConnected = false + private val billingClientState = MutableSharedFlow( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + + init { + billingClientState.tryEmit(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) + } override suspend fun checkForPurchases() = executeServiceRequest { // Load the cached data @@ -146,29 +154,23 @@ class BillingManagerImpl @Inject constructor( } private suspend fun executeServiceRequest(runnable: suspend () -> Unit) { - when (billingClient.isReady) { - true -> runnable() - false -> startServiceConnection(runnable) + if (billingClientState.first() != BillingClient.BillingResponseCode.OK) { + Timber.i("Starting billing service") + billingClient.startConnection(this) } - } - private suspend fun startServiceConnection(onSuccess: suspend () -> Unit) = withContext(Dispatchers.IO) { - val result = suspendCoroutine { cont -> - val listener = object : BillingClientStateListener { - override fun onBillingSetupFinished(result: BillingResult) = cont.resume(result) - override fun onBillingServiceDisconnected() = Timber.i("Billing service disconnected") - } + billingClientState.first { state -> state == BillingClient.BillingResponseCode.OK } + runnable() + } - Timber.i("Starting billing service") - billingClient.startConnection(listener) - } + override fun onBillingSetupFinished(result: BillingResult) { + Timber.i("Billing response: ${result.responseCode}") + billingClientState.tryEmit(result.responseCode) + } - if (result.responseCode == BillingClient.BillingResponseCode.OK) { - Timber.i("Billing service connected") - onSuccess() - } else { - Timber.w("Billing response: ${result.responseCode}") - } + override fun onBillingServiceDisconnected() { + Timber.i("Billing service disconnected") + billingClientState.tryEmit(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) } } -- GitLab From cda924f097abd5b6627352a3e22092dde3a9850e Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 27 Oct 2021 15:25:32 -0400 Subject: [PATCH 183/213] Improve message blocking logs --- .../main/java/com/moez/QKSMS/blocking/BlockingClient.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt index 6f9a2fe40..13c6b9edf 100644 --- a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt +++ b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt @@ -37,6 +37,14 @@ interface BlockingClient { // This means there's a good chance that if a number is blocked in QK, it won't be blocked there, so we // shouldn't unblock the conversation in that case object DoNothing : Action() + + override fun toString(): String { + return when (this) { + is Block -> "Block" + is Unblock -> "Unblock" + is DoNothing -> "DoNothing" + } + } } /** -- GitLab From c86ea1082d2c0f3af846487fbe8198ce5cc01b44 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Thu, 28 Oct 2021 17:14:35 -0400 Subject: [PATCH 184/213] Handle VCard attachments with no name Fixes #1812 --- .../common/util/extensions/VCardExtension.kt | 28 +++++++++++++++++++ .../feature/compose/AttachmentAdapter.kt | 8 +++++- .../QKSMS/feature/compose/part/VCardBinder.kt | 10 +++++-- .../main/res/layout/mms_vcard_list_item.xml | 9 +++--- 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 presentation/src/main/java/com/moez/QKSMS/common/util/extensions/VCardExtension.kt diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/VCardExtension.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/VCardExtension.kt new file mode 100644 index 000000000..8099e399c --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/VCardExtension.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ + +package com.moez.QKSMS.common.util.extensions + +import ezvcard.VCard + +fun VCard.getDisplayName(): String? { + return formattedName?.value + ?: telephoneNumbers?.firstOrNull()?.text + ?: emails?.firstOrNull()?.value +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt index 3563798bc..f01bb088b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt @@ -21,10 +21,12 @@ package com.moez.QKSMS.feature.compose import android.content.Context import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isVisible import com.bumptech.glide.Glide import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.extensions.getDisplayName import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.model.Attachment import ezvcard.Ezvcard @@ -78,9 +80,13 @@ class AttachmentAdapter @Inject constructor( is Attachment.Contact -> Observable.just(attachment.vCard) .mapNotNull { vCard -> Ezvcard.parse(vCard).first() } + .map { vcard -> vcard.getDisplayName() ?: "" } .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { vcard -> holder.name?.text = vcard.formattedName.value } + .subscribe { displayName -> + holder.name?.text = displayName + holder.name?.isVisible = displayName.isNotEmpty() + } } } 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 9e068137d..3d464d51b 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 @@ -21,9 +21,11 @@ package com.moez.QKSMS.feature.compose.part import android.content.Context import android.view.Gravity import android.widget.FrameLayout +import androidx.core.view.isVisible import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.getDisplayName import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint @@ -61,9 +63,13 @@ class VCardBinder @Inject constructor(colors: Colors, private val context: Conte Observable.just(part.getUri()) .map(context.contentResolver::openInputStream) .mapNotNull { inputStream -> inputStream.use { Ezvcard.parse(it).first() } } + .map { vcard -> vcard.getDisplayName() ?: "" } .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { vcard -> holder.name?.text = vcard.formattedName.value } + .subscribe { displayName -> + holder.name?.text = displayName + holder.name.isVisible = displayName.isNotEmpty() + } val params = holder.vCardBackground.layoutParams as FrameLayout.LayoutParams if (!message.isMe()) { @@ -81,4 +87,4 @@ class VCardBinder @Inject constructor(colors: Colors, private val context: Conte } } -} \ No newline at end of file +} diff --git a/presentation/src/main/res/layout/mms_vcard_list_item.xml b/presentation/src/main/res/layout/mms_vcard_list_item.xml index b237b0165..5e9d33e2c 100644 --- a/presentation/src/main/res/layout/mms_vcard_list_item.xml +++ b/presentation/src/main/res/layout/mms_vcard_list_item.xml @@ -27,7 +27,9 @@ + android:layout_height="wrap_content" + android:paddingTop="8dp" + android:paddingBottom="8dp"> - \ No newline at end of file + -- GitLab From 7e7fe81d700f1fcaf682bb7a70a37fcff2d7f020 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Tue, 8 Mar 2022 18:26:45 +0530 Subject: [PATCH 185/213] update missing value in icons and colors --- presentation/build.gradle | 6 ++--- .../main/res/drawable/ic_block_black_24dp.xml | 27 +++++++++++++++++++ .../drawable/ic_chevron_right_black_24dp.xml | 27 +++++++++++++++++++ .../src/main/res/values-zh/strings.xml | 2 +- presentation/src/main/res/values/colors.xml | 2 +- presentation/src/main/res/values/strings.xml | 7 ++++- 6 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 presentation/src/main/res/drawable/ic_block_black_24dp.xml create mode 100644 presentation/src/main/res/drawable/ic_chevron_right_black_24dp.xml diff --git a/presentation/build.gradle b/presentation/build.gradle index 046f7a18e..6b39c8d13 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -64,10 +64,10 @@ android { } compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } - + // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } diff --git a/presentation/src/main/res/drawable/ic_block_black_24dp.xml b/presentation/src/main/res/drawable/ic_block_black_24dp.xml new file mode 100644 index 000000000..c3f491ef7 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_block_black_24dp.xml @@ -0,0 +1,27 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_chevron_right_black_24dp.xml b/presentation/src/main/res/drawable/ic_chevron_right_black_24dp.xml new file mode 100644 index 000000000..676914f6e --- /dev/null +++ b/presentation/src/main/res/drawable/ic_chevron_right_black_24dp.xml @@ -0,0 +1,27 @@ + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values-zh/strings.xml b/presentation/src/main/res/values-zh/strings.xml index 0de2809e4..48b2941cd 100644 --- a/presentation/src/main/res/values-zh/strings.xml +++ b/presentation/src/main/res/values-zh/strings.xml @@ -342,7 +342,7 @@ 已複製 存檔的對話 您必須解鎖QKSMS+才能使用此功能 - 存檔的會話 + %s條新訊息 diff --git a/presentation/src/main/res/values/colors.xml b/presentation/src/main/res/values/colors.xml index 1023f8e15..d361908c9 100644 --- a/presentation/src/main/res/values/colors.xml +++ b/presentation/src/main/res/values/colors.xml @@ -74,7 +74,7 @@ @lineageos.platform:color/color_default_accent @lineageos.platform:color/color_default_selector @lineageos.platform:color/color_default_foreground - #00838F + #06C9AF diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 9c4a97e6b..be0757ce7 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -476,5 +476,10 @@ Authors - + QKSMS+ + Unlock amazing new features, and support development + Enjoying QKSMS? + Share some love and rate us on Google Play! + OKAY! + DISMISS -- GitLab From 5a279ab928f0ef52568b67cc86fa3420e7d6ea3f Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Tue, 8 Mar 2022 18:50:51 +0530 Subject: [PATCH 186/213] update missing value in icons and colors --- build.gradle | 2 +- .../QKSMS/common/util/extensions/ContextExtensions.kt | 4 ++-- .../java/com/moez/QKSMS/service/AutoDeleteService.kt | 11 ++++++++--- .../java/com/moez/QKSMS/feature/main/MainActivity.kt | 5 ++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 2d802bcc7..9764e7491 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { ext.exoplayer_version = "2.8.1" ext.glide_version = "4.8.0" ext.junit_version = '4.12' - ext.kotlin_version = '1.3.60' + ext.kotlin_version = '1.3.50' ext.lifecycle_version = '2.1.0' ext.material_version = '1.0.0' ext.mockito_version = '2.18.3' diff --git a/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt index f5534a9fc..3e66047a4 100644 --- a/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt +++ b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt @@ -87,5 +87,5 @@ fun Context.isInstalled(packageName: String): Boolean { val Context.versionCode: Int get() = packageManager.getPackageInfo(packageName, 0).versionCode -val Context.jobScheduler: JobScheduler - get() = getSystemService()!! +//val Context.jobScheduler: JobScheduler +// get() = getSystemService()!! diff --git a/data/src/main/java/com/moez/QKSMS/service/AutoDeleteService.kt b/data/src/main/java/com/moez/QKSMS/service/AutoDeleteService.kt index b29edca71..2f09687b3 100644 --- a/data/src/main/java/com/moez/QKSMS/service/AutoDeleteService.kt +++ b/data/src/main/java/com/moez/QKSMS/service/AutoDeleteService.kt @@ -1,13 +1,16 @@ package com.moez.QKSMS.service import android.annotation.SuppressLint +import android.app.PendingIntent.getActivity import android.app.job.JobInfo import android.app.job.JobParameters +import android.app.job.JobScheduler import android.app.job.JobService import android.content.ComponentName import android.content.Context +import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.getSystemService -import com.moez.QKSMS.common.util.extensions.jobScheduler +//import com.moez.QKSMS.common.util.extensions.jobScheduler import com.moez.QKSMS.interactor.DeleteOldMessages import dagger.android.AndroidInjection import io.reactivex.disposables.CompositeDisposable @@ -20,6 +23,7 @@ import javax.inject.Inject class AutoDeleteService : JobService() { companion object { + private lateinit var jobScheduler: JobScheduler private const val JobId = 8120235 @SuppressLint("MissingPermission") // Added in [presentation]'s AndroidManifest.xml @@ -30,13 +34,14 @@ class AutoDeleteService : JobService() { .setPeriodic(TimeUnit.DAYS.toMillis(1)) .setPersisted(true) .build() + jobScheduler=context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler - context.jobScheduler.schedule(periodicJob) + jobScheduler.schedule(periodicJob) } fun cancelJob(context: Context) { Timber.i("Canceling job") - context.jobScheduler.cancel(JobId) + jobScheduler.cancel(JobId) } } 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 2285ea3b3..c2d9f52b1 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 @@ -23,7 +23,7 @@ import android.animation.ObjectAnimator import android.app.AlertDialog import android.content.Intent import android.content.res.ColorStateList -import android.graphics.PorterDuff +import android.os.Build import android.os.Bundle import android.view.Gravity import android.view.Menu @@ -38,7 +38,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import com.jakewharton.rxbinding2.view.clicks import com.jakewharton.rxbinding2.widget.textChanges @@ -416,4 +415,4 @@ class MainActivity : QkThemedActivity(), MainView { backPressedSubject.onNext(NavItem.BACK) } -} +} \ No newline at end of file -- GitLab From 76fbb1cb0d7922919702afb92eb88d17162a4062 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 07:39:25 +0530 Subject: [PATCH 187/213] update missing value in string --- .../moez/QKSMS/feature/main/MainActivity.kt | 15 +++++++------- presentation/src/main/res/values/strings.xml | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) 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 c2d9f52b1..05bda5590 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 @@ -139,13 +139,11 @@ class MainActivity : QkThemedActivity(), MainView { } (syncing as? ViewStub)?.setOnInflateListener { _, _ -> - syncingProgress?.progressTintList = ColorStateList.valueOf(getColor(R.color.tools_theme)) - syncingProgress?.indeterminateTintList = ColorStateList.valueOf(getColor(R.color.tools_theme)) + syncingProgress?.progressTintList = ColorStateList.valueOf(theme.blockingFirst().theme) + syncingProgress?.indeterminateTintList = ColorStateList.valueOf(theme.blockingFirst().theme) } toggle.syncState() - toolbar.setNavigationIcon(lineageos.platform.R.drawable.ic_hamburger) - toolbar.navigationIcon?.setTint(resources.getColor(R.color.tools_theme)) toolbar.setNavigationOnClickListener { dismissKeyboard() homeIntent.onNext(Unit) @@ -184,8 +182,9 @@ class MainActivity : QkThemedActivity(), MainView { rateIcon.setTint(theme.theme) compose.setBackgroundTint(theme.theme) - // Set the FAB compose icon color - compose.setTint(getColor(R.color.tools_theme)) + // Set the FAB compose icon color + compose.setTint(theme.textPrimary) + } // These theme attributes don't apply themselves on API 21 if (Build.VERSION.SDK_INT <= 22) { @@ -381,7 +380,7 @@ class MainActivity : QkThemedActivity(), MainView { override fun showDeleteDialog(conversations: List) { val count = conversations.size - AlertDialog.Builder(this, R.style.customAlertDialog) + AlertDialog.Builder(this) .setTitle(R.string.dialog_delete_title) .setMessage(resources.getQuantityString(R.plurals.dialog_delete_message, count, count)) .setPositiveButton(R.string.button_delete) { _, _ -> confirmDeleteIntent.onNext(conversations) } @@ -396,7 +395,7 @@ class MainActivity : QkThemedActivity(), MainView { override fun showArchivedSnackbar() { Snackbar.make(drawerLayout, R.string.toast_archived, Snackbar.LENGTH_LONG).apply { setAction(R.string.button_undo) { undoArchiveIntent.onNext(Unit) } - setActionTextColor(getColor(R.color.tools_theme)) + setActionTextColor(colors.theme().theme) show() } } diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index be0757ce7..56408cc34 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ Archived Settings Notifications + Theme Search inbox… Type a name or number @@ -102,6 +103,7 @@ Contact card Scheduled for Selected time must be in the future! + You must unlock QKSMS+ to use scheduled messaging Added to scheduled messages Write a message… Copy text @@ -140,6 +142,7 @@ Address copied Conversation title Notifications + Theme Archive Unarchive Block @@ -157,6 +160,7 @@ Never Restore Select a backup + Please unlock QKSMS+ to use backup and restore Backup in progress… Restore in progress… Restore from backup @@ -199,6 +203,7 @@ General QK Reply + Theme Night mode Pure black night mode Start time @@ -312,6 +317,13 @@ Source code License Copyright + + Developer + Changelog + Contact + + + Message Information App Version - Message is an open source replacement to the stock messaging app on Android. @@ -365,6 +377,12 @@ Show less Open conversation + Material + QKSMS+ + HEX + Apply + + Moez Bhatti https://github.com/moezbhatti/qksms https://github.com/moezbhatti/qksms/releases @@ -476,6 +494,8 @@ Authors + + QKSMS+ Unlock amazing new features, and support development Enjoying QKSMS? -- GitLab From dfb3f4518d0294b8146f8281f83ec5a25430ab9f Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 09:07:08 +0530 Subject: [PATCH 188/213] update missing value in string, and yml for build --- .gitlab-ci.yml | 2 +- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 6 +++--- presentation/build.gradle | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d193d7876..9f417027e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "registry.gitlab.e.foundation:5000/e/apps/docker-android-apps-cicd:legacy" +image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:legacy" stages: - build diff --git a/build.gradle b/build.gradle index 9764e7491..fcd7bf78a 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { ext.exoplayer_version = "2.8.1" ext.glide_version = "4.8.0" ext.junit_version = '4.12' - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.31' ext.lifecycle_version = '2.1.0' ext.material_version = '1.0.0' ext.mockito_version = '2.18.3' @@ -43,7 +43,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.4' + classpath 'com.android.tools.build:gradle:3.6.0' classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'io.fabric.tools:gradle:1.29.0' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4b9cec46b..305c05a24 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Dec 03 23:30:52 EST 2019 +#Wed Mar 09 07:51:50 IST 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/presentation/build.gradle b/presentation/build.gradle index 6b39c8d13..6242020ae 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -156,6 +156,7 @@ dependencies { implementation "com.google.dagger:dagger-android-support:$dagger_version" kapt "com.google.dagger:dagger-compiler:$dagger_version" kapt "com.google.dagger:dagger-android-processor:$dagger_version" + annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version" compileOnly "javax.annotation:jsr250-api:1.0" // ezvcard -- GitLab From fb4c67f8d51031fd47719a447be8fe19d355a0d2 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 09:18:32 +0530 Subject: [PATCH 189/213] update gradle --- build.gradle | 5 ++--- gradle/wrapper/gradle-wrapper.properties | 2 +- presentation/build.gradle | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index fcd7bf78a..86bd12580 100644 --- a/build.gradle +++ b/build.gradle @@ -36,18 +36,17 @@ buildscript { ext.abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2] repositories { - maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.google.com' } jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.0' + classpath 'com.android.tools.build:gradle:7.1.1' classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'io.fabric.tools:gradle:1.29.0' classpath "io.realm:realm-gradle-plugin:$realm_version" + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.2' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 305c05a24..9118bd9e6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Mar 09 07:51:50 IST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/presentation/build.gradle b/presentation/build.gradle index 6242020ae..f0ee5d478 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -24,7 +24,6 @@ apply plugin: 'kotlin-kapt' android { compileSdkVersion 29 - buildToolsVersion "29.0.3" flavorDimensions "analytics" defaultConfig { -- GitLab From de69a5c4d846f62822a4d13300dadd29b26d0c13 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 09:28:13 +0530 Subject: [PATCH 190/213] added missing file --- .../injection/ThemePickerComponent.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/themepicker/injection/ThemePickerComponent.kt diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/injection/ThemePickerComponent.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/injection/ThemePickerComponent.kt new file mode 100644 index 000000000..6cc684553 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/injection/ThemePickerComponent.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.themepicker.injection + +import com.moez.QKSMS.feature.themepicker.ThemePickerController +import com.moez.QKSMS.injection.scope.ControllerScope +import dagger.Subcomponent + +@ControllerScope +@Subcomponent(modules = [ThemePickerModule::class]) +interface ThemePickerComponent { + + fun inject(controller: ThemePickerController) + + @Subcomponent.Builder + interface Builder { + fun themePickerModule(module: ThemePickerModule): Builder + fun build(): ThemePickerComponent + } + +} \ No newline at end of file -- GitLab From 8b1d956729d4242b6f16471cc2f846645c099fe4 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 09:34:52 +0530 Subject: [PATCH 191/213] update themePickerBuilder in AppComponent --- .../src/main/java/com/moez/QKSMS/injection/AppComponent.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt index 57beba612..eb90df007 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt @@ -38,6 +38,7 @@ import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoCompone import com.moez.QKSMS.feature.settings.SettingsController import com.moez.QKSMS.feature.settings.about.AboutController import com.moez.QKSMS.feature.settings.swipe.SwipeActionsController +import com.moez.QKSMS.feature.themepicker.injection.ThemePickerComponent import com.moez.QKSMS.feature.widget.WidgetAdapter import com.moez.QKSMS.injection.android.ActivityBuilderModule import com.moez.QKSMS.injection.android.BroadcastReceiverBuilderModule @@ -56,6 +57,7 @@ import javax.inject.Singleton interface AppComponent { fun conversationInfoBuilder(): ConversationInfoComponent.Builder + fun themePickerBuilder(): ThemePickerComponent.Builder fun inject(application: QKApplication) @@ -77,7 +79,6 @@ interface AppComponent { */ fun inject(service: QkChooserTargetService) - fun inject(view: AvatarView) fun inject(view: DetailedChipView) fun inject(view: PagerTitleView) @@ -87,4 +88,4 @@ interface AppComponent { fun inject(view: QkSwitch) fun inject(view: QkTextView) -} +} \ No newline at end of file -- GitLab From 3721ba04b315d592b9cff142c56c1323aeea7fff Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 09:40:22 +0530 Subject: [PATCH 192/213] update themePickerBuilder in AppComponent --- .../com/moez/QKSMS/feature/themepicker/ThemePickerController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ae4dc56f2..65b339304 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 @@ -39,7 +39,7 @@ import kotlinx.android.synthetic.main.theme_picker_hsv.* import javax.inject.Inject class ThemePickerController( - val recipientId: Long = 0L + val recipientId: Long = 0L ) : QkController(), ThemePickerView { @Inject override lateinit var presenter: ThemePickerPresenter -- GitLab From 9dcf6c9b5c60d68bee2d69b00c413de85fc12177 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 09:47:56 +0530 Subject: [PATCH 193/213] update ImageRepository --- .../src/main/java/com/moez/QKSMS/injection/AppModule.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index 04ad72a68..afab6678c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -78,8 +78,6 @@ import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ContactRepositoryImpl import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.ConversationRepositoryImpl -import com.moez.QKSMS.repository.ImageRepository -import com.moez.QKSMS.repository.ImageRepositoryImpl import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.repository.MessageRepositoryImpl import com.moez.QKSMS.repository.ScheduledMessageRepository @@ -210,9 +208,6 @@ class AppModule(private var application: Application) { @Provides fun provideConversationRepository(repository: ConversationRepositoryImpl): ConversationRepository = repository - @Provides - fun provideImageRepository(repository: ImageRepositoryImpl): ImageRepository = repository - @Provides fun provideMessageRepository(repository: MessageRepositoryImpl): MessageRepository = repository -- GitLab From ab5dc7d8547076fbca5258bcd71165169d38cdee Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 09:54:56 +0530 Subject: [PATCH 194/213] added missing classes --- .../com/moez/QKSMS/manager/RatingManager.kt | 37 +++++++++++ .../moez/QKSMS/mapper/RatingManagerImpl.kt | 62 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 data/src/main/java/com/moez/QKSMS/manager/RatingManager.kt create mode 100644 data/src/main/java/com/moez/QKSMS/mapper/RatingManagerImpl.kt diff --git a/data/src/main/java/com/moez/QKSMS/manager/RatingManager.kt b/data/src/main/java/com/moez/QKSMS/manager/RatingManager.kt new file mode 100644 index 000000000..0bf948912 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/manager/RatingManager.kt @@ -0,0 +1,37 @@ + +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.manager + +import io.reactivex.Observable + +interface RatingManager { + + val shouldShowRating: Observable + + /** + * Whether or not we should show the rating UI should depend on the number of sessions + */ + fun addSession() + + fun rate() + + fun dismiss() + +} \ No newline at end of file diff --git a/data/src/main/java/com/moez/QKSMS/mapper/RatingManagerImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/RatingManagerImpl.kt new file mode 100644 index 000000000..7b13074fa --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/mapper/RatingManagerImpl.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.mapper + +import com.f2prateek.rx.preferences2.RxSharedPreferences +import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.manager.RatingManager +import io.reactivex.rxkotlin.Observables +import javax.inject.Inject + +class RatingManagerImpl @Inject constructor( + rxPrefs: RxSharedPreferences, + private val analyticsManager: AnalyticsManager +) : RatingManager { + + companion object { + private const val RATING_THRESHOLD = 10 + } + + private val sessions = rxPrefs.getInteger("sessions", 0) + private val rated = rxPrefs.getBoolean("rated", false) + private val dismissed = rxPrefs.getBoolean("dismissed", false) + + override val shouldShowRating = Observables.combineLatest( + sessions.asObservable(), + rated.asObservable(), + dismissed.asObservable() + ) { sessions, rated, dismissed -> + sessions > RATING_THRESHOLD && !rated && !dismissed + } + + override fun addSession() { + sessions.set(sessions.get() + 1) + } + + override fun rate() { + analyticsManager.track("Clicked Rate") + rated.set(true) + } + + override fun dismiss() { + analyticsManager.track("Clicked Rate (Dismiss)") + dismissed.set(true) + } + +} \ No newline at end of file -- GitLab From e1500a7fe75f61c313e2f7e489cdf0ee9820daf7 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 10:23:53 +0530 Subject: [PATCH 195/213] added missing classes --- domain/build.gradle | 2 +- presentation/build.gradle | 10 +- .../moez/QKSMS/feature/main/MainViewModel.kt | 43 +++--- .../feature/themepicker/ThemePagerAdapter.kt | 55 ++++++++ .../feature/themepicker/ThemePickerView.kt | 35 +++++ .../res/layout/theme_picker_controller.xml | 57 ++++++++ .../src/main/res/layout/theme_picker_hsv.xml | 130 ++++++++++++++++++ 7 files changed, 305 insertions(+), 27 deletions(-) create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePagerAdapter.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerView.kt create mode 100644 presentation/src/main/res/layout/theme_picker_controller.xml create mode 100644 presentation/src/main/res/layout/theme_picker_hsv.xml diff --git a/domain/build.gradle b/domain/build.gradle index 5c7277b38..34087c58a 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -23,7 +23,7 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 29 + compileSdkVersion 30 compileOptions { sourceCompatibility 1.8 diff --git a/presentation/build.gradle b/presentation/build.gradle index f0ee5d478..453a67264 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -23,13 +23,13 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 29 + compileSdkVersion 30 flavorDimensions "analytics" defaultConfig { applicationId "foundation.e.message" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 2218 versionName "3.9.4" setProperty("archivesBaseName", "QKSMS-v${versionName}") @@ -63,12 +63,12 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } // For Kotlin projects kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } lintOptions { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index 0d404c866..0354df58e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -23,7 +23,7 @@ import androidx.recyclerview.widget.ItemTouchHelper import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel -import com.moez.QKSMS.common.util.BillingManager + import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.interactor.DeleteConversations import com.moez.QKSMS.interactor.MarkAllSeen @@ -38,6 +38,7 @@ import com.moez.QKSMS.interactor.SyncContacts import com.moez.QKSMS.interactor.SyncMessages import com.moez.QKSMS.listener.ContactAddedListener import com.moez.QKSMS.manager.BillingManager + import com.moez.QKSMS.manager.ChangelogManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.manager.RatingManager @@ -61,26 +62,26 @@ import javax.inject.Inject import javax.inject.Named class MainViewModel @Inject constructor( - billingManager: BillingManager, - contactAddedListener: ContactAddedListener, - markAllSeen: MarkAllSeen, - migratePreferences: MigratePreferences, - syncRepository: SyncRepository, - private val changelogManager: ChangelogManager, - private val conversationRepo: ConversationRepository, - private val deleteConversations: DeleteConversations, - private val markArchived: MarkArchived, - private val markPinned: MarkPinned, - private val markRead: MarkRead, - private val markUnarchived: MarkUnarchived, - private val markUnpinned: MarkUnpinned, - private val markUnread: MarkUnread, - private val navigator: Navigator, - private val permissionManager: PermissionManager, - private val prefs: Preferences, - private val ratingManager: RatingManager, - private val syncContacts: SyncContacts, - private val syncMessages: SyncMessages + billingManager: BillingManager, + contactAddedListener: ContactAddedListener, + markAllSeen: MarkAllSeen, + migratePreferences: MigratePreferences, + syncRepository: SyncRepository, + private val changelogManager: ChangelogManager, + private val conversationRepo: ConversationRepository, + private val deleteConversations: DeleteConversations, + private val markArchived: MarkArchived, + private val markPinned: MarkPinned, + private val markRead: MarkRead, + private val markUnarchived: MarkUnarchived, + private val markUnpinned: MarkUnpinned, + private val markUnread: MarkUnread, + private val navigator: Navigator, + private val permissionManager: PermissionManager, + private val prefs: Preferences, + private val ratingManager: RatingManager, + private val syncContacts: SyncContacts, + private val syncMessages: SyncMessages ) : QkViewModel(MainState(page = Inbox(data = conversationRepo.getConversations()))) { init { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePagerAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePagerAdapter.kt new file mode 100644 index 000000000..c7185c211 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePagerAdapter.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.themepicker + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.viewpager.widget.PagerAdapter +import com.moez.QKSMS.R +import javax.inject.Inject + +class ThemePagerAdapter @Inject constructor(private val context: Context) : PagerAdapter() { + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + return when (position) { + 1 -> container.findViewById(R.id.hsvPicker) + else -> container.findViewById(R.id.materialColors) + } + } + + override fun getPageTitle(position: Int): CharSequence? { + return when (position) { + 1 -> context.getString(R.string.theme_plus) + else -> context.getString(R.string.theme_material) + } + } + + override fun isViewFromObject(view: View, `object`: Any): Boolean { + return view == `object` + } + + override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { + } + + override fun getCount(): Int { + return 2 + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerView.kt new file mode 100644 index 000000000..a3005f01c --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerView.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.themepicker + +import com.moez.QKSMS.common.base.QkViewContract +import io.reactivex.Observable + +interface ThemePickerView : QkViewContract { + + fun themeSelected(): Observable + fun hsvThemeSelected(): Observable + fun clearHsvThemeClicks(): Observable<*> + fun applyHsvThemeClicks(): Observable<*> + fun viewQksmsPlusClicks(): Observable<*> + + fun setCurrentTheme(color: Int) + fun showQksmsPlusSnackbar() + +} \ No newline at end of file diff --git a/presentation/src/main/res/layout/theme_picker_controller.xml b/presentation/src/main/res/layout/theme_picker_controller.xml new file mode 100644 index 000000000..7ea2a1ee9 --- /dev/null +++ b/presentation/src/main/res/layout/theme_picker_controller.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/theme_picker_hsv.xml b/presentation/src/main/res/layout/theme_picker_hsv.xml new file mode 100644 index 000000000..372525624 --- /dev/null +++ b/presentation/src/main/res/layout/theme_picker_hsv.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- GitLab From 73a0137d96a0c14e6fce4daf769b93b652db3745 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 11:58:50 +0530 Subject: [PATCH 196/213] added update missing classes in conflict --- .../com/moez/QKSMS/util/NightModeManager.kt | 94 ++++++++++++- .../java/com/moez/QKSMS/common/Navigator.kt | 36 +++-- .../moez/QKSMS/common/util/BillingManager.kt | 127 +++--------------- .../moez/QKSMS/common/widget/AvatarView.kt | 1 - .../moez/QKSMS/common/widget/TightTextView.kt | 2 +- .../QKSMS/feature/backup/BackupPresenter.kt | 2 +- .../QKSMS/feature/compose/ComposeActivity.kt | 23 ++-- .../QKSMS/feature/compose/ComposeViewModel.kt | 56 ++++---- .../feature/compose/ComposeWindowCallback.kt | 12 +- .../QKSMS/feature/compose/MessagesAdapter.kt | 33 ++--- .../QKSMS/feature/compose/part/MediaBinder.kt | 11 +- .../QKSMS/feature/compose/part/PartBinder.kt | 11 +- .../feature/compose/part/PartsAdapter.kt | 14 +- .../conversationinfo/ConversationInfoView.kt | 2 +- .../QKSMS/feature/gallery/GalleryViewModel.kt | 22 ++- .../com/moez/QKSMS/feature/main/MainState.kt | 1 + .../com/moez/QKSMS/feature/main/MainView.kt | 5 +- .../moez/QKSMS/feature/main/MainViewModel.kt | 12 +- .../moez/QKSMS/feature/main/SearchAdapter.kt | 7 +- .../feature/settings/SettingsController.kt | 7 +- .../QKSMS/feature/settings/SettingsView.kt | 1 + .../feature/settings/about/AboutController.kt | 5 +- .../feature/settings/about/AboutPresenter.kt | 18 +-- .../settings/swipe/SwipeActionsController.kt | 2 +- .../QKSMS/feature/themepicker/ThemeAdapter.kt | 4 +- .../QKSMS/feature/widget/WidgetAdapter.kt | 10 +- .../src/main/res/layout/about_controller.xml | 52 +++---- .../main/res/layout/settings_theme_widget.xml | 35 +++++ .../src/main/res/layout/theme_list_item.xml | 41 ++++++ .../res/layout/theme_palette_list_item.xml | 35 +++++ 30 files changed, 383 insertions(+), 298 deletions(-) create mode 100644 presentation/src/main/res/layout/settings_theme_widget.xml create mode 100644 presentation/src/main/res/layout/theme_list_item.xml create mode 100644 presentation/src/main/res/layout/theme_palette_list_item.xml diff --git a/data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt b/data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt index 943704201..8cfac4158 100644 --- a/data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt +++ b/data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt @@ -18,6 +18,7 @@ */ package com.moez.QKSMS.util + import android.app.AlarmManager import android.app.PendingIntent import android.content.Context @@ -33,20 +34,99 @@ import javax.inject.Singleton @Singleton class NightModeManager @Inject constructor( - private val context: Context, - private val prefs: Preferences, - private val widgetManager: WidgetManager + private val context: Context, + private val prefs: Preferences, + private val widgetManager: WidgetManager ) { fun updateCurrentTheme() { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + when (prefs.nightMode.get()) { + Preferences.NIGHT_MODE_SYSTEM -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + } + + Preferences.NIGHT_MODE_OFF -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + } + + Preferences.NIGHT_MODE_ON -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + } + + Preferences.NIGHT_MODE_AUTO -> { + val nightStartTime = getPreviousInstanceOfTime(prefs.nightStart.get()) + val nightEndTime = getPreviousInstanceOfTime(prefs.nightEnd.get()) + + // If the last nightStart was more recent than the last nightEnd, then it's night time + val night = nightStartTime > nightEndTime + prefs.night.set(night) + AppCompatDelegate.setDefaultNightMode(when (night) { + true -> AppCompatDelegate.MODE_NIGHT_YES + false -> AppCompatDelegate.MODE_NIGHT_NO + }) + widgetManager.updateTheme() + } + } } fun updateNightMode(mode: Int) { - prefs.nightMode.set(Preferences.NIGHT_MODE_SYSTEM) - prefs.night.set(false) - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + prefs.nightMode.set(mode) + + // If it's not on auto mode, set the appropriate night mode + if (mode != Preferences.NIGHT_MODE_AUTO) { + prefs.night.set(mode == Preferences.NIGHT_MODE_ON) + AppCompatDelegate.setDefaultNightMode(when (mode) { + Preferences.NIGHT_MODE_OFF -> AppCompatDelegate.MODE_NIGHT_NO + Preferences.NIGHT_MODE_ON -> AppCompatDelegate.MODE_NIGHT_YES + Preferences.NIGHT_MODE_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + else -> AppCompatDelegate.MODE_NIGHT_NO + }) widgetManager.updateTheme() + } + + updateAlarms() + } + + fun setNightStart(hour: Int, minute: Int) { + prefs.nightStart.set("$hour:$minute") + updateAlarms() + } + + fun setNightEnd(hour: Int, minute: Int) { + prefs.nightEnd.set("$hour:$minute") + updateAlarms() + } + + private fun updateAlarms() { + val dayCalendar = createCalendar(prefs.nightEnd.get()) + val day = Intent(context, NightModeReceiver::class.java) + val dayIntent = PendingIntent.getBroadcast(context, 0, day, 0) + + val nightCalendar = createCalendar(prefs.nightStart.get()) + val night = Intent(context, NightModeReceiver::class.java) + val nightIntent = PendingIntent.getBroadcast(context, 1, night, 0) + + context.sendBroadcast(day) + context.sendBroadcast(night) + + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + if (prefs.nightMode.get() == Preferences.NIGHT_MODE_AUTO) { + alarmManager.setInexactRepeating( + AlarmManager.RTC_WAKEUP, + dayCalendar.timeInMillis, + AlarmManager.INTERVAL_DAY, + dayIntent + ) + alarmManager.setInexactRepeating( + AlarmManager.RTC_WAKEUP, + nightCalendar.timeInMillis, + AlarmManager.INTERVAL_DAY, + nightIntent + ) + } else { + alarmManager.cancel(dayIntent) + alarmManager.cancel(nightIntent) + } } private fun createCalendar(time: String): Calendar { diff --git a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt index b4e3937c2..30556ef3d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt @@ -50,11 +50,11 @@ import javax.inject.Singleton @Singleton class Navigator @Inject constructor( - private val context: Context, - private val analyticsManager: AnalyticsManager, - private val notificationManager: NotificationManager, - private val billingManager: BillingManager, - private val permissions: PermissionManager + private val context: Context, + private val analyticsManager: AnalyticsManager, + private val billingManager: BillingManager, + private val notificationManager: NotificationManager, + private val permissions: PermissionManager ) { private fun startActivity(intent: Intent) { @@ -141,9 +141,24 @@ class Navigator @Inject constructor( startActivity(intent) } - fun openUri(uri: Uri) { - val intent = Intent(Intent.ACTION_VIEW, uri) - startActivity(intent) + fun showDeveloper() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti")) + startActivityExternal(intent) + } + + fun showSourceCode() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms")) + startActivityExternal(intent) + } + + fun showChangelog() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/releases")) + startActivityExternal(intent) + } + + fun showLicense() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/blob/master/LICENSE")) + startActivityExternal(intent) } fun showBlockedConversations() { @@ -277,6 +292,7 @@ class Navigator @Inject constructor( if (threadId != 0L) { notificationManager.createNotificationChannel(threadId) } + val channelId = notificationManager.buildNotificationChannelId(threadId) val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_CHANNEL_ID, channelId) @@ -285,6 +301,4 @@ class Navigator @Inject constructor( } } - - -} +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt index 69f0c0654..1178f245a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2020 Moez Bhatti * * This file is part of QKSMS. * @@ -16,129 +16,32 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.common.util + +package com.moez.QKSMS.manager import android.app.Activity -import android.content.Context -import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.BillingClient.BillingResponse -import com.android.billingclient.api.BillingClient.SkuType -import com.android.billingclient.api.BillingClientStateListener -import com.android.billingclient.api.BillingFlowParams -import com.android.billingclient.api.Purchase -import com.android.billingclient.api.PurchasesUpdatedListener -import com.android.billingclient.api.SkuDetails -import com.android.billingclient.api.SkuDetailsParams -import com.moez.QKSMS.BuildConfig -import com.moez.QKSMS.manager.AnalyticsManager -import io.reactivex.Flowable import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers -import io.reactivex.subjects.BehaviorSubject -import io.reactivex.subjects.Subject -import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class BillingManager @Inject constructor( - context: Context, - private val analyticsManager: AnalyticsManager -) : PurchasesUpdatedListener { +interface BillingManager { companion object { const val SKU_PLUS = "remove_ads" const val SKU_PLUS_DONATE = "qksms_plus_donate" } - val products: Observable> = BehaviorSubject.create() - val upgradeStatus: Observable - - private val skus = listOf(SKU_PLUS, SKU_PLUS_DONATE) - private val purchaseListObservable = BehaviorSubject.create>() - - private val billingClient: BillingClient = BillingClient.newBuilder(context).setListener(this).build() - private var isServiceConnected = false - - init { - startServiceConnection { - queryPurchases() - querySkuDetailsAsync() - } - - upgradeStatus = when (BuildConfig.FLAVOR) { - "noAnalytics" -> BehaviorSubject.createDefault(true) - - else -> purchaseListObservable - .map { purchases -> purchases.any { it.sku == SKU_PLUS } || purchases.any { it.sku == SKU_PLUS_DONATE } } - .doOnNext { upgraded -> analyticsManager.setUserProperty("Upgraded", upgraded) } - } - } - - private fun queryPurchases() { - executeServiceRequest { - // Load the cached data - purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) - - // On a fresh device, the purchase might not be cached, and so we'll need to force a refresh - billingClient.queryPurchaseHistoryAsync(SkuType.INAPP) { _, _ -> - purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) - } - } - } + data class Product( + val sku: String, + val price: String, + val priceCurrencyCode: String + ) + val products: Observable> + val upgradeStatus: Observable - private fun startServiceConnection(onSuccess: () -> Unit) { - val listener = object : BillingClientStateListener { - override fun onBillingSetupFinished(@BillingResponse billingResponseCode: Int) { - if (billingResponseCode == BillingResponse.OK) { - isServiceConnected = true - onSuccess() - } else { - Timber.w("Billing response: $billingResponseCode") - purchaseListObservable.onNext(listOf()) - } - } - - override fun onBillingServiceDisconnected() { - isServiceConnected = false - } - } - - Flowable.fromCallable { billingClient.startConnection(listener) } - .subscribeOn(Schedulers.io()) - .subscribe() - } - - private fun querySkuDetailsAsync() { - executeServiceRequest { - val subParams = SkuDetailsParams.newBuilder().setSkusList(skus).setType(BillingClient.SkuType.INAPP) - billingClient.querySkuDetailsAsync(subParams.build()) { responseCode, skuDetailsList -> - if (responseCode == BillingResponse.OK) { - (products as Subject).onNext(skuDetailsList) - } - } - } - } - - fun initiatePurchaseFlow(activity: Activity, sku: String) { - executeServiceRequest { - val params = BillingFlowParams.newBuilder().setSku(sku).setType(SkuType.INAPP) - billingClient.launchBillingFlow(activity, params.build()) - } - } + suspend fun checkForPurchases() - private fun executeServiceRequest(runnable: () -> Unit) { - when (isServiceConnected) { - true -> runnable() - false -> startServiceConnection(runnable) - } - } + suspend fun queryProducts() - override fun onPurchasesUpdated(resultCode: Int, purchases: List?) { - if (resultCode == BillingResponse.OK) { - purchaseListObservable.onNext(purchases.orEmpty()) - } - } + suspend fun initiatePurchaseFlow(activity: Activity, sku: String) -} +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index b6916ce72..748b2d32a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -80,7 +80,6 @@ class AvatarView @JvmOverloads constructor( super.onFinishInflate() if (!isInEditMode) { - applyTheme(threadId) updateView() } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/TightTextView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/TightTextView.kt index 0c104b81c..ce86aef7a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/TightTextView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/TightTextView.kt @@ -37,7 +37,7 @@ class TightTextView @JvmOverloads constructor( val maxLineWidth = (0 until layout.lineCount) .map(layout::getLineWidth) - .max() ?: 0f + .maxOrNull() ?: 0f val width = Math.ceil(maxLineWidth.toDouble()).toInt() + compoundPaddingLeft + compoundPaddingRight if (width < measuredWidth) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt index 6c527243d..a6a7426ce 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt @@ -63,7 +63,7 @@ class BackupPresenter @Inject constructor( .distinctUntilChanged() .switchMap { backupRepo.getBackups() } .doOnNext { backups -> newState { copy(backups = backups) } } - .map { backups -> backups.map { it.date }.max() ?: 0L } + .map { backups -> backups.map { it.date }.maxOrNull() ?: 0L } .map { lastBackup -> when (lastBackup) { 0L -> context.getString(R.string.backup_never) 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 b9522f872..5115d5014 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 @@ -28,13 +28,9 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle -import android.os.Handler import android.provider.ContactsContract import android.provider.MediaStore import android.text.format.DateFormat -import android.util.Log -import android.util.TypedValue -import android.view.ContextThemeWrapper import android.view.Menu import android.view.MenuItem import androidx.appcompat.app.AlertDialog @@ -75,7 +71,7 @@ import java.util.* import javax.inject.Inject import kotlin.collections.HashMap -class ComposeActivity : QkThemedActivity(), ComposeView { +class ComposeActivity(override val selectPreferredSIM: Observable<*>) : QkThemedActivity(), ComposeView { companion object { private const val SelectContactRequestCode = 0 @@ -115,7 +111,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val inputContentIntent by lazy { message.inputContentSelected } override val scheduleSelectedIntent: Subject = PublishSubject.create() override val changeSimIntent by lazy { sim.clicks() } - override val selectPreferredSIM by lazy { viewSelectPreferredSim.clicks() } override val scheduleCancelIntent by lazy { scheduledCancel.clicks() } override val sendIntent by lazy { send.clicks() } override val viewQksmsPlusIntent: Subject = PublishSubject.create() @@ -249,7 +244,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun clearSelection() = messageAdapter.clearSelection() override fun showDetails(details: String) { - AlertDialog.Builder(this, R.style.customAlertDialog) + AlertDialog.Builder(this) .setTitle(R.string.compose_details_title) .setMessage(details) .setCancelable(true) @@ -272,8 +267,8 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun requestDatePicker() { val calendar = Calendar.getInstance() - DatePickerDialog(this, R.style.customAlertDialog, DatePickerDialog.OnDateSetListener { _, year, month, day -> - TimePickerDialog(this, R.style.customAlertDialog, TimePickerDialog.OnTimeSetListener { _, hour, minute -> + DatePickerDialog(this, DatePickerDialog.OnDateSetListener { _, year, month, day -> + TimePickerDialog(this, TimePickerDialog.OnTimeSetListener { _, hour, minute -> calendar.set(Calendar.YEAR, year) calendar.set(Calendar.MONTH, month) calendar.set(Calendar.DAY_OF_MONTH, day) @@ -349,7 +344,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun showQksmsPlusSnackbar(message: Int) { Snackbar.make(contentView, message, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusIntent.onNext(Unit) } - setActionTextColor(getColor(R.color.tools_theme)) + setActionTextColor(colors.theme().theme) show() } } @@ -397,10 +392,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { super.onSaveInstanceState(outState) } - override fun onRestoreInstanceState(savedInstanceState: Bundle?) { - cameraDestination = savedInstanceState?.getParcelable(CameraDestinationKey) - super.onRestoreInstanceState(savedInstanceState) - } +// override fun onRestoreInstanceState(savedInstanceState: Bundle?) { +// cameraDestination = savedInstanceState?.getParcelable(CameraDestinationKey) +// super.onRestoreInstanceState(savedInstanceState) +// } override fun onBackPressed() = backPressedIntent.onNext(Unit) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 8da7c8fc5..da5c26f95 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -20,17 +20,13 @@ package com.moez.QKSMS.feature.compose import android.content.Context import android.net.Uri -import android.os.Build +import android.os.Vibrator import android.provider.ContactsContract -import android.telephony.SmsManager import android.telephony.SmsMessage -import android.util.Log -import android.view.inputmethod.EditorInfo -import androidx.annotation.RequiresApi +import androidx.core.content.getSystemService import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel -import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.common.util.ClipboardUtils import com.moez.QKSMS.common.util.MessageDetailsFormatter import com.moez.QKSMS.common.util.extensions.makeToast @@ -78,29 +74,29 @@ import javax.inject.Inject import javax.inject.Named class ComposeViewModel @Inject constructor( - @Named("query") private val query: String, - @Named("threadId") private val threadId: Long, - @Named("addresses") private val addresses: List, - @Named("text") private val sharedText: String, - @Named("attachments") private val sharedAttachments: Attachments, - private val contactRepo: ContactRepository, - private val context: Context, - private val activeConversationManager: ActiveConversationManager, - private val addScheduledMessage: AddScheduledMessage, - private val billingManager: BillingManager, - private val cancelMessage: CancelDelayedMessage, - private val conversationRepo: ConversationRepository, - private val deleteMessages: DeleteMessages, - private val markRead: MarkRead, - private val messageDetailsFormatter: MessageDetailsFormatter, - private val messageRepo: MessageRepository, - private val navigator: Navigator, - private val permissionManager: PermissionManager, - private val phoneNumberUtils: PhoneNumberUtils, - private val prefs: Preferences, - private val retrySending: RetrySending, - private val sendMessage: SendMessage, - private val subscriptionManager: SubscriptionManagerCompat + @Named("query") private val query: String, + @Named("threadId") private val threadId: Long, + @Named("addresses") private val addresses: List, + @Named("text") private val sharedText: String, + @Named("attachments") private val sharedAttachments: Attachments, + private val contactRepo: ContactRepository, + private val context: Context, + private val activeConversationManager: ActiveConversationManager, + private val addScheduledMessage: AddScheduledMessage, + private val billingManager: BillingManager, + private val cancelMessage: CancelDelayedMessage, + private val conversationRepo: ConversationRepository, + private val deleteMessages: DeleteMessages, + private val markRead: MarkRead, + private val messageDetailsFormatter: MessageDetailsFormatter, + private val messageRepo: MessageRepository, + private val navigator: Navigator, + private val permissionManager: PermissionManager, + private val phoneNumberUtils: PhoneNumberUtils, + private val prefs: Preferences, + private val retrySending: RetrySending, + private val sendMessage: SendMessage, + private val subscriptionManager: SubscriptionManagerCompat ) : QkViewModel(ComposeState( editingMode = threadId == 0L && addresses.isEmpty(), threadId = threadId, @@ -747,4 +743,4 @@ class ComposeViewModel @Inject constructor( ?.let { bytes -> String(bytes) } } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt index 0ea9eab79..bf991d7ac 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt @@ -35,8 +35,8 @@ import androidx.annotation.RequiresApi import com.moez.QKSMS.feature.compose.editing.DetailedChipView class ComposeWindowCallback( - private val localCallback: Window.Callback, - private val activity: Activity + private val localCallback: Window.Callback, + private val activity: Activity ) : Window.Callback { override fun dispatchKeyEvent(keyEvent: KeyEvent): Boolean { @@ -83,10 +83,14 @@ class ComposeWindowCallback( return localCallback.onCreatePanelMenu(i, menu) } - override fun onPreparePanel(i: Int, view: View, menu: Menu): Boolean { + override fun onPreparePanel(i: Int, view: View?, menu: Menu): Boolean { return localCallback.onPreparePanel(i, view, menu) } +// override fun onPreparePanel(i: Int, view: View, menu: Menu): Boolean { +// return localCallback.onPreparePanel(i, view, menu) +// } + override fun onMenuOpened(i: Int, menu: Menu): Boolean { return localCallback.onMenuOpened(i, menu) } @@ -144,4 +148,4 @@ class ComposeWindowCallback( override fun onActionModeFinished(actionMode: ActionMode) { localCallback.onActionModeFinished(actionMode) } -} +} \ No newline at end of file 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 749c71d04..b95d7a9db 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 @@ -26,8 +26,6 @@ import android.text.Layout import android.text.Spannable import android.text.SpannableString import android.text.style.StyleSpan -import android.util.TypedValue -import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -76,14 +74,14 @@ import javax.inject.Inject import javax.inject.Provider class MessagesAdapter @Inject constructor( - subscriptionManager: SubscriptionManagerCompat, - private val context: Context, - private val colors: Colors, - private val dateFormatter: DateFormatter, - private val partsAdapterProvider: Provider, - private val phoneNumberUtils: PhoneNumberUtils, - private val prefs: Preferences, - private val textViewStyler: TextViewStyler + subscriptionManager: SubscriptionManagerCompat, + private val context: Context, + private val colors: Colors, + private val dateFormatter: DateFormatter, + private val partsAdapterProvider: Provider, + private val phoneNumberUtils: PhoneNumberUtils, + private val prefs: Preferences, + private val textViewStyler: TextViewStyler ) : QkRealmAdapter() { companion object { @@ -148,13 +146,10 @@ class MessagesAdapter @Inject constructor( if (viewType == VIEW_TYPE_MESSAGE_OUT) { view = layoutInflater.inflate(R.layout.message_list_item_out, parent, false) - view.findViewById(R.id.cancelIcon).setTint(context.getColor(R.color.tools_theme)) - view.findViewById(R.id.cancel).setTint(context.getColor(R.color.tools_theme)) + view.findViewById(R.id.cancelIcon).setTint(theme.theme) + view.findViewById(R.id.cancel).setTint(theme.theme) } else { view = layoutInflater.inflate(R.layout.message_list_item_in, parent, false) - view.avatar.threadId = conversation?.id ?: 0 - view.body.setTextColor(context.getColor(R.color.white)) - view.body.setBackgroundTint(context.getColor(R.color.tools_theme)) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -246,16 +241,10 @@ class MessagesAdapter @Inject constructor( // Bind the avatar and bubble colour if (!message.isMe()) { holder.avatar.setRecipient(contactCache[message.address]) - //holder.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) + holder.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) holder.body.setTextColor(theme.textPrimary) holder.body.setBackgroundTint(theme.theme) - view.avatar.threadId = conversation?.id ?: 0 - view.avatar.setContact(contactCache[message.address]) - view.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) - view.avatar.setBackgroundTint(context.getColor(R.color.tools_theme)) - view.body.setTextColor(context.getColor(R.color.white)) - view.body.setBackgroundTint(context.getColor(R.color.tools_theme)) } // Bind the body text diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt index 007ab41be..2f5fa2ffb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt @@ -35,15 +35,16 @@ import javax.inject.Inject class MediaBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { override val partLayout = R.layout.mms_preview_list_item + override var theme = colors.theme() override fun canBindPart(part: MmsPart) = part.isImage() || part.isVideo() override fun bindPart( - holder: QkViewHolder, - part: MmsPart, - message: Message, - canGroupWithPrevious: Boolean, - canGroupWithNext: Boolean + holder: QkViewHolder, + part: MmsPart, + message: Message, + canGroupWithPrevious: Boolean, + canGroupWithNext: Boolean ) { holder.video.setVisible(part.isVideo()) holder.containerView.setOnClickListener { clicks.onNext(part.id) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt index 2db403b90..4ba23dfba 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt @@ -31,15 +31,16 @@ abstract class PartBinder { abstract val partLayout: Int + abstract var theme: Colors.Theme abstract fun canBindPart(part: MmsPart): Boolean abstract fun bindPart( - holder: QkViewHolder, - part: MmsPart, - message: Message, - canGroupWithPrevious: Boolean, - canGroupWithNext: Boolean + holder: QkViewHolder, + part: MmsPart, + message: Message, + canGroupWithPrevious: Boolean, + canGroupWithNext: Boolean ) } 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 e95c29ca0..72d0a9bf0 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 @@ -35,14 +35,20 @@ import kotlinx.android.synthetic.main.message_list_item_in.* import javax.inject.Inject class PartsAdapter @Inject constructor( - colors: Colors, - fileBinder: FileBinder, - mediaBinder: MediaBinder, - vCardBinder: VCardBinder + colors: Colors, + fileBinder: FileBinder, + mediaBinder: MediaBinder, + vCardBinder: VCardBinder ) : QkAdapter() { private val partBinders = listOf(mediaBinder, vCardBinder, fileBinder) + var theme: Colors.Theme = colors.theme() + set(value) { + field = value + partBinders.forEach { binder -> binder.theme = value } + } + val clicks: Observable = Observable.merge(partBinders.map { it.clicks }) private lateinit var message: Message diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt index 5a86ffffe..08a43f0fe 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt @@ -41,4 +41,4 @@ interface ConversationInfoView : QkViewContract { fun requestDefaultSms() fun showDeleteDialog() -} +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt index f263740e5..20fb0a7cb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt @@ -37,13 +37,13 @@ import javax.inject.Inject import javax.inject.Named class GalleryViewModel @Inject constructor( - conversationRepo: ConversationRepository, - @Named("partId") private val partId: Long, - private val context: Context, - private val messageRepo: MessageRepository, - private val navigator: Navigator, - private val saveImage: SaveImage, - private val permissions: PermissionManager + conversationRepo: ConversationRepository, + @Named("partId") private val partId: Long, + private val context: Context, + private val messageRepo: MessageRepository, + private val navigator: Navigator, + private val saveImage: SaveImage, + private val permissions: PermissionManager ) : QkViewModel(GalleryState()) { init { @@ -61,13 +61,7 @@ class GalleryViewModel @Inject constructor( override fun bindView(view: GalleryView) { super.bindView(view) - view.permissionResult() - .map { permissionManager.hasStorage() } - .autoDisposable(view.scope()) - .subscribe{ - if(permissionManager.hasStorage()) - saveImage.execute(partId) { context.makeToast(R.string.gallery_toast_saved) } - } + // When the screen is touched, toggle the visibility of the navigation UI view.screenTouched() .withLatestFrom(state) { _, state -> state.navigationVisible } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt index 432ec2dcd..9da85458e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt @@ -28,6 +28,7 @@ data class MainState( val page: MainPage = Inbox(), val drawerOpen: Boolean = false, val showRating: Boolean = false, + val upgraded: Boolean = false, val syncing: SyncRepository.SyncProgress = SyncRepository.SyncProgress.Idle, val defaultSms: Boolean = true, val smsPermission: Boolean = true, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt index 0148a5306..81f87df27 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt @@ -33,6 +33,9 @@ interface MainView : QkView { val homeIntent: Observable<*> val navigationIntent: Observable val optionsItemIntent: Observable + val plusBannerIntent: Observable<*> + val dismissRatingIntent: Observable<*> + val rateIntent: Observable<*> val conversationsSelectedIntent: Observable> val confirmDeleteIntent: Observable> val swipeConversationIntent: Observable> @@ -52,4 +55,4 @@ interface MainView : QkView { } -enum class NavItem { BACK, INBOX, ARCHIVED, BACKUP, SCHEDULED, BLOCKING, SETTINGS, PLUS, HELP, INVITE } +enum class NavItem { BACK, INBOX, ARCHIVED, BACKUP, SCHEDULED, BLOCKING, SETTINGS, PLUS, HELP, INVITE } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index 0354df58e..11bd3e09c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -18,12 +18,10 @@ */ package com.moez.QKSMS.feature.main -import android.net.Uri import androidx.recyclerview.widget.ItemTouchHelper import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkViewModel - import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.interactor.DeleteConversations import com.moez.QKSMS.interactor.MarkAllSeen @@ -38,7 +36,6 @@ import com.moez.QKSMS.interactor.SyncContacts import com.moez.QKSMS.interactor.SyncMessages import com.moez.QKSMS.listener.ContactAddedListener import com.moez.QKSMS.manager.BillingManager - import com.moez.QKSMS.manager.ChangelogManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.manager.RatingManager @@ -59,7 +56,6 @@ import kotlinx.coroutines.launch import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject -import javax.inject.Named class MainViewModel @Inject constructor( billingManager: BillingManager, @@ -189,7 +185,7 @@ class MainViewModel @Inject constructor( view.changelogMoreIntent .autoDisposable(view.scope()) - .subscribe { navigator.openUri(Uri.parse("https://gitlab.e.foundation/e/apps/Message/-/releases")) } + .subscribe { navigator.showChangelog() } view.queryChangedIntent .debounce(200, TimeUnit.MILLISECONDS) @@ -454,12 +450,6 @@ class MainViewModel @Inject constructor( } .autoDisposable(view.scope()) .subscribe() - - - - if (threadId == 0L) { - syncContacts.execute(Unit) - } } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt index 4eec1d3c1..4189d9cc8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt @@ -18,13 +18,16 @@ */ package com.moez.QKSMS.feature.main +import android.annotation.SuppressLint import android.content.Context +import android.os.Build import android.text.SpannableString import android.text.Spanned import android.text.style.BackgroundColorSpan import android.view.LayoutInflater import android.view.ViewGroup -import com.google.android.material.color.MaterialColors.getColor +import androidx.annotation.RequiresApi + import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkAdapter @@ -57,6 +60,8 @@ class SearchAdapter @Inject constructor( } } + @SuppressLint("StringFormatInvalid") + @RequiresApi(Build.VERSION_CODES.M) override fun onBindViewHolder(holder: QkViewHolder, position: Int) { val previous = data.getOrNull(position - 1) val result = getItem(position) 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 f0998dffd..dae2f7a95 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 @@ -19,6 +19,7 @@ package com.moez.QKSMS.feature.settings import android.animation.ObjectAnimator +import android.annotation.SuppressLint import android.app.TimePickerDialog import android.content.Context import android.os.Build @@ -40,7 +41,6 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.widget.FieldDialog import com.moez.QKSMS.common.widget.PreferenceView import com.moez.QKSMS.common.widget.QkSwitch import com.moez.QKSMS.common.widget.TextInputDialog @@ -102,6 +102,7 @@ class SettingsController : QkController + TimePickerDialog(activity, { _, newHour, newMinute -> startTimeSelectedSubject.onNext(Pair(newHour, newMinute)) }, hour, minute, DateFormat.is24HourFormat(activity)).show() } override fun showEndTimePicker(hour: Int, minute: Int) { - TimePickerDialog(activity, R.style.customAlertDialog, TimePickerDialog.OnTimeSetListener { _, newHour, newMinute -> + TimePickerDialog(activity, { _, newHour, newMinute -> endTimeSelectedSubject.onNext(Pair(newHour, newMinute)) }, hour, minute, DateFormat.is24HourFormat(activity)).show() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt index 5272c7108..e818b034d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt @@ -45,5 +45,6 @@ interface SettingsView : QkViewContract { suspend fun showAutoDeleteWarningDialog(messages: Int): Boolean fun showMmsSizePicker() fun showSwipeActions() + fun showThemePicker() fun showAbout() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt index 4fb150652..7385a29c9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutController.kt @@ -31,8 +31,7 @@ import javax.inject.Inject class AboutController : QkController(), AboutView { - @Inject - override lateinit var presenter: AboutPresenter + @Inject override lateinit var presenter: AboutPresenter init { appComponent.inject(this) @@ -40,7 +39,7 @@ class AboutController : QkController(), AboutVi } override fun onViewCreated() { - app_version.summary = BuildConfig.VERSION_NAME + version.summary = BuildConfig.VERSION_NAME } override fun onAttach(view: View) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutPresenter.kt index dd6c57511..a3373a7cf 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutPresenter.kt @@ -18,7 +18,6 @@ */ package com.moez.QKSMS.feature.settings.about -import android.net.Uri import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter @@ -37,14 +36,15 @@ class AboutPresenter @Inject constructor( .autoDisposable(view.scope()) .subscribe { preference -> when (preference.id) { - R.id.fork -> navigator.openUri(Uri.parse("https://github.com/moezbhatti/qksms")) - R.id.source -> navigator.openUri(Uri.parse("https://gitlab.e.foundation/e/apps/message")) - R.id.copyright -> navigator.openUri(Uri.parse("https://gitlab.e.foundation/e/apps/Message/-/blob/master/AUTHORS")) - R.id.license -> navigator.openUri(Uri.parse("https://gitlab.e.foundation/e/apps/Message/-/blob/master/LICENSE")) - R.id.author -> navigator.openUri(Uri.parse("https://gitlab.e.foundation/e/apps/Message/-/blob/master/AUTHORS")) - R.id.privacy -> navigator.openUri(Uri.parse("https://e.foundation/legal-notice-privacy/")) - R.id.service_terms -> navigator.openUri(Uri.parse("https://e.foundation/legal-notice-privacy/")) - R.id.app_version -> navigator.openUri(Uri.parse("https://gitlab.e.foundation/e/apps/Message/-/releases")) + R.id.developer -> navigator.showDeveloper() + + R.id.source -> navigator.showSourceCode() + + R.id.changelog -> navigator.showChangelog() + + R.id.contact -> navigator.showSupport() + + R.id.license -> navigator.showLicense() } } } 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 ab2b6fcd9..fe3c05a7b 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 @@ -21,7 +21,7 @@ package com.moez.QKSMS.feature.settings.swipe import android.view.View import androidx.core.content.ContextCompat import androidx.core.view.isVisible -import com.google.android.material.color.MaterialColors.getColor + import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.QkDialog 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 cf99614e7..26e88c15d 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 @@ -41,8 +41,8 @@ import kotlinx.android.synthetic.main.theme_palette_list_item.view.* import javax.inject.Inject class ThemeAdapter @Inject constructor( - private val context: Context, - private val colors: Colors + private val context: Context, + private val colors: Colors ) : QkAdapter>() { val colorSelected: Subject = PublishSubject.create() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt index 316746334..608b41625 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt @@ -18,6 +18,7 @@ */ package com.moez.QKSMS.feature.widget +import android.annotation.SuppressLint import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Context @@ -66,7 +67,7 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { private val night get() = prefs.night.get() private val black get() = prefs.black.get() - + private val theme get() = colors.theme() private val background get() = context.getColorCompat(when { night && black -> R.color.black @@ -108,6 +109,7 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { } } + @SuppressLint("StringFormatInvalid") private fun getConversationView(position: Int): RemoteViews { val conversation = conversations[position] @@ -115,9 +117,9 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { // Avatar remoteViews.setViewVisibility(R.id.avatar, if (smallWidget) View.GONE else View.VISIBLE) - remoteViews.setInt(R.id.avatar, "setBackgroundColor", context.getColor(R.color.tools_theme)) - remoteViews.setTextColor(R.id.initial, context.getColor(R.color.tools_theme)) - remoteViews.setInt(R.id.icon, "setColorFilter", context.getColor(R.color.textPrimary)) + remoteViews.setInt(R.id.avatar, "setBackgroundColor", theme.theme) + remoteViews.setTextColor(R.id.initial, theme.textPrimary) + remoteViews.setInt(R.id.icon, "setColorFilter", theme.textPrimary) remoteViews.setInt(R.id.avatarMask, "setColorFilter", background) val contact = conversation.recipients.map { recipient -> diff --git a/presentation/src/main/res/layout/about_controller.xml b/presentation/src/main/res/layout/about_controller.xml index 8fe217125..22e9fe0d5 100644 --- a/presentation/src/main/res/layout/about_controller.xml +++ b/presentation/src/main/res/layout/about_controller.xml @@ -1,4 +1,5 @@ - @@ -32,38 +34,18 @@ android:paddingBottom="8dp"> + app:title="@string/about_version_title" + tools:summary="3.0.8" /> - - - - - - - + app:summary="@string/about_developer" + app:title="@string/about_developer_title" /> + + app:summary="@string/about_contact" + app:title="@string/about_contact_title" /> + app:summary="@string/about_license" + app:title="@string/about_license_title" /> + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/theme_list_item.xml b/presentation/src/main/res/layout/theme_list_item.xml new file mode 100644 index 000000000..da05ee17d --- /dev/null +++ b/presentation/src/main/res/layout/theme_list_item.xml @@ -0,0 +1,41 @@ + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/theme_palette_list_item.xml b/presentation/src/main/res/layout/theme_palette_list_item.xml new file mode 100644 index 000000000..1c8ee0cd3 --- /dev/null +++ b/presentation/src/main/res/layout/theme_palette_list_item.xml @@ -0,0 +1,35 @@ + + + + + + + + + \ No newline at end of file -- GitLab From 82dba08901164e34395fc1639c22688e9c06cdf7 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 12:22:04 +0530 Subject: [PATCH 197/213] update java 11 to 8 --- presentation/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index 453a67264..07e1382e1 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -63,12 +63,12 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { - jvmTarget = "11" + jvmTarget = "1.8" } lintOptions { -- GitLab From 3280912e5e2aea8ebb7b1ec824e974ebe56183cb Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 12:27:50 +0530 Subject: [PATCH 198/213] update java 8 to 11 --- android-smsmms/build.gradle | 2 -- data/build.gradle | 2 +- domain/build.gradle | 6 +++--- presentation/build.gradle | 6 +++--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/android-smsmms/build.gradle b/android-smsmms/build.gradle index 81dbda501..fd0c7c194 100644 --- a/android-smsmms/build.gradle +++ b/android-smsmms/build.gradle @@ -25,8 +25,6 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 25 - versionCode 1 - versionName "1.0" } lintOptions { diff --git a/data/build.gradle b/data/build.gradle index 1c3474a8c..7fedad83c 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -44,7 +44,7 @@ android { } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } } diff --git a/domain/build.gradle b/domain/build.gradle index 34087c58a..e7f4a6d13 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -36,12 +36,12 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() + jvmTarget = JavaVersion.VERSION_11.toString() } } diff --git a/presentation/build.gradle b/presentation/build.gradle index 07e1382e1..453a67264 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -63,12 +63,12 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } // For Kotlin projects kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } lintOptions { -- GitLab From 756624a3f174b2e8a1c9776e9f0e0c8564eb89cd Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 12:34:26 +0530 Subject: [PATCH 199/213] update yml latest --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9f417027e..ac554a342 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:legacy" +image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest" stages: - build -- GitLab From f7db2d387372ba42307b6e57ca2268244d379152 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 9 Mar 2022 20:46:26 +0530 Subject: [PATCH 200/213] android.enableR8=true --- data/build.gradle | 3 ++- gradle.properties | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data/build.gradle b/data/build.gradle index 7fedad83c..7f75289f4 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -65,7 +65,8 @@ dependencies { kapt "com.google.dagger:dagger-android-processor:$dagger_version" kapt "com.google.dagger:dagger-compiler:$dagger_version" compileOnly "javax.annotation:jsr250-api:1.0" - +//Resolve jdk8+ Generation Annotations - javax annotation does not exist + compileOnly 'com.github.pengrad:jdk9-deps:1.0' // rxjava implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version" implementation "io.reactivex.rxjava2:rxjava:$rxjava_version" diff --git a/gradle.properties b/gradle.properties index bb91abfe1..5cc4715de 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,6 +13,7 @@ org.gradle.jvmargs=-Xmx1536m # https://blog.gradle.org/introducing-gradle-build-cache org.gradle.caching=true +android.enableR8=true android.useAndroidX=true android.enableJetifier=true -- GitLab From ecdf3fe977b840a95d6a746b2cf4823278ef9e2b Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Thu, 10 Mar 2022 11:36:05 +0530 Subject: [PATCH 201/213] update code for Billing manager and build --- .../com/moez/QKSMS/manager/BillingManager.kt | 9 ++-- .../QKSMS/common/base/QkThemedActivity.kt | 5 +- .../moez/QKSMS/common/util/BillingManager.kt | 47 ------------------- .../QKSMS/feature/backup/BackupPresenter.kt | 1 - .../QKSMS/feature/compose/ComposeActivity.kt | 2 +- .../moez/QKSMS/feature/compose/ComposeView.kt | 1 - .../QKSMS/feature/compose/ComposeViewModel.kt | 2 +- .../feature/scheduled/ScheduledViewModel.kt | 11 ++--- .../feature/settings/SettingsPresenter.kt | 39 ++++++++++----- .../themepicker/ThemePickerPresenter.kt | 12 ++--- .../com/moez/QKSMS/injection/AppModule.kt | 21 +-------- 11 files changed, 47 insertions(+), 103 deletions(-) delete mode 100644 presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt diff --git a/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt index e92e763ac..9fa64c3b2 100644 --- a/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt +++ b/domain/src/main/java/com/moez/QKSMS/manager/BillingManager.kt @@ -22,19 +22,18 @@ package com.moez.QKSMS.manager import android.app.Activity import io.reactivex.Observable + interface BillingManager { companion object { const val SKU_PLUS = "remove_ads" const val SKU_PLUS_DONATE = "qksms_plus_donate" } - data class Product( - val sku: String, - val price: String, - val priceCurrencyCode: String + val sku: String, + val price: String, + val priceCurrencyCode: String ) - val products: Observable> val upgradeStatus: Observable 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 4a9225eec..14b858b2c 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 @@ -24,7 +24,6 @@ import android.graphics.BitmapFactory import android.os.Build import android.os.Bundle import android.view.View -import androidx.core.content.ContextCompat import androidx.core.view.iterator import androidx.lifecycle.Lifecycle import com.moez.QKSMS.R @@ -100,7 +99,7 @@ abstract class QkThemedActivity : QkActivity() { @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { - setTheme(R.style.AppTheme) + setTheme(getActivityThemeRes(prefs.black.get())) super.onCreate(savedInstanceState) // When certain preferences change, we need to recreate the activity @@ -134,7 +133,7 @@ abstract class QkThemedActivity : QkActivity() { super.onPostCreate(savedInstanceState) // Set the color for the overflow and navigation icon - val textSecondary = ContextCompat.getColor(this, R.color.colorAccent) + val textSecondary = resolveThemeColor(android.R.attr.textColorSecondary) toolbar?.overflowIcon = toolbar?.overflowIcon?.apply { setTint(textSecondary) } // Update the colours of the menu items diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt deleted file mode 100644 index 1178f245a..000000000 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2020 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ - -package com.moez.QKSMS.manager - -import android.app.Activity -import io.reactivex.Observable - -interface BillingManager { - - companion object { - const val SKU_PLUS = "remove_ads" - const val SKU_PLUS_DONATE = "qksms_plus_donate" - } - - data class Product( - val sku: String, - val price: String, - val priceCurrencyCode: String - ) - - val products: Observable> - val upgradeStatus: Observable - - suspend fun checkForPurchases() - - suspend fun queryProducts() - - suspend fun initiatePurchaseFlow(activity: Activity, sku: String) - -} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt index a6a7426ce..8d945e2e6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt @@ -25,7 +25,6 @@ import com.moez.QKSMS.common.base.QkPresenter import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.interactor.PerformBackup -import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.repository.BackupRepository import com.uber.autodispose.android.lifecycle.scope 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 5115d5014..db78cd06a 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 @@ -71,7 +71,7 @@ import java.util.* import javax.inject.Inject import kotlin.collections.HashMap -class ComposeActivity(override val selectPreferredSIM: Observable<*>) : QkThemedActivity(), ComposeView { +class ComposeActivity : QkThemedActivity(), ComposeView { companion object { private const val SelectContactRequestCode = 0 diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index 65b9fd865..d8a78eec2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -52,7 +52,6 @@ interface ComposeView : QkView { val scheduleSelectedIntent: Observable val scheduleCancelIntent: Observable<*> val changeSimIntent: Observable<*> - val selectPreferredSIM: Observable<*> val sendIntent: Observable val viewQksmsPlusIntent: Subject val backPressedIntent: Observable diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index da5c26f95..28ab8e4e5 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -743,4 +743,4 @@ class ComposeViewModel @Inject constructor( ?.let { bytes -> String(bytes) } } -} +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt index 5b58655fe..3cc99fae4 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt @@ -29,16 +29,15 @@ import com.moez.QKSMS.manager.BillingManager import com.moez.QKSMS.repository.ScheduledMessageRepository import com.uber.autodispose.android.lifecycle.scope import com.uber.autodispose.autoDisposable -import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import javax.inject.Inject class ScheduledViewModel @Inject constructor( - billingManager: BillingManager, - private val context: Context, - private val navigator: Navigator, - private val scheduledMessageRepo: ScheduledMessageRepository, - private val sendScheduledMessage: SendScheduledMessage + billingManager: BillingManager, + private val context: Context, + private val navigator: Navigator, + private val scheduledMessageRepo: ScheduledMessageRepository, + private val sendScheduledMessage: SendScheduledMessage ) : QkViewModel(ScheduledState( scheduledMessages = scheduledMessageRepo.getScheduledMessages() )) { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt index 977a75008..f28400ba2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsPresenter.kt @@ -46,18 +46,18 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class SettingsPresenter @Inject constructor( - colors: Colors, - syncRepo: SyncRepository, - private val analytics: AnalyticsManager, - private val context: Context, - private val billingManager: BillingManager, - private val dateFormatter: DateFormatter, - private val deleteOldMessages: DeleteOldMessages, - private val messageRepo: MessageRepository, - private val navigator: Navigator, - private val nightModeManager: NightModeManager, - private val prefs: Preferences, - private val syncMessages: SyncMessages + colors: Colors, + syncRepo: SyncRepository, + private val analytics: AnalyticsManager, + private val context: Context, + private val billingManager: BillingManager, + private val dateFormatter: DateFormatter, + private val deleteOldMessages: DeleteOldMessages, + private val messageRepo: MessageRepository, + private val navigator: Navigator, + private val nightModeManager: NightModeManager, + private val prefs: Preferences, + private val syncMessages: SyncMessages ) : QkPresenter(SettingsState( nightModeId = prefs.nightMode.get() )) { @@ -152,6 +152,21 @@ class SettingsPresenter @Inject constructor( Timber.v("Preference click: ${context.resources.getResourceName(it.id)}") when (it.id) { + R.id.theme -> view.showThemePicker() + + R.id.night -> view.showNightModeDialog() + + R.id.nightStart -> { + val date = nightModeManager.parseTime(prefs.nightStart.get()) + view.showStartTimePicker(date.get(Calendar.HOUR_OF_DAY), date.get(Calendar.MINUTE)) + } + + R.id.nightEnd -> { + val date = nightModeManager.parseTime(prefs.nightEnd.get()) + view.showEndTimePicker(date.get(Calendar.HOUR_OF_DAY), date.get(Calendar.MINUTE)) + } + + R.id.black -> prefs.black.set(!prefs.black.get()) R.id.autoEmoji -> prefs.autoEmoji.set(!prefs.autoEmoji.get()) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt index a27315ec9..0e8b2ddd1 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt @@ -33,12 +33,12 @@ import javax.inject.Inject import javax.inject.Named class ThemePickerPresenter @Inject constructor( - prefs: Preferences, - @Named("recipientId") private val recipientId: Long, - private val billingManager: BillingManager, - private val colors: Colors, - private val navigator: Navigator, - private val widgetManager: WidgetManager + prefs: Preferences, + @Named("recipientId") private val recipientId: Long, + private val billingManager: BillingManager, + private val colors: Colors, + private val navigator: Navigator, + private val widgetManager: WidgetManager ) : QkPresenter(ThemePickerState(recipientId = recipientId)) { private val theme: Preference = prefs.theme(recipientId) diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index afab6678c..cafd2d997 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -35,26 +35,7 @@ import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoCompone import com.moez.QKSMS.feature.themepicker.injection.ThemePickerComponent import com.moez.QKSMS.listener.ContactAddedListener import com.moez.QKSMS.listener.ContactAddedListenerImpl -import com.moez.QKSMS.manager.ActiveConversationManager -import com.moez.QKSMS.manager.ActiveConversationManagerImpl -import com.moez.QKSMS.manager.AlarmManager -import com.moez.QKSMS.manager.AlarmManagerImpl -import com.moez.QKSMS.manager.AnalyticsManager -import com.moez.QKSMS.manager.AnalyticsManagerImpl -import com.moez.QKSMS.manager.BillingManager -import com.moez.QKSMS.manager.ChangelogManager -import com.moez.QKSMS.manager.ChangelogManagerImpl -import com.moez.QKSMS.manager.KeyManager -import com.moez.QKSMS.manager.KeyManagerImpl -import com.moez.QKSMS.manager.NotificationManager -import com.moez.QKSMS.manager.PermissionManager -import com.moez.QKSMS.manager.PermissionManagerImpl -import com.moez.QKSMS.manager.RatingManager -import com.moez.QKSMS.manager.ReferralManager -import com.moez.QKSMS.manager.ReferralManagerImpl -import com.moez.QKSMS.manager.ShortcutManager -import com.moez.QKSMS.manager.WidgetManager -import com.moez.QKSMS.manager.WidgetManagerImpl +import com.moez.QKSMS.manager.* import com.moez.QKSMS.mapper.CursorToContact import com.moez.QKSMS.mapper.CursorToContactGroup import com.moez.QKSMS.mapper.CursorToContactGroupImpl -- GitLab From 750a5ea3a5772f39a57981c6a3b822eeb351ef6f Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Fri, 11 Mar 2022 09:43:30 +0530 Subject: [PATCH 202/213] update gradle appcompat version and compile skd version --- build.gradle | 2 +- presentation/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 86bd12580..4892e4a84 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.androidx_appcompat_version = '1.1.0' + ext.androidx_appcompat_version = '1.4.1' ext.androidx_constraintlayout_version = '1.1.3' ext.androidx_core_version = '1.2.0' ext.androidx_emoji_version = '1.0.0' diff --git a/presentation/build.gradle b/presentation/build.gradle index 453a67264..909ec19dd 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -23,7 +23,7 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 30 + compileSdkVersion 31 flavorDimensions "analytics" defaultConfig { -- GitLab From 95cc0410a62977c3d0597bc95f14d1a47ca30f9d Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Fri, 11 Mar 2022 10:10:19 +0530 Subject: [PATCH 203/213] Undo appcompat version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4892e4a84..86bd12580 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.androidx_appcompat_version = '1.4.1' + ext.androidx_appcompat_version = '1.1.0' ext.androidx_constraintlayout_version = '1.1.3' ext.androidx_core_version = '1.2.0' ext.androidx_emoji_version = '1.0.0' -- GitLab From 4d4bda54e20222f18dfce57dadeadd01f9dc9768 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Fri, 11 Mar 2022 10:35:45 +0530 Subject: [PATCH 204/213] update version for crash Landroidx/appcompat/R$drawable; --- build.gradle | 3 ++- presentation/build.gradle | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 86bd12580..06b2a41a8 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { ext.mockito_version = '2.18.3' ext.moshi_version = '1.8.0' ext.okhttp3_version = '4.1.0' - ext.realm_version = '5.8.0' + ext.realm_version = '6.0.2'//'5.8.0' ext.realm_adapters_version = '3.1.0' ext.rxandroid_version = '2.0.1' ext.rxdogtag_version = '0.2.0' @@ -47,6 +47,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "io.realm:realm-gradle-plugin:$realm_version" classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.2' + } } diff --git a/presentation/build.gradle b/presentation/build.gradle index 909ec19dd..569fa6c4a 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -170,6 +170,7 @@ dependencies { kapt "io.realm:realm-annotations:$realm_version" kapt "io.realm:realm-annotations-processor:$realm_version" + // rxjava implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version" implementation "io.reactivex.rxjava2:rxjava:$rxjava_version" -- GitLab From 0e7470fc006907e30be804b5ab5599a7bdd1b000 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Fri, 11 Mar 2022 11:12:38 +0530 Subject: [PATCH 205/213] Resolved Crash in setting theme --- .../feature/settings/SettingsController.kt | 2 -- .../res/drawable/ic_color_lens_black_24dp.xml | 27 +++++++++++++++++++ .../main/res/layout/settings_controller.xml | 7 +++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 presentation/src/main/res/drawable/ic_color_lens_black_24dp.xml 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 dae2f7a95..f09adc3e9 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 @@ -19,7 +19,6 @@ package com.moez.QKSMS.feature.settings import android.animation.ObjectAnimator -import android.annotation.SuppressLint import android.app.TimePickerDialog import android.content.Context import android.os.Build @@ -102,7 +101,6 @@ class SettingsController : QkController + ~ + ~ This file is part of QKSMS. + ~ + ~ QKSMS is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ (at your option) any later version. + ~ + ~ QKSMS is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License + ~ along with QKSMS. If not, see . + --> + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/settings_controller.xml b/presentation/src/main/res/layout/settings_controller.xml index 203cf9243..9efc424bd 100644 --- a/presentation/src/main/res/layout/settings_controller.xml +++ b/presentation/src/main/res/layout/settings_controller.xml @@ -36,6 +36,13 @@ + Date: Mon, 14 Mar 2022 13:04:07 +0530 Subject: [PATCH 206/213] Remove QKSMS_plus calling part --- .../moez/QKSMS/feature/main/MainActivity.kt | 4 ++-- .../com/moez/QKSMS/feature/main/MainView.kt | 2 +- .../moez/QKSMS/feature/main/MainViewModel.kt | 2 +- .../src/main/res/layout/drawer_view.xml | 22 +++++++++---------- 4 files changed, 15 insertions(+), 15 deletions(-) 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 05bda5590..4a669241d 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 @@ -101,7 +101,7 @@ class MainActivity : QkThemedActivity(), MainView { scheduled.clicks().map { NavItem.SCHEDULED }, blocking.clicks().map { NavItem.BLOCKING }, settings.clicks().map { NavItem.SETTINGS }, - plus.clicks().map { NavItem.PLUS }, +// plus.clicks().map { NavItem.PLUS }, help.clicks().map { NavItem.HELP }, invite.clicks().map { NavItem.INVITE })) } @@ -243,7 +243,7 @@ class MainActivity : QkThemedActivity(), MainView { listOf(plusBadge1, plusBadge2).forEach { badge -> badge.isVisible = drawerBadgesExperiment.variant && !state.upgraded } - plus.isVisible = state.upgraded +// plus.isVisible = state.upgraded plusBanner.isVisible = !state.upgraded rateLayout.setVisible(state.showRating) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt index 81f87df27..03cc29d59 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt @@ -55,4 +55,4 @@ interface MainView : QkView { } -enum class NavItem { BACK, INBOX, ARCHIVED, BACKUP, SCHEDULED, BLOCKING, SETTINGS, PLUS, HELP, INVITE } \ No newline at end of file +enum class NavItem { BACK, INBOX, ARCHIVED, BACKUP, SCHEDULED, BLOCKING, SETTINGS, HELP, INVITE } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index 11bd3e09c..d2f96e4fa 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -263,7 +263,7 @@ class MainViewModel @Inject constructor( NavItem.SCHEDULED -> navigator.showScheduled() NavItem.BLOCKING -> navigator.showBlockedConversations() NavItem.SETTINGS -> navigator.showSettings() - NavItem.PLUS -> navigator.showQksmsPlusActivity("main_menu") +// NavItem.PLUS -> navigator.showQksmsPlusActivity("main_menu") NavItem.HELP -> navigator.showSupport() NavItem.INVITE -> navigator.showInvite() else -> Unit diff --git a/presentation/src/main/res/layout/drawer_view.xml b/presentation/src/main/res/layout/drawer_view.xml index a7c33dade..a8637ff96 100644 --- a/presentation/src/main/res/layout/drawer_view.xml +++ b/presentation/src/main/res/layout/drawer_view.xml @@ -165,20 +165,20 @@ - + + + - + + + + - + + + - + Date: Mon, 14 Mar 2022 13:16:41 +0530 Subject: [PATCH 207/213] refine code, remove comment --- .../com/moez/QKSMS/feature/main/MainActivity.kt | 2 +- .../com/moez/QKSMS/feature/main/MainViewModel.kt | 2 +- presentation/src/main/res/layout/drawer_view.xml | 14 -------------- 3 files changed, 2 insertions(+), 16 deletions(-) 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 4a669241d..023f36dcf 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 @@ -243,7 +243,7 @@ class MainActivity : QkThemedActivity(), MainView { listOf(plusBadge1, plusBadge2).forEach { badge -> badge.isVisible = drawerBadgesExperiment.variant && !state.upgraded } -// plus.isVisible = state.upgraded + plusBanner.isVisible = !state.upgraded rateLayout.setVisible(state.showRating) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index d2f96e4fa..a24ad1e92 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -263,7 +263,7 @@ class MainViewModel @Inject constructor( NavItem.SCHEDULED -> navigator.showScheduled() NavItem.BLOCKING -> navigator.showBlockedConversations() NavItem.SETTINGS -> navigator.showSettings() -// NavItem.PLUS -> navigator.showQksmsPlusActivity("main_menu") + NavItem.HELP -> navigator.showSupport() NavItem.INVITE -> navigator.showInvite() else -> Unit diff --git a/presentation/src/main/res/layout/drawer_view.xml b/presentation/src/main/res/layout/drawer_view.xml index a8637ff96..2a419e887 100644 --- a/presentation/src/main/res/layout/drawer_view.xml +++ b/presentation/src/main/res/layout/drawer_view.xml @@ -165,20 +165,6 @@ - - - - - - - - - - - - - - Date: Mon, 14 Mar 2022 13:49:38 +0530 Subject: [PATCH 208/213] refine code, remove comment --- .../com/moez/QKSMS/common/util/extensions/ContextExtensions.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt index 3e66047a4..81a7c867c 100644 --- a/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt +++ b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt @@ -86,6 +86,3 @@ fun Context.isInstalled(packageName: String): Boolean { val Context.versionCode: Int get() = packageManager.getPackageInfo(packageName, 0).versionCode - -//val Context.jobScheduler: JobScheduler -// get() = getSystemService()!! -- GitLab From 970f0712ff8353ecee44b484f0372854956b8938 Mon Sep 17 00:00:00 2001 From: narinder Rana Date: Mon, 21 Mar 2022 11:45:32 +0000 Subject: [PATCH 209/213] remove unused code --- .../src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 023f36dcf..791b634ae 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 @@ -101,7 +101,7 @@ class MainActivity : QkThemedActivity(), MainView { scheduled.clicks().map { NavItem.SCHEDULED }, blocking.clicks().map { NavItem.BLOCKING }, settings.clicks().map { NavItem.SETTINGS }, -// plus.clicks().map { NavItem.PLUS }, + help.clicks().map { NavItem.HELP }, invite.clicks().map { NavItem.INVITE })) } @@ -414,4 +414,4 @@ class MainActivity : QkThemedActivity(), MainView { backPressedSubject.onNext(NavItem.BACK) } -} \ No newline at end of file +} -- GitLab From 795ccb2a2d4d6bdaedebe72040757ced8a31e7b5 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Tue, 22 Mar 2022 13:48:11 +0530 Subject: [PATCH 210/213] remove comment and refine code --- data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt | 2 +- .../java/com/moez/QKSMS/feature/compose/ComposeActivity.kt | 5 ----- .../com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt | 4 ---- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt b/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt index 294a2c647..f3a458dbb 100644 --- a/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt +++ b/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt @@ -34,7 +34,7 @@ class GlideAppModule : AppGlideModule() { } override fun registerComponents(context: Context, glide: Glide, registry: Registry) { - // registry.prepend(GifDrawable::class.java, ReEncodingGifResourceEncoder(context, glide.bitmapPool)) + } } 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 db78cd06a..9b25f548d 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 @@ -392,11 +392,6 @@ class ComposeActivity : QkThemedActivity(), ComposeView { super.onSaveInstanceState(outState) } -// override fun onRestoreInstanceState(savedInstanceState: Bundle?) { -// cameraDestination = savedInstanceState?.getParcelable(CameraDestinationKey) -// super.onRestoreInstanceState(savedInstanceState) -// } - override fun onBackPressed() = backPressedIntent.onNext(Unit) } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt index bf991d7ac..fbfc33b3e 100755 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeWindowCallback.kt @@ -87,10 +87,6 @@ class ComposeWindowCallback( return localCallback.onPreparePanel(i, view, menu) } -// override fun onPreparePanel(i: Int, view: View, menu: Menu): Boolean { -// return localCallback.onPreparePanel(i, view, menu) -// } - override fun onMenuOpened(i: Int, menu: Menu): Boolean { return localCallback.onMenuOpened(i, menu) } -- GitLab From bdfd11e8e848c46853582f87b19e324d2ec7a4d8 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 23 Mar 2022 07:55:07 +0530 Subject: [PATCH 211/213] add customAlertDialog --- .../java/com/moez/QKSMS/feature/compose/ComposeActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 9b25f548d..267194928 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 @@ -244,7 +244,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun clearSelection() = messageAdapter.clearSelection() override fun showDetails(details: String) { - AlertDialog.Builder(this) + AlertDialog.Builder(this, R.style.customAlertDialog) .setTitle(R.string.compose_details_title) .setMessage(details) .setCancelable(true) @@ -267,8 +267,8 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun requestDatePicker() { val calendar = Calendar.getInstance() - DatePickerDialog(this, DatePickerDialog.OnDateSetListener { _, year, month, day -> - TimePickerDialog(this, TimePickerDialog.OnTimeSetListener { _, hour, minute -> + DatePickerDialog(this, R.style.customAlertDialog, DatePickerDialog.OnDateSetListener { _, year, month, day -> + TimePickerDialog(this, R.style.customAlertDialog, TimePickerDialog.OnTimeSetListener { _, hour, minute -> calendar.set(Calendar.YEAR, year) calendar.set(Calendar.MONTH, month) calendar.set(Calendar.DAY_OF_MONTH, day) -- GitLab From 193c456b18fc91e25126324b91175415205f2cfa Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 23 Mar 2022 08:23:27 +0530 Subject: [PATCH 212/213] manage String files --- presentation/src/main/res/values-ar/strings.xml | 2 +- presentation/src/main/res/values-bn/strings.xml | 8 ++++---- presentation/src/main/res/values-es/strings.xml | 2 +- presentation/src/main/res/values-vi/strings.xml | 2 +- presentation/src/main/res/values-zh-rCN/strings.xml | 10 +++++----- presentation/src/main/res/values/strings.xml | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/presentation/src/main/res/values-ar/strings.xml b/presentation/src/main/res/values-ar/strings.xml index 7ee038ed9..826a01d62 100644 --- a/presentation/src/main/res/values-ar/strings.xml +++ b/presentation/src/main/res/values-ar/strings.xml @@ -259,7 +259,7 @@ الضغط التلقائي لمرفقات رسائل الوسائط مزامنة الرسائل إعادة مزامنة رسائلك مع قاعدة البيانات الأصلية للرسائل في النظام - حول QKSMS + حول Message النسخة %s تسجيل التصحيح مفعَّل تسجيل التصحيح معطَّل diff --git a/presentation/src/main/res/values-bn/strings.xml b/presentation/src/main/res/values-bn/strings.xml index c2728a930..48224ac15 100644 --- a/presentation/src/main/res/values-bn/strings.xml +++ b/presentation/src/main/res/values-bn/strings.xml @@ -60,8 +60,8 @@ QKSMS-কে তোমার ডিফল্ট এসএমএস অ্যাপ করো পরিবর্তন করো অনুমতি প্রয়োজন - QKSMS-এর এসএমএস বার্তাগুলো পাঠানোর এবং দেখার অনুমতি প্রয়োজন - QKSMS-এর তোমার পরিচিতিগুলি দেখার জন্য অনুমতি প্রয়োজন + মেসেজের এসএমএস বার্তাগুলো পাঠানোর এবং দেখার অনুমতি প্রয়োজন + মেসেজের তোমার পরিচিতিগুলি দেখার জন্য অনুমতি প্রয়োজন মঞ্জুর করো ইনবক্স আর্কাইভকৃত @@ -251,7 +251,7 @@ এমএমএসের সংযুক্তি সক্রিয়ভাবে সংকোচন করুন বার্তাগুলি সিঙ্ক করুন আপনার বার্তাগুলি অ্যান্ড্রয়েডের নিজস্ব এসএমএস এর ডাটাবেস সাতে পুনরায় সিঙ্ক করুন - QKSMS সম্পর্কে + মেসেজ সম্পর্কে সংস্করণ %s ডিবাগ লগিং চালু করা হয়ছে ডিবাগ লগিং বন্ধ করা হয়ছে @@ -262,7 +262,7 @@ অবরুদ্ধ কথাবার্তা অবরুদ্ধকরণ ব্যবস্থাপক QKSMS - QKSMS-এর অন্তর্ভুক্ত অবরোধী সক্ষমতা(blocking) + মেসেজের অন্তর্ভুক্ত অবরোধী সক্ষমতা(blocking) Block spam messages, numbers & unknown calls with blacklist & Schedule তোমার কল আর বার্তা একটাই সুবিধাজনক জায়গা থেকে ফিল্টার করো বা ছাঁকো! Community IQ™ দিয়ে অবাঞ্ছিত বার্তা আটকানো যায় সমাজে পরিচিত স্প্যামারদের থেকে স্বয়ংক্রিয়ভাবে \"Should I Answer\"(আমার কি উত্তর দেয়া উচিত?) অ্যাপ্লিকেশন ব্যবহার করে অযাচিত নম্বর-এর বার্তাগুলি ফিল্টার করো diff --git a/presentation/src/main/res/values-es/strings.xml b/presentation/src/main/res/values-es/strings.xml index 0748ec6ca..4c53c4eef 100644 --- a/presentation/src/main/res/values-es/strings.xml +++ b/presentation/src/main/res/values-es/strings.xml @@ -249,7 +249,7 @@ Eliminar mensajes entrantes de los remitentes bloqueados en lugar de ocultarlos Conversaciones bloqueadas Gestor de bloqueo - QKSMS + Message Función de bloqueo integrado en QKSMS Bloquee mensajes de spam, números y llamadas desconocidas con lista negra y programación ¡Filtrre automáticamente sus llamadas y mensajes en un lugar conveniente! La comunidad IQ™ te permite prevenir mensajes no deseados de spammers conocidos por la comunidad diff --git a/presentation/src/main/res/values-vi/strings.xml b/presentation/src/main/res/values-vi/strings.xml index 5f5835419..2c9450e09 100644 --- a/presentation/src/main/res/values-vi/strings.xml +++ b/presentation/src/main/res/values-vi/strings.xml @@ -240,7 +240,7 @@ Tự động nén các tệp đính kèm MMS Đồng bộ tin nhắn Đồng bộ lại tin nhắn với thiết bị - Thông tin QKSMS + Thông tin Message Phiên bản: %s Đã bật bản ghi gỡ lỗi Đã tắt bản ghi gỡ lỗi diff --git a/presentation/src/main/res/values-zh-rCN/strings.xml b/presentation/src/main/res/values-zh-rCN/strings.xml index 36399b65c..b831e9e16 100644 --- a/presentation/src/main/res/values-zh-rCN/strings.xml +++ b/presentation/src/main/res/values-zh-rCN/strings.xml @@ -144,7 +144,7 @@ 从未 还原 选择备份 - 请解锁 QKSMS+ 以使用备份和还原 + 请解锁 Message 以使用备份和还原 正在进行备份… 正在还原… 从备份还原 @@ -251,8 +251,8 @@ 丢弃屏蔽的发件人所发送的信息 已屏蔽的会话 屏蔽管理器 - QKSMS - QKSMS 中内建的屏蔽功能 + Message + Message 中内建的屏蔽功能 Block spam messages, numbers & unknown calls with blacklist & Schedule 自动在适当的位置过滤您的来电和信息!Community IQ™ 允许您阻止社区中已知的垃圾发件人所发送的垃圾信息 使用 \"Should I Answer\" 应用过滤未知号码的消息 @@ -288,7 +288,7 @@ 终身升级只需 %1$s %2$s 解锁并捐赠只需 %1$s %2$s - 感谢你对 QKSMS 的支持! + 感谢你对 Message 的支持! 现在你可以使用 QKSMS+ 的全部功能! 发生错误,请重试 QKSMS+ 对 F-Droid 用户免费!如果您想支持开发,可以随意捐款。 @@ -313,7 +313,7 @@ 自动回复 使用预设文本自动回复短信 更多 - QKSMS 积极更新中,您的购买将包含未来 QKSMS+ 的所有功能! + Message 积极更新中,您的购买将包含未来 QKSMS+ 的所有功能! 载入中... 查看更多对话 标记为已读 diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 56408cc34..3eba897cb 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -498,7 +498,7 @@ QKSMS+ Unlock amazing new features, and support development - Enjoying QKSMS? + Enjoying Message? Share some love and rate us on Google Play! OKAY! DISMISS -- GitLab From 093933bc5ca1d949c44f609b0bae3a94cd8e6c7b Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 23 Mar 2022 08:27:40 +0530 Subject: [PATCH 213/213] manage String files --- presentation/src/main/res/values-sl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/res/values-sl/strings.xml b/presentation/src/main/res/values-sl/strings.xml index feec57f00..6a6b4be54 100644 --- a/presentation/src/main/res/values-sl/strings.xml +++ b/presentation/src/main/res/values-sl/strings.xml @@ -256,7 +256,7 @@ Samodejno stisni MMS priponke Sinhroniziraj sporočila Znova sinhroniziraj sporočila s sistemsko SMS bazo - Več o QKSMS + Več o Message Različica %s Beleženje dnevnika za odpravljanje napak omogočeno Beleženje dnevnika za odpravljanje napak onemogočeno -- GitLab