Skip to content

Commit

Permalink
#678 - Allow deciding between sending as group or individual
Browse files Browse the repository at this point in the history
  • Loading branch information
moezbhatti committed Jun 5, 2018
1 parent 11880a9 commit fc99ae8
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 54 deletions.
Expand Up @@ -24,6 +24,7 @@
import android.net.Uri;
import android.widget.Toast;
import com.klinker.android.logger.Log;
import org.jetbrains.annotations.Nullable;

/**
* @hide
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
45 changes: 17 additions & 28 deletions data/src/main/java/repository/MessageRepositoryImpl.kt
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -141,39 +137,32 @@ class MessageRepositoryImpl @Inject constructor(
return getConversation(threadId) ?: getConversationFromCp(threadId)
}

override fun getOrCreateConversation(address: String): Maybe<Conversation> {
override fun getOrCreateConversation(address: String): Conversation? {
return getOrCreateConversation(listOf(address))
}

override fun getOrCreateConversation(addresses: List<String>): Maybe<Conversation> {
return Maybe.just(addresses)
.map { recipients ->
recipients.map { address ->
when (MessageUtils.isEmailAddress(address)) {
true -> MessageUtils.extractAddrSpec(address)
false -> address
}
override fun getOrCreateConversation(addresses: List<String>): 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) {
Expand Down Expand Up @@ -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"
}
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion data/src/main/java/service/HeadlessSmsSendService.kt
Expand Up @@ -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))
}
}
Expand Down
5 changes: 2 additions & 3 deletions domain/src/main/java/repository/MessageRepository.kt
Expand Up @@ -18,7 +18,6 @@
*/
package repository

import io.reactivex.Maybe
import io.realm.RealmResults
import model.Attachment
import model.Conversation
Expand All @@ -42,9 +41,9 @@ interface MessageRepository {

fun getOrCreateConversation(threadId: Long): Conversation?

fun getOrCreateConversation(address: String): Maybe<Conversation>
fun getOrCreateConversation(address: String): Conversation?

fun getOrCreateConversation(addresses: List<String>): Maybe<Conversation>
fun getOrCreateConversation(addresses: List<String>): Conversation?

fun saveDraft(threadId: Long, draft: String)

Expand Down
6 changes: 6 additions & 0 deletions presentation/src/main/java/feature/compose/ComposeActivity.kt
Expand Up @@ -79,6 +79,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView {
override val chipDeletedIntent: Subject<Contact> by lazy { chipsAdapter.chipDeleted }
override val menuReadyIntent: Observable<Unit> = menu.map { Unit }
override val optionsItemIntent: Subject<Int> = PublishSubject.create()
override val sendAsGroupIntent by lazy { sendAsGroupBackground.clicks() }
override val messageClickIntent: Subject<Message> by lazy { messageAdapter.clicks }
override val messagesSelectedIntent by lazy { messageAdapter.selectionChanges }
override val cancelSendingIntent: Subject<Message> by lazy { messageAdapter.cancelSending }
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions presentation/src/main/java/feature/compose/ComposeState.kt
Expand Up @@ -32,6 +32,7 @@ data class ComposeState(
val contactsVisible: Boolean = false,
val selectedConversation: Long = 0,
val selectedContacts: List<Contact> = ArrayList(),
val sendAsGroup: Boolean = true,
val conversationtitle: String = "",
val query: String = "",
val searchSelectionId: Long = -1,
Expand Down
1 change: 1 addition & 0 deletions presentation/src/main/java/feature/compose/ComposeView.kt
Expand Up @@ -37,6 +37,7 @@ interface ComposeView : QkView<ComposeState> {
val chipDeletedIntent: Subject<Contact>
val menuReadyIntent: Observable<Unit>
val optionsItemIntent: Observable<Int>
val sendAsGroupIntent: Observable<*>
val messageClickIntent: Subject<Message>
val messagesSelectedIntent: Observable<List<Long>>
val cancelSendingIntent: Subject<Message>
Expand Down
41 changes: 32 additions & 9 deletions presentation/src/main/java/feature/compose/ComposeViewModel.kt
Expand Up @@ -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 -> {
Expand All @@ -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 ->
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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())
Expand Down
61 changes: 59 additions & 2 deletions presentation/src/main/res/layout/compose_activity.xml
Expand Up @@ -287,12 +287,69 @@

</android.support.v7.widget.Toolbar>

<android.support.constraint.Group
android:id="@+id/sendAsGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="sendAsGroupBackground, sendAsGroupTitle, sendAsGroupSummary, sendAsGroupSwitch, sendAsGroupShadow" />

<View
android:id="@+id/sendAsGroupBackground"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?attr/colorPrimary"
app:layout_constraintBottom_toBottomOf="@id/sendAsGroupSummary"
app:layout_constraintTop_toBottomOf="@id/toolbar" />

<common.widget.QkTextView
android:id="@+id/sendAsGroupTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/compose_send_group_title"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/sendAsGroupSwitch"
app:layout_constraintStart_toStartOf="@id/sendAsGroupBackground"
app:layout_constraintTop_toTopOf="@id/sendAsGroupBackground"
app:textSize="primary" />

<common.widget.QkTextView
android:id="@+id/sendAsGroupSummary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:text="@string/compose_send_group_summary"
android:textColor="?android:attr/textColorTertiary"
app:layout_constraintEnd_toEndOf="@id/sendAsGroupTitle"
app:layout_constraintStart_toStartOf="@id/sendAsGroupTitle"
app:layout_constraintTop_toBottomOf="@id/sendAsGroupTitle"
app:textSize="secondary" />

<common.widget.QkSwitch
android:id="@+id/sendAsGroupSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:checked="true"
android:clickable="false"
app:layout_constraintBottom_toBottomOf="@id/sendAsGroupBackground"
app:layout_constraintEnd_toEndOf="@id/sendAsGroupBackground"
app:layout_constraintTop_toTopOf="@id/sendAsGroupBackground" />

<View
android:id="@+id/sendAsGroupShadow"
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@drawable/ab_shadow"
app:layout_constraintTop_toBottomOf="@id/sendAsGroupBackground" />

<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@drawable/ab_shadow"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />

<View
Expand Down
2 changes: 2 additions & 0 deletions presentation/src/main/res/values/strings.xml
Expand Up @@ -83,6 +83,8 @@

<string name="compose_title_selected">%d selected</string>
<string name="compose_subtitle_results">%1$d of %2$d results</string>
<string name="compose_send_group_title">Send as group message</string>
<string name="compose_send_group_summary">Recipients and replies will be visible to everyone</string>
<string name="compose_messages_empty">This is the start of your conversation. Say something nice!</string>
<string name="compose_hint">Write a message…</string>
<string name="compose_menu_copy">Copy text</string>
Expand Down

0 comments on commit fc99ae8

Please sign in to comment.