/* * Copyright (C) 2017 Moez Bhatti * * 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 . */ 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 = PublishSubject.create() override val queryChangedIntent: Observable by lazy { chipsAdapter.textChanges } override val queryBackspaceIntent: Observable<*> by lazy { chipsAdapter.backspaces } override val queryEditorActionIntent: Observable by lazy { chipsAdapter.actions } override val chipSelectedIntent: Subject by lazy { contactsAdapter.contactSelected } 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 } override val attachmentDeletedIntent: Subject 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 = 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 = 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 { 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) } }