Skip to content

Commit

Permalink
Migrate device screen to decompose (without deeplink) (#750)
Browse files Browse the repository at this point in the history
**Background**

Right now we have outdated navigation with google

**Changes**

Migrate options screen to decompose

**Test plan**

Try all screens in device tab
  • Loading branch information
LionZXY authored Dec 19, 2023
1 parent 992771b commit 5078c63
Show file tree
Hide file tree
Showing 48 changed files with 534 additions and 447 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [Refactor] Migrate file manager to decompose
- [Refactor] Migrate options screen to decompose
- [Refactor] Migrate device screen to decompose
- [Feature] New report bug

# 1.6.7
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.flipperdevices.deeplink.model

import android.os.Parcelable
import androidx.compose.runtime.Immutable
import com.flipperdevices.bridge.dao.api.model.FlipperFilePath
import com.flipperdevices.bridge.dao.api.model.FlipperKeyPath
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

@Serializable
@Immutable
sealed class Deeplink : Parcelable {
@Parcelize
@Serializable
Expand Down
3 changes: 3 additions & 0 deletions components/info/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ dependencies {
implementation(projects.components.deeplink.api)

implementation(projects.components.core.ui.navigation)
implementation(projects.components.core.ui.decompose)

implementation(libs.decompose)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.flipperdevices.info.api.screen

import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.ui.decompose.DecomposeComponent

interface DeviceScreenDecomposeComponent : DecomposeComponent {
fun interface Factory {
operator fun invoke(
componentContext: ComponentContext,
): DeviceScreenDecomposeComponent
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@ package com.flipperdevices.info.api.screen

import com.flipperdevices.core.ui.navigation.AggregateFeatureEntry
import com.flipperdevices.core.ui.navigation.FeatureScreenRootRoute
import com.flipperdevices.deeplink.model.Deeplink

interface InfoFeatureEntry : AggregateFeatureEntry {
override val ROUTE: FeatureScreenRootRoute
get() = FeatureScreenRootRoute.DEVICE_INFO

fun fullInfo(): String

fun getWebUpdateByDeeplink(deeplink: Deeplink): String
}
6 changes: 2 additions & 4 deletions components/info/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
implementation(projects.components.core.ui.res)
implementation(projects.components.core.ui.ktx)
implementation(projects.components.core.ui.navigation)
implementation(projects.components.core.ui.decompose)
implementation(projects.components.core.ui.lifecycle)
implementation(projects.components.core.ui.theme)
implementation(projects.components.core.ui.dialog)
Expand Down Expand Up @@ -46,15 +47,12 @@ dependencies {

implementation(libs.appcompat)

implementation(libs.tangle.viewmodel.compose)
implementation(libs.tangle.viewmodel.api)
anvil(libs.tangle.viewmodel.compiler)

implementation(libs.compose.ui)
implementation(libs.compose.material)
implementation(libs.compose.tooling)
implementation(libs.compose.foundation)
implementation(libs.compose.navigation)
implementation(libs.bundles.decompose)

implementation(libs.kotlin.serialization.json)
implementation(libs.kotlin.coroutines)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.flipperdevices.info.impl.api

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.value.Value
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.info.api.screen.DeviceScreenDecomposeComponent
import com.flipperdevices.info.impl.model.DeviceScreenNavigationConfig
import com.flipperdevices.settings.api.SettingsDecomposeComponent
import com.flipperdevices.ui.decompose.DecomposeComponent
import com.squareup.anvil.annotations.ContributesBinding
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject

class DeviceScreenDecomposeComponentImpl @AssistedInject constructor(
@Assisted componentContext: ComponentContext,
private val settingsFactory: SettingsDecomposeComponent.Factory,
private val updateFactory: UpdateScreenDecomposeComponent.Factory,
private val fullInfoDecomposeComponentFactory: FullInfoDecomposeComponent.Factory
) : DeviceScreenDecomposeComponent, ComponentContext by componentContext {
private val navigation = StackNavigation<DeviceScreenNavigationConfig>()

val stack: Value<ChildStack<DeviceScreenNavigationConfig, DecomposeComponent>> = childStack(
source = navigation,
serializer = DeviceScreenNavigationConfig.serializer(),
initialConfiguration = DeviceScreenNavigationConfig.Update(),
handleBackButton = true,
childFactory = ::child,
)

private fun child(
config: DeviceScreenNavigationConfig,
componentContext: ComponentContext
): DecomposeComponent = when (config) {
is DeviceScreenNavigationConfig.Update -> updateFactory(
componentContext,
config.deeplink,
navigation
)

DeviceScreenNavigationConfig.FullInfo -> fullInfoDecomposeComponentFactory(
componentContext = componentContext,
onBack = navigation::pop
)

DeviceScreenNavigationConfig.Options -> settingsFactory(componentContext)
}

@Composable
@Suppress("NonSkippableComposable")
override fun Render() {
val childStack by stack.subscribeAsState()

Children(
stack = childStack,
) {
it.instance.Render()
}
}

@AssistedFactory
@ContributesBinding(AppGraph::class, DeviceScreenDecomposeComponent.Factory::class)
interface Factory : DeviceScreenDecomposeComponent.Factory {
override operator fun invoke(
componentContext: ComponentContext,
): DeviceScreenDecomposeComponentImpl
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.flipperdevices.info.impl.api

import androidx.compose.runtime.Composable
import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.core.ui.ktx.viewModelWithFactory
import com.flipperdevices.info.impl.compose.screens.ComposableFullDeviceInfoScreen
import com.flipperdevices.info.impl.viewmodel.DeviceStatusViewModel
import com.flipperdevices.info.impl.viewmodel.deviceinfo.BasicInfoViewModel
import com.flipperdevices.info.impl.viewmodel.deviceinfo.FullInfoViewModel
import com.flipperdevices.info.impl.viewmodel.deviceinfo.ShareFullInfoFileViewModel
import com.flipperdevices.ui.decompose.DecomposeComponent
import com.flipperdevices.ui.decompose.DecomposeOnBackParameter
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import javax.inject.Provider

class FullInfoDecomposeComponent @AssistedInject constructor(
@Assisted componentContext: ComponentContext,
@Assisted private val onBack: DecomposeOnBackParameter,
private val shareFullInfoViewModelProvider: Provider<ShareFullInfoFileViewModel>,
private val basicInfoViewModelProvider: Provider<BasicInfoViewModel>,
private val fullInfoViewModelProvider: Provider<FullInfoViewModel>,
private val deviceStatusViewModelProvider: Provider<DeviceStatusViewModel>
) : DecomposeComponent, ComponentContext by componentContext {
@Composable
@Suppress("NonSkippableComposable")
override fun Render() {
ComposableFullDeviceInfoScreen(
onBack = onBack::invoke,
shareViewModel = viewModelWithFactory(key = null) {
shareFullInfoViewModelProvider.get()
},
basicInfoViewModel = viewModelWithFactory(key = null) {
basicInfoViewModelProvider.get()
},
fullInfoViewModel = viewModelWithFactory(key = null) {
fullInfoViewModelProvider.get()
},
deviceStatusViewModel = viewModelWithFactory(key = null) {
deviceStatusViewModelProvider.get()
}
)
}

@AssistedFactory
fun interface Factory {
operator fun invoke(
componentContext: ComponentContext,
onBack: DecomposeOnBackParameter
): FullInfoDecomposeComponent
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package com.flipperdevices.info.impl.api

import android.content.Intent
import androidx.core.net.toUri
import androidx.navigation.NavController
import com.flipperdevices.bottombar.api.BottomNavigationHandleDeeplink
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.deeplink.api.DeepLinkHandler
import com.flipperdevices.deeplink.api.DispatcherPriority
import com.flipperdevices.deeplink.model.Deeplink
import com.flipperdevices.info.api.screen.InfoFeatureEntry
import com.squareup.anvil.annotations.ContributesMultibinding
import javax.inject.Inject

@ContributesMultibinding(AppGraph::class, DeepLinkHandler::class)
class InfoDeeplinkHandler @Inject constructor(
private val infoFeatureEntry: InfoFeatureEntry,
private val bottomHandleDeeplink: BottomNavigationHandleDeeplink
) : DeepLinkHandler {
override fun isSupportLink(link: Deeplink): DispatcherPriority? {
Expand All @@ -27,7 +24,7 @@ class InfoDeeplinkHandler @Inject constructor(

override fun processLink(navController: NavController, link: Deeplink) {
val intent = Intent().apply {
data = infoFeatureEntry.getWebUpdateByDeeplink(link).toUri()
// data = infoFeatureEntry.getWebUpdateByDeeplink(link).toUri()
}
bottomHandleDeeplink.handleDeepLink(intent)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,80 +1,41 @@
package com.flipperdevices.info.impl.api

import android.net.Uri
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import androidx.navigation.navigation
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.core.ui.navigation.AggregateFeatureEntry
import com.flipperdevices.core.ui.navigation.LocalGlobalNavigationNavStack
import com.flipperdevices.deeplink.model.Deeplink
import com.flipperdevices.deeplink.model.DeeplinkConstants
import com.flipperdevices.deeplink.model.DeeplinkNavType
import com.flipperdevices.info.api.screen.DeviceScreenDecomposeComponent
import com.flipperdevices.info.api.screen.InfoFeatureEntry
import com.flipperdevices.info.impl.compose.screens.ComposableDeviceInfoScreen
import com.flipperdevices.info.impl.compose.screens.ComposableFullDeviceInfoScreen
import com.flipperdevices.settings.api.SettingsFeatureEntry
import com.flipperdevices.updater.api.UpdaterCardApi
import com.flipperdevices.updater.api.UpdaterFeatureEntry
import com.flipperdevices.ui.decompose.rememberComponentContext
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesMultibinding
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import javax.inject.Inject

private const val DEEPLINK_KEY = DeeplinkConstants.KEY
private const val DEEPLINK_SCHEME = DeeplinkConstants.SCHEMA
private const val DEEPLINK_WEB_UPDATER_URL = "${DEEPLINK_SCHEME}web_updater={$DEEPLINK_KEY}"

@ContributesBinding(AppGraph::class, InfoFeatureEntry::class)
@ContributesMultibinding(AppGraph::class, AggregateFeatureEntry::class)
class InfoFeatureEntryImpl @Inject constructor(
private val updaterCardApi: UpdaterCardApi,
private val settingFeatureEntry: SettingsFeatureEntry,
private val updaterFeatureEntry: UpdaterFeatureEntry
private val deviceScreenDecomposeComponentFactory: DeviceScreenDecomposeComponent.Factory
) : InfoFeatureEntry {

private val infoRoute = "@${ROUTE.name}/$DEEPLINK_KEY={$DEEPLINK_KEY}"

override fun fullInfo(): String = "@${ROUTE.name}full"
override fun getWebUpdateByDeeplink(deeplink: Deeplink): String {
val deeplinkStr = Uri.encode(Json.encodeToString(deeplink))
return "${DEEPLINK_SCHEME}web_updater=$deeplinkStr"
}

private val arguments = listOf(
navArgument(DeeplinkConstants.KEY) {
nullable = true
type = DeeplinkNavType()
}
)

private val deeplinkArguments = listOf(
navDeepLink { uriPattern = DEEPLINK_WEB_UPDATER_URL }
)

override fun NavGraphBuilder.navigation(navController: NavHostController) {
navigation(startDestination = infoRoute, route = ROUTE.name) {
composable(
route = infoRoute,
arguments = arguments,
deepLinks = deeplinkArguments
) {
val globalNavController = LocalGlobalNavigationNavStack.current
ComposableDeviceInfoScreen(
updaterCardApi,
onOpenFullDeviceInfo = { navController.navigate(fullInfo()) },
onOpenOptions = { navController.navigate(settingFeatureEntry.ROUTE.name) },
onStartUpdateRequest = {
globalNavController.navigate(updaterFeatureEntry.getUpdaterScreen(it))
}
)
}
composable("@${ROUTE.name}full") {
ComposableFullDeviceInfoScreen(navController)
navigation(startDestination = "@${ROUTE.name}", route = ROUTE.name) {
composable("@${ROUTE.name}") {
val componentContext = rememberComponentContext()
val fileManagerComponent = remember(componentContext) {
deviceScreenDecomposeComponentFactory(componentContext) as DeviceScreenDecomposeComponentImpl
}
val childStack by fileManagerComponent.stack.subscribeAsState()

Children(
stack = childStack
) {
it.instance.Render()
}
}
}
}
Expand Down
Loading

0 comments on commit 5078c63

Please sign in to comment.