Files
quik/presentation/src/main/java/feature/compose/ComposeActivity.kt
T
Moez Bhatti d6fa04f56d Show progress when conversation is loading
Potentially related to #1079
2018-06-06 13:47:41 -04:00

284 lines
11 KiB
Kotlin

/*
* 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/>.
*/
package feature.compose
import android.Manifest
import android.app.Activity
import android.arch.lifecycle.ViewModelProvider
import android.arch.lifecycle.ViewModelProviders
import android.content.ContentValues
import android.content.Intent
import android.graphics.PorterDuff
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.support.v4.app.ActivityCompat
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import com.google.android.flexbox.FlexboxLayoutManager
import com.jakewharton.rxbinding2.view.clicks
import com.jakewharton.rxbinding2.widget.textChanges
import com.moez.QKSMS.R
import com.uber.autodispose.android.lifecycle.scope
import com.uber.autodispose.kotlin.autoDisposable
import common.base.QkThemedActivity
import common.util.extensions.autoScrollToStart
import common.util.extensions.scrapViews
import common.util.extensions.setBackgroundTint
import common.util.extensions.setTint
import common.util.extensions.setVisible
import common.util.extensions.showKeyboard
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 model.Attachment
import model.Contact
import model.Message
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
class ComposeActivity : QkThemedActivity(), ComposeView {
companion object {
const val CAMERA_REQUEST_CODE = 0
const val GALLERY_REQUEST_CODE = 1
}
@Inject lateinit var attachmentAdapter: AttachmentAdapter
@Inject lateinit var chipsAdapter: ChipsAdapter
@Inject lateinit var contactsAdapter: ContactAdapter
@Inject lateinit var messageAdapter: MessagesAdapter
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
override val activityVisibleIntent: Subject<Boolean> = PublishSubject.create()
override val queryChangedIntent: Observable<CharSequence> by lazy { chipsAdapter.textChanges }
override val queryBackspaceIntent: Observable<*> by lazy { chipsAdapter.backspaces }
override val queryEditorActionIntent: Observable<Int> by lazy { chipsAdapter.actions }
override val chipSelectedIntent: Subject<Contact> by lazy { contactsAdapter.contactSelected }
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 }
override val attachmentDeletedIntent: Subject<Attachment> by lazy { attachmentAdapter.attachmentDeleted }
override val textChangedIntent by lazy { message.textChanges() }
override val attachIntent by lazy { attach.clicks() }
override val cameraIntent by lazy { camera.clicks() }
override val galleryIntent by lazy { gallery.clicks() }
override val attachmentSelectedIntent: Subject<Uri> = PublishSubject.create()
override val inputContentIntent by lazy { message.inputContentSelected }
override val changeSimIntent by lazy { sim.clicks() }
override val sendIntent by lazy { send.clicks() }
override val backPressedIntent: Subject<Unit> = PublishSubject.create()
private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ComposeViewModel::class.java] }
private var cameraDestination: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.compose_activity)
showBackButton(true)
viewModel.bindView(this)
chipsAdapter.view = chips
contacts.itemAnimator = null
chips.itemAnimator = null
chips.layoutManager = FlexboxLayoutManager(this)
val layoutManager = LinearLayoutManager(this).apply { stackFromEnd = true }
messageAdapter.autoScrollToStart(messageList)
messageAdapter.emptyView = messagesEmpty
messageList.setHasFixedSize(true)
messageList.layoutManager = layoutManager
messageList.adapter = messageAdapter
attachments.adapter = attachmentAdapter
messageBackground.backgroundTintMode = PorterDuff.Mode.MULTIPLY
message.supportsInputContent = true
theme
.doOnNext { loading.setTint(it.theme) }
.doOnNext { send.setBackgroundTint(it.theme) }
.doOnNext { send.setTint(it.textPrimary) }
.doOnNext { messageAdapter.theme = it }
.autoDisposable(scope())
.subscribe { messageList.scrapViews() }
window.callback = ComposeWindowCallback(window.callback, this)
}
override fun onStart() {
super.onStart()
activityVisibleIntent.onNext(true)
}
override fun onPause() {
super.onPause()
activityVisibleIntent.onNext(false)
}
override fun render(state: ComposeState) {
if (state.hasError) {
finish()
return
}
threadId.onNext(state.selectedConversation)
title = when {
state.selectedMessages > 0 -> getString(R.string.compose_title_selected, state.selectedMessages)
state.query.isNotEmpty() -> state.query
else -> state.conversationtitle
}
toolbarSubtitle.setVisible(state.query.isNotEmpty())
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)
// 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.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.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()
if (chipsAdapter.data.isEmpty() && state.selectedContacts.isNotEmpty()) {
message.showKeyboard()
}
chipsAdapter.data = state.selectedContacts
contactsAdapter.data = state.contacts
loading.setVisible(state.loading)
sendAsGroup.setVisible(state.editingMode && state.selectedContacts.size >= 2)
sendAsGroupSwitch.isChecked = state.sendAsGroup
messageList.setVisible(state.sendAsGroup)
messageAdapter.data = state.messages
messageAdapter.highlight = state.searchSelectionId
attachments.setVisible(state.attachments.isNotEmpty())
attachmentAdapter.data = state.attachments
attach.animate().rotation(if (state.attaching) 45f else 0f).start()
camera.setVisible(state.attaching)
gallery.setVisible(state.attaching)
counter.text = state.remaining
counter.setVisible(counter.text.isNotBlank())
sim.setVisible(state.subscription != null)
simIndex.text = "${state.subscription?.simSlotIndex?.plus(1)}"
send.isEnabled = state.canSend
send.imageAlpha = if (state.canSend) 255 else 128
}
override fun clearSelection() {
messageAdapter.clearSelection()
}
override fun requestStoragePermission() {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 0)
}
override fun requestCamera() {
cameraDestination = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
.let { timestamp -> ContentValues().apply { put(MediaStore.Images.Media.TITLE, timestamp) } }
.let { cv -> contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cv) }
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
.putExtra(MediaStore.EXTRA_OUTPUT, cameraDestination)
startActivityForResult(intent, CAMERA_REQUEST_CODE)
}
override fun requestGallery() {
val intent = Intent(Intent.ACTION_PICK)
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
.putExtra(Intent.EXTRA_LOCAL_ONLY, false)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setType("image/*")
startActivityForResult(intent, GALLERY_REQUEST_CODE)
}
override fun setDraft(draft: String) {
message.setText(draft)
}
override fun scrollToMessage(id: Long) {
messageAdapter.data?.second
?.indexOfLast { message -> message.id == id }
?.takeIf { position -> position != -1 }
?.let(messageList::scrollToPosition)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.compose, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
optionsItemIntent.onNext(item.itemId)
return true
}
override fun getColoredMenuItems(): List<Int> {
return super.getColoredMenuItems() + R.id.call
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
CAMERA_REQUEST_CODE -> cameraDestination
GALLERY_REQUEST_CODE -> data?.data
else -> null
}?.let(attachmentSelectedIntent::onNext)
}
}
override fun onBackPressed() {
backPressedIntent.onNext(Unit)
}
}