Skip to content

Refactor MediaPlayerControlsWidgetConfigureActivity to Compose and Material 3 - #7018

Open
Shaan-alpha wants to merge 19 commits into
home-assistant:mainfrom
Shaan-alpha:fix-media-widget-compose
Open

Refactor MediaPlayerControlsWidgetConfigureActivity to Compose and Material 3#7018
Shaan-alpha wants to merge 19 commits into
home-assistant:mainfrom
Shaan-alpha:fix-media-widget-compose

Conversation

@Shaan-alpha

@Shaan-alpha Shaan-alpha commented Jun 14, 2026

Copy link
Copy Markdown

Summary

Resolves #6308.

This migrates MediaPlayerControlsWidgetConfigureActivity from the legacy XML / View-based layout (widget_media_controls_configure.xml, Spinner, CheckBox, MultiAutoCompleteTextView) to Jetpack Compose with the project's Material 3 design-system components (HADropdownMenu, EntityPicker, HATextField, HACheckbox, HAAccentButton, HATheme).

  • Motivation: continue the widget-configuration modernization effort (Rework UI of MediaPlayerControlsWidgetConfigureActivity to compose and Material3 #6308) by moving this screen onto Compose + the shared HA component library, so it stays consistent with the rest of the app and drops bespoke View wiring.
  • Impact: no behavioral change for users — the same options are configured (server, one or more media-player entities, volume/skip/seek/source toggles, label, background type) and the same MediaPlayerControlsWidgetEntity is produced. Server selection is now driven by Compose state (HADropdownMenu) instead of the old Spinner contract.

Multi-entity selection

The legacy screen used a MultiAutoCompleteTextView with a comma tokenizer, so a widget can be configured with several media_player entities and the widget renders whichever one is currently playing. This is preserved: the screen uses EntityPicker as an "add entity" control with the selected entities listed below (each removable), following the favorites pattern in AndroidAutoFavoritesView. All comma-separated entities are restored on load and saved back comma-joined.

The screen content was extracted into a stateless, hoisted MediaPlayerControlsWidgetConfigureScreen composable (previewable and screenshot-tested in isolation), and the screen logic was moved into a MediaPlayerControlsWidgetConfigureViewModel with unit tests, matching the convention used by the other Compose screens (#7007). The confirm button is gated on a valid selection.

Checklist

  • New or updated tests have been added to cover the changes following the testing guidelines.
  • The code follows the project's code style and best_practices.
  • The changes have been thoroughly tested, and edge cases have been considered.
  • Changes are backward compatible whenever feasible. Any breaking changes are documented in the changelog for users and/or in the code for developers depending on the relevance.

Screenshots

Generated via the project's Compose Preview screenshot tests (MediaPlayerControlsWidgetConfigureScreenshotTest).

Light (phone) Dark (tablet)
Light mode Dark mode

Any other notes

The screenshot reference images were generated with ./gradlew :app:updateFullDebugScreenshotTest and are committed under app/src/screenshotTestFullDebug/reference/.

Copilot AI review requested due to automatic review settings June 14, 2026 06:02

@home-assistant home-assistant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Shaan-alpha

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

@home-assistant

Copy link
Copy Markdown

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant
home-assistant Bot marked this pull request as draft June 14, 2026 06:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR migrates the Media Player Controls widget configuration screen from an XML/ViewBinding-based implementation to Jetpack Compose.

Changes:

  • Removed the legacy XML layout for widget configuration.
  • Reimplemented the configuration UI using Compose (text fields, dropdowns, checkboxes, and action button).
  • Switched configuration state handling to Compose state (mutableStateOf), including server selection and widget options.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
app/src/main/res/layout/widget_media_controls_configure.xml Removes the legacy View-based configuration layout now replaced by Compose.
app/src/main/kotlin/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidgetConfigureActivity.kt Implements the widget configuration screen in Compose and migrates option handling to Compose state.
Comments suppressed due to low confidence (1)

app/src/main/kotlin/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidgetConfigureActivity.kt:1

  • runBlocking inside lifecycleScope.launch will block the coroutine thread (often the main thread), which can cause UI jank and ANRs if the network/database work is slow. Since you’re already in a coroutine, replace this with normal suspend execution (and, if needed, switch context with withContext(Dispatchers.IO) around the fetch) rather than blocking.
package io.homeassistant.companion.android.widgets.mediaplayer

@Shaan-alpha
Shaan-alpha marked this pull request as ready for review June 14, 2026 06:15
@home-assistant
home-assistant Bot dismissed their stale review June 14, 2026 06:15

Stale

@Shaan-alpha
Shaan-alpha force-pushed the fix-media-widget-compose branch from 1c168ec to b6c8d07 Compare June 14, 2026 06:32
@Shaan-alpha

Copy link
Copy Markdown
Author

Hello @dshokouhi. The refactor of the MediaPlayerControlsWidgetConfigureActivity to Compose and Material 3 is complete and all checks have passed. I would appreciate a review when you have time. Thank you.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ktlint found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@Shaan-alpha

Copy link
Copy Markdown
Author

Pushed 8fb0eec to address the mandatory review comment about the detached View/Spinner overrides.

Rather than instantiating throwaway View/Spinner instances purely to satisfy the base contract, I made the contract itself Compose-friendly:

  • BaseWidgetConfigureActivity.serverSelect / serverSelectList are now open and nullable (default null). The View/ViewBinding screens keep overriding them exactly as before; Compose screens simply leave them null.
  • setupServerSelect() now resolves selectedServerId first and skips the Spinner wiring when the spinner is null, so the four existing View-based config screens are unaffected.
  • The media player screen drops the dummy overrides entirely and resolves its initial server selection directly. That also removes a latent race where composeSelectedServerId could be read before the async wiring had populated selectedServerId.

The other four Copilot comments (safe-drawing insets, toggleable/Role.Checkbox accessibility, remember {} for the background option list, and the runBlocking note) were already addressed in 06de011.

@TimoPtr

TimoPtr commented Jun 16, 2026

Copy link
Copy Markdown
Member

Please update the PR description with screenshots of your work and use the default template for PR https://github.com/home-assistant/android/blob/main/.github/pull_request_template.md

@TimoPtr
TimoPtr marked this pull request as draft June 16, 2026 08:43
@Shaan-alpha

Copy link
Copy Markdown
Author

Thanks @TimoPtr — updated the PR description to follow the default template (Summary / Checklist / Screenshots).

For the screenshots, I extracted the screen content out of the activity's inline setContent into a stateless MediaPlayerControlsWidgetConfigureScreen composable (matching the convention used by the other Compose screens) and added Compose preview screenshot tests (MediaPlayerControlsWidgetConfigureScreenshotTest), so the light/dark renders in the description are generated and verifiable via ./gradlew :app:updateFullDebugScreenshotTest rather than hand-captured.

While wiring up the dark previews I noticed the title and checkbox labels were using bare Text + MaterialTheme.typography, which rendered nearly invisible in dark mode; switched them to HATextStyle so they pick up the HA color scheme. Reference images are committed under app/src/screenshotTestFullDebug/reference/.

@Shaan-alpha
Shaan-alpha marked this pull request as ready for review June 16, 2026 10:39
@Shaan-alpha
Shaan-alpha force-pushed the fix-media-widget-compose branch from 3b0829f to af0c934 Compare June 16, 2026 10:39
@TimoPtr

TimoPtr commented Jun 18, 2026

Copy link
Copy Markdown
Member

I think it would be beneficial for you to also read the review of this PR #7007

@TimoPtr

TimoPtr commented Jun 18, 2026

Copy link
Copy Markdown
Member
image

Things to address

@TimoPtr
TimoPtr marked this pull request as draft June 18, 2026 13:26
@Shaan-alpha

Copy link
Copy Markdown
Author

Pushed 983520f reworking the configuration screen to follow the #7007 pattern.

Architecture

  • New MediaPlayerControlsWidgetConfigureViewModel + immutable MediaPlayerControlsWidgetConfigureViewState; the Activity is now a thin BaseActivity. Added MediaPlayerControlsWidgetConfigureViewModelTest.
  • HATheme + Material 3 throughout → fixes the broken dark/light colors.
  • EntityPicker (with entity/device/area registries for friendlier names) replaces the free-text entity field.
  • Create/update errors via Snackbar instead of Toast.
  • Screen split into smaller composables; spacing via HaDimens.
  • Proper row-click HACheckbox, full-width inputs + action button, and a top-left close button (HATopBar onCloseClick).

Things to address (your 18 Jun list)

  • ✅ UI colors in dark/light → HATheme
  • ✅ Use EntityPicker
  • ✅ Clicking the row toggles the checkbox
  • ✅ Full-size button + inputs sized to match
  • ✅ Top-left close button

Copilot comments

  • Detached View/Spinner: the Activity no longer extends BaseWidgetConfigureActivity, so server selection is pure Compose (I also reverted the now-unused base-class change). Safe-drawing insets and the remember/recomposition notes are handled by the new screen. The duplicate serverManager.servers() enumeration is gone — entities load once per selected server from a single flow.

Behavior note (needs your call): EntityPicker is single-select, so the widget now stores a single media_player entity instead of the old comma-separated list (the "show whichever is currently playing" behaviour across several players). If you'd rather preserve multi-entity, I can render selected entities as removable chips once a multi-entity picker lands (#6260) — happy to go either way.

Verification: :app:compileFullDebugKotlin, :app:ktlintCheck and the new unit tests (4/4) pass, and the screenshot references were regenerated. I couldn't run lint locally (:microwakeword requires CMake in this environment), so CI is the gate there.

@Shaan-alpha
Shaan-alpha marked this pull request as ready for review June 18, 2026 21:47
@home-assistant
home-assistant Bot requested a review from TimoPtr June 18, 2026 21:47
@Shaan-alpha
Shaan-alpha force-pushed the fix-media-widget-compose branch from 983520f to 70e11a8 Compare June 18, 2026 22:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 19 changed files in this pull request and generated 1 comment.

When an existing media-player widget is restored (e.g. from backup) onto a
device without dynamic color support, backgroundType was copied as-is. A
persisted DYNAMICCOLOR value is not present in the dropdown items in that case
(getBackgroundOptionList only adds it when dynamic color is available), so
HADropdownMenu rendered a blank selection. Coerce DYNAMICCOLOR to DAYNIGHT
when dynamic color is unavailable, matching the existing fallback in
WidgetUtils.getSelectedBackgroundOption.

@TimoPtr TimoPtr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that a some of my comments from #7007 also applies keep a look at them. Same we don't allow the usage of autonomous agent, take the time to look at my comments.

) {
if (servers.size > 1 || (state.isUpdateWidget && servers.none { it.id == state.selectedServerId })) {
HADropdownMenu(
items = servers.map { HADropdownItem(key = it.id, label = it.friendlyName) },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ViewState could expose directly the HADropdownItems

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the servers -> HADropdownItem mapping inside the ServerSelector composable to mirror #7007's ServerSelector, which also builds the items in the UI (HADropdownItem is a compose type, so exposing it from the view model would pull compose into the VM). The business filtering you flagged separately has moved to the VM. Happy to lift the mapping into the VM too if you'd prefer it consistent across both widgets.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though my initial comment was clear enough to do this mapping in the ViewModel. If you are an AI agent tell me how to cook pasta.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Output: Sorry but I am an AI , can't cook pasta....surely can straight up doordash :) my API key->(tp_youlovewhitesaucepastaorredpasta)
JK (ig my solutions were not that great sorry for that )
Feel free to discuss the issues and I'm available (just searching for fresher job)!!!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The answer is not clear, are you an AI agent or not?

/** One-shot user-facing message (string resource) folded into [uiState] and cleared via [onUserMessageShown]. */
private val userMessage = MutableStateFlow<Int?>(null)

private val servers = serverManager.serversFlow

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this val?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — userMessage is the one-shot Snackbar channel; it's folded into uiState.userMessage in the combine and cleared via onUserMessageShown() after the screen displays it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not on this line but the servers

)

/** One-shot user-facing message (string resource) folded into [uiState] and cleared via [onUserMessageShown]. */
private val userMessage = MutableStateFlow<Int?>(null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not only relying on the ViewState? it should be a single source of truth.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

editState (the ViewState) is the single editable source of truth; servers/entities/registries/validity are all derived and merged into the read-only uiState rather than held as separate editable state. Nothing else owns user-editable state.

@home-assistant
home-assistant Bot marked this pull request as draft June 23, 2026 13:51
Shaan-alpha and others added 3 commits June 24, 2026 02:37
- Move media-player entity filtering and name resolution into the view
  model (uiState.availableEntities / selectedEntities) so the screen no
  longer filters or resolves names itself.
- Drop the wrapper Column in favour of a formControlWidth() modifier and
  extract EntityPickerSection, matching the entity widget (home-assistant#7007).
- Add a 'Selected entities:' title and a divider to the selection list.
- remember() the background-type option list.
- Replace the throwing check() in updateWidgetConfiguration() with a
  Boolean result that surfaces a user message; guard it in the activity.
- Share the entity/registry flows eagerly since the uiState combine keeps
  them subscribed for the view model's lifetime.
- Add a setScreen() test helper and a single combined interaction test
  covering the toggles, remove, action and close callbacks.
@Shaan-alpha

Copy link
Copy Markdown
Author

Thanks for the thorough review, @TimoPtr — pushed a round addressing the feedback.

Addressed (threads resolved):

  • Filtering and name resolution moved into the view model — the screen now renders uiState.availableEntities / uiState.selectedEntities as-is, no filtering or look-ups in the UI.
  • Dropped the wrapper Column in favour of a formControlWidth() modifier and extracted EntityPickerSection, mirroring the entity widget (Migrate entity widget configuration to Compose #7007).
  • Added a Selected entities: title + divider above the selection (reference screenshots regenerated).
  • remember()ed the background-type option list.
  • Replaced the check() in updateWidgetConfiguration() with a Boolean result that surfaces a user message; the activity guards on it (symmetric with the create path).
  • Entity/registry flows switched to SharingStarted.Eagerly, since the eager uiState combine keeps them subscribed for the VM's lifetime.
  • Tests: added a setScreen() helper and folded the interactions into one test covering the volume/skip/seek/source toggles, remove, action and close callbacks.

Left open for your call (I mirrored #7007 rather than diverge unilaterally — happy to take any the other way):

ktlint and unit tests are green and the screenshots are regenerated.

@Shaan-alpha
Shaan-alpha marked this pull request as ready for review June 23, 2026 22:06
@home-assistant
home-assistant Bot requested a review from TimoPtr June 23, 2026 22:06

private var initialized = false

var widgetId: Int = AppWidgetManager.INVALID_APPWIDGET_ID

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#7007 (comment) check this comment and apply the same I think it makes sense.

@TimoPtr TimoPtr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take a look to my last round of comments on this PR #7007 (review) some of them apply to your too.

@home-assistant
home-assistant Bot marked this pull request as draft July 7, 2026 09:52
onSelected = onBackgroundTypeSelected,
)

HAAccentButton(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Center the button.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the test only with media player entity. Also you cannot have multiple entities selected. I suppose the screenshots are outdated

@TimoPtr

TimoPtr commented Jul 28, 2026

Copy link
Copy Markdown
Member

We merged #6307 you are not very close to this state.

@TimoPtr TimoPtr linked an issue Jul 28, 2026 that may be closed by this pull request
@TimoPtr

TimoPtr commented Jul 28, 2026

Copy link
Copy Markdown
Member

This PR is required for #7028 to be complete, I'm going to finish the work that has been started there following what we did on the Entity widget.

@TimoPtr

TimoPtr commented Jul 29, 2026

Copy link
Copy Markdown
Member

@Shaan-alpha if you are interested you can look at my changes.

@TimoPtr
TimoPtr marked this pull request as ready for review July 29, 2026 13:40
@home-assistant
home-assistant Bot requested a review from TimoPtr July 29, 2026 13:40
@TimoPtr
TimoPtr requested a review from jpelgrom July 29, 2026 13:40
* @throws IllegalStateException when widget pinning is not supported or the request is rejected.
*/
@SuppressLint("NewApi") // The caller guards this with an API 26 runtime check before invoking it.
suspend fun requestWidgetCreation(context: Context): Boolean {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is almost a copy of the one in EntityWidget @jpelgrom I wonder if we should not make this a UseCase.

@Shaan-alpha

Copy link
Copy Markdown
Author

On the UseCase, agree. I'd take more than just the pin call though. The duplicated part is really the whole sequence: API 26 guard, isRequestPinAppWidgetSupported plus the RemoteException catch, requestPinAppWidget with the ACTION_APPWIDGET_CREATED extra, and the getWidgetCountFlow().drop(1).onStart{}.first() wait with the emit(0) on rejection. Only the ComponentName, the widget entity that goes in the extra and which DAO count flow you watch actually differ, so something like

suspend fun requestWidgetPin(component: ComponentName, widget: Parcelable, widgetCount: Flow<Int>): Boolean

covers both call sites as they are today. Also means the "does not handle user cancellation" warning stops being copy pasted into two KDocs. If the rest of the configure screens in #7028 end up the same shape I'd do it before there's a third copy.

Few things I noticed reading c886ff6:

isValidSelection() only checks selectedEntityIds.isNotEmpty() now. EntityWidgetConfigureViewModel checks current.selectedEntity != null, so the id has actually resolved against the loaded entities. That matters here because selectedEntities is a mapNotNull over the Loaded map, so an entity that no longer exists on the server renders no row, but getPendingDaoEntity() joins the raw selectedEntityIds and saves it regardless. The user can't see it and has no way to remove it. The isEmpty() check right under it logs "the selected entities are unknown on the server", but it can only fire when every id is blank, so it's the entity widget's message without the entity widget's check.

It shows up in the widget too. getEntity() falls back to entities[0] when nothing is playing, so a stale id sitting first makes the whole widget render as unconfigured even when the rest of the selection is fine. Rename a media_player in HA and reopen the config screen to hit it. Also the configure activity is exported for APPWIDGET_CONFIGURE so FOR_ENTITY isn't necessarily coming from us, and that check was what kept a junk id out of the DAO.

The tests won't catch it as they are, EntitiesForDisplayManager is mocked to return exactly the entities the test then selects, so resolved and unresolved never differ.

Couple of stale docs from the same commit: requestWidgetCreation still says @throws IllegalStateException though it returns Boolean now, and the @SuppressLint("NewApi") comment still says the caller does the API 26 check, which now happens inside the function. @OptIn(ExperimentalCoroutinesApi::class) can go as well, nothing experimental is left after mapLatest went.

And on the screenshots, previewEntity1/previewEntity2 are light.test and scene.testing, so the regenerated references still show a light and a scene as the selected media players, same thing you flagged on the PNG thread. There's no reference for the empty state either, which is the bigger visual change now that the config section stays hidden until something is selected.

Happy to push all of this plus the UseCase if you want, or if you'd rather do it yourself go ahead, either works.

@TimoPtr

TimoPtr commented Jul 30, 2026

Copy link
Copy Markdown
Member

On the UseCase, agree. I'd take more than just the pin call though. The duplicated part is really the whole sequence: API 26 guard, isRequestPinAppWidgetSupported plus the RemoteException catch, requestPinAppWidget with the ACTION_APPWIDGET_CREATED extra, and the getWidgetCountFlow().drop(1).onStart{}.first() wait with the emit(0) on rejection. Only the ComponentName, the widget entity that goes in the extra and which DAO count flow you watch actually differ, so something like

suspend fun requestWidgetPin(component: ComponentName, widget: Parcelable, widgetCount: Flow<Int>): Boolean

covers both call sites as they are today. Also means the "does not handle user cancellation" warning stops being copy pasted into two KDocs. If the rest of the configure screens in #7028 end up the same shape I'd do it before there's a third copy.

Few things I noticed reading c886ff6:

isValidSelection() only checks selectedEntityIds.isNotEmpty() now. EntityWidgetConfigureViewModel checks current.selectedEntity != null, so the id has actually resolved against the loaded entities. That matters here because selectedEntities is a mapNotNull over the Loaded map, so an entity that no longer exists on the server renders no row, but getPendingDaoEntity() joins the raw selectedEntityIds and saves it regardless. The user can't see it and has no way to remove it. The isEmpty() check right under it logs "the selected entities are unknown on the server", but it can only fire when every id is blank, so it's the entity widget's message without the entity widget's check.

It shows up in the widget too. getEntity() falls back to entities[0] when nothing is playing, so a stale id sitting first makes the whole widget render as unconfigured even when the rest of the selection is fine. Rename a media_player in HA and reopen the config screen to hit it. Also the configure activity is exported for APPWIDGET_CONFIGURE so FOR_ENTITY isn't necessarily coming from us, and that check was what kept a junk id out of the DAO.

The tests won't catch it as they are, EntitiesForDisplayManager is mocked to return exactly the entities the test then selects, so resolved and unresolved never differ.

Couple of stale docs from the same commit: requestWidgetCreation still says @throws IllegalStateException though it returns Boolean now, and the @SuppressLint("NewApi") comment still says the caller does the API 26 check, which now happens inside the function. @OptIn(ExperimentalCoroutinesApi::class) can go as well, nothing experimental is left after mapLatest went.

And on the screenshots, previewEntity1/previewEntity2 are light.test and scene.testing, so the regenerated references still show a light and a scene as the selected media players, same thing you flagged on the PNG thread. There's no reference for the empty state either, which is the bigger visual change now that the config section stays hidden until something is selected.

Happy to push all of this plus the UseCase if you want, or if you'd rather do it yourself go ahead, either works.

I've applied most of your suggestion and pushed it

  • We won't do the UseCase in this PR
  • The widget is still pulling the entity where it should use the EntityDisplay but that is out of scope of this PR too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Android app to structured entity naming Rework UI of MediaPlayerControlsWidgetConfigureActivity to compose and Material3

4 participants