Skip to content

Commit

Permalink
Add preview of images in Apps catalog (#815)
Browse files Browse the repository at this point in the history
**Background**

Currently int application users doesn't have an ability to preiew
screenshots of hub applications.

**Changes**

- Created module screenshotspreview which allows to display preview
images
- Added module integration with modules inside faphub via callbacks

**Test plan**

1. Open Hub->Apps
2. Wait until apps loaded and click on the image inside app item
3. Open app screen details and click on the image
4. Open Hub->Apps->Search and repeat the steps 2-3 from above
  • Loading branch information
makeevrserg committed Apr 10, 2024
1 parent 34afaf5 commit 340d062
Show file tree
Hide file tree
Showing 28 changed files with 605 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [KMP] Migration core:log
- [Feature] Add ready app updates popup and notification dot
- [Feature] Per app loading in installed tab
- [Feature] Add ability to preview screenshots of fap entries
- [Refactor] Migrate to markdown renderer from upstream
- [Refactor] Basic implementation of new transport ble
- [FIX] Replace decompose push to move safety push
Expand Down
3 changes: 3 additions & 0 deletions components/faphub/appcard/composable/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ plugins {
android.namespace = "com.flipperdevices.faphub.appcard.composable"

dependencies {
implementation(projects.components.rootscreen.api)

implementation(projects.components.faphub.screenshotspreview.api)
implementation(projects.components.faphub.dao.api)
implementation(projects.components.faphub.errors.api)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import com.flipperdevices.faphub.appcard.composable.components.AppCardScreenshot
import com.flipperdevices.faphub.appcard.composable.components.ComposableAppCategory
import com.flipperdevices.faphub.appcard.composable.components.ComposableAppIcon
import com.flipperdevices.faphub.dao.api.model.FapItemShort
import com.flipperdevices.faphub.screenshotspreview.api.model.ScreenshotsPreviewParam
import com.flipperdevices.rootscreen.api.LocalRootNavigation
import com.flipperdevices.rootscreen.model.RootScreenConfig

private val DEFAULT_NAME
get() = String((Array(size = 10) { 'L' }).toCharArray())
Expand All @@ -36,6 +39,7 @@ fun AppCard(
modifier: Modifier = Modifier,
installationButton: @Composable (Modifier) -> Unit
) {
val rootNavigation = LocalRootNavigation.current
Column(modifier) {
AppCardTop(
fapItem = fapItem,
Expand All @@ -52,6 +56,15 @@ fun AppCard(
)
AppCardScreenshots(
screenshots = fapItem?.screenshots,
onScreenshotClicked = onScreenshotClicked@{ index ->
val requireFapItem = fapItem ?: return@onScreenshotClicked
val param = ScreenshotsPreviewParam(
title = requireFapItem.name,
screenshotsUrls = requireFapItem.screenshots,
selected = index
)
rootNavigation.push(RootScreenConfig.ScreenshotPreview(param))
},
modifier = Modifier.padding(top = 12.dp),
screenshotModifier = Modifier
.padding(end = 6.dp)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.flipperdevices.faphub.appcard.composable.components

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.runtime.Composable
Expand All @@ -11,6 +12,7 @@ private const val DEFAULT_SCREENSHOT_SIZE = 6
@Composable
fun AppCardScreenshots(
screenshots: ImmutableList<String>?,
onScreenshotClicked: (index: Int) -> Unit,
modifier: Modifier = Modifier,
screenshotModifier: Modifier = Modifier,
) {
Expand All @@ -29,7 +31,9 @@ fun AppCardScreenshots(
items(screenshots.size) { index ->
val screenshotUrl = screenshots[index]
ComposableAppScreenshot(
modifier = screenshotModifier,
modifier = screenshotModifier.clickable {
onScreenshotClicked.invoke(index)
},
url = screenshotUrl
)
}
Expand Down
1 change: 1 addition & 0 deletions components/faphub/fapscreen/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ plugins {
android.namespace = "com.flipperdevices.faphub.fapscreen.impl"

dependencies {
implementation(projects.components.faphub.screenshotspreview.api)
implementation(projects.components.faphub.fapscreen.api)
implementation(projects.components.faphub.errors.api)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
Expand All @@ -32,6 +31,9 @@ import com.flipperdevices.faphub.fapscreen.impl.composable.description.Composabl
import com.flipperdevices.faphub.fapscreen.impl.composable.header.ComposableFapHeader
import com.flipperdevices.faphub.fapscreen.impl.model.FapDetailedControlState
import com.flipperdevices.faphub.fapscreen.impl.model.FapScreenLoadingState
import com.flipperdevices.faphub.screenshotspreview.api.model.ScreenshotsPreviewParam
import com.flipperdevices.rootscreen.api.LocalRootNavigation
import com.flipperdevices.rootscreen.model.RootScreenConfig

@Composable
fun ComposableFapScreen(
Expand Down Expand Up @@ -104,6 +106,7 @@ private fun ComposableFapScreenInternal(
installationButton: @Composable (FapItem?, Modifier) -> Unit,
modifier: Modifier = Modifier
) = Column(modifier) {
val rootNavigation = LocalRootNavigation.current
ComposableFapScreenBar(fapName = fapItem?.name, url = shareUrl, onBack = onBack)
SwipeRefresh(onRefresh = onRefresh) {
Column(Modifier.verticalScroll(rememberScrollState())) {
Expand All @@ -125,6 +128,15 @@ private fun ComposableFapScreenInternal(
AppCardScreenshots(
screenshots = fapItem?.screenshots,
modifier = Modifier.padding(top = 18.dp, start = 14.dp),
onScreenshotClicked = onScreenshotClicked@{ index ->
val requireFapItem = fapItem ?: return@onScreenshotClicked
val param = ScreenshotsPreviewParam(
title = requireFapItem.name,
screenshotsUrls = requireFapItem.screenshots,
selected = index
)
rootNavigation.push(RootScreenConfig.ScreenshotPreview(param))
},
screenshotModifier = Modifier
.padding(end = 8.dp)
.size(width = 189.dp, height = 94.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class FapHubDecomposeComponentImpl @AssistedInject constructor(
serializer = FapHubNavigationConfig.serializer(),
initialStack = { getInitialStack(deeplink) },
handleBackButton = true,
childFactory = ::child,
childFactory = ::child
)

private fun child(
Expand Down
1 change: 1 addition & 0 deletions components/faphub/screenshotspreview/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
12 changes: 12 additions & 0 deletions components/faphub/screenshotspreview/api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
plugins {
id("flipper.android-lib")
id("kotlinx-serialization")
}

android.namespace = "com.flipperdevices.faphub.screenshotspreview.api"

dependencies {
implementation(projects.components.core.ui.decompose)
implementation(libs.kotlin.immutable.collections)
implementation(libs.decompose)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.flipperdevices.faphub.screenshotspreview.api

import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.faphub.screenshotspreview.api.model.ScreenshotsPreviewParam
import com.flipperdevices.ui.decompose.DecomposeOnBackParameter
import com.flipperdevices.ui.decompose.ScreenDecomposeComponent

abstract class ScreenshotsPreviewDecomposeComponent(
componentContext: ComponentContext
) : ScreenDecomposeComponent(componentContext) {
fun interface Factory {
operator fun invoke(
componentContext: ComponentContext,
param: ScreenshotsPreviewParam,
onBack: DecomposeOnBackParameter,
): ScreenshotsPreviewDecomposeComponent
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.flipperdevices.faphub.screenshotspreview.api.model

import kotlinx.collections.immutable.ImmutableList
import kotlinx.serialization.Serializable

@Serializable
/**
* @param title name of catalog item
* @param screenshotsUrls list of web-urls where screenshot available
* @param selected index of selected [screenshotsUrls] item
*/
data class ScreenshotsPreviewParam(
val title: String,
val screenshotsUrls: ImmutableList<String>,
val selected: Int,
)
1 change: 1 addition & 0 deletions components/faphub/screenshotspreview/impl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
36 changes: 36 additions & 0 deletions components/faphub/screenshotspreview/impl/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
plugins {
id("flipper.android-compose")
id("flipper.anvil")
id("kotlinx-serialization")
}

android.namespace = "com.flipperdevices.faphub.screenshotspreview.impl"

dependencies {
implementation(projects.components.faphub.screenshotspreview.api)

implementation(projects.components.core.share)

implementation(projects.components.core.di)
implementation(projects.components.core.log)
implementation(projects.components.core.ktx)
implementation(projects.components.core.ui.res)
implementation(projects.components.core.ui.theme)
implementation(projects.components.core.ui.ktx)
implementation(projects.components.core.ui.decompose)
implementation(projects.components.core.ui.lifecycle)

// Compose
implementation(libs.compose.ui)
implementation(libs.compose.tooling)
implementation(libs.compose.foundation)
implementation(libs.compose.material)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.bundles.decompose)

implementation(libs.zoomable)

implementation(projects.components.faphub.appcard.composable)

implementation(libs.kotlin.immutable.collections)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.flipperdevices.faphub.screenshotspreview.impl.api

import androidx.compose.runtime.Composable
import com.arkivanov.decompose.ComponentContext
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.core.ui.lifecycle.viewModelWithFactory
import com.flipperdevices.faphub.screenshotspreview.api.ScreenshotsPreviewDecomposeComponent
import com.flipperdevices.faphub.screenshotspreview.api.model.ScreenshotsPreviewParam
import com.flipperdevices.faphub.screenshotspreview.impl.composable.ComposableFullScreenshotScreen
import com.flipperdevices.faphub.screenshotspreview.impl.viewmodel.UrlImageShareViewModel
import com.flipperdevices.ui.decompose.DecomposeOnBackParameter
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import me.gulya.anvil.assisted.ContributesAssistedFactory
import javax.inject.Provider

@ContributesAssistedFactory(AppGraph::class, ScreenshotsPreviewDecomposeComponent.Factory::class)
class FullScreenScreenshotDecomposeComponentImpl @AssistedInject constructor(
@Assisted componentContext: ComponentContext,
@Assisted private val param: ScreenshotsPreviewParam,
@Assisted private val onBack: DecomposeOnBackParameter,
private val urlImageShareViewModelProvider: Provider<UrlImageShareViewModel>,
) : ScreenshotsPreviewDecomposeComponent(componentContext) {

@Composable
@Suppress("NonSkippableComposable")
override fun Render() {
val urlImageShareViewModel = viewModelWithFactory(null) {
urlImageShareViewModelProvider.get()
}

ComposableFullScreenshotScreen(
urlImageShareViewModel = urlImageShareViewModel,
onBack = onBack::invoke,
title = param.title,
selected = param.selected,
screenshots = param.screenshotsUrls
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.flipperdevices.faphub.screenshotspreview.impl.composable

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import com.flipperdevices.core.ui.theme.FlipperThemeInternal
import com.flipperdevices.faphub.screenshotspreview.impl.composable.content.ComposableFullScreenshotAppBar
import com.flipperdevices.faphub.screenshotspreview.impl.composable.content.ComposableScreenshotsList
import com.flipperdevices.faphub.screenshotspreview.impl.composable.content.ComposableScreenshotsPager
import com.flipperdevices.faphub.screenshotspreview.impl.viewmodel.UrlImageShareViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
import java.net.URL

@Composable
internal fun ComposableFullScreenshotScreen(
onBack: () -> Unit,
title: String,
selected: Int,
screenshots: ImmutableList<String>,
urlImageShareViewModel: UrlImageShareViewModel
) {
val pagerState = rememberPagerState(selected) {
screenshots.size
}

Box(
modifier = Modifier
.fillMaxSize()
.navigationBarsPadding(),
contentAlignment = Alignment.Center
) {
ComposableScreenshotsPager(
screenshots = screenshots,
pagerState = pagerState,
modifier = Modifier
.fillMaxSize()
.align(Alignment.Center)
)

ComposableScreenshotsList(
screenshots = screenshots,
pagerState = pagerState,
modifier = Modifier.align(Alignment.BottomCenter)
)

ComposableFullScreenshotAppBar(
onBack = onBack,
title = title,
itemsAmount = screenshots.size,
selectedItemIndex = pagerState.currentPage,
modifier = Modifier.align(Alignment.TopCenter),
onSaveClicked = {
val url = screenshots[pagerState.currentPage].let(::URL)
urlImageShareViewModel.shareUrlImage(url)
}
)
}
}

@Preview(showBackground = true)
@Composable
private fun FullScreenshotScreenPreview() {
FlipperThemeInternal {
ComposableFullScreenshotScreen(
onBack = {},
screenshots = List(size = 20) {
"https://catalog.flipperzero.one/api/v0/application/version/assets/660e569e6c9840814200ecb8"
}.toPersistentList(),
title = "Snake game",
selected = 3,
urlImageShareViewModel = UrlImageShareViewModel(
applicationContext = LocalContext.current.applicationContext
)
)
}
}
Loading

0 comments on commit 340d062

Please sign in to comment.