-
-
Notifications
You must be signed in to change notification settings - Fork 964
Migrate TemplateWidgetConfigureActivity to Compose #6444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
424be49
9edff4e
990fc94
d8bd871
8325a6b
bbce8d6
7a611cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,244 +1,97 @@ | ||||||||||||
| package io.homeassistant.companion.android.widgets.template | ||||||||||||
|
|
||||||||||||
| import android.appwidget.AppWidgetManager | ||||||||||||
| import android.os.Build | ||||||||||||
| import android.os.Bundle | ||||||||||||
| import android.view.View | ||||||||||||
| import android.widget.AdapterView | ||||||||||||
| import android.widget.ArrayAdapter | ||||||||||||
| import android.widget.Spinner | ||||||||||||
| import androidx.core.content.ContextCompat | ||||||||||||
| import androidx.core.graphics.toColorInt | ||||||||||||
| import androidx.core.text.HtmlCompat | ||||||||||||
| import androidx.core.view.isVisible | ||||||||||||
| import androidx.core.widget.doAfterTextChanged | ||||||||||||
| import androidx.activity.compose.setContent | ||||||||||||
| import androidx.activity.viewModels | ||||||||||||
| import androidx.lifecycle.lifecycleScope | ||||||||||||
| import dagger.hilt.android.AndroidEntryPoint | ||||||||||||
| import io.homeassistant.companion.android.BaseActivity | ||||||||||||
| import io.homeassistant.companion.android.common.R as commonR | ||||||||||||
| import io.homeassistant.companion.android.database.widget.TemplateWidgetDao | ||||||||||||
| import io.homeassistant.companion.android.database.widget.TemplateWidgetEntity | ||||||||||||
| import io.homeassistant.companion.android.database.widget.WidgetBackgroundType | ||||||||||||
| import io.homeassistant.companion.android.databinding.WidgetTemplateConfigureBinding | ||||||||||||
| import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel | ||||||||||||
| import io.homeassistant.companion.android.util.applySafeDrawingInsets | ||||||||||||
| import io.homeassistant.companion.android.util.compose.HomeAssistantAppTheme | ||||||||||||
| import io.homeassistant.companion.android.util.getHexForColor | ||||||||||||
| import io.homeassistant.companion.android.widgets.BaseWidgetConfigureActivity | ||||||||||||
| import io.homeassistant.companion.android.widgets.common.WidgetUtils | ||||||||||||
| import kotlinx.coroutines.Dispatchers | ||||||||||||
| import kotlinx.coroutines.CancellationException | ||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||
| import kotlinx.coroutines.withContext | ||||||||||||
| import kotlinx.serialization.SerializationException | ||||||||||||
| import timber.log.Timber | ||||||||||||
|
|
||||||||||||
| // TODO Migrate to compose https://github.com/home-assistant/android/issues/6304 | ||||||||||||
| @AndroidEntryPoint | ||||||||||||
| class TemplateWidgetConfigureActivity : BaseWidgetConfigureActivity<TemplateWidgetEntity, TemplateWidgetDao>() { | ||||||||||||
| private lateinit var binding: WidgetTemplateConfigureBinding | ||||||||||||
| class TemplateWidgetConfigureActivity : BaseActivity() { | ||||||||||||
|
|
||||||||||||
| override val serverSelect: View | ||||||||||||
| get() = binding.serverSelect | ||||||||||||
| private val viewModel: TemplateWidgetConfigureViewModel by viewModels() | ||||||||||||
|
|
||||||||||||
| override val serverSelectList: Spinner | ||||||||||||
| get() = binding.serverSelectList | ||||||||||||
|
|
||||||||||||
| private var requestLauncherSetup = false | ||||||||||||
| private val supportedTextColors: List<String> | ||||||||||||
| get() = listOf( | ||||||||||||
| application.getHexForColor(commonR.color.colorWidgetButtonLabelBlack), | ||||||||||||
| application.getHexForColor(android.R.color.white), | ||||||||||||
| ) | ||||||||||||
|
Comment on lines
+22
to
+26
|
||||||||||||
|
|
||||||||||||
| public override fun onCreate(savedInstanceState: Bundle?) { | ||||||||||||
| override fun onCreate(savedInstanceState: Bundle?) { | ||||||||||||
| super.onCreate(savedInstanceState) | ||||||||||||
|
|
||||||||||||
| // Set the result to CANCELED. This will cause the widget host to cancel | ||||||||||||
| // out of the widget placement if the user presses the back button. | ||||||||||||
| setResult(RESULT_CANCELED) | ||||||||||||
|
|
||||||||||||
| binding = WidgetTemplateConfigureBinding.inflate(layoutInflater) | ||||||||||||
| setContentView(binding.root) | ||||||||||||
| binding.root.applySafeDrawingInsets() | ||||||||||||
|
|
||||||||||||
| // Find the widget id from the intent. | ||||||||||||
| val intent = intent | ||||||||||||
| val extras = intent.extras | ||||||||||||
| if (extras != null) { | ||||||||||||
| appWidgetId = extras.getInt( | ||||||||||||
| AppWidgetManager.EXTRA_APPWIDGET_ID, | ||||||||||||
| AppWidgetManager.INVALID_APPWIDGET_ID, | ||||||||||||
| ) | ||||||||||||
| requestLauncherSetup = extras.getBoolean( | ||||||||||||
| ManageWidgetsViewModel.CONFIGURE_REQUEST_LAUNCHER, | ||||||||||||
| false, | ||||||||||||
| ) | ||||||||||||
| } | ||||||||||||
| val widgetId = extras?.getInt( | ||||||||||||
| AppWidgetManager.EXTRA_APPWIDGET_ID, | ||||||||||||
| AppWidgetManager.INVALID_APPWIDGET_ID, | ||||||||||||
| ) ?: AppWidgetManager.INVALID_APPWIDGET_ID | ||||||||||||
|
|
||||||||||||
| val requestLauncherSetup = extras?.getBoolean( | ||||||||||||
| ManageWidgetsViewModel.CONFIGURE_REQUEST_LAUNCHER, | ||||||||||||
| false, | ||||||||||||
| ) ?: false | ||||||||||||
|
|
||||||||||||
| // If this activity was started with an intent without an app widget ID, finish with an error. | ||||||||||||
| if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID && !requestLauncherSetup) { | ||||||||||||
| if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID && !requestLauncherSetup) { | ||||||||||||
| finish() | ||||||||||||
| return | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| val backgroundTypeValues = WidgetUtils.getBackgroundOptionList(this) | ||||||||||||
| binding.backgroundType.adapter = | ||||||||||||
| ArrayAdapter( | ||||||||||||
| this, | ||||||||||||
| android.R.layout.simple_spinner_dropdown_item, | ||||||||||||
| backgroundTypeValues, | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| lifecycleScope.launch { | ||||||||||||
| val templateWidget = dao.get(appWidgetId) | ||||||||||||
|
|
||||||||||||
| if (templateWidget?.serverId != null) { | ||||||||||||
| // Set server ID early for template rendering | ||||||||||||
| selectedServerId = templateWidget.serverId | ||||||||||||
| } | ||||||||||||
| setupServerSelect(templateWidget?.serverId) | ||||||||||||
| viewModel.onSetup(widgetId = widgetId, supportedTextColors = supportedTextColors) | ||||||||||||
|
|
||||||||||||
| if (templateWidget != null) { | ||||||||||||
| binding.templateText.setText(templateWidget.template) | ||||||||||||
| binding.textSize.setText(templateWidget.textSize.toInt().toString()) | ||||||||||||
| binding.addButton.setText(commonR.string.update_widget) | ||||||||||||
| if (templateWidget.template.isNotEmpty()) { | ||||||||||||
| renderTemplateText(templateWidget.template) | ||||||||||||
| } else { | ||||||||||||
| binding.renderedTemplate.text = getString(commonR.string.empty_template) | ||||||||||||
| binding.addButton.isEnabled = false | ||||||||||||
| } | ||||||||||||
| binding.backgroundType.setSelection( | ||||||||||||
| WidgetUtils.getSelectedBackgroundOption( | ||||||||||||
| this@TemplateWidgetConfigureActivity, | ||||||||||||
| templateWidget.backgroundType, | ||||||||||||
| backgroundTypeValues, | ||||||||||||
| ), | ||||||||||||
| setContent { | ||||||||||||
| HomeAssistantAppTheme { | ||||||||||||
| TemplateWidgetConfigureScreen( | ||||||||||||
| viewModel = viewModel, | ||||||||||||
| onActionClick = { onActionClick(requestLauncherSetup) }, | ||||||||||||
| ) | ||||||||||||
| binding.textColor.isVisible = templateWidget.backgroundType == WidgetBackgroundType.TRANSPARENT | ||||||||||||
| binding.textColorWhite.isChecked = | ||||||||||||
| templateWidget.textColor?.let { | ||||||||||||
| it.toColorInt() == ContextCompat.getColor( | ||||||||||||
| this@TemplateWidgetConfigureActivity, | ||||||||||||
| android.R.color.white, | ||||||||||||
| ) | ||||||||||||
| } | ||||||||||||
| ?: true | ||||||||||||
| binding.textColorBlack.isChecked = | ||||||||||||
| templateWidget.textColor?.let { | ||||||||||||
| it.toColorInt() == | ||||||||||||
| ContextCompat.getColor( | ||||||||||||
| this@TemplateWidgetConfigureActivity, | ||||||||||||
| commonR.color.colorWidgetButtonLabelBlack, | ||||||||||||
| ) | ||||||||||||
| } | ||||||||||||
| ?: false | ||||||||||||
| } else { | ||||||||||||
| binding.backgroundType.setSelection(0) | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| binding.templateText.doAfterTextChanged { renderTemplateText() } | ||||||||||||
|
|
||||||||||||
| binding.backgroundType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { | ||||||||||||
| override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { | ||||||||||||
| binding.textColor.isVisible = | ||||||||||||
| parent?.adapter?.getItem(position) == getString(commonR.string.widget_background_type_transparent) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| override fun onNothingSelected(parent: AdapterView<*>?) { | ||||||||||||
| binding.textColor.visibility = View.GONE | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| binding.addButton.setOnClickListener { | ||||||||||||
| private fun onActionClick(requestLauncherSetup: Boolean) { | ||||||||||||
| lifecycleScope.launch { | ||||||||||||
| if (requestLauncherSetup) { | ||||||||||||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||||||||||
| lifecycleScope.launch { | ||||||||||||
| requestWidgetCreation() | ||||||||||||
| } | ||||||||||||
| } else { | ||||||||||||
| showAddWidgetError() // this shouldn't be possible | ||||||||||||
| } | ||||||||||||
| requestPinWidget() | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not checking the version anymore? If you do so it would remove the warning in the ViewModel by properly annotating the function |
||||||||||||
| } else { | ||||||||||||
| lifecycleScope.launch { | ||||||||||||
| updateWidget() | ||||||||||||
| } | ||||||||||||
| onUpdateWidget() | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| override fun onServerSelected(serverId: Int) = renderTemplateText() | ||||||||||||
|
|
||||||||||||
| override suspend fun getPendingDaoEntity(): TemplateWidgetEntity { | ||||||||||||
| val serverId = checkNotNull(selectedServerId) { "Selected server ID is null" } | ||||||||||||
| val template = checkNotNull(binding.templateText.text?.toString()) { "Template text is null" } | ||||||||||||
|
|
||||||||||||
| return TemplateWidgetEntity( | ||||||||||||
| id = appWidgetId, | ||||||||||||
| serverId = serverId, | ||||||||||||
| template = template, | ||||||||||||
| textSize = binding.textSize.text.toString().toFloat(), | ||||||||||||
| backgroundType = when (binding.backgroundType.selectedItem as String?) { | ||||||||||||
| getString(commonR.string.widget_background_type_dynamiccolor) -> WidgetBackgroundType.DYNAMICCOLOR | ||||||||||||
| getString(commonR.string.widget_background_type_transparent) -> WidgetBackgroundType.TRANSPARENT | ||||||||||||
| else -> WidgetBackgroundType.DAYNIGHT | ||||||||||||
| }, | ||||||||||||
| textColor = if (binding.backgroundType.selectedItem as String? == | ||||||||||||
| getString(commonR.string.widget_background_type_transparent) | ||||||||||||
| ) { | ||||||||||||
| getHexForColor( | ||||||||||||
| if (binding.textColorWhite.isChecked) { | ||||||||||||
| android.R.color.white | ||||||||||||
| } else { | ||||||||||||
| commonR.color.colorWidgetButtonLabelBlack | ||||||||||||
| }, | ||||||||||||
| ) | ||||||||||||
| } else { | ||||||||||||
| null | ||||||||||||
| }, | ||||||||||||
| lastUpdate = dao.get(appWidgetId)?.lastUpdate ?: "Loading", | ||||||||||||
| ) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| override val widgetClass: Class<*> = TemplateWidget::class.java | ||||||||||||
|
|
||||||||||||
| private fun renderTemplateText() { | ||||||||||||
| val editableText = binding.templateText.text ?: return | ||||||||||||
| if (editableText.isNotEmpty()) { | ||||||||||||
| renderTemplateText(editableText.toString()) | ||||||||||||
| } else { | ||||||||||||
| binding.renderedTemplate.text = getString(commonR.string.empty_template) | ||||||||||||
| binding.addButton.isEnabled = false | ||||||||||||
| private suspend fun requestPinWidget() { | ||||||||||||
| try { | ||||||||||||
| viewModel.requestWidgetCreation(this@TemplateWidgetConfigureActivity) | ||||||||||||
| finish() | ||||||||||||
|
Comment on lines
+63
to
+76
|
||||||||||||
| } catch (e: IllegalStateException) { | ||||||||||||
| if (e is CancellationException) throw e | ||||||||||||
|
Comment on lines
+77
to
+78
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This is way we use in the repo, please adjust in the activity and the viewModel |
||||||||||||
| showAddWidgetError() | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private fun renderTemplateText(template: String) { | ||||||||||||
| val serverId = selectedServerId | ||||||||||||
| if (serverId == null) { | ||||||||||||
| Timber.w("Not rendering template because server is not set") | ||||||||||||
| return | ||||||||||||
| private suspend fun onUpdateWidget() { | ||||||||||||
| try { | ||||||||||||
| viewModel.updateWidgetConfiguration(this@TemplateWidgetConfigureActivity) | ||||||||||||
| setResult(RESULT_OK) | ||||||||||||
| finish() | ||||||||||||
| } catch (e: IllegalStateException) { | ||||||||||||
|
Comment on lines
+83
to
+88
|
||||||||||||
| if (e is CancellationException) throw e | ||||||||||||
| showAddWidgetError() | ||||||||||||
| } | ||||||||||||
|
Comment on lines
+73
to
91
|
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| lifecycleScope.launch { | ||||||||||||
| var templateText: String? | ||||||||||||
| var enabled: Boolean | ||||||||||||
| withContext(Dispatchers.IO) { | ||||||||||||
| try { | ||||||||||||
| templateText = | ||||||||||||
| serverManager.integrationRepository(serverId) | ||||||||||||
| .renderTemplate(template, mapOf()) | ||||||||||||
| .toString() | ||||||||||||
| enabled = true | ||||||||||||
| } catch (e: Exception) { | ||||||||||||
| Timber.e(e, "Exception while rendering template") | ||||||||||||
| // SerializationException suggests that template is not a String (= error) | ||||||||||||
| templateText = getString( | ||||||||||||
| if (e.cause is SerializationException) { | ||||||||||||
| commonR.string.template_error | ||||||||||||
| } else { | ||||||||||||
| commonR.string.template_render_error | ||||||||||||
| }, | ||||||||||||
|
Comment on lines
-229
to
-234
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is a good idea to keep this logic that was giving a bit more details to the user making the template on what is the current error. |
||||||||||||
| ) | ||||||||||||
| enabled = false | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| binding.renderedTemplate.text = | ||||||||||||
| templateText?.let { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY) } | ||||||||||||
| binding.addButton.isEnabled = enabled && isValidServerId() | ||||||||||||
| } | ||||||||||||
| private fun showAddWidgetError() { | ||||||||||||
| viewModel.showError(commonR.string.widget_creation_error) | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error is always the same and always trigger from the activity I propose you to not use the viewModel and directly send this to the screen, it would simplify things. |
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
Uh oh!
There was an error while loading. Please reload this page.