diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/details/DetailsUtils.kt b/UIViews/src/main/java/com/programmersbox/uiviews/details/DetailsUtils.kt index 41b3092d3..7a3dda2b7 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/details/DetailsUtils.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/details/DetailsUtils.kt @@ -66,6 +66,7 @@ import com.programmersbox.uiviews.GenericInfo import com.programmersbox.uiviews.R import com.programmersbox.uiviews.lists.ListChoiceScreen import com.programmersbox.uiviews.utils.Screen +import com.programmersbox.uiviews.utils.components.ToolTipWrapper import com.programmersbox.uiviews.utils.launchCatching import com.programmersbox.uiviews.utils.showErrorToast import com.programmersbox.uiviews.utils.toComposeColor diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/details/DetailsView.kt b/UIViews/src/main/java/com/programmersbox/uiviews/details/DetailsView.kt index 77afda69f..68706859d 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/details/DetailsView.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/details/DetailsView.kt @@ -33,18 +33,13 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer -import androidx.compose.material3.RichTooltip import androidx.compose.material3.Snackbar import androidx.compose.material3.SnackbarDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TooltipBox -import androidx.compose.material3.TooltipDefaults -import androidx.compose.material3.TooltipState import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberDrawerState -import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf @@ -84,6 +79,7 @@ import com.programmersbox.uiviews.utils.LocalNavHostPadding import com.programmersbox.uiviews.utils.NotificationLogo import com.programmersbox.uiviews.utils.animate import com.programmersbox.uiviews.utils.components.OtakuScaffold +import com.programmersbox.uiviews.utils.components.ToolTipWrapper import com.programmersbox.uiviews.utils.components.minus import com.programmersbox.uiviews.utils.toComposeColor import dev.chrisbanes.haze.HazeState @@ -396,20 +392,3 @@ fun DetailsView( } } } - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -internal fun ToolTipWrapper( - info: @Composable () -> Unit, - modifier: Modifier = Modifier, - tooltipState: TooltipState = rememberTooltipState(), - content: @Composable () -> Unit, -) { - TooltipBox( - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), - tooltip = { RichTooltip { info() } }, - state = tooltipState, - modifier = modifier, - content = content - ) -} \ No newline at end of file diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/settings/ExtensionListScreen.kt b/UIViews/src/main/java/com/programmersbox/uiviews/settings/ExtensionListScreen.kt index f2b6d7fab..4331cb92c 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/settings/ExtensionListScreen.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/settings/ExtensionListScreen.kt @@ -4,23 +4,21 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.Crossfade import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons @@ -34,7 +32,6 @@ import androidx.compose.material.icons.filled.Extension import androidx.compose.material.icons.filled.InstallMobile import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Refresh -import androidx.compose.material.icons.filled.SendTimeExtension import androidx.compose.material.icons.filled.Update import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.AlertDialog @@ -47,21 +44,24 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LeadingIconTab import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.adaptive.AnimatedPane +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.ListDetailPaneScaffold +import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.adaptive.rememberListDetailPaneScaffoldNavigator import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -78,6 +78,7 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.core.net.toUri @@ -99,8 +100,10 @@ import com.programmersbox.sharedutils.AppUpdate import com.programmersbox.uiviews.OtakuWorldCatalog import com.programmersbox.uiviews.R import com.programmersbox.uiviews.checkers.SourceUpdateChecker +import com.programmersbox.uiviews.lists.calculateStandardPaneScaffoldDirective import com.programmersbox.uiviews.utils.BackButton import com.programmersbox.uiviews.utils.DownloadAndInstaller +import com.programmersbox.uiviews.utils.InsetCenterAlignedTopAppBar import com.programmersbox.uiviews.utils.InsetSmallTopAppBar import com.programmersbox.uiviews.utils.LightAndDarkPreviews import com.programmersbox.uiviews.utils.LocalCurrentSource @@ -109,12 +112,13 @@ import com.programmersbox.uiviews.utils.LocalSourcesRepository import com.programmersbox.uiviews.utils.PreviewTheme import com.programmersbox.uiviews.utils.SettingsHandling import com.programmersbox.uiviews.utils.components.OtakuScaffold +import com.programmersbox.uiviews.utils.components.ToolTipWrapper import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.koin.compose.koinInject import kotlin.time.Duration.Companion.minutes -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class) @Composable fun ExtensionList( sourceRepository: SourceRepository = LocalSourcesRepository.current, @@ -132,16 +136,6 @@ fun ExtensionList( ) { val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() val scope = rememberCoroutineScope() - val pagerState = rememberPagerState( - initialPage = 0, - initialPageOffsetFraction = 0f - ) { 2 } - - LaunchedEffect(key1 = viewModel.installed) { - if (viewModel.installed.isEmpty()) { - pagerState.animateScrollToPage(1) - } - } val context = LocalContext.current val downloadAndInstall = remember { DownloadAndInstaller(context) } @@ -175,136 +169,112 @@ fun ExtensionList( ) } + val navigator = rememberListDetailPaneScaffoldNavigator( + scaffoldDirective = calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()) + ) + + BackHandler(navigator.canNavigateBack()) { + navigator.navigateBack() + } + OtakuScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - Column { - InsetSmallTopAppBar( - title = { Text(stringResource(R.string.extensions)) }, - navigationIcon = { BackButton() }, - actions = { - var showDropDown by remember { mutableStateOf(false) } - var checked by remember { mutableStateOf(false) } - IconButton(onClick = { showDropDown = true }) { Icon(Icons.Default.MoreVert, null) } - DropdownMenu( - expanded = showDropDown, - onDismissRequest = { showDropDown = false } - ) { - DropdownMenuItem( - text = { Text("Check for Source Updates") }, - onClick = { - showDropDown = false - if (!checked) updateCheck() - scope.launch { - checked = true - delay(10.minutes) - checked = false - } - }, - leadingIcon = { Icon(Icons.Default.Update, null) }, - enabled = !checked - ) - - DropdownMenuItem( - text = { Text("Refresh Extensions") }, - onClick = { - showDropDown = false - viewModel.refreshExtensions() - }, - leadingIcon = { Icon(Icons.Default.Refresh, null) } - ) + InsetSmallTopAppBar( + title = { Text(stringResource(R.string.extensions)) }, + navigationIcon = { BackButton() }, + actions = { + var showDropDown by remember { mutableStateOf(false) } + var checked by remember { mutableStateOf(false) } - if (viewModel.hasCustomBridge) { - DropdownMenuItem( - text = { Text("Add Custom Tachiyomi Bridge") }, - onClick = { - showUrlDialog = true - showDropDown = false - }, - leadingIcon = { Icon(Icons.Default.AddCircleOutline, null) }, - ) - } + ToolTipWrapper(info = { Text("View Installed Extensions") }) { + IconButton(onClick = { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail) }) { + Icon(Icons.Default.Extension, null) } - }, - scrollBehavior = scrollBehavior, - ) - PrimaryTabRow( - // Our selected tab is our current page - selectedTabIndex = pagerState.currentPage, - ) { - // Add tabs for all of our pages - LeadingIconTab( - text = { Text(stringResource(R.string.installed)) }, - selected = pagerState.currentPage == 0, - onClick = { scope.launch { pagerState.animateScrollToPage(0) } }, - icon = { Icon(Icons.Default.Extension, null) } - ) + } - LeadingIconTab( - text = { Text(stringResource(R.string.extensions)) }, - selected = pagerState.currentPage == 1, - onClick = { scope.launch { pagerState.animateScrollToPage(1) } }, - icon = { Icon(Icons.Default.SendTimeExtension, null) } - ) - } - } - }, - ) { paddingValues -> - Crossfade( - targetState = viewModel.installed.isEmpty(), - label = "", - ) { target -> - when (target) { - true -> { - Box( - Modifier - .fillMaxSize() - .padding(paddingValues) + IconButton(onClick = { showDropDown = true }) { Icon(Icons.Default.MoreVert, null) } + + DropdownMenu( + expanded = showDropDown, + onDismissRequest = { showDropDown = false } ) { - RemoteExtensionItems( - remoteSources = viewModel.remoteSources, - onDownloadAndInstall = { downloadLink, destinationPath -> - downloadAndInstall.downloadAndInstall(downloadLink, destinationPath) - } + DropdownMenuItem( + text = { Text("Check for Source Updates") }, + onClick = { + showDropDown = false + if (!checked) updateCheck() + scope.launch { + checked = true + delay(10.minutes) + checked = false + } + }, + leadingIcon = { Icon(Icons.Default.Update, null) }, + enabled = !checked ) - } - } - false -> { - HorizontalPager( - state = pagerState, - contentPadding = paddingValues, - ) { page -> - when (page) { - 0 -> InstalledExtensionItems( - installedSources = viewModel.installed, - sourcesList = viewModel.remoteSourcesVersions, - onDownloadAndInstall = { downloadLink, destinationPath -> - downloadAndInstall.downloadAndInstall(downloadLink, destinationPath) - } - ) + DropdownMenuItem( + text = { Text("Refresh Extensions") }, + onClick = { + showDropDown = false + viewModel.refreshExtensions() + }, + leadingIcon = { Icon(Icons.Default.Refresh, null) } + ) - 1 -> RemoteExtensionItems( - remoteSources = viewModel.remoteSources, - onDownloadAndInstall = { downloadLink, destinationPath -> - downloadAndInstall.downloadAndInstall(downloadLink, destinationPath) - } + if (viewModel.hasCustomBridge) { + DropdownMenuItem( + text = { Text("Add Custom Tachiyomi Bridge") }, + onClick = { + showUrlDialog = true + showDropDown = false + }, + leadingIcon = { Icon(Icons.Default.AddCircleOutline, null) }, ) } } + }, + scrollBehavior = scrollBehavior, + ) + }, + ) { paddingValues -> + ListDetailPaneScaffold( + scaffoldState = navigator.scaffoldState, + windowInsets = WindowInsets(0.dp), + listPane = { + AnimatedPane(modifier = Modifier.fillMaxSize()) { + InstalledExtensionItems( + installedSources = viewModel.installed, + sourcesList = viewModel.remoteSourcesVersions, + onDownloadAndInstall = { downloadLink, destinationPath -> + downloadAndInstall.downloadAndInstall(downloadLink, destinationPath) + } + ) } - } - } - + }, + detailPane = { + AnimatedPane(modifier = Modifier.fillMaxSize()) { + RemoteExtensionItems( + remoteSources = viewModel.remoteSources, + onDownloadAndInstall = { downloadLink, destinationPath -> + downloadAndInstall.downloadAndInstall(downloadLink, destinationPath) + }, + ) + } + }, + modifier = Modifier.padding(paddingValues), + ) } } -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable private fun InstalledExtensionItems( installedSources: Map, sourcesList: List, onDownloadAndInstall: (String, String) -> Unit, + modifier: Modifier = Modifier, ) { val currentSourceRepository = LocalCurrentSource.current val context = LocalContext.current @@ -313,7 +283,13 @@ private fun InstalledExtensionItems( val uninstall = Intent(Intent.ACTION_DELETE, uri) context.startActivity(uninstall) } - Column { + Column( + modifier = modifier + ) { + InsetCenterAlignedTopAppBar( + title = { Text("Installed Extensions") }, + insetPadding = WindowInsets(0.dp) + ) ListItem( headlineContent = { val source by LocalCurrentSource.current.asFlow().collectAsState(initial = null) @@ -404,13 +380,16 @@ private fun InstalledExtensionItems( } } -@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalFoundationApi::class) @Composable private fun RemoteExtensionItems( remoteSources: Map, onDownloadAndInstall: (String, String) -> Unit, + modifier: Modifier = Modifier, ) { - Column { + Column( + modifier = modifier + ) { var search by remember { mutableStateOf("") } OutlinedTextField( value = search, @@ -419,6 +398,10 @@ private fun RemoteExtensionItems( trailingIcon = { IconButton(onClick = { search = "" }) { Icon(Icons.Default.Clear, null) } }, + singleLine = true, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Search + ), modifier = Modifier.fillMaxWidth() ) LazyColumn( @@ -559,7 +542,13 @@ private fun RemoteItem( Row { IconButton( onClick = { showSources = !showSources } - ) { Icon(Icons.Default.ArrowDropDown, null, modifier = Modifier.rotateWithBoolean(showSources)) } + ) { + Icon( + Icons.Default.ArrowDropDown, + null, + modifier = Modifier.rotateWithBoolean(showSources) + ) + } IconButton( onClick = { showDialog = true } diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/utils/components/ToolTipWrapper.kt b/UIViews/src/main/java/com/programmersbox/uiviews/utils/components/ToolTipWrapper.kt new file mode 100644 index 000000000..fbf4fd6ff --- /dev/null +++ b/UIViews/src/main/java/com/programmersbox/uiviews/utils/components/ToolTipWrapper.kt @@ -0,0 +1,27 @@ +package com.programmersbox.uiviews.utils.components + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.RichTooltip +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.TooltipState +import androidx.compose.material3.rememberTooltipState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ToolTipWrapper( + info: @Composable () -> Unit, + modifier: Modifier = Modifier, + tooltipState: TooltipState = rememberTooltipState(), + content: @Composable () -> Unit, +) { + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { RichTooltip { info() } }, + state = tooltipState, + modifier = modifier, + content = content + ) +} \ No newline at end of file