mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-05-17 11:19:01 +00:00
Implement input chooser activity
This commit is contained in:
parent
a90925f81a
commit
46c8b9efa1
16 changed files with 229 additions and 46 deletions
|
|
@ -26,17 +26,18 @@ class DeviceCommunicationService : WearableListenerService() {
|
|||
super.onMessageReceived(event)
|
||||
when (event.path) {
|
||||
DeviceCommunication.REQUEST_AUTH -> processAuthRequest(event)
|
||||
DeviceCommunication.SHOW_REGISTER -> openActivity(LoginActivity::class.java)
|
||||
DeviceCommunication.SHOW_LOGIN -> openActivity(LoginActivity::class.java)
|
||||
DeviceCommunication.SHOW_RYA -> openActivity(MainActivity::class.java)
|
||||
DeviceCommunication.SHOW_REGISTER -> openActivity(event, LoginActivity::class.java)
|
||||
DeviceCommunication.SHOW_LOGIN -> openActivity(event, LoginActivity::class.java)
|
||||
DeviceCommunication.SHOW_RYA -> openActivity(event, MainActivity::class.java)
|
||||
DeviceCommunication.SHOW_TASK_EDIT -> openTaskForm(event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openActivity(activityClass: Class<*>) {
|
||||
private fun openActivity(event: MessageEvent, activityClass: Class<*>) {
|
||||
val intent = Intent(this, activityClass)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(intent)
|
||||
messageClient.sendMessage(event.sourceNodeId, "/action_completed", null)
|
||||
}
|
||||
|
||||
private fun openTaskForm(event: MessageEvent) {
|
||||
|
|
@ -44,7 +45,8 @@ class DeviceCommunicationService : WearableListenerService() {
|
|||
val startIntent = Intent(this, TaskFormActivity::class.java).apply {
|
||||
putExtra(TaskFormActivity.TASK_ID_KEY, taskID)
|
||||
}
|
||||
startActivity(startIntent)
|
||||
startActivity(startIntent)
|
||||
messageClient.sendMessage(event.sourceNodeId, "/action_completed", null)
|
||||
}
|
||||
|
||||
private fun processAuthRequest(event: MessageEvent) {
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
NAME=4.0
|
||||
CODE=4190
|
||||
CODE=4200
|
||||
|
|
@ -52,6 +52,7 @@
|
|||
<activity android:name="com.habitrpg.wearos.habitica.ui.activities.LevelupActivity" />
|
||||
|
||||
<activity android:name="com.habitrpg.wearos.habitica.ui.activities.ConfirmationActivity" />
|
||||
<activity android:name="com.habitrpg.wearos.habitica.ui.activities.InputActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -58,7 +58,9 @@ class MainApplication : Application() {
|
|||
}
|
||||
|
||||
private fun logLaunch() {
|
||||
Firebase.analytics.logEvent("wear_launched", null)
|
||||
if (!BuildConfig.DEBUG) {
|
||||
Firebase.analytics.logEvent("wear_launched", null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFirebase() {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ class ApiClient @Inject constructor(
|
|||
val responseBuilder = response.newBuilder()
|
||||
responseBuilder.header("was-cached", (response.networkResponse == null).toString())
|
||||
if (request.method == "GET") {
|
||||
if (response.code == 504) {
|
||||
if (response.code == 504 || response.request.header("x-api-user") != hostConfig.userID) {
|
||||
// Cache miss. Network might be down, but retry call without cache to be sure.
|
||||
chain.proceed(request.newBuilder()
|
||||
.header("Cache-Control", "no-cache")
|
||||
|
|
|
|||
|
|
@ -87,9 +87,12 @@ abstract class BaseActivity<B: ViewBinding, VM: BaseViewModel> : ComponentActivi
|
|||
}
|
||||
}
|
||||
|
||||
internal fun openRemoteActivity(url: String) {
|
||||
internal fun openRemoteActivity(url: String, keepActive: Boolean = false) {
|
||||
sendMessage("open_activity", url, null)
|
||||
startActivity(Intent(this, ContinuePhoneActivity::class.java))
|
||||
startActivity(Intent(this, ContinuePhoneActivity::class.java)
|
||||
.apply {
|
||||
putExtra("keep_active", keepActive)
|
||||
})
|
||||
}
|
||||
|
||||
internal fun sendMessage(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import kotlin.time.toDuration
|
|||
class ContinuePhoneActivity : BaseActivity<ActivityContinuePhoneBinding, ContinuePhoneViewModel>() {
|
||||
override val viewModel: ContinuePhoneViewModel by viewModels()
|
||||
|
||||
private var secondsToShow = 2
|
||||
private var secondsToShow = 5
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
binding = ActivityContinuePhoneBinding.inflate(layoutInflater)
|
||||
|
|
@ -29,16 +29,23 @@ class ContinuePhoneActivity : BaseActivity<ActivityContinuePhoneBinding, Continu
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
messageClient.addListener(viewModel)
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
delay(secondsToShow.toDuration(DurationUnit.SECONDS))
|
||||
viewModel.onActionCompleted = {
|
||||
finish()
|
||||
}
|
||||
|
||||
if (!viewModel.keepActive) {
|
||||
lifecycleScope.launch {
|
||||
delay(secondsToShow.toDuration(DurationUnit.SECONDS))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
val alphaAnimation = AlphaAnimation(0f, 1f)
|
||||
val translateAnimation = TranslateAnimation((-20f).dpToPx(this), 0f, 0f, 0f)
|
||||
val set = AnimationSet(true)
|
||||
|
|
@ -51,4 +58,9 @@ class ContinuePhoneActivity : BaseActivity<ActivityContinuePhoneBinding, Continu
|
|||
set.addAnimation(translateAnimation)
|
||||
binding.iconView.startAnimation(set)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
messageClient.removeListener(viewModel)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.habitrpg.wearos.habitica.ui.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.speech.RecognizerIntent
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.postDelayed
|
||||
import com.habitrpg.android.habitica.databinding.ActivityInputBinding
|
||||
import com.habitrpg.wearos.habitica.ui.viewmodels.InputViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class InputActivity: BaseActivity<ActivityInputBinding, InputViewModel>() {
|
||||
override val viewModel: InputViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
binding = ActivityInputBinding.inflate(layoutInflater)
|
||||
super.onCreate(savedInstanceState)
|
||||
binding.titleView.text = viewModel.title
|
||||
|
||||
binding.speechInput.setOnClickListener {
|
||||
showSpeechInput()
|
||||
}
|
||||
binding.keyboardInput.setOnClickListener {
|
||||
showKeyboard()
|
||||
}
|
||||
|
||||
binding.editText.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
if (binding.editText.text?.isNotEmpty() == true) {
|
||||
returnInput(binding.editText.text.toString())
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun returnInput(inputString: String?) {
|
||||
val data = Intent()
|
||||
data.putExtra("input", inputString)
|
||||
setResult(Activity.RESULT_OK, data)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun showKeyboard() {
|
||||
binding.editText.hint = binding.titleView.text
|
||||
binding.editText.requestFocus()
|
||||
binding.editText.postDelayed(100) {
|
||||
val imm: InputMethodManager =
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(binding.editText, InputMethodManager.SHOW_FORCED)
|
||||
}
|
||||
}
|
||||
|
||||
private val speechInputResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
val spokenText: String? = it.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.firstOrNull()
|
||||
returnInput(spokenText)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSpeechInput() {
|
||||
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
|
||||
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
|
||||
putExtra(RecognizerIntent.EXTRA_PROMPT, binding.titleView.text)
|
||||
}
|
||||
speechInputResult.launch(intent)
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ class LoginActivity: BaseActivity<ActivityLoginBinding, LoginViewModel>() {
|
|||
}
|
||||
|
||||
private fun openRegisterOnPhone() {
|
||||
openRemoteActivity(DeviceCommunication.SHOW_REGISTER)
|
||||
openRemoteActivity(DeviceCommunication.SHOW_REGISTER, true)
|
||||
}
|
||||
|
||||
private fun openLoginOnPhone() {
|
||||
|
|
|
|||
|
|
@ -30,8 +30,13 @@ class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() {
|
|||
startLoginActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
messageClient.addListener(viewModel)
|
||||
|
||||
if (!viewModel.hasAuthentication) {
|
||||
sendMessage("provide_auth", "/request/auth", null) {
|
||||
if (it) {
|
||||
showAccountLoader(true)
|
||||
|
|
@ -40,6 +45,13 @@ class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() {
|
|||
startLoginActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onStop() {
|
||||
messageClient.removeListener(viewModel)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
private fun startMainActivity() {
|
||||
|
|
@ -64,9 +76,4 @@ class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() {
|
|||
binding.textView.isVisible = show
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
messageClient.removeListener(viewModel)
|
||||
super.onPause()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,14 @@
|
|||
package com.habitrpg.wearos.habitica.ui.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding
|
||||
|
|
@ -45,23 +42,16 @@ class TaskFormActivity : BaseActivity<ActivityTaskFormBinding, TaskFormViewModel
|
|||
binding = ActivityTaskFormBinding.inflate(layoutInflater)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding.editText.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
if (binding.editText.text?.isNotEmpty() == true) {
|
||||
binding.editTaskWrapper.isVisible = false
|
||||
binding.taskConfirmationWrapper.isVisible = true
|
||||
binding.confirmationText.text = binding.editText.text
|
||||
binding.editText.clearFocus()
|
||||
}
|
||||
}
|
||||
false
|
||||
binding.editText.setOnClickListener {
|
||||
it.clearFocus()
|
||||
requestInput()
|
||||
}
|
||||
|
||||
binding.editButton.setOnClickListener {
|
||||
binding.editTaskWrapper.isVisible = true
|
||||
binding.taskConfirmationWrapper.isVisible = false
|
||||
if (intent.extras?.containsKey("task_type") == true) {
|
||||
binding.editText.requestFocus()
|
||||
showKeyboard()
|
||||
requestInput()
|
||||
}
|
||||
}
|
||||
binding.todoButton.setOnClickListener { taskType = TaskType.TODO }
|
||||
|
|
@ -91,26 +81,28 @@ class TaskFormActivity : BaseActivity<ActivityTaskFormBinding, TaskFormViewModel
|
|||
binding.taskTypeHeader.isVisible = false
|
||||
binding.taskTypeWrapper.isVisible = false
|
||||
binding.header.textView.text = getString(R.string.create_task, taskType?.value)
|
||||
binding.editText.requestFocus()
|
||||
requestInput()
|
||||
} else {
|
||||
taskType = TaskType.TODO
|
||||
binding.header.textView.text = getString(R.string.new_task)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (binding.editText.hasFocus()) {
|
||||
showKeyboard()
|
||||
private val inputResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
val input = it.data?.getStringExtra("input")
|
||||
if (input?.isNotBlank() == true) {
|
||||
binding.editTaskWrapper.isVisible = false
|
||||
binding.taskConfirmationWrapper.isVisible = true
|
||||
binding.confirmationText.text = input
|
||||
binding.editText.setText(input)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showKeyboard() {
|
||||
binding.editText.postDelayed(100) {
|
||||
val imm: InputMethodManager =
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(binding.editText, InputMethodManager.SHOW_FORCED)
|
||||
private fun requestInput() {
|
||||
val intent = Intent(this, InputActivity::class.java).apply {
|
||||
putExtra("title", getString(R.string.task_title_hint))
|
||||
}
|
||||
inputResult.launch(intent)
|
||||
}
|
||||
|
||||
private fun updateTaskTypeButton(button: TextView, thisType: TaskType) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.habitrpg.wearos.habitica.ui.viewmodels
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.google.android.gms.wearable.MessageClient
|
||||
import com.google.android.gms.wearable.MessageEvent
|
||||
import com.habitrpg.wearos.habitica.data.repositories.TaskRepository
|
||||
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
|
||||
import com.habitrpg.wearos.habitica.managers.LoadingManager
|
||||
|
|
@ -9,10 +12,18 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class ContinuePhoneViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
userRepository: UserRepository,
|
||||
taskRepository: TaskRepository,
|
||||
exceptionBuilder: ExceptionHandlerBuilder,
|
||||
loadingManager: LoadingManager
|
||||
) : BaseViewModel(userRepository, taskRepository, exceptionBuilder, loadingManager) {
|
||||
) : BaseViewModel(userRepository, taskRepository, exceptionBuilder, loadingManager), MessageClient.OnMessageReceivedListener {
|
||||
val keepActive = savedStateHandle.get<Boolean>("keep_active") ?: false
|
||||
var onActionCompleted: (() -> Unit)? = null
|
||||
|
||||
override fun onMessageReceived(event: MessageEvent) {
|
||||
when (event.path) {
|
||||
"/action_completed" -> onActionCompleted?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.habitrpg.wearos.habitica.ui.viewmodels
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.habitrpg.wearos.habitica.data.repositories.TaskRepository
|
||||
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
|
||||
import com.habitrpg.wearos.habitica.managers.LoadingManager
|
||||
import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class InputViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
userRepository: UserRepository,
|
||||
taskRepository: TaskRepository,
|
||||
exceptionBuilder: ExceptionHandlerBuilder,
|
||||
loadingManager: LoadingManager
|
||||
) : BaseViewModel(userRepository, taskRepository, exceptionBuilder, loadingManager) {
|
||||
val title = savedStateHandle.get<String>("title") ?: ""
|
||||
}
|
||||
5
wearos/src/main/res/drawable/ic_microphone.xml
Normal file
5
wearos/src/main/res/drawable/ic_microphone.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
|
||||
</vector>
|
||||
53
wearos/src/main/res/layout/activity_input.xml
Normal file
53
wearos/src/main/res/layout/activity_input.xml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
<TextView
|
||||
android:id="@+id/title_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="@dimen/spacing_large"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/picker_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
android:id="@+id/speech_input"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:src="@drawable/ic_microphone"
|
||||
android:tint="@color/watch_black"
|
||||
android:scaleType="center"
|
||||
android:background="@drawable/circle"
|
||||
android:backgroundTint="@color/watch_purple_100"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
<ImageView
|
||||
android:id="@+id/keyboard_input"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:src="@drawable/ic_keyboard"
|
||||
android:tint="@color/watch_black"
|
||||
android:scaleType="center"
|
||||
android:background="@drawable/circle"
|
||||
android:backgroundTint="@color/watch_purple_100"/>
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
<EditText
|
||||
android:id="@+id/edit_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
|
@ -65,6 +65,7 @@
|
|||
android:paddingHorizontal="18dp"
|
||||
android:hint="@string/task_title_hint"
|
||||
android:background="@drawable/row_background_outline"
|
||||
android:focusable="false"
|
||||
android:inputType="textCapSentences" />
|
||||
<TextView
|
||||
android:id="@+id/task_type_header"
|
||||
|
|
|
|||
Loading…
Reference in a new issue