mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-05-24 08:25:00 -04:00
Implement repository onboarding
This commit is contained in:
@@ -16,6 +16,8 @@ class SettingsManager @Inject constructor() {
|
||||
val appListSortOrder = _appListSortOrder.asStateFlow()
|
||||
private val _showFilterOnboarding = MutableStateFlow(true)
|
||||
val showFilterOnboarding = _showFilterOnboarding.asStateFlow()
|
||||
private val _showRepositoriesOnboarding = MutableStateFlow(true)
|
||||
val showRepositoriesOnboarding = _showRepositoriesOnboarding.asStateFlow()
|
||||
|
||||
fun saveAppListFilter(sortOrder: AppListSortOrder, filterIncompatible: Boolean) {
|
||||
_appListSortOrder.value = sortOrder
|
||||
@@ -27,4 +29,9 @@ class SettingsManager @Inject constructor() {
|
||||
// TODO persist
|
||||
}
|
||||
|
||||
fun onRepositoriesOnboardingSeen() {
|
||||
_showRepositoriesOnboarding.value = false
|
||||
// TODO persist
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -201,6 +201,8 @@ fun Main(onListeningForIntent: () -> Unit = {}) {
|
||||
(backStack.last() as? NavigationKey.RepoDetails)?.repoId
|
||||
} else null
|
||||
|
||||
override fun onOnboardingSeen() = viewModel.onOnboardingSeen()
|
||||
|
||||
override fun onRepositorySelected(repositoryItem: RepositoryItem) {
|
||||
backStack.add(NavigationKey.RepoDetails(repositoryItem.repoId))
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -52,19 +51,16 @@ import com.viktormykhailiv.compose.hints.hintAnchor
|
||||
import com.viktormykhailiv.compose.hints.rememberHint
|
||||
import com.viktormykhailiv.compose.hints.rememberHintAnchorState
|
||||
import com.viktormykhailiv.compose.hints.rememberHintController
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import org.fdroid.database.AppListSortOrder
|
||||
import org.fdroid.fdroid.ui.theme.FDroidContent
|
||||
import org.fdroid.next.R
|
||||
import org.fdroid.ui.utils.BigLoadingIndicator
|
||||
import org.fdroid.ui.utils.OnboardingCard
|
||||
import org.fdroid.ui.utils.getAppListInfo
|
||||
import org.fdroid.utils.OnboardingCard
|
||||
import org.fdroid.ui.utils.getHintOverlayColor
|
||||
|
||||
@Composable
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class,
|
||||
FlowPreview::class
|
||||
)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun AppList(
|
||||
appListInfo: AppListInfo,
|
||||
currentPackageName: String?,
|
||||
@@ -76,21 +72,25 @@ fun AppList(
|
||||
val scrollBehavior = enterAlwaysScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
val hintController = rememberHintController(
|
||||
overlay = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.8f),
|
||||
overlay = getHintOverlayColor(),
|
||||
)
|
||||
val hint = rememberHint {
|
||||
OnboardingCard(
|
||||
title = stringResource(R.string.onboarding_app_list_filter_title),
|
||||
message = stringResource(R.string.onboarding_app_list_filter_message),
|
||||
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp),
|
||||
) {
|
||||
appListInfo.actions.onOnboardingSeen()
|
||||
hintController.dismiss()
|
||||
}
|
||||
onGotIt = {
|
||||
appListInfo.actions.onOnboardingSeen()
|
||||
hintController.dismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
val hintAnchor = rememberHintAnchorState(hint)
|
||||
LaunchedEffect(appListInfo.showOnboarding) {
|
||||
if (appListInfo.showOnboarding) hintController.show(hintAnchor)
|
||||
if (appListInfo.showOnboarding) {
|
||||
hintController.show(hintAnchor)
|
||||
appListInfo.actions.onOnboardingSeen()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.fdroid.ui.repositories
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.content.res.Configuration.UI_MODE_TYPE_NORMAL
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
@@ -14,13 +16,20 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.viktormykhailiv.compose.hints.rememberHint
|
||||
import com.viktormykhailiv.compose.hints.rememberHintAnchorState
|
||||
import com.viktormykhailiv.compose.hints.rememberHintController
|
||||
import org.fdroid.fdroid.ui.theme.FDroidContent
|
||||
import org.fdroid.next.R
|
||||
import org.fdroid.ui.utils.BigLoadingIndicator
|
||||
import org.fdroid.ui.utils.OnboardingCard
|
||||
import org.fdroid.ui.utils.getHintOverlayColor
|
||||
import org.fdroid.ui.utils.getRepositoriesInfo
|
||||
|
||||
@Composable
|
||||
@@ -29,6 +38,30 @@ fun Repositories(
|
||||
info: RepositoryInfo,
|
||||
onBackClicked: () -> Unit,
|
||||
) {
|
||||
val hintController = rememberHintController(
|
||||
overlay = getHintOverlayColor(),
|
||||
)
|
||||
val hint = rememberHint {
|
||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
|
||||
OnboardingCard(
|
||||
title = stringResource(R.string.repo_list_info_title),
|
||||
message = stringResource(R.string.repo_list_info_text),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 32.dp, vertical = 8.dp),
|
||||
onGotIt = {
|
||||
info.onOnboardingSeen()
|
||||
hintController.dismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
val hintAnchor = rememberHintAnchorState(hint)
|
||||
LaunchedEffect(info.model.showOnboarding) {
|
||||
if (info.model.showOnboarding) {
|
||||
hintController.show(hintAnchor)
|
||||
info.onOnboardingSeen()
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -43,6 +76,7 @@ fun Repositories(
|
||||
title = {
|
||||
Text(stringResource(R.string.app_details_repositories))
|
||||
},
|
||||
// TODO show when repos were last updated
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
@@ -73,7 +107,7 @@ fun Repositories(
|
||||
@Composable
|
||||
fun RepositoriesScaffoldLoadingPreview() {
|
||||
FDroidContent {
|
||||
val model = RepositoryModel(null)
|
||||
val model = RepositoryModel(null, false)
|
||||
val info = getRepositoriesInfo(model)
|
||||
Repositories(info) {}
|
||||
}
|
||||
@@ -106,7 +140,7 @@ fun RepositoriesScaffoldPreview() {
|
||||
name = "My second repository",
|
||||
),
|
||||
)
|
||||
val model = RepositoryModel(repos)
|
||||
val model = RepositoryModel(repos, false)
|
||||
val info = getRepositoriesInfo(model, repos[0].repoId)
|
||||
Repositories(info) { }
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ import androidx.compose.ui.draw.dropShadow
|
||||
import androidx.compose.ui.graphics.shadow.Shadow
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.fdroid.utils.DraggableItem
|
||||
import org.fdroid.utils.dragContainer
|
||||
import org.fdroid.utils.rememberDragDropState
|
||||
import org.fdroid.ui.utils.DraggableItem
|
||||
import org.fdroid.ui.utils.dragContainer
|
||||
import org.fdroid.ui.utils.rememberDragDropState
|
||||
|
||||
@Composable
|
||||
fun RepositoriesList(
|
||||
|
||||
@@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
fun RepositoriesPresenter(
|
||||
repositoriesFlow: Flow<List<RepositoryItem>>,
|
||||
repoSortingMapFlow: StateFlow<Map<Long, Int>>,
|
||||
showOnboardingFlow: StateFlow<Boolean>,
|
||||
): RepositoryModel {
|
||||
val repositories = repositoriesFlow.collectAsState(null).value
|
||||
val repoSortingMap = repoSortingMapFlow.collectAsState().value
|
||||
@@ -17,5 +18,6 @@ fun RepositoriesPresenter(
|
||||
// newly added repos will not be in repoSortingMap, so they need fallback
|
||||
repoSortingMap[repo.repoId] ?: repositories.find { it.repoId == repo.repoId }?.weight
|
||||
},
|
||||
showOnboarding = showOnboardingFlow.collectAsState().value,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import mu.KotlinLogging
|
||||
import org.fdroid.index.RepoManager
|
||||
import org.fdroid.settings.SettingsManager
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class RepositoriesViewModel @Inject constructor(
|
||||
app: Application,
|
||||
private val repoManager: RepoManager,
|
||||
private val settingsManager: SettingsManager,
|
||||
) : AndroidViewModel(app) {
|
||||
|
||||
private val log = KotlinLogging.logger { }
|
||||
@@ -34,6 +36,7 @@ class RepositoriesViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
private val repoSortingMap: MutableStateFlow<Map<Long, Int>>
|
||||
private val showOnboarding = settingsManager.showRepositoriesOnboarding
|
||||
|
||||
init {
|
||||
// just add repos to sortingMap, because they are already pre-sorted by weight
|
||||
@@ -49,6 +52,7 @@ class RepositoriesViewModel @Inject constructor(
|
||||
RepositoriesPresenter(
|
||||
repositoriesFlow = repos,
|
||||
repoSortingMapFlow = repoSortingMap,
|
||||
showOnboardingFlow = showOnboarding,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -75,4 +79,6 @@ class RepositoriesViewModel @Inject constructor(
|
||||
|
||||
fun addRepo() {
|
||||
}
|
||||
|
||||
fun onOnboardingSeen() = settingsManager.onRepositoriesOnboardingSeen()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.fdroid.ui.repositories
|
||||
interface RepositoryInfo {
|
||||
val model: RepositoryModel
|
||||
val currentRepositoryId: Long?
|
||||
fun onOnboardingSeen()
|
||||
fun onRepositorySelected(repositoryItem: RepositoryItem)
|
||||
fun onAddRepo()
|
||||
fun onRepositoryMoved(fromIndex: Int, toIndex: Int)
|
||||
@@ -11,4 +12,5 @@ interface RepositoryInfo {
|
||||
|
||||
data class RepositoryModel(
|
||||
val repositories: List<RepositoryItem>?,
|
||||
val showOnboarding: Boolean,
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyColumnDragAndDropDemo.kt;drc=e6d33dd5d0a60001a5784d84123b05308d35f410
|
||||
*/
|
||||
|
||||
package org.fdroid.utils
|
||||
package org.fdroid.ui.utils
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.Spring
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.fdroid.utils
|
||||
package org.fdroid.ui.utils
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
@@ -229,6 +229,7 @@ fun getRepositoriesInfo(
|
||||
): RepositoryInfo = object : RepositoryInfo {
|
||||
override val model: RepositoryModel = model
|
||||
override val currentRepositoryId: Long? = currentRepositoryId
|
||||
override fun onOnboardingSeen() {}
|
||||
override fun onRepositorySelected(repositoryItem: RepositoryItem) {}
|
||||
override fun onAddRepo() {}
|
||||
override fun onRepositoryMoved(fromIndex: Int, toIndex: Int) {}
|
||||
|
||||
@@ -7,11 +7,16 @@ import android.content.Intent
|
||||
import android.os.PowerManager
|
||||
import android.text.format.DateUtils
|
||||
import android.util.Log
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.UriHandler
|
||||
import androidx.core.content.ContextCompat
|
||||
import java.text.Normalizer
|
||||
import java.text.Normalizer.Form.NFKD
|
||||
|
||||
@Composable
|
||||
fun getHintOverlayColor() = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.8f)
|
||||
|
||||
fun Context.startActivitySafe(i: Intent?) {
|
||||
if (i == null) return
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user