Skip to content

Commit

Permalink
#45 - Rename group chats
Browse files Browse the repository at this point in the history
  • Loading branch information
moezbhatti committed Jun 9, 2018
1 parent d4093cc commit bd6c5ca
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 12 deletions.
10 changes: 9 additions & 1 deletion data/src/main/java/migration/QkRealmMigration.kt
Expand Up @@ -19,13 +19,14 @@
package migration

import io.realm.DynamicRealm
import io.realm.FieldAttribute
import io.realm.RealmMigration


class QkRealmMigration : RealmMigration {

companion object {
const val SCHEMA_VERSION: Long = 2
const val SCHEMA_VERSION: Long = 3
}

override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Expand All @@ -45,6 +46,13 @@ class QkRealmMigration : RealmMigration {
version++
}

if (version == 2L) {
realm.schema.get("Conversation")
?.addField("name", String::class.java, FieldAttribute.REQUIRED)

version++
}

if (version < newVersion) {
throw IllegalStateException("Migration missing from v$oldVersion to v$newVersion")
}
Expand Down
11 changes: 11 additions & 0 deletions data/src/main/java/repository/MessageRepositoryImpl.kt
Expand Up @@ -93,6 +93,17 @@ class MessageRepositoryImpl @Inject constructor(
.findAll())
}

override fun setConversationName(id: Long, name: String) {
Realm.getDefaultInstance().use { realm ->
realm.executeTransaction {
realm.where(Conversation::class.java)
.equalTo("id", id)
.findFirst()
?.name = name
}
}
}

override fun searchConversations(query: String): List<SearchResult> {
val conversations = getConversationsSnapshot()

Expand Down
13 changes: 10 additions & 3 deletions data/src/main/java/repository/SyncRepositoryImpl.kt
Expand Up @@ -59,7 +59,11 @@ class SyncRepositoryImpl @Inject constructor(
/**
* Holds data that should be persisted across full syncs
*/
private data class PersistedData(val id: Long, val archived: Boolean, val blocked: Boolean)
private data class PersistedData(
val id: Long,
val archived: Boolean,
val blocked: Boolean,
val name: String)

override val syncProgress: Subject<SyncRepository.SyncProgress> = BehaviorSubject.createDefault(SyncRepository.SyncProgress.Idle())

Expand All @@ -77,9 +81,11 @@ class SyncRepositoryImpl @Inject constructor(
.equalTo("archived", true)
.or()
.equalTo("blocked", true)
.or()
.isNotEmpty("name")
.endGroup()
.findAll()
.map { PersistedData(it.id, it.archived, it.blocked) }
.map { PersistedData(it.id, it.archived, it.blocked, it.name) }

realm.delete(Contact::class.java)
realm.delete(Conversation::class.java)
Expand All @@ -102,7 +108,7 @@ class SyncRepositoryImpl @Inject constructor(
persistedData += oldBlockedSenders.get()
.map { threadIdString -> threadIdString.toLong() }
.filter { threadId -> persistedData.none { it.id == threadId } }
.map { threadId -> PersistedData(threadId, false, true) }
.map { threadId -> PersistedData(threadId, false, true, "") }

// Sync conversations
cursorToConversation.getConversationsCursor()?.use { conversationCursor ->
Expand All @@ -113,6 +119,7 @@ class SyncRepositoryImpl @Inject constructor(
val conversation = conversations.firstOrNull { conversation -> conversation.id == data.id }
conversation?.archived = data.archived
conversation?.blocked = data.blocked
conversation?.name = data.name
}

realm.insertOrUpdate(conversations)
Expand Down
7 changes: 6 additions & 1 deletion domain/src/main/java/model/Conversation.kt
Expand Up @@ -36,5 +36,10 @@ open class Conversation : RealmObject() {
var me: Boolean = false
var draft: String = ""

fun getTitle() = recipients.joinToString { recipient -> recipient.getDisplayName() }
// For group chats, the user is allowed to set a custom title for the conversation
var name: String = ""

fun getTitle(): String {
return name.takeIf { it.isNotBlank() } ?: recipients.joinToString { recipient -> recipient.getDisplayName() }
}
}
2 changes: 2 additions & 0 deletions domain/src/main/java/repository/MessageRepository.kt
Expand Up @@ -31,6 +31,8 @@ interface MessageRepository {

fun getConversationsSnapshot(): List<Conversation>

fun setConversationName(id: Long, name: String)

fun searchConversations(query: String): List<SearchResult>

fun getBlockedConversations(): RealmResults<Conversation>
Expand Down
Expand Up @@ -166,8 +166,7 @@ class ComposeViewModel @Inject constructor(
}
.filter { conversation -> conversation.isValid }
.filter { conversation -> conversation.id != 0L }
.distinctUntilChanged()
.subscribe { conversation.onNext(it) }
.subscribe(conversation::onNext)

// When the conversation changes, update the threadId and the messages for the adapter
disposables += conversation
Expand Down
Expand Up @@ -22,17 +22,22 @@ import android.arch.lifecycle.ViewModelProvider
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.InputFilter
import com.jakewharton.rxbinding2.view.clicks
import com.moez.QKSMS.R
import com.uber.autodispose.android.lifecycle.scope
import com.uber.autodispose.kotlin.autoDisposable
import common.Navigator
import common.base.QkThemedActivity
import common.util.extensions.animateLayoutChanges
import common.util.extensions.dpToPx
import common.util.extensions.resolveThemeColor
import common.util.extensions.scrapViews
import common.util.extensions.setVisible
import common.widget.QkEditText
import dagger.android.AndroidInjection
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import kotlinx.android.synthetic.main.conversation_info_activity.*
import javax.inject.Inject

Expand All @@ -44,6 +49,8 @@ class ConversationInfoActivity : QkThemedActivity(), ConversationInfoView {
@Inject lateinit var itemDecoration: GridSpacingItemDecoration
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory

override val nameIntent by lazy { name.clicks() }
override val nameChangedIntent: Subject<String> = PublishSubject.create()
override val notificationsIntent by lazy { notifications.clicks() }
override val themeIntent by lazy { themePrefs.clicks() }
override val archiveIntent by lazy { archive.clicks() }
Expand Down Expand Up @@ -87,6 +94,9 @@ class ConversationInfoActivity : QkThemedActivity(), ConversationInfoView {
recipientAdapter.threadId = state.threadId
recipientAdapter.updateData(state.recipients)

name.setVisible(state.recipients?.size ?: 0 >= 2)
name.summary = state.name

notifications.setVisible(!state.blocked)

archive.setVisible(!state.blocked)
Expand All @@ -103,6 +113,27 @@ class ConversationInfoActivity : QkThemedActivity(), ConversationInfoView {
mediaAdapter.updateData(state.media)
}

override fun showNameDialog(name: String) {
val editText = QkEditText(this).apply {
val padding = 8.dpToPx(this@ConversationInfoActivity)
setPadding(padding * 3, padding, padding * 3, padding)
setSingleLine(true)
setHint(R.string.info_name_hint)
setText(name)
setHintTextColor(context.resolveThemeColor(android.R.attr.textColorTertiary))
setTextColor(context.resolveThemeColor(android.R.attr.textColorPrimary))
filters = arrayOf(InputFilter.LengthFilter(30))
background = null
}

AlertDialog.Builder(this)
.setTitle(R.string.info_name)
.setView(editText)
.setPositiveButton(R.string.button_save, { _, _ -> nameChangedIntent.onNext(editText.text.toString()) })
.setNegativeButton(R.string.button_cancel, null)
.show()
}

override fun showDeleteDialog() {
AlertDialog.Builder(this)
.setTitle(R.string.dialog_delete_title)
Expand Down
Expand Up @@ -24,6 +24,7 @@ import model.MmsPart
import model.Recipient

data class ConversationInfoState(
val name: String = "",
val recipients: RealmList<Recipient>? = null,
val threadId: Long = 0,
val archived: Boolean = false,
Expand Down
Expand Up @@ -18,18 +18,21 @@
*/
package feature.conversationinfo

import io.reactivex.Observable
import common.base.QkView
import io.reactivex.Observable

interface ConversationInfoView : QkView<ConversationInfoState> {

val nameIntent: Observable<*>
val nameChangedIntent: Observable<String>
val notificationsIntent: Observable<Unit>
val themeIntent: Observable<Unit>
val archiveIntent: Observable<Unit>
val blockIntent: Observable<Unit>
val deleteIntent: Observable<Unit>
val confirmDeleteIntent: Observable<Unit>

fun showNameDialog(name: String)
fun showDeleteDialog()

}
Expand Up @@ -27,9 +27,10 @@ import interactor.MarkArchived
import interactor.MarkBlocked
import interactor.MarkUnarchived
import interactor.MarkUnblocked
import io.reactivex.Observable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.withLatestFrom
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
import model.Conversation
import repository.MessageRepository
import util.extensions.asObservable
Expand All @@ -38,21 +39,21 @@ import javax.inject.Named

class ConversationInfoViewModel @Inject constructor(
@Named("threadId") threadId: Long,
messageRepo: MessageRepository,
private val markArchived: MarkArchived,
private val markUnarchived: MarkUnarchived,
private val markBlocked: MarkBlocked,
private val markUnblocked: MarkUnblocked,
private val messageRepo: MessageRepository,
private val navigator: Navigator,
private val deleteConversations: DeleteConversations
) : QkViewModel<ConversationInfoView, ConversationInfoState>(
ConversationInfoState(threadId = threadId, media = messageRepo.getPartsForConversation(threadId))
) {

private val conversation: Observable<Conversation>
private val conversation: Subject<Conversation> = BehaviorSubject.create()

init {
conversation = messageRepo.getConversationAsync(threadId)
disposables += messageRepo.getConversationAsync(threadId)
.asObservable<Conversation>()
.filter { conversation -> conversation.isLoaded }
.doOnNext { conversation ->
Expand All @@ -62,6 +63,7 @@ class ConversationInfoViewModel @Inject constructor(
}
.filter { conversation -> conversation.isValid }
.filter { conversation -> conversation.id != 0L }
.subscribe(conversation::onNext)

disposables += markArchived
disposables += markUnarchived
Expand All @@ -75,6 +77,12 @@ class ConversationInfoViewModel @Inject constructor(
.distinctUntilChanged()
.subscribe { recipients -> newState { copy(recipients = recipients) } }

// 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 }
Expand All @@ -91,6 +99,21 @@ class ConversationInfoViewModel @Inject constructor(
override fun bindView(view: ConversationInfoView) {
super.bindView(view)

// Show the conversation title dialog
view.nameIntent
.withLatestFrom(conversation) { _, conversation -> conversation }
.map { conversation -> conversation.name }
.autoDisposable(view.scope())
.subscribe(view::showNameDialog)

// Set the conversation title
view.nameChangedIntent
.withLatestFrom(conversation) { name, conversation ->
messageRepo.setConversationName(conversation.id, name)
}
.autoDisposable(view.scope())
.subscribe()

// Show the notifications settings for the conversation
view.notificationsIntent
.withLatestFrom(conversation, { _, conversation -> conversation })
Expand Down
27 changes: 27 additions & 0 deletions presentation/src/main/res/drawable/ic_people_black_24dp.xml
@@ -0,0 +1,27 @@
<!--
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
~
~ 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 <http://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
</vector>
Expand Up @@ -61,6 +61,14 @@
android:layout_marginBottom="8dp"
android:background="?android:attr/divider" />

<common.widget.PreferenceView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:icon="@drawable/ic_people_black_24dp"
app:title="@string/info_name" />

<common.widget.PreferenceView
android:id="@+id/notifications"
android:layout_width="match_parent"
Expand Down
3 changes: 3 additions & 0 deletions presentation/src/main/res/values/strings.xml
Expand Up @@ -104,6 +104,8 @@
<string name="message_status_failed">Failed to send. Tap to try again</string>

<string name="info_title">Details</string>
<string name="info_name">Conversation title</string>
<string name="info_name_hint">Enter title</string>
<string name="info_notifications">Notifications</string>
<string name="info_theme">Theme</string>
<string name="info_archive">Archive</string>
Expand Down Expand Up @@ -215,6 +217,7 @@

<string name="button_cancel">Cancel</string>
<string name="button_delete">Delete</string>
<string name="button_save">Save</string>
<string name="button_more">More</string>
<string name="button_set">Set</string>
<string name="button_unblock">Unblock</string>
Expand Down

0 comments on commit bd6c5ca

Please sign in to comment.