From fc99ae83ad98229cb72ea199faebb7bb4f13ffe0 Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Tue, 5 Jun 2018 19:45:42 -0400 Subject: [PATCH] #678 - Allow deciding between sending as group or individual --- .../database/sqlite/SqliteWrapper.java | 13 +--- .../java/repository/MessageRepositoryImpl.kt | 45 ++++++-------- .../java/service/HeadlessSmsSendService.kt | 2 +- .../main/java/repository/MessageRepository.kt | 5 +- .../java/feature/compose/ComposeActivity.kt | 6 ++ .../main/java/feature/compose/ComposeState.kt | 1 + .../main/java/feature/compose/ComposeView.kt | 1 + .../java/feature/compose/ComposeViewModel.kt | 41 ++++++++++--- .../src/main/res/layout/compose_activity.xml | 61 ++++++++++++++++++- presentation/src/main/res/values/strings.xml | 2 + 10 files changed, 123 insertions(+), 54 deletions(-) diff --git a/android-smsmms/src/main/java/android/database/sqlite/SqliteWrapper.java b/android-smsmms/src/main/java/android/database/sqlite/SqliteWrapper.java index e0a5439f6..508ea26cf 100755 --- a/android-smsmms/src/main/java/android/database/sqlite/SqliteWrapper.java +++ b/android-smsmms/src/main/java/android/database/sqlite/SqliteWrapper.java @@ -24,6 +24,7 @@ import android.net.Uri; import android.widget.Toast; import com.klinker.android.logger.Log; +import org.jetbrains.annotations.Nullable; /** * @hide @@ -52,6 +53,7 @@ public static void checkSQLiteException(Context context, SQLiteException e) { } } + @Nullable public static Cursor query(Context context, ContentResolver resolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { try { @@ -63,17 +65,6 @@ public static Cursor query(Context context, ContentResolver resolver, Uri uri, } } - @SuppressWarnings("deprecation") - public static boolean requery(Context context, Cursor cursor) { - try { - return cursor.requery(); - } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when requery: ", e); - checkSQLiteException(context, e); - return false; - } - } - public static int update(Context context, ContentResolver resolver, Uri uri, ContentValues values, String where, String[] selectionArgs) { try { diff --git a/data/src/main/java/repository/MessageRepositoryImpl.kt b/data/src/main/java/repository/MessageRepositoryImpl.kt index 0088289e1..f82aec853 100644 --- a/data/src/main/java/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/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.database.sqlite.SqliteWrapper import android.graphics.Bitmap import android.net.Uri import android.os.Build @@ -37,9 +38,6 @@ import com.klinker.android.send_message.Settings import com.klinker.android.send_message.StripAccents import com.klinker.android.send_message.Transaction import filter.ConversationFilter -import io.reactivex.Maybe -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers import io.realm.Case import io.realm.Realm import io.realm.RealmResults @@ -56,8 +54,6 @@ import model.SearchResult import util.MessageUtils import util.Preferences import util.extensions.anyOf -import util.extensions.asMaybe -import util.extensions.insertOrUpdate import util.extensions.map import java.io.ByteArrayOutputStream import javax.inject.Inject @@ -141,39 +137,32 @@ class MessageRepositoryImpl @Inject constructor( return getConversation(threadId) ?: getConversationFromCp(threadId) } - override fun getOrCreateConversation(address: String): Maybe { + override fun getOrCreateConversation(address: String): Conversation? { return getOrCreateConversation(listOf(address)) } - override fun getOrCreateConversation(addresses: List): Maybe { - return Maybe.just(addresses) - .map { recipients -> - recipients.map { address -> - when (MessageUtils.isEmailAddress(address)) { - true -> MessageUtils.extractAddrSpec(address) - false -> address - } + override fun getOrCreateConversation(addresses: List): Conversation? { + return addresses + .map { address -> + when (MessageUtils.isEmailAddress(address)) { + true -> MessageUtils.extractAddrSpec(address) + false -> address } } - .map { recipients -> + .let { recipients -> Uri.parse("content://mms-sms/threadID").buildUpon().apply { recipients.forEach { recipient -> appendQueryParameter("recipient", recipient) } - } - } - .flatMap { uriBuilder -> - context.contentResolver.query(uriBuilder.build(), arrayOf(BaseColumns._ID), null, null, null).asMaybe() + }.build() } - .map { cursor -> cursor.getLong(0) } - .filter { threadId -> threadId != 0L } - .map { threadId -> + .let { uri -> SqliteWrapper.query(context, context.contentResolver, uri, arrayOf(BaseColumns._ID), null, null, null) } + ?.use { cursor -> if (cursor.moveToFirst()) cursor.getLong(0) else null } + ?.takeIf { threadId -> threadId != 0L } + ?.let { threadId -> var conversation = getConversation(threadId) if (conversation != null) conversation = Realm.getDefaultInstance().copyFromRealm(conversation) - conversation ?: getConversationFromCp(threadId)?.apply { insertOrUpdate() } ?: Conversation() + conversation ?: getConversationFromCp(threadId) } - .onErrorReturn { Conversation() } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) } override fun saveDraft(threadId: Long, draft: String) { @@ -561,7 +550,7 @@ class MessageRepositoryImpl @Inject constructor( this.subId = subId id = messageIds.newId() - threadId = getOrCreateConversation(address).blockingGet().id + threadId = getOrCreateConversation(address)?.id ?: 0L boxId = Telephony.Sms.MESSAGE_TYPE_INBOX type = "sms" } @@ -755,7 +744,7 @@ class MessageRepositoryImpl @Inject constructor( conversation.recipients.clear() conversation.recipients.addAll(recipients) - conversation.insertOrUpdate() + realm.executeTransaction { it.insertOrUpdate(conversation) } realm.close() conversation diff --git a/data/src/main/java/service/HeadlessSmsSendService.kt b/data/src/main/java/service/HeadlessSmsSendService.kt index 15e498a6a..c4093b2fc 100644 --- a/data/src/main/java/service/HeadlessSmsSendService.kt +++ b/data/src/main/java/service/HeadlessSmsSendService.kt @@ -39,7 +39,7 @@ class HeadlessSmsSendService : IntentService("HeadlessSmsSendService") { intent.extras?.getString(Intent.EXTRA_TEXT)?.takeIf { it.isNotBlank() }?.let { body -> val intentUri = intent.data val recipients = getRecipients(intentUri).split(";") - val threadId = messageRepo.getOrCreateConversation(recipients).blockingGet()?.id ?: 0L + val threadId = messageRepo.getOrCreateConversation(recipients)?.id ?: 0L sendMessage.execute(SendMessage.Params(-1, threadId, recipients, body)) } } diff --git a/domain/src/main/java/repository/MessageRepository.kt b/domain/src/main/java/repository/MessageRepository.kt index e2d39fcf0..1789a727d 100644 --- a/domain/src/main/java/repository/MessageRepository.kt +++ b/domain/src/main/java/repository/MessageRepository.kt @@ -18,7 +18,6 @@ */ package repository -import io.reactivex.Maybe import io.realm.RealmResults import model.Attachment import model.Conversation @@ -42,9 +41,9 @@ interface MessageRepository { fun getOrCreateConversation(threadId: Long): Conversation? - fun getOrCreateConversation(address: String): Maybe + fun getOrCreateConversation(address: String): Conversation? - fun getOrCreateConversation(addresses: List): Maybe + fun getOrCreateConversation(addresses: List): Conversation? fun saveDraft(threadId: Long, draft: String) diff --git a/presentation/src/main/java/feature/compose/ComposeActivity.kt b/presentation/src/main/java/feature/compose/ComposeActivity.kt index 80dbe3753..8a9771b4d 100644 --- a/presentation/src/main/java/feature/compose/ComposeActivity.kt +++ b/presentation/src/main/java/feature/compose/ComposeActivity.kt @@ -79,6 +79,7 @@ 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 messageClickIntent: Subject by lazy { messageAdapter.clicks } override val messagesSelectedIntent by lazy { messageAdapter.selectionChanges } override val cancelSendingIntent: Subject by lazy { messageAdapter.cancelSending } @@ -185,6 +186,11 @@ class ComposeActivity : QkThemedActivity(), ComposeView { chipsAdapter.data = state.selectedContacts contactsAdapter.data = state.contacts + + sendAsGroup.setVisible(state.editingMode && state.selectedContacts.size >= 2) + sendAsGroupSwitch.isChecked = state.sendAsGroup + + messageList.setVisible(state.sendAsGroup) messageAdapter.data = state.messages messageAdapter.highlight = state.searchSelectionId diff --git a/presentation/src/main/java/feature/compose/ComposeState.kt b/presentation/src/main/java/feature/compose/ComposeState.kt index 4ddfca2ac..ec101be7f 100644 --- a/presentation/src/main/java/feature/compose/ComposeState.kt +++ b/presentation/src/main/java/feature/compose/ComposeState.kt @@ -32,6 +32,7 @@ data class ComposeState( val contactsVisible: Boolean = false, val selectedConversation: Long = 0, val selectedContacts: List = ArrayList(), + val sendAsGroup: Boolean = true, val conversationtitle: String = "", val query: String = "", val searchSelectionId: Long = -1, diff --git a/presentation/src/main/java/feature/compose/ComposeView.kt b/presentation/src/main/java/feature/compose/ComposeView.kt index 281862292..101d473fe 100644 --- a/presentation/src/main/java/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/feature/compose/ComposeView.kt @@ -37,6 +37,7 @@ interface ComposeView : QkView { val chipDeletedIntent: Subject val menuReadyIntent: Observable val optionsItemIntent: Observable + val sendAsGroupIntent: Observable<*> val messageClickIntent: Subject val messagesSelectedIntent: Observable> val cancelSendingIntent: Subject diff --git a/presentation/src/main/java/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/feature/compose/ComposeViewModel.kt index 67f9c61a3..b29e79e47 100644 --- a/presentation/src/main/java/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/feature/compose/ComposeViewModel.kt @@ -127,7 +127,10 @@ class ComposeViewModel @Inject constructor( address.isNotBlank() -> { newState { it.copy(editingMode = false) } - messageRepo.getOrCreateConversation(address).toObservable() + Observable.just(address) + .mapNotNull { messageRepo.getOrCreateConversation(it) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) } else -> { @@ -146,7 +149,7 @@ class ComposeViewModel @Inject constructor( .skipUntil(state.filter { state -> state.editingMode }) .takeUntil(state.filter { state -> !state.editingMode }) .map { contacts -> contacts.map { it.numbers.firstOrNull()?.address ?: "" } } - .flatMapMaybe { addresses -> messageRepo.getOrCreateConversation(addresses) } + .mapNotNull { addresses -> messageRepo.getOrCreateConversation(addresses) } .mergeWith(initialConversation) .filter { conversation -> conversation.isLoaded } .doOnNext { conversation -> @@ -379,6 +382,12 @@ class ComposeViewModel @Inject constructor( .subscribe { newState { it.copy(query = "", searchSelectionId = -1) } } + // Toggle the group sending mode + view.sendAsGroupIntent + .autoDisposable(view.scope()) + .subscribe { newState { it.copy(sendAsGroup = !it.sendAsGroup) } } + + // Scroll to search position searchSelection .filter { id -> id != -1L } @@ -524,17 +533,31 @@ class ComposeViewModel @Inject constructor( view.sendIntent .withLatestFrom(view.textChangedIntent, { _, body -> body }) .map { body -> body.toString() } - .withLatestFrom(state, attachments, conversation, { body, state, attachments, conversation -> + .withLatestFrom(state, attachments, conversation, selectedContacts, { body, state, attachments, conversation, contacts -> val subId = state.subscription?.subscriptionId ?: -1 - val threadId = conversation.id - val addresses = conversation.recipients.map { it.address } - sendMessage.execute(SendMessage.Params(subId, threadId, addresses, body, attachments)) + + if (state.sendAsGroup) { + val threadId = conversation.id + val addresses = conversation.recipients.map { it.address } + sendMessage.execute(SendMessage.Params(subId, threadId, addresses, body, attachments)) + } else { + contacts + .map { contact -> contact.numbers } + .mapNotNull { numbers -> numbers.firstOrNull() } + .map { number -> number.address } + .mapNotNull { address -> messageRepo.getOrCreateConversation(address) } + .filter { it.recipients.isNotEmpty() } + .forEach { + val address = it.recipients.map { it.address } + sendMessage.execute(SendMessage.Params(subId, it.id, address, body, attachments)) + } + } + view.setDraft("") this.attachments.onNext(ArrayList()) - }) - .withLatestFrom(state, { _, state -> + if (state.editingMode) { - newState { it.copy(editingMode = false) } + newState { it.copy(editingMode = false, sendAsGroup = true, hasError = !state.sendAsGroup) } } }) .autoDisposable(view.scope()) diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index 318f46de0..6c3019939 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -287,12 +287,69 @@ + + + + + + + + + + + + %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! Write a messageā€¦ Copy text