forked from upstream-mirrors/quik
Indicate in UI when scheduled messages are present
Added a new property to scheduled messages to save which conversation the scheduled message belongs to.
This commit is contained in:
@@ -36,7 +36,7 @@ class QkRealmMigration @Inject constructor(
|
||||
) : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SchemaVersion: Long = 11
|
||||
const val SchemaVersion: Long = 12
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
@@ -233,6 +233,14 @@ class QkRealmMigration @Inject constructor(
|
||||
|
||||
version++
|
||||
}
|
||||
if (version == 11L) {
|
||||
realm.schema.get("ScheduledMessage")
|
||||
?.addField("conversationId", Long::class.java, FieldAttribute.REQUIRED)
|
||||
// Because there was never any property associated with which conversation/recipients a scheduled message was for,
|
||||
// we can't update this field on a realm migration. It will be set to a default of 0
|
||||
|
||||
version++
|
||||
}
|
||||
|
||||
check(version >= newVersion) { "Migration missing from v$oldVersion to v$newVersion" }
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ class ScheduledMessageRepositoryImpl @Inject constructor() : ScheduledMessageRep
|
||||
recipients: List<String>,
|
||||
sendAsGroup: Boolean,
|
||||
body: String,
|
||||
attachments: List<String>
|
||||
attachments: List<String>,
|
||||
conversationId: Long
|
||||
): ScheduledMessage {
|
||||
Realm.getDefaultInstance().use { realm ->
|
||||
val id = (realm
|
||||
@@ -34,7 +35,7 @@ class ScheduledMessageRepositoryImpl @Inject constructor() : ScheduledMessageRep
|
||||
val attachmentsRealmList = RealmList(*attachments.toTypedArray())
|
||||
|
||||
val message = ScheduledMessage(id, date, subId, recipientsRealmList, sendAsGroup, body,
|
||||
attachmentsRealmList)
|
||||
attachmentsRealmList, conversationId)
|
||||
|
||||
realm.executeTransaction { realm.insertOrUpdate(message) }
|
||||
|
||||
@@ -63,6 +64,13 @@ class ScheduledMessageRepositoryImpl @Inject constructor() : ScheduledMessageRep
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
override fun getScheduledMessagesForConversation(conversationId: Long): RealmResults<ScheduledMessage> {
|
||||
return Realm.getDefaultInstance()
|
||||
.where(ScheduledMessage::class.java)
|
||||
.equalTo("conversationId", conversationId)
|
||||
.findAllAsync()
|
||||
}
|
||||
|
||||
override fun deleteScheduledMessage(id: Long) {
|
||||
val subscription = Completable.fromAction {
|
||||
Realm.getDefaultInstance().use { realm ->
|
||||
|
||||
@@ -41,7 +41,8 @@ class AddScheduledMessage @Inject constructor(
|
||||
val recipients: List<String>,
|
||||
val sendAsGroup: Boolean,
|
||||
val body: String,
|
||||
val attachments: List<String>
|
||||
val attachments: List<String>,
|
||||
val conversationId: Long
|
||||
)
|
||||
|
||||
@SuppressLint("Range")
|
||||
@@ -50,7 +51,13 @@ class AddScheduledMessage @Inject constructor(
|
||||
return Flowable.just(params)
|
||||
.map { // step 1 - save, as-is, to db to get primary key id
|
||||
scheduledMessageRepo.saveScheduledMessage(
|
||||
it.date, it.subId, it.recipients, it.sendAsGroup, it.body, it.attachments
|
||||
it.date,
|
||||
it.subId,
|
||||
it.recipients,
|
||||
it.sendAsGroup,
|
||||
it.body,
|
||||
it.attachments,
|
||||
it.conversationId
|
||||
)
|
||||
}
|
||||
.map { scheduledMessageDb ->
|
||||
|
||||
@@ -29,7 +29,8 @@ open class ScheduledMessage(
|
||||
var recipients: RealmList<String> = RealmList(),
|
||||
var sendAsGroup: Boolean = true,
|
||||
var body: String = "",
|
||||
var attachments: RealmList<String> = RealmList()
|
||||
var attachments: RealmList<String> = RealmList(),
|
||||
var conversationId: Long = 0
|
||||
) : RealmObject() {
|
||||
|
||||
fun copy(
|
||||
@@ -39,10 +40,11 @@ open class ScheduledMessage(
|
||||
recipients: RealmList<String> = this.recipients,
|
||||
sendAsGroup: Boolean = this.sendAsGroup,
|
||||
body: String = this.body,
|
||||
attachments: RealmList<String> = this.attachments
|
||||
attachments: RealmList<String> = this.attachments,
|
||||
conversationId: Long = this.conversationId
|
||||
): ScheduledMessage {
|
||||
|
||||
return ScheduledMessage(id, date, subId, recipients, sendAsGroup, body, attachments)
|
||||
return ScheduledMessage(id, date, subId, recipients, sendAsGroup, body, attachments, conversationId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,9 @@
|
||||
*/
|
||||
package dev.octoshrimpy.quik.repository
|
||||
|
||||
import dev.octoshrimpy.quik.model.Recipient
|
||||
import dev.octoshrimpy.quik.model.ScheduledMessage
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmResults
|
||||
|
||||
interface ScheduledMessageRepository {
|
||||
@@ -32,7 +34,8 @@ interface ScheduledMessageRepository {
|
||||
recipients: List<String>,
|
||||
sendAsGroup: Boolean,
|
||||
body: String,
|
||||
attachments: List<String>
|
||||
attachments: List<String>,
|
||||
conversationId: Long
|
||||
): ScheduledMessage
|
||||
|
||||
/**
|
||||
@@ -50,6 +53,11 @@ interface ScheduledMessageRepository {
|
||||
*/
|
||||
fun getScheduledMessage(id: Long): ScheduledMessage?
|
||||
|
||||
/**
|
||||
* Returns all scheduled messages with the given [conversationId]
|
||||
*/
|
||||
fun getScheduledMessagesForConversation(conversationId: Long): RealmResults<ScheduledMessage>
|
||||
|
||||
/**
|
||||
* Deletes the scheduled message with the given [id]
|
||||
*/
|
||||
|
||||
@@ -160,8 +160,9 @@ class Navigator @Inject constructor(
|
||||
startActivity(Intent(context, BackupActivity::class.java))
|
||||
}
|
||||
|
||||
fun showScheduled() {
|
||||
fun showScheduled(conversationId: Long?) {
|
||||
val intent = Intent(context, ScheduledActivity::class.java)
|
||||
conversationId?.let { intent.putExtra("conversationId", it) }
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
||||
@@ -469,6 +469,8 @@ class ComposeActivity : QkThemedActivity(), ComposeView {
|
||||
// Don't set the adapters unless needed
|
||||
if (state.editingMode && chips.adapter == null) chips.adapter = chipsAdapter
|
||||
|
||||
toolbar.menu.findItem(R.id.viewScheduledMessages)?.isVisible = !state.editingMode && state.selectedMessages == 0
|
||||
&& state.query.isEmpty() && state.hasScheduledMessages
|
||||
toolbar.menu.findItem(R.id.select_all)?.isVisible = !state.editingMode && (messageAdapter.itemCount > 1) && state.selectedMessages != 0
|
||||
toolbar.menu.findItem(R.id.add)?.isVisible = state.editingMode
|
||||
toolbar.menu.findItem(R.id.call)?.isVisible = !state.editingMode && state.selectedMessages == 0
|
||||
|
||||
@@ -47,6 +47,7 @@ data class ComposeState(
|
||||
val remaining: String = "",
|
||||
val subscription: SubscriptionInfoCompat? = null,
|
||||
val canSend: Boolean = false,
|
||||
val hasScheduledMessages: Boolean = false,
|
||||
val validRecipientNumbers: Int = 1,
|
||||
val audioMsgRecording: Boolean = false,
|
||||
val saveDraft: Boolean = true,
|
||||
|
||||
@@ -72,6 +72,7 @@ import dev.octoshrimpy.quik.model.getText
|
||||
import dev.octoshrimpy.quik.repository.ContactRepository
|
||||
import dev.octoshrimpy.quik.repository.ConversationRepository
|
||||
import dev.octoshrimpy.quik.repository.MessageRepository
|
||||
import dev.octoshrimpy.quik.repository.ScheduledMessageRepository
|
||||
import dev.octoshrimpy.quik.util.ActiveSubscriptionObservable
|
||||
import dev.octoshrimpy.quik.util.FileUtils
|
||||
import dev.octoshrimpy.quik.util.PhoneNumberUtils
|
||||
@@ -114,6 +115,7 @@ class ComposeViewModel @Inject constructor(
|
||||
private val markRead: MarkRead,
|
||||
private val messageDetailsFormatter: MessageDetailsFormatter,
|
||||
private val messageRepo: MessageRepository,
|
||||
private val scheduledMessageRepo: ScheduledMessageRepository,
|
||||
private val navigator: Navigator,
|
||||
private val permissionManager: PermissionManager,
|
||||
private val phoneNumberUtils: PhoneNumberUtils,
|
||||
@@ -265,6 +267,22 @@ class ComposeViewModel @Inject constructor(
|
||||
newState { copy(subscription = sub) }
|
||||
}.subscribe()
|
||||
|
||||
// checks if there are any scheduled messages in convo
|
||||
disposables += conversation
|
||||
.distinctUntilChanged { conversation -> conversation.id }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.switchMap { conversation ->
|
||||
scheduledMessageRepo
|
||||
.getScheduledMessagesForConversation(conversation.id)
|
||||
.asFlowable()
|
||||
.toObservable()
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { liveResults ->
|
||||
val hasAny = liveResults.isNotEmpty()
|
||||
newState { copy(hasScheduledMessages = hasAny) }
|
||||
}
|
||||
|
||||
// actions
|
||||
if (mode == "scheduling")
|
||||
newState { copy(scheduling = true) }
|
||||
@@ -338,6 +356,15 @@ class ComposeViewModel @Inject constructor(
|
||||
.autoDisposable(view.scope())
|
||||
.subscribe { newState { copy() } }
|
||||
|
||||
// Show scheduled messages
|
||||
view.optionsItemIntent
|
||||
.filter {it == R.id.viewScheduledMessages}
|
||||
.withLatestFrom(state, conversation)
|
||||
.autoDisposable(view.scope())
|
||||
.subscribe { (_, _, conversation) ->
|
||||
navigator.showScheduled(conversation.id)
|
||||
}
|
||||
|
||||
// toggle select all / select none
|
||||
view.optionsItemIntent
|
||||
.filter { it == R.id.select_all }
|
||||
@@ -1099,6 +1126,7 @@ class ComposeViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
val subId = state.subscription?.subscriptionId ?: -1
|
||||
val conversationId = (conversation.id)
|
||||
val addresses = when (conversation.recipients.isNotEmpty()) {
|
||||
true -> conversation.recipients.map { it.address }
|
||||
false -> chips.map { chip -> chip.address }
|
||||
@@ -1116,10 +1144,11 @@ class ComposeViewModel @Inject constructor(
|
||||
addresses,
|
||||
sendAsGroup,
|
||||
body.toString(),
|
||||
state.attachments.map { it.uri.toString() }
|
||||
state.attachments.map { it.uri.toString() },
|
||||
conversationId
|
||||
)
|
||||
).also {
|
||||
newState { copy(scheduled = 0) }
|
||||
newState { copy(scheduled = 0, hasScheduledMessages = true ) }
|
||||
showScheduledToast = true
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -24,6 +24,7 @@ import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.octoshrimpy.quik.R
|
||||
import dev.octoshrimpy.quik.common.Navigator
|
||||
import dev.octoshrimpy.quik.common.base.QkRealmAdapter
|
||||
@@ -33,7 +34,9 @@ import dev.octoshrimpy.quik.common.util.DateFormatter
|
||||
import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor
|
||||
import dev.octoshrimpy.quik.common.util.extensions.setTint
|
||||
import dev.octoshrimpy.quik.model.Conversation
|
||||
import dev.octoshrimpy.quik.repository.ScheduledMessageRepository
|
||||
import dev.octoshrimpy.quik.util.PhoneNumberUtils
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.android.synthetic.main.conversation_list_item.*
|
||||
import kotlinx.android.synthetic.main.conversation_list_item.view.*
|
||||
import javax.inject.Inject
|
||||
@@ -42,9 +45,11 @@ class ConversationsAdapter @Inject constructor(
|
||||
private val colors: Colors,
|
||||
private val context: Context,
|
||||
private val dateFormatter: DateFormatter,
|
||||
private val scheduledMessageRepo: ScheduledMessageRepository,
|
||||
private val navigator: Navigator,
|
||||
private val phoneNumberUtils: PhoneNumberUtils
|
||||
) : QkRealmAdapter<Conversation>() {
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
init {
|
||||
// This is how we access the threadId for the swipe actions
|
||||
@@ -117,6 +122,16 @@ class ConversationsAdapter @Inject constructor(
|
||||
// Make the preview in italics if draft
|
||||
if (conversation.draft.isNotEmpty()) holder.snippet.setTypeface(null, Typeface.ITALIC)
|
||||
|
||||
// Get Scheduled Messages
|
||||
val disposable = scheduledMessageRepo
|
||||
.getScheduledMessagesForConversation(conversation.id)
|
||||
.asFlowable()
|
||||
.toObservable()
|
||||
.subscribe { messages ->
|
||||
holder.scheduled.isVisible = messages.isNotEmpty()
|
||||
}
|
||||
disposables.add(disposable)
|
||||
|
||||
holder.pinned.isVisible = conversation.pinned
|
||||
holder.unread.setTint(theme)
|
||||
}
|
||||
@@ -129,4 +144,10 @@ class ConversationsAdapter @Inject constructor(
|
||||
return if (getItem(position)?.unread == false) 0 else 1
|
||||
}
|
||||
|
||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||
super.onDetachedFromRecyclerView(recyclerView)
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ class MainViewModel @Inject constructor(
|
||||
else -> newState { copy(hasError = true) }
|
||||
}
|
||||
NavItem.BACKUP -> navigator.showBackup()
|
||||
NavItem.SCHEDULED -> navigator.showScheduled()
|
||||
NavItem.SCHEDULED -> navigator.showScheduled(null)
|
||||
NavItem.BLOCKING -> navigator.showBlockedConversations()
|
||||
NavItem.SETTINGS -> navigator.showSettings()
|
||||
// NavItem.PLUS -> navigator.showQksmsPlusActivity("main_menu")
|
||||
|
||||
@@ -66,7 +66,7 @@ class PlusViewModel @Inject constructor(
|
||||
|
||||
view.scheduleClicks
|
||||
.autoDisposable(view.scope())
|
||||
.subscribe { navigator.showScheduled() }
|
||||
.subscribe { navigator.showScheduled(null) }
|
||||
|
||||
view.backupClicks
|
||||
.autoDisposable(view.scope())
|
||||
|
||||
@@ -99,7 +99,7 @@ class ScheduledActivity : QkThemedActivity(), ScheduledView {
|
||||
((scheduledMessageAdapter.itemCount != 0) && (state.selectedMessages == 1))
|
||||
|
||||
// show compose button
|
||||
compose.isVisible = state.upgraded
|
||||
compose.isVisible = state.upgraded && (state.conversationId == null)
|
||||
upgrade.isVisible = !state.upgraded
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -23,6 +23,7 @@ import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import dev.octoshrimpy.quik.injection.ViewModelKey
|
||||
import javax.inject.Named
|
||||
|
||||
@Module
|
||||
class ScheduledActivityModule {
|
||||
@@ -32,4 +33,9 @@ class ScheduledActivityModule {
|
||||
@ViewModelKey(ScheduledViewModel::class)
|
||||
fun provideScheduledViewModel(viewModel: ScheduledViewModel): ViewModel = viewModel
|
||||
|
||||
}
|
||||
@Provides
|
||||
@Named("conversationId")
|
||||
fun getConversationId(activity: ScheduledActivity):
|
||||
Long? = activity.intent.extras?.getLong("conversationId")
|
||||
|
||||
}
|
||||
|
||||
@@ -25,4 +25,5 @@ data class ScheduledState(
|
||||
val scheduledMessages: RealmResults<ScheduledMessage>? = null,
|
||||
val upgraded: Boolean = true,
|
||||
val selectedMessages: Int = 0,
|
||||
val conversationId: Long? = null,
|
||||
)
|
||||
|
||||
@@ -15,8 +15,10 @@ import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxkotlin.plusAssign
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class ScheduledViewModel @Inject constructor(
|
||||
@Named("conversationId") private val conversationId: Long?,
|
||||
billingManager: BillingManager,
|
||||
private val context: Context,
|
||||
private val navigator: Navigator,
|
||||
@@ -24,10 +26,12 @@ class ScheduledViewModel @Inject constructor(
|
||||
private val sendScheduledMessageInteractor: SendScheduledMessage,
|
||||
private val deleteScheduledMessagesInteractor: DeleteScheduledMessages,
|
||||
) : QkViewModel<ScheduledView, ScheduledState>(ScheduledState(
|
||||
scheduledMessages = scheduledMessageRepo.getScheduledMessages()
|
||||
scheduledMessages = scheduledMessageRepo.getScheduledMessages(),
|
||||
conversationId = conversationId
|
||||
)) {
|
||||
|
||||
init {
|
||||
loadMessages(conversationId)
|
||||
disposables += billingManager.upgradeStatus
|
||||
.subscribe { upgraded -> newState { copy(upgraded = upgraded) } }
|
||||
}
|
||||
@@ -157,4 +161,12 @@ class ScheduledViewModel @Inject constructor(
|
||||
.autoDisposable(view.scope())
|
||||
.subscribe { navigator.showQksmsPlusActivity("schedule_fab") }
|
||||
}
|
||||
|
||||
private fun loadMessages(conversationId: Long?) {
|
||||
val results = if (conversationId != null)
|
||||
scheduledMessageRepo.getScheduledMessagesForConversation(conversationId)
|
||||
else
|
||||
scheduledMessageRepo.getScheduledMessages()
|
||||
newState { copy(scheduledMessages = results) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/pinned"
|
||||
app:layout_constraintEnd_toStartOf="@id/scheduled"
|
||||
app:layout_constraintStart_toStartOf="@id/title"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
@@ -80,9 +80,23 @@
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingTop="2dp"
|
||||
android:src="@drawable/ic_baseline_push_pin_24"
|
||||
android:tint="?android:attr/textColorTertiary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/scheduled"
|
||||
app:layout_constraintTop_toTopOf="@id/snippet"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/scheduled"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:paddingStart="4dp"
|
||||
android:src="@drawable/ic_event_black_24dp"
|
||||
android:tint="?android:attr/textColorTertiary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/unread"
|
||||
app:layout_constraintTop_toTopOf="@id/snippet"
|
||||
tools:visibility="visible" />
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
android:visible="false"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/viewScheduledMessages"
|
||||
android:icon="@drawable/ic_event_black_24dp"
|
||||
android:title="@string/compose_scheduled_view"
|
||||
android:visible="false"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/select_all"
|
||||
android:icon="?attr/actionModeSelectAllDrawable"
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
<string name="compose_scheduled_future">Selected time must be in the future!</string>
|
||||
<string name="compose_scheduled_plus">You must unlock QUIK+ to use scheduled messaging</string>
|
||||
<string name="compose_scheduled_toast">Added to scheduled messages</string>
|
||||
<string name="compose_scheduled_view">Scheduled messages for this conversation</string>
|
||||
<string name="compose_hint">Write a message…</string>
|
||||
<string name="compose_no_valid_recipients">Can\'t send to other participants</string>
|
||||
<string name="compose_menu_copy">Copy text</string>
|
||||
|
||||
Reference in New Issue
Block a user