Skip to content

Conversation

@sockeye-d
Copy link

Adds a new appstore UI, allowing users to search for, download, and install apps from either the Rebble Web Services appstore API or Core Devices appstore API. Installed apps are then checked for updates with a background service and a notification is generated when an app is updatable.

This also upgrades material3 to an alpha version for floating action button menus, which may be able to be replaced if this is undesirable.

…ctionButtonMenu, change installation flow slightly to use said FAB
@github-actions
Copy link
Contributor

Lint report

Results

Suppressed Results

Nothing here.

Lint report

Results

  • [WARNING] [ModifierParameter] Optional Modifier parameter should have a default value of Modifier
  • [ERROR] [UnusedMaterial3ScaffoldPaddingParameter] Content padding parameter \_ is not used
  • [WARNING] [PluralsCandidate] Formatting %d followed by words \("apps"\): This should probably be a plural rather than a string
  • [WARNING] [UnusedResources] The resource R.drawable.ic\_appstore\_source\_disabled appears to be unused
    • <vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
      <path android:fillColor="@android:color/white" android:pathData="M446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514L446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514L446,514Q446,514 446,514Q446,514 446,514ZM552,419L552,419Q552,419 552,419Q552,419 552,419L552,419Q552,419 552,419Q552,419 552,419ZM446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514L446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514L446,514Q446,514 446,514Q446,514 446,514ZM552,419L552,419Q552,419 552,419Q552,419 552,419L552,419Q552,419 552,419Q552,419 552,419ZM446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514ZM552,419L552,419Q552,419 552,419Q552,419 552,419Q552,419 552,419Q552,419 552,419Q552,419 552,419Q552,419 552,419ZM791,904L56,169L112,112L848,848L791,904ZM480,840Q329,840 224.5,793.5Q120,747 120,680L120,280Q120,254 137.5,230.5Q155,207 187,187L439,439Q367,436 306,421Q245,406 200,381L200,501Q251,530 323,545Q395,560 480,560Q500,560 519,559.5Q538,559 557,557L627,627Q593,634 556,637Q519,640 480,640Q395,640 323,625Q251,610 200,581L200,680Q209,709 297.5,734.5Q386,760 480,760Q544,760 608.5,747Q673,734 715,715L773,773Q724,804 647.5,822Q571,840 480,840ZM830,717L760,647L760,581Q749,587 738,592Q727,597 715,602L654,541Q684,533 710.5,523.5Q737,514 760,501L760,381Q719,404 666,418Q613,432 550,437L474,361Q518,361 566,354Q614,347 655.5,335.5Q697,324 725.5,309.5Q754,295 760,281Q749,252 659.5,226Q570,200 480,200Q443,200 404.5,205Q366,210 331,218L265,152Q310,137 365,128.5Q420,120 480,120Q629,120 734.5,167Q840,214 840,280L840,680Q840,690 837.5,699Q835,708 830,717Z"/>
      </vector>
    • <vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
      <path android:fillColor="@android:color/white" android:pathData="M560,720L320,480L560,240L616,296L432,480L616,664L560,720Z"/>
      </vector>
    • <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
      android:viewportWidth="24"
      android:viewportHeight="24"
      android:width="24dp"
      android:height="24dp">
      <path
      android:pathData="M18.5 12A6.5 6.5 0 0 1 5.5 12A6.5 6.5 0 0 1 18.5 12Z"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M20.5 12A8.5 8.5 0 0 1 3.5 12A8.5 8.5 0 0 1 20.5 12Z"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M17.5 18.5V21.5h-2v-1.5"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M6.5 18.5V21.5h2v-1.5"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M6.5 5.5V2.5h2v1.5"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M17.5 5.5V2.5h-2v1.5"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      </vector>
    • <string name="appDiscourseLink">Discuss on Discourse</string>
    • <string name="save">Save</string>
    • <string name="installing">Installing</string>
    • <string name="unoffical_compatibility_info_header">Unofficially supported compatibility</string>

Suppressed Results

Nothing here.

@github-actions
Copy link
Contributor

Lint report

Results

Suppressed Results

Nothing here.

Lint report

Results

  • [WARNING] [ModifierParameter] Optional Modifier parameter should have a default value of Modifier
  • [ERROR] [UnusedMaterial3ScaffoldPaddingParameter] Content padding parameter \_ is not used
  • [WARNING] [PluralsCandidate] Formatting %d followed by words \("apps"\): This should probably be a plural rather than a string
  • [WARNING] [UnusedResources] The resource R.drawable.ic\_appstore\_source\_disabled appears to be unused
    • <vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
      <path android:fillColor="@android:color/white" android:pathData="M446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514L446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514L446,514Q446,514 446,514Q446,514 446,514ZM552,419L552,419Q552,419 552,419Q552,419 552,419L552,419Q552,419 552,419Q552,419 552,419ZM446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514L446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514L446,514Q446,514 446,514Q446,514 446,514ZM552,419L552,419Q552,419 552,419Q552,419 552,419L552,419Q552,419 552,419Q552,419 552,419ZM446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514Q446,514 446,514ZM552,419L552,419Q552,419 552,419Q552,419 552,419Q552,419 552,419Q552,419 552,419Q552,419 552,419Q552,419 552,419ZM791,904L56,169L112,112L848,848L791,904ZM480,840Q329,840 224.5,793.5Q120,747 120,680L120,280Q120,254 137.5,230.5Q155,207 187,187L439,439Q367,436 306,421Q245,406 200,381L200,501Q251,530 323,545Q395,560 480,560Q500,560 519,559.5Q538,559 557,557L627,627Q593,634 556,637Q519,640 480,640Q395,640 323,625Q251,610 200,581L200,680Q209,709 297.5,734.5Q386,760 480,760Q544,760 608.5,747Q673,734 715,715L773,773Q724,804 647.5,822Q571,840 480,840ZM830,717L760,647L760,581Q749,587 738,592Q727,597 715,602L654,541Q684,533 710.5,523.5Q737,514 760,501L760,381Q719,404 666,418Q613,432 550,437L474,361Q518,361 566,354Q614,347 655.5,335.5Q697,324 725.5,309.5Q754,295 760,281Q749,252 659.5,226Q570,200 480,200Q443,200 404.5,205Q366,210 331,218L265,152Q310,137 365,128.5Q420,120 480,120Q629,120 734.5,167Q840,214 840,280L840,680Q840,690 837.5,699Q835,708 830,717Z"/>
      </vector>
    • <vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
      <path android:fillColor="@android:color/white" android:pathData="M560,720L320,480L560,240L616,296L432,480L616,664L560,720Z"/>
      </vector>
    • <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
      android:viewportWidth="24"
      android:viewportHeight="24"
      android:width="24dp"
      android:height="24dp">
      <path
      android:pathData="M18.5 12A6.5 6.5 0 0 1 5.5 12A6.5 6.5 0 0 1 18.5 12Z"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M20.5 12A8.5 8.5 0 0 1 3.5 12A8.5 8.5 0 0 1 20.5 12Z"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M17.5 18.5V21.5h-2v-1.5"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M6.5 18.5V21.5h2v-1.5"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M6.5 5.5V2.5h2v1.5"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      <path
      android:pathData="M17.5 5.5V2.5h-2v1.5"
      android:strokeColor="#000000"
      android:strokeWidth="1" />
      </vector>
    • <string name="appDiscourseLink">Discuss on Discourse</string>
    • <string name="save">Save</string>
    • <string name="installing">Installing</string>
    • <string name="unoffical_compatibility_info_header">Unofficially supported compatibility</string>

Suppressed Results

Nothing here.

Copy link
Owner

@matejdro matejdro left a comment

Choose a reason for hiding this comment

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

Thanks for the PR!

It already looks pretty good. I have made several minor comments, mostly related to the architecture. Unfortunately, because amount of added code is large, there are also quite a few comments.

@Composable
private fun WatchappListScreenContent(
state: WatchappListState,
appStatuses: Outcome<Map<Uuid, AppStatus>>,
Copy link
Owner

Choose a reason for hiding this comment

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

Instead of both of these properties being a map, I think just adding this to the WatchappListState would be much cleaner. Instead of using a LockerWrapper, we could expose our own data class, with these two properties + a reference to the LockerWrapper.

Copy link
Author

Choose a reason for hiding this comment

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

b3cbd17 should fix this, instead of doing this I moved it all into a single flow that combines private flows instead of exposing them directly to the screen.

Copy link
Owner

@matejdro matejdro Jan 29, 2026

Choose a reason for hiding this comment

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

Hm, there is still appInstallSources. Ideally, all of this should be exposed directly in the state, without any maps.

import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier

@Suppress("ModifierNotUsedAtRoot") // ExposedDropdownMenuBox is weird
Copy link
Owner

Choose a reason for hiding this comment

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

Commenting suppressions 👍

import com.matejdro.micropebble.ui.R

@Composable
fun NoSourcesDisplay(
Copy link
Owner

Choose a reason for hiding this comment

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

This one is only needed in the appstore module. Is there a reason it's in the common module?

actionLogger.logAction { "AppstoreDetailsViewModel.onServiceRegistered()" }

resources.launchResourceControlTask(_appState) {
emit(Outcome.Progress())
Copy link
Owner

Choose a reason for hiding this comment

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

Nit: this is unnecessary, launchResourceControlTask will automatically emit progress at the start. Same for the functions below

private val actionLogger: ActionLogger,
private val api: ApiClient,
) : SingleScreenViewModel<AppstoreCollectionScreenKey>(resources.scope) {
val platform by lazy { key.platformFilter?.let { WatchType.fromCodename(it) } }
Copy link
Owner

Choose a reason for hiding this comment

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

Please do not return any data from non-flow values in the ViewModel.

All ViewModel -> UI data should move through Flows. Can this be added to the appState?

Copy link
Author

Choose a reason for hiding this comment

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

The value never changes though, there is no state to it since the platform filter just comes from the key.

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah, but the idea is that everything is returned from the ViewModel via a single flow variable. That way it's easier to reason on what data exact is retrieved.

@matejdro
Copy link
Owner

I have pushed CI improvement for the Datastore directly, as it is a pretty specific thing and it was easier than explaining (but you can check it out if you want, it ends up with pretty neat pattern, where Datastore is injected directly into constructor).

import kotlin.time.toJavaInstant
import com.matejdro.micropebble.sharedresources.R as sharedR

private val LocalDateTimeFormatter = compositionLocalOf { DateTimeFormatter.ISO_INSTANT }
Copy link
Owner

Choose a reason for hiding this comment

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants