diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt index 3b42535b6..2bdb9b05d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2017,2021 Moez Bhatti ,to268 * * This file is part of QKSMS. * @@ -30,7 +30,9 @@ import android.os.Build import android.os.Bundle import android.provider.ContactsContract import android.provider.MediaStore +import android.speech.tts.TextToSpeech import android.text.format.DateFormat +import android.util.Log import android.view.Menu import android.view.MenuItem import androidx.appcompat.app.AlertDialog @@ -66,12 +68,16 @@ import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.compose_activity.* +import kotlinx.android.synthetic.main.compose_activity.attachments +import kotlinx.android.synthetic.main.compose_activity.sim +import kotlinx.android.synthetic.main.compose_activity.simIndex +import kotlinx.android.synthetic.main.message_list_item_in.* import java.text.SimpleDateFormat import java.util.* import javax.inject.Inject import kotlin.collections.HashMap -class ComposeActivity : QkThemedActivity(), ComposeView { +class ComposeActivity : QkThemedActivity(), ComposeView, TextToSpeech.OnInitListener { companion object { private const val SelectContactRequestCode = 0 @@ -82,12 +88,18 @@ class ComposeActivity : QkThemedActivity(), ComposeView { private const val CameraDestinationKey = "camera_destination" } - @Inject lateinit var attachmentAdapter: AttachmentAdapter - @Inject lateinit var chipsAdapter: ChipsAdapter - @Inject lateinit var dateFormatter: DateFormatter - @Inject lateinit var messageAdapter: MessagesAdapter - @Inject lateinit var navigator: Navigator - @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + lateinit var attachmentAdapter: AttachmentAdapter + @Inject + lateinit var chipsAdapter: ChipsAdapter + @Inject + lateinit var dateFormatter: DateFormatter + @Inject + lateinit var messageAdapter: MessagesAdapter + @Inject + lateinit var navigator: Navigator + @Inject + lateinit var viewModelFactory: ViewModelProvider.Factory override val activityVisibleIntent: Subject = PublishSubject.create() override val chipsSelectedIntent: Subject> = PublishSubject.create() @@ -119,6 +131,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[ComposeViewModel::class.java] } private var cameraDestination: Uri? = null + private var tts: TextToSpeech? = null override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) @@ -160,6 +173,9 @@ class ComposeActivity : QkThemedActivity(), ComposeView { if (Build.VERSION.SDK_INT <= 22) { messageBackground.setBackgroundTint(resolveThemeColor(R.attr.bubbleColor)) } + + // Set tts info + tts = TextToSpeech(this, this) } override fun onStart() { @@ -206,6 +222,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { toolbar.menu.findItem(R.id.details)?.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.speech)?.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() @@ -251,6 +268,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .show() } + override fun speechText(text: String) { + tts!!.speak(text, TextToSpeech.QUEUE_FLUSH, null, "") + } + override fun requestDefaultSms() { navigator.showDefaultSmsDialog(this) } @@ -399,4 +420,27 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun onBackPressed() = backPressedIntent.onNext(Unit) + // Text to speech + override fun onInit(status: Int) { + if (status == TextToSpeech.SUCCESS) { + val result = tts!!.setLanguage(Locale.getDefault()) + + if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { + Log.e("TTS", "The default language is not supported !") + } + + } else { + Log.e("TTS", "Initialization failed !") + } + } + + override fun onDestroy() { + if (tts != null) { + tts!!.stop() + tts!!.shutdown() + } + + super.onDestroy() + } + } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index d8a78eec2..50fd3ea3b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -58,6 +58,7 @@ interface ComposeView : QkView { fun clearSelection() fun showDetails(details: String) + fun speechText(text: String) fun requestDefaultSms() fun requestStoragePermission() fun requestSmsPermission() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index ba601f2f7..9fac4b364 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2017,2021 Moez Bhatti ,to268 * * This file is part of QKSMS. * @@ -74,29 +74,29 @@ import javax.inject.Inject import javax.inject.Named class ComposeViewModel @Inject constructor( - @Named("query") private val query: String, - @Named("threadId") private val threadId: Long, - @Named("addresses") private val addresses: List, - @Named("text") private val sharedText: String, - @Named("attachments") private val sharedAttachments: Attachments, - private val contactRepo: ContactRepository, - private val context: Context, - private val activeConversationManager: ActiveConversationManager, - private val addScheduledMessage: AddScheduledMessage, - private val billingManager: BillingManager, - private val cancelMessage: CancelDelayedMessage, - private val conversationRepo: ConversationRepository, - private val deleteMessages: DeleteMessages, - private val markRead: MarkRead, - private val messageDetailsFormatter: MessageDetailsFormatter, - private val messageRepo: MessageRepository, - private val navigator: Navigator, - private val permissionManager: PermissionManager, - private val phoneNumberUtils: PhoneNumberUtils, - private val prefs: Preferences, - private val retrySending: RetrySending, - private val sendMessage: SendMessage, - private val subscriptionManager: SubscriptionManagerCompat + @Named("query") private val query: String, + @Named("threadId") private val threadId: Long, + @Named("addresses") private val addresses: List, + @Named("text") private val sharedText: String, + @Named("attachments") private val sharedAttachments: Attachments, + private val contactRepo: ContactRepository, + private val context: Context, + private val activeConversationManager: ActiveConversationManager, + private val addScheduledMessage: AddScheduledMessage, + private val billingManager: BillingManager, + private val cancelMessage: CancelDelayedMessage, + private val conversationRepo: ConversationRepository, + private val deleteMessages: DeleteMessages, + private val markRead: MarkRead, + private val messageDetailsFormatter: MessageDetailsFormatter, + private val messageRepo: MessageRepository, + private val navigator: Navigator, + private val permissionManager: PermissionManager, + private val phoneNumberUtils: PhoneNumberUtils, + private val prefs: Preferences, + private val retrySending: RetrySending, + private val sendMessage: SendMessage, + private val subscriptionManager: SubscriptionManagerCompat ) : QkViewModel(ComposeState( editingMode = threadId == 0L && addresses.isEmpty(), threadId = threadId, @@ -340,6 +340,16 @@ class ComposeViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe { view.showDetails(it) } + // Speech text + view.optionsItemIntent + .filter { it == R.id.speech } + .withLatestFrom(view.messagesSelectedIntent) { _, messages -> messages } + .mapNotNull { messages -> messages.firstOrNull().also { view.clearSelection() } } + .mapNotNull(messageRepo::getMessage) + .mapNotNull(Message::getText) + .autoDisposable(view.scope()) + .subscribe { view.speechText(it) } + // Delete the messages view.optionsItemIntent .filter { it == R.id.delete } diff --git a/presentation/src/main/res/drawable/ic_speech_black_24dp.xml b/presentation/src/main/res/drawable/ic_speech_black_24dp.xml new file mode 100644 index 000000000..cba949793 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_speech_black_24dp.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/presentation/src/main/res/menu/compose.xml b/presentation/src/main/res/menu/compose.xml index c39f25ac2..e98db8d52 100644 --- a/presentation/src/main/res/menu/compose.xml +++ b/presentation/src/main/res/menu/compose.xml @@ -1,6 +1,6 @@