Skip to content
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

Remotecontrols/add errors #944

Merged
merged 5 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Attention: don't forget to add the flag for F-Droid before release
- [Feature] Better user-ux when configuring remote control
- [Feature] Navigate to previous setup item on remote controls
- [Feature] Add flipper action dialogs into remote control and move it into bottombar
- [Feature] Add error display into remote controls screens
- [Feature] Add new icons for remote-controls
- [Refactor] Load RemoteControls from flipper, emulating animation
- [Refactor] Update to Kotlin 2.0
Expand Down
29 changes: 29 additions & 0 deletions components/remote-controls/api-backend-flipper/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id("flipper.multiplatform")
id("flipper.multiplatform-dependencies")
id("kotlinx-serialization")
id("flipper.anvil")
}

android.namespace = "com.flipperdevices.remotecontrols.api.backend.flipper"

androidDependencies {
implementation(libs.kotlin.coroutines)
implementation(libs.kotlin.serialization.json)
implementation(projects.components.core.di)
implementation(projects.components.remoteControls.coreModel)
implementation(projects.components.remoteControls.apiBackend)
implementation(projects.components.faphub.target.api)
implementation(projects.components.faphub.errors.api)
implementation(projects.components.bridge.service.api)
implementation(projects.components.bridge.api)
implementation(projects.components.bridge.rpcinfo.api)
implementation(projects.components.bridge.rpc.api)

implementation(libs.dagger)
implementation(libs.square.anvil.annotations)
implementation(libs.ktor.client)
implementation(libs.ktor.serialization)
implementation(libs.ktor.logging)
implementation(libs.ktor.negotiation)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.flipperdevices.ifrmvp.api.infrared

/**
* This api will also check for [FapHubError]
*/
interface FlipperInfraredBackendApi : InfraredBackendApi
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.flipperdevices.ifrmvp.api.infrared.internal

import com.flipperdevices.bridge.api.manager.ktx.state.ConnectionState
import com.flipperdevices.bridge.rpc.api.model.exceptions.NoSdCardException
import com.flipperdevices.bridge.rpcinfo.api.FlipperStorageInformationApi
import com.flipperdevices.bridge.rpcinfo.model.FlipperInformationStatus
import com.flipperdevices.bridge.rpcinfo.model.StorageStats
import com.flipperdevices.bridge.service.api.provider.FlipperServiceProvider
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.faphub.errors.api.throwable.FirmwareNotSupported
import com.flipperdevices.faphub.errors.api.throwable.FlipperNotConnected
import com.flipperdevices.faphub.target.api.FlipperTargetProviderApi
import com.flipperdevices.faphub.target.model.FlipperTarget
import com.flipperdevices.ifrmvp.api.infrared.FlipperInfraredBackendApi
import com.flipperdevices.ifrmvp.api.infrared.InfraredBackendApi
import com.flipperdevices.ifrmvp.backend.model.BrandsResponse
import com.flipperdevices.ifrmvp.backend.model.CategoriesResponse
import com.flipperdevices.ifrmvp.backend.model.IfrFileContentResponse
import com.flipperdevices.ifrmvp.backend.model.InfraredsResponse
import com.flipperdevices.ifrmvp.backend.model.PagesLayoutBackendModel
import com.flipperdevices.ifrmvp.backend.model.SignalRequestModel
import com.flipperdevices.ifrmvp.backend.model.SignalResponseModel
import com.squareup.anvil.annotations.ContributesBinding
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import javax.inject.Inject

@ContributesBinding(AppGraph::class, FlipperInfraredBackendApi::class)
class FlipperInfraredBackendApiImpl @Inject constructor(
private val api: InfraredBackendApi,
private val flipperTargetProviderApi: FlipperTargetProviderApi,
private val flipperServiceProvider: FlipperServiceProvider,
private val flipperStorageInformationApi: FlipperStorageInformationApi,
) : FlipperInfraredBackendApi {
private suspend fun isSdCardPresent(): Boolean {
val stats = flipperStorageInformationApi.getStorageInformationFlow()
.map { fStorageInformation -> fStorageInformation.externalStorageStatus }
.filterIsInstance<FlipperInformationStatus.Ready<StorageStats?>>()
.map { fStatusInformation -> fStatusInformation.data }
.filterNotNull()
.first()
return stats is StorageStats.Loaded
}

private suspend fun isDeviceConnected(): Boolean {
return flipperServiceProvider.getServiceApi()
.connectionInformationApi
.getConnectionStateFlow()
.first() is ConnectionState.Ready
}

@Suppress("ThrowsCount", "RethrowCaughtException")
private suspend fun <T> wrapRequest(block: suspend () -> T): T {
return try {
when (flipperTargetProviderApi.getFlipperTarget().value) {
FlipperTarget.NotConnected -> throw FlipperNotConnected()
FlipperTarget.Unsupported -> throw FirmwareNotSupported()
else -> Unit
}
if (!isDeviceConnected()) {
throw FlipperNotConnected()
}
if (!isSdCardPresent()) {
throw NoSdCardException()
}
block.invoke()
} catch (e: Throwable) {
throw e
}
}

override suspend fun getCategories(): CategoriesResponse {
return wrapRequest { api.getCategories() }
}

override suspend fun getManufacturers(categoryId: Long): BrandsResponse {
return wrapRequest { api.getManufacturers(categoryId) }
}

override suspend fun getSignal(request: SignalRequestModel): SignalResponseModel {
return wrapRequest { api.getSignal(request) }
}

override suspend fun getIfrFileContent(ifrFileId: Long): IfrFileContentResponse {
return wrapRequest { api.getIfrFileContent(ifrFileId) }
}

override suspend fun getUiFile(ifrFileId: Long): PagesLayoutBackendModel {
return wrapRequest { api.getUiFile(ifrFileId) }
}

override suspend fun getInfrareds(brandId: Long): InfraredsResponse {
return wrapRequest { api.getInfrareds(brandId) }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flipperdevices.ifrmvp.api.infrared
package com.flipperdevices.ifrmvp.api.infrared.internal

import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.ifrmvp.api.infrared.InfraredBackendApi
import com.flipperdevices.ifrmvp.api.infrared.model.InfraredHost
import com.flipperdevices.ifrmvp.backend.model.BrandsResponse
import com.flipperdevices.ifrmvp.backend.model.CategoriesResponse
Expand Down
2 changes: 2 additions & 0 deletions components/remote-controls/brands/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ dependencies {
implementation(projects.components.core.ui.res)

implementation(projects.components.remoteControls.apiBackend)
implementation(projects.components.remoteControls.apiBackendFlipper)
implementation(projects.components.remoteControls.coreModel)
implementation(projects.components.remoteControls.coreUi)
implementation(projects.components.remoteControls.brands.api)
implementation(projects.components.faphub.errors.api)

implementation(projects.components.rootscreen.api)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flipperdevices.remotecontrols.impl.brands.composable

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
Expand All @@ -10,7 +11,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.flipperdevices.ifrmvp.core.ui.layout.shared.ErrorComposable
import com.flipperdevices.faphub.errors.api.FapErrorSize
import com.flipperdevices.faphub.errors.api.FapHubComposableErrorsRenderer
import com.flipperdevices.ifrmvp.core.ui.layout.shared.SharedTopBar
import com.flipperdevices.remotecontrols.impl.brands.composable.composable.BrandsLoadedContent
import com.flipperdevices.remotecontrols.impl.brands.composable.composable.BrandsLoadingComposable
Expand All @@ -20,6 +22,7 @@ import com.flipperdevices.remotecontrols.brands.impl.R as BrandsR
@Composable
fun BrandsScreen(
brandsDecomposeComponent: BrandsDecomposeComponent,
errorsRenderer: FapHubComposableErrorsRenderer,
modifier: Modifier = Modifier
) {
val coroutineScope = rememberCoroutineScope()
Expand All @@ -38,8 +41,13 @@ fun BrandsScreen(
) { scaffoldPaddings ->
Crossfade(targetState = model) { model ->
when (model) {
BrandsDecomposeComponent.Model.Error -> {
ErrorComposable(onReload = brandsDecomposeComponent::tryLoad)
is BrandsDecomposeComponent.Model.Error -> {
errorsRenderer.ComposableThrowableError(
throwable = model.throwable,
onRetry = brandsDecomposeComponent::tryLoad,
fapErrorSize = FapErrorSize.FULLSCREEN,
modifier = Modifier.fillMaxSize()
)
}

is BrandsDecomposeComponent.Model.Loaded -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package com.flipperdevices.remotecontrols.impl.brands.presentation.data

import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.core.ktx.jre.FlipperDispatchers
import com.flipperdevices.ifrmvp.api.infrared.InfraredBackendApi
import com.flipperdevices.ifrmvp.api.infrared.FlipperInfraredBackendApi
import com.flipperdevices.ifrmvp.backend.model.BrandModel
import com.squareup.anvil.annotations.ContributesBinding
import kotlinx.coroutines.withContext
import javax.inject.Inject

@ContributesBinding(AppGraph::class, BrandsRepository::class)
class BackendBrandsRepository @Inject constructor(
private val infraredBackendApi: InfraredBackendApi,
private val infraredBackendApi: FlipperInfraredBackendApi,
) : BrandsRepository {
override suspend fun fetchBrands(
categoryId: Long
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flipperdevices.remotecontrols.impl.brands.presentation.decompose

import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.faphub.errors.api.throwable.FapHubError
import com.flipperdevices.ifrmvp.backend.model.BrandModel
import com.flipperdevices.remotecontrols.impl.brands.presentation.util.charSection
import com.flipperdevices.ui.decompose.DecomposeOnBackParameter
Expand All @@ -27,7 +28,7 @@ interface BrandsDecomposeComponent {

sealed interface Model {
data object Loading : Model
data object Error : Model
data class Error(val throwable: FapHubError) : Model
class Loaded(
val brands: ImmutableList<BrandModel>,
val query: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class BrandsDecomposeComponentImpl @AssistedInject constructor(
}

is BrandsListViewModel.State.Error -> {
BrandsDecomposeComponent.Model.Error
BrandsDecomposeComponent.Model.Error(pagingState.throwable)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import androidx.compose.runtime.Composable
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.childContext
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.faphub.errors.api.FapHubComposableErrorsRenderer
import com.flipperdevices.remotecontrols.api.BrandsScreenDecomposeComponent
import com.flipperdevices.remotecontrols.impl.brands.composable.BrandsScreen
import com.flipperdevices.remotecontrols.impl.brands.presentation.decompose.BrandsDecomposeComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import me.gulya.anvil.assisted.ContributesAssistedFactory

@Suppress("LongParameterList")
@ContributesAssistedFactory(AppGraph::class, BrandsScreenDecomposeComponent.Factory::class)
class BrandsScreenDecomposeComponentImpl @AssistedInject constructor(
@Assisted componentContext: ComponentContext,
Expand All @@ -19,6 +21,7 @@ class BrandsScreenDecomposeComponentImpl @AssistedInject constructor(
@Assisted onBrandClick: (brandId: Long, brandName: String) -> Unit,
@Assisted onBrandLongClick: (brandId: Long) -> Unit,
brandsDecomposeComponentFactory: BrandsDecomposeComponent.Factory,
private val errorsRenderer: FapHubComposableErrorsRenderer
) : BrandsScreenDecomposeComponent(componentContext) {
private val brandsComponent = brandsDecomposeComponentFactory.createBrandsComponent(
componentContext = childContext("BrandsComponent"),
Expand All @@ -30,6 +33,9 @@ class BrandsScreenDecomposeComponentImpl @AssistedInject constructor(

@Composable
override fun Render() {
BrandsScreen(brandsDecomposeComponent = brandsComponent)
BrandsScreen(
brandsDecomposeComponent = brandsComponent,
errorsRenderer = errorsRenderer
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.flipperdevices.remotecontrols.impl.brands.presentation.viewmodel
import com.flipperdevices.core.log.LogTagProvider
import com.flipperdevices.core.log.error
import com.flipperdevices.core.ui.lifecycle.DecomposeViewModel
import com.flipperdevices.faphub.errors.api.throwable.FapHubError
import com.flipperdevices.faphub.errors.api.throwable.toFapHubError
import com.flipperdevices.ifrmvp.backend.model.BrandModel
import com.flipperdevices.remotecontrols.impl.brands.presentation.data.BrandsRepository
import dagger.assisted.Assisted
Expand All @@ -27,7 +29,9 @@ class BrandsListViewModel @AssistedInject constructor(
_state.update { State.Loading }
brandsRepository.fetchBrands(categoryId)
.onSuccess { _state.emit(State.Loaded(it.toImmutableList())) }
.onFailure { _state.emit(State.Error) }
.onFailure {
_state.emit(State.Error(it.toFapHubError()))
}
.onFailure { throwable -> error(throwable) { "#tryLoad could not load brands" } }
}

Expand All @@ -38,7 +42,7 @@ class BrandsListViewModel @AssistedInject constructor(
sealed interface State {
data object Loading : State
data class Loaded(val brands: ImmutableList<BrandModel>) : State
data object Error : State
data class Error(val throwable: FapHubError) : State
}

@AssistedFactory
Expand Down
2 changes: 2 additions & 0 deletions components/remote-controls/categories/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ dependencies {
implementation(projects.components.core.ui.decompose)
implementation(projects.components.core.ui.ktx)
implementation(projects.components.core.ui.res)
implementation(projects.components.faphub.errors.api)

implementation(projects.components.remoteControls.apiBackend)
implementation(projects.components.remoteControls.apiBackendFlipper)
implementation(projects.components.remoteControls.coreModel)
implementation(projects.components.remoteControls.coreUi)
implementation(projects.components.remoteControls.categories.api)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flipperdevices.remotecontrols.impl.categories.composable

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
Expand All @@ -9,7 +10,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.flipperdevices.core.ui.theme.LocalPalletV2
import com.flipperdevices.ifrmvp.core.ui.layout.shared.ErrorComposable
import com.flipperdevices.faphub.errors.api.FapErrorSize
import com.flipperdevices.faphub.errors.api.FapHubComposableErrorsRenderer
import com.flipperdevices.ifrmvp.core.ui.layout.shared.SharedTopBar
import com.flipperdevices.remotecontrols.impl.categories.composable.components.DeviceCategoriesLoadedContent
import com.flipperdevices.remotecontrols.impl.categories.composable.components.DeviceCategoriesLoadingContent
Expand All @@ -19,6 +21,7 @@ import com.flipperdevices.remotecontrols.categories.impl.R as CategoriesR
@Composable
internal fun DeviceCategoriesScreen(
deviceCategoriesComponent: DeviceCategoriesComponent,
errorsRenderer: FapHubComposableErrorsRenderer,
modifier: Modifier = Modifier
) {
val model by deviceCategoriesComponent.model.collectAsState()
Expand All @@ -35,8 +38,13 @@ internal fun DeviceCategoriesScreen(
) { scaffoldPaddings ->
Crossfade(model) { model ->
when (model) {
DeviceCategoriesComponent.Model.Error -> {
ErrorComposable(onReload = deviceCategoriesComponent::tryLoad)
is DeviceCategoriesComponent.Model.Error -> {
errorsRenderer.ComposableThrowableError(
throwable = model.throwable,
onRetry = deviceCategoriesComponent::tryLoad,
fapErrorSize = FapErrorSize.FULLSCREEN,
modifier = Modifier.fillMaxSize()
)
}

is DeviceCategoriesComponent.Model.Loaded -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package com.flipperdevices.remotecontrols.impl.categories.presentation.data

import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.core.ktx.jre.FlipperDispatchers
import com.flipperdevices.ifrmvp.api.infrared.InfraredBackendApi
import com.flipperdevices.ifrmvp.api.infrared.FlipperInfraredBackendApi
import com.flipperdevices.ifrmvp.backend.model.DeviceCategory
import com.squareup.anvil.annotations.ContributesBinding
import kotlinx.coroutines.withContext
import javax.inject.Inject

@ContributesBinding(AppGraph::class, DeviceCategoriesRepository::class)
class BackendDeviceCategoriesRepository @Inject constructor(
private val infraredBackendApi: InfraredBackendApi,
private val infraredBackendApi: FlipperInfraredBackendApi,
) : DeviceCategoriesRepository {

override suspend fun fetchCategories(): Result<List<DeviceCategory>> = runCatching {
Expand Down
Loading
Loading