From f5ce1a297211f706f151c2b9edc8436a673ee0cf Mon Sep 17 00:00:00 2001 From: SuperDragonXD <70206496+SuperDragonXD@users.noreply.github.com> Date: Fri, 26 Jul 2024 21:53:52 +0800 Subject: [PATCH 1/3] Implement bottom navigation for smaller devices --- app/build.gradle.kts | 17 +- .../ui/components/home/HomeBottomBar.kt | 110 +++++++++ .../ui/components/home/HomeTopBar.kt | 143 ++++++++++++ .../ui/components/home/IconPreviewGrid.kt | 7 +- .../ui/components/home/IconRequestFAB.kt | 208 +++++++++++------- .../ui/components/home/search/SearchBar.kt | 16 +- .../lawnicons/ui/destination/Home.kt | 101 +++++---- app/src/main/res/values/strings.xml | 1 + build.gradle.kts | 3 +- 9 files changed, 471 insertions(+), 135 deletions(-) create mode 100644 app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/HomeBottomBar.kt create mode 100644 app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/HomeTopBar.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 619a15f5915..92451330b7b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,5 @@ import app.cash.licensee.LicenseeTask import com.android.build.gradle.internal.api.ApkVariantOutputImpl -import com.android.build.gradle.tasks.MergeResources import java.io.FileInputStream import java.util.Locale import java.util.Properties @@ -101,7 +100,7 @@ android { applicationVariants.all { outputs.all { (this as? ApkVariantOutputImpl)?.outputFileName = - "Lawnicons $versionName v${versionCode}_${buildType.name}.apk" + "Lawnicons $versionName v$versionCode ${buildType.name}.apk".replace(" ", ".") } } } @@ -122,9 +121,9 @@ androidComponents.onVariants { variant -> } // Process SVGs before every build. -tasks.withType().configureEach { - dependsOn(projects.svgProcessor.dependencyProject.tasks.named("run")) -} +// tasks.withType().configureEach { +// dependsOn(projects.svgProcessor.dependencyProject.tasks.named("run")) +// } licensee { allow("Apache-2.0") @@ -136,10 +135,10 @@ dependencies { implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.activity:activity-compose:1.9.1") implementation(platform("androidx.compose:compose-bom:2024.06.00")) - implementation("androidx.compose.ui:ui") - implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.ui:ui-util") - debugImplementation("androidx.compose.ui:ui-tooling") + implementation("androidx.compose.ui:ui:1.6.6") + implementation("androidx.compose.ui:ui-tooling-preview:1.6.6") + implementation("androidx.compose.ui:ui-util:1.6.6") + debugImplementation("androidx.compose.ui:ui-tooling:1.6.6") implementation("androidx.compose.animation:animation") implementation("androidx.compose.material:material-icons-core-android:1.6.8") implementation("androidx.compose.material3:material3:1.3.0-beta05") diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/HomeBottomBar.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/HomeBottomBar.kt new file mode 100644 index 00000000000..b425c041c55 --- /dev/null +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/HomeBottomBar.kt @@ -0,0 +1,110 @@ +package app.lawnchair.lawnicons.ui.components.home + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.PlainTooltip +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.rememberTooltipState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.lawnchair.lawnicons.R +import app.lawnchair.lawnicons.model.IconRequestModel +import app.lawnchair.lawnicons.ui.util.Constants + +@Composable +fun HomeBottomBar( + context: Context, + iconRequestModel: IconRequestModel?, + snackbarHostState: SnackbarHostState, + onNavigate: () -> Unit, + onExpandSearch: () -> Unit, + modifier: Modifier = Modifier, +) { + BottomAppBar( + actions = { + SimpleTooltipBox( + label = stringResource(id = R.string.github), + ) { + IconButton( + onClick = { + val webpage = Uri.parse(Constants.GITHUB) + val intent = Intent(Intent.ACTION_VIEW, webpage) + if (intent.resolveActivity(context.packageManager) != null) { + context.startActivity(intent) + } + }, + ) { + Icon( + painter = painterResource(id = R.drawable.github_foreground), + contentDescription = stringResource(id = R.string.github), + modifier = Modifier.requiredSize(24.dp), + ) + } + } + IconRequestIconButton( + iconRequestModel = iconRequestModel, + snackbarHostState = snackbarHostState, + ) + SimpleTooltipBox( + label = stringResource(id = R.string.about), + ) { + IconButton(onClick = onNavigate) { + Icon( + painter = painterResource(id = R.drawable.contacts_foreground), + contentDescription = stringResource(id = R.string.about), + modifier = Modifier.requiredSize(24.dp), + ) + } + } + }, + floatingActionButton = { + SimpleTooltipBox( + label = stringResource(id = R.string.search), + ) { + FloatingActionButton(onClick = onExpandSearch) { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = stringResource(id = R.string.search), + ) + } + } + }, + modifier = modifier, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SimpleTooltipBox( + label: String, + modifier: Modifier = Modifier, + content: @Composable (() -> Unit), +) { + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(label) + } + }, + state = rememberTooltipState(), + modifier = modifier, + ) { + content() + } +} diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/HomeTopBar.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/HomeTopBar.kt new file mode 100644 index 00000000000..5bbd8c6a13f --- /dev/null +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/HomeTopBar.kt @@ -0,0 +1,143 @@ +package app.lawnchair.lawnicons.ui.components.home + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.lawnchair.lawnicons.R +import app.lawnchair.lawnicons.model.IconInfo +import app.lawnchair.lawnicons.model.IconInfoModel +import app.lawnchair.lawnicons.model.SearchMode +import app.lawnchair.lawnicons.ui.components.home.search.LawniconsSearchBar +import app.lawnchair.lawnicons.ui.components.home.search.SearchContents + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +fun HomeTopBar( + isSearchExpanded: Boolean, + onFocusChange: () -> Unit, + isExpandedScreen: Boolean, + onClearSearch: () -> Unit, + onChangeMode: (SearchMode) -> Unit, + onSearchIcons: (String) -> Unit, + searchedIconInfoModel: IconInfoModel?, + onNavigate: () -> Unit, + isIconPicker: Boolean, + searchTerm: String, + searchMode: SearchMode, + onSendResult: (IconInfo) -> Unit, + focusRequester: FocusRequester, + scrollBehavior: TopAppBarScrollBehavior, + appIcon: ImageBitmap, + modifier: Modifier = Modifier, +) { + AnimatedContent(targetState = isSearchExpanded || isExpandedScreen, label = "TopAppBar to SearchBar", modifier = modifier) { targetState -> + if (targetState) { + searchedIconInfoModel?.let { + SearchBar( + searchTerm = searchTerm, + onClearSearch = onClearSearch, + onChangeMode = onChangeMode, + onSearchIcons = onSearchIcons, + iconInfoModel = it, + onNavigate = onNavigate, + isExpandedScreen = isExpandedScreen, + isIconPicker = isIconPicker, + searchMode = searchMode, + onSendResult = onSendResult, + onFocusChange = onFocusChange, + inputFieldModifier = Modifier.focusRequester(focusRequester), + ) + } + } else { + CenterAlignedTopAppBar( + scrollBehavior = scrollBehavior, + title = { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + bitmap = appIcon, + contentDescription = stringResource(id = R.string.app_name), + modifier = Modifier.size(36.dp), + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + stringResource(id = R.string.app_name), + ) + } + }, + ) + } + } +} + +@Composable +private fun SearchBar( + onClearSearch: () -> Unit, + onChangeMode: (SearchMode) -> Unit, + onSearchIcons: (String) -> Unit, + searchTerm: String, + iconInfoModel: IconInfoModel, + onNavigate: () -> Unit, + isExpandedScreen: Boolean, + isIconPicker: Boolean, + searchMode: SearchMode, + onSendResult: (IconInfo) -> Unit, + onFocusChange: () -> Unit, + modifier: Modifier = Modifier, + inputFieldModifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + LawniconsSearchBar( + query = searchTerm, + isQueryEmpty = searchTerm == "", + onClearAndBackClick = { + onClearSearch() + onFocusChange() + }, + onQueryChange = { newValue -> + onSearchIcons(newValue) + }, + iconInfoModel = iconInfoModel, + onNavigate = onNavigate, + isExpandedScreen = isExpandedScreen, + isIconPicker = isIconPicker, + content = { + SearchContents( + searchTerm = searchTerm, + searchMode = searchMode, + onModeChange = { mode -> + onChangeMode(mode) + }, + iconInfo = iconInfoModel.iconInfo, + onSendResult = { + onSendResult(it) + }, + ) + }, + inputFieldModifier = inputFieldModifier, + ) + } +} diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreviewGrid.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreviewGrid.kt index 5ca6ad76883..7c7ec33072c 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreviewGrid.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreviewGrid.kt @@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -56,9 +56,8 @@ fun IconPreviewGrid( ) { LazyVerticalGridScrollbar( modifier = Modifier - .then( - if (isExpandedScreen) Modifier.width(640.dp) else Modifier, - ) + .widthIn(max = 640.dp) + .fillMaxWidth() .statusBarsPadding() .padding(top = 26.dp), state = gridState, diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconRequestFAB.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconRequestFAB.kt index 3e622eb4f05..70a5162234c 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconRequestFAB.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconRequestFAB.kt @@ -14,12 +14,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState @@ -46,6 +48,7 @@ import app.lawnchair.lawnicons.model.IconRequestModel import app.lawnchair.lawnicons.ui.components.core.Card import app.lawnchair.lawnicons.ui.util.Constants import app.lawnchair.lawnicons.ui.util.isScrollingUp +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -57,30 +60,80 @@ fun IconRequestFAB( snackbarHostState: SnackbarHostState, modifier: Modifier = Modifier, ) { - if (iconRequestModel != null) { - if (iconRequestModel.iconCount > 0) { - IconRequestFAB( - iconRequestList = iconRequestModel.list, - lazyGridState = lazyGridState, - snackbarHostState = snackbarHostState, - modifier = modifier, + RequestHandler( + iconRequestModel = iconRequestModel, + snackbarHostState = snackbarHostState, + ) { interactionSource -> + ExtendedFloatingActionButton( + text = { + Text(stringResource(R.string.request_icons)) + }, + icon = { + Icon( + painter = painterResource(id = R.drawable.icon_request_app), + contentDescription = null, + ) + }, + onClick = {}, + expanded = lazyGridState.isScrollingUp(), + interactionSource = interactionSource, + modifier = modifier, + ) + } +} + +@Composable +fun IconRequestIconButton( + iconRequestModel: IconRequestModel?, + snackbarHostState: SnackbarHostState, + modifier: Modifier = Modifier, +) { + RequestHandler( + iconRequestModel = iconRequestModel, + snackbarHostState = snackbarHostState, + ) { interactionSource -> + IconButton( + onClick = {}, + interactionSource = interactionSource, + modifier = modifier, + ) { + Icon( + painter = painterResource(id = R.drawable.icon_request_app), + contentDescription = stringResource(R.string.request_icons), + modifier = Modifier.requiredSize(24.dp), ) } } } +@Composable +fun RequestHandler( + iconRequestModel: IconRequestModel?, + snackbarHostState: SnackbarHostState, + content: @Composable ((interactionSource: MutableInteractionSource) -> Unit), +) { + if (iconRequestModel != null && iconRequestModel.iconCount > 0) { + RequestHandler( + iconRequestList = iconRequestModel.list, + snackbarHostState = snackbarHostState, + ) { + content(it) + } + } +} + @OptIn(ExperimentalMaterial3Api::class) @Composable -fun IconRequestFAB( +fun RequestHandler( iconRequestList: List, snackbarHostState: SnackbarHostState, - lazyGridState: LazyGridState, - modifier: Modifier = Modifier, + content: @Composable ((interactionSource: MutableInteractionSource) -> Unit), ) { val context = LocalContext.current + val viewConfiguration = LocalViewConfiguration.current - val list = iconRequestList.joinToString("\n") { "${it.name}\n${it.componentName}" } - val request = buildForm(list.replace("\n", "%20")) + val requestList = formatIconRequestList(iconRequestList) + val encodedRequestList = buildForm(requestList.replace("\n", "%20")) val sheetExpanded = remember { mutableStateOf(false) } val sheetState = rememberModalBottomSheetState( @@ -89,9 +142,8 @@ fun IconRequestFAB( val coroutineScope = rememberCoroutineScope() val interactionSource = remember { MutableInteractionSource() } - val viewConfiguration = LocalViewConfiguration.current - val directLinkEnabled = request.length < Constants.DIRECT_LINK_MAX_LENGTH + val directLinkEnabled = encodedRequestList.length < Constants.DIRECT_LINK_MAX_LENGTH LaunchedEffect(interactionSource) { var isLongClick = false @@ -111,28 +163,9 @@ fun IconRequestFAB( is PressInteraction.Release -> { if (!isLongClick) { if (directLinkEnabled) { - openLink(context, request) + openLink(context, encodedRequestList) } else { - copyTextToClipboard(context, list) - coroutineScope.launch { - val result = snackbarHostState - .showSnackbar( - message = context.getString(R.string.snackbar_request_too_large), - actionLabel = context.getString(R.string.snackbar_use_fallback_link), - withDismissAction = true, - duration = SnackbarDuration.Indefinite, - ) - when (result) { - SnackbarResult.ActionPerformed -> { - /* Handle snackbar action performed */ - openLink(context, Constants.ICON_REQUEST_FORM) - } - - SnackbarResult.Dismissed -> { - snackbarHostState.currentSnackbarData?.dismiss() - } - } - } + openSnackbarContent(context, requestList, coroutineScope, snackbarHostState) } } } @@ -144,56 +177,48 @@ fun IconRequestFAB( } } - ExtendedFloatingActionButton( - text = { - Text(stringResource(R.string.request_icons)) - }, - icon = { - Icon( - painter = painterResource(id = R.drawable.icon_request_app), - contentDescription = null, - ) - }, - onClick = {}, - expanded = lazyGridState.isScrollingUp(), - interactionSource = interactionSource, - modifier = modifier, - ) + content(interactionSource) + AnimatedVisibility(visible = sheetExpanded.value) { ModalBottomSheet( onDismissRequest = { sheetExpanded.value = false }, sheetState = sheetState, ) { + IconRequestSheet(requestList, context) + } + } +} + +@Composable +private fun IconRequestSheet(list: String, context: Context) { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Card { Column( modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, + .verticalScroll(rememberScrollState()) + .padding(16.dp), ) { - Card { - Column( - modifier = Modifier - .verticalScroll(rememberScrollState()) - .padding(16.dp), + Text( + text = list, + fontFamily = FontFamily.Monospace, + modifier = Modifier + .horizontalScroll(rememberScrollState()), + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + TextButton( + onClick = { + copyTextToClipboard(context, list) + }, ) { - Text( - text = list, - fontFamily = FontFamily.Monospace, - modifier = Modifier - .horizontalScroll(rememberScrollState()), - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - TextButton( - onClick = { - copyTextToClipboard(context, list) - }, - ) { - Text(stringResource(R.string.copy_to_clipboard)) - } - } + Text(stringResource(R.string.copy_to_clipboard)) } } } @@ -201,12 +226,43 @@ fun IconRequestFAB( } } +private fun formatIconRequestList(iconRequestList: List) = + iconRequestList.joinToString("\n") { "${it.name}\n${it.componentName}" } + private fun copyTextToClipboard(context: Context, text: String) { val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText(context.getString(R.string.copied_text), text) clipboard.setPrimaryClip(clip) } +private fun openSnackbarContent( + context: Context, + list: String, + coroutineScope: CoroutineScope, + snackbarHostState: SnackbarHostState, +) { + copyTextToClipboard(context, list) + coroutineScope.launch { + val result = snackbarHostState + .showSnackbar( + message = context.getString(R.string.snackbar_request_too_large), + actionLabel = context.getString(R.string.snackbar_use_fallback_link), + withDismissAction = true, + duration = SnackbarDuration.Indefinite, + ) + when (result) { + SnackbarResult.ActionPerformed -> { + /* Handle snackbar action performed */ + openLink(context, Constants.ICON_REQUEST_FORM) + } + + SnackbarResult.Dismissed -> { + snackbarHostState.currentSnackbarData?.dismiss() + } + } + } +} + private fun openLink(context: Context, link: String) { val website = Uri.parse(link) val intent = Intent(Intent.ACTION_VIEW, website) diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchBar.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchBar.kt index dd460f4fdbc..6dbb88bd124 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchBar.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchBar.kt @@ -58,6 +58,7 @@ fun LawniconsSearchBar( iconInfoModel: IconInfoModel, onNavigate: () -> Unit, modifier: Modifier = Modifier, + inputFieldModifier: Modifier = Modifier, isExpandedScreen: Boolean = false, isIconPicker: Boolean = false, content: @Composable (() -> Unit), @@ -71,6 +72,7 @@ fun LawniconsSearchBar( onNavigate = onNavigate, content = content, modifier = modifier, + inputFieldModifier = inputFieldModifier, isExpandedScreen = isExpandedScreen, isIconPicker = isIconPicker, ) @@ -99,6 +101,7 @@ fun LawniconsSearchBar( iconCount: Int, onNavigate: () -> Unit, modifier: Modifier = Modifier, + inputFieldModifier: Modifier = Modifier, isExpandedScreen: Boolean = false, isIconPicker: Boolean = false, content: @Composable (() -> Unit), @@ -134,7 +137,12 @@ fun LawniconsSearchBar( onQueryChange = onQueryChange, onSearch = { active = false }, active = active, - onActiveChange = { active = it }, + onActiveChange = { + active = it + if (!active) { + onClearAndBackClick() + } + }, placeholder = { Text( stringResource( @@ -166,6 +174,7 @@ fun LawniconsSearchBar( } }, isExpandedScreen = isExpandedScreen, + inputFieldModifier = inputFieldModifier, ) { content() } @@ -184,12 +193,15 @@ private fun ResponsiveSearchBar( leadingIcon: @Composable () -> Unit, trailingIcon: @Composable () -> Unit, isExpandedScreen: Boolean, + modifier: Modifier = Modifier, + inputFieldModifier: Modifier = Modifier, content: @Composable () -> Unit, ) { if (isExpandedScreen) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, + modifier = modifier, ) { DockedSearchBar( inputField = { @@ -197,6 +209,7 @@ private fun ResponsiveSearchBar( query = query, onQueryChange = onQueryChange, onSearch = onSearch, + modifier = inputFieldModifier, expanded = active, onExpandedChange = onActiveChange, placeholder = placeholder, @@ -218,6 +231,7 @@ private fun ResponsiveSearchBar( query = query, onQueryChange = onQueryChange, onSearch = onSearch, + modifier = inputFieldModifier, expanded = active, onExpandedChange = onActiveChange, placeholder = placeholder, diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Home.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Home.kt index 700abda7069..bc0d6862876 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Home.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Home.kt @@ -2,24 +2,29 @@ package app.lawnchair.lawnicons.ui.destination import androidx.compose.animation.Crossfade import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.lawnchair.lawnicons.model.IconInfo import app.lawnchair.lawnicons.model.SearchMode +import app.lawnchair.lawnicons.ui.components.home.HomeBottomBar +import app.lawnchair.lawnicons.ui.components.home.HomeTopBar import app.lawnchair.lawnicons.ui.components.home.IconPreviewGrid import app.lawnchair.lawnicons.ui.components.home.IconRequestFAB import app.lawnchair.lawnicons.ui.components.home.search.LawniconsSearchBar @@ -28,10 +33,11 @@ import app.lawnchair.lawnicons.ui.components.home.search.SearchContents import app.lawnchair.lawnicons.ui.theme.LawniconsTheme import app.lawnchair.lawnicons.ui.util.PreviewLawnicons import app.lawnchair.lawnicons.ui.util.SampleData +import app.lawnchair.lawnicons.util.appIcon import app.lawnchair.lawnicons.viewmodel.LawniconsViewModel import kotlinx.collections.immutable.toImmutableList -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun Home( onNavigate: () -> Unit, @@ -48,9 +54,15 @@ fun Home( val searchMode = searchMode val searchTerm = searchTerm + val expandSearch = remember { mutableStateOf(false) } + val context = LocalContext.current + val lazyGridState = rememberLazyGridState() val snackbarHostState = remember { SnackbarHostState() } + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val focusRequester = remember { FocusRequester() } + Crossfade( modifier = modifier, targetState = iconInfoModel != null, @@ -59,52 +71,48 @@ fun Home( if (visible) { Scaffold( topBar = { - searchedIconInfoModel?.let { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - LawniconsSearchBar( - query = searchTerm, - isQueryEmpty = searchTerm == "", - onClearAndBackClick = { - lawniconsViewModel.clearSearch() - }, - onQueryChange = { newValue -> - lawniconsViewModel.searchIcons(newValue) - }, - iconInfoModel = it, - onNavigate = onNavigate, - isExpandedScreen = isExpandedScreen, - isIconPicker = isIconPicker, - content = { - SearchContents( - searchTerm = searchTerm, - searchMode = searchMode, - onModeChange = { mode -> - lawniconsViewModel.changeMode(mode) - }, - iconInfo = it.iconInfo, - onSendResult = { - onSendResult(it) - }, - ) - }, - ) - } + HomeTopBar( + isSearchExpanded = expandSearch.value, + onFocusChange = { expandSearch.value = !expandSearch.value }, + isExpandedScreen = isExpandedScreen, + onClearSearch = { clearSearch() }, + onChangeMode = { changeMode(it) }, + onSearchIcons = { searchIcons(it) }, + searchedIconInfoModel = searchedIconInfoModel, + onNavigate = onNavigate, + searchTerm = searchTerm, + searchMode = searchMode, + isIconPicker = isIconPicker, + onSendResult = onSendResult, + focusRequester = focusRequester, + scrollBehavior = scrollBehavior, + appIcon = context.appIcon().asImageBitmap(), + ) + }, + bottomBar = { + if (!isExpandedScreen) { + HomeBottomBar( + context = context, + iconRequestModel = iconRequestModel, + snackbarHostState = snackbarHostState, + onNavigate = onNavigate, + onExpandSearch = { expandSearch.value = !expandSearch.value }, + ) } }, floatingActionButton = { - IconRequestFAB( - iconRequestModel = iconRequestModel, - snackbarHostState = snackbarHostState, - lazyGridState = lazyGridState, - ) + if (isExpandedScreen) { + IconRequestFAB( + iconRequestModel = iconRequestModel, + snackbarHostState = snackbarHostState, + lazyGridState = lazyGridState, + ) + } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { contentPadding -> iconInfoModel?.let { val padding = contentPadding // Ignore padding value @@ -123,6 +131,11 @@ fun Home( ) } } + LaunchedEffect(expandSearch.value) { + if (expandSearch.value) { + focusRequester.requestFocus() + } + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d00ad51d7a4..91f637a3be9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,4 +75,5 @@ Copied icon request details to clipboard. To request them, submit the details into the form. Open form + Search diff --git a/build.gradle.kts b/build.gradle.kts index ea39f8e1a44..379455588c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,8 @@ plugins { allprojects { plugins.withType().configureEach { extensions.configure { - toolchain.languageVersion = JavaLanguageVersion.of(21) + // Downgrade temporarily to make Compose previews work + toolchain.languageVersion = JavaLanguageVersion.of(17) } } From c05a2df6ac573f86050b823c5fe4fddbd64ae920 Mon Sep 17 00:00:00 2001 From: SuperDragonXD <70206496+SuperDragonXD@users.noreply.github.com> Date: Fri, 26 Jul 2024 22:35:38 +0800 Subject: [PATCH 2/3] Initial fix on in-app picker Todo: fix icon colors --- .../app/lawnchair/lawnicons/MainActivity.kt | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/MainActivity.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/MainActivity.kt index 06e90530536..2ddec052487 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/MainActivity.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/MainActivity.kt @@ -2,6 +2,8 @@ package app.lawnchair.lawnicons import android.content.Context import android.content.Intent +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity @@ -13,6 +15,7 @@ import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSiz import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.ui.platform.LocalContext import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import app.lawnchair.lawnicons.model.IconInfo @@ -51,10 +54,22 @@ class MainActivity : ComponentActivity() { ) { val intent = Intent() - val bitmap = ResourcesCompat.getDrawable(context.resources, iconInfo.id, null) - ?.toBitmap() + val primaryForegroundColor = context.getColor(R.color.primaryForeground) + val primaryBackgroundColor = context.getColor(R.color.primaryBackground) + + val drawable: Drawable? = + ResourcesCompat.getDrawable(context.resources, iconInfo.id, theme)?.mutate()?.let { + DrawableCompat.wrap( + it, + ) + } + + if (drawable != null) { + DrawableCompat.setTintList(drawable, ColorStateList.valueOf(primaryForegroundColor)) + DrawableCompat.setTintList(drawable, ColorStateList.valueOf(primaryBackgroundColor)) + + val bitmap = drawable.toBitmap() - if (bitmap != null) { try { intent.putExtra( "icon", @@ -69,6 +84,7 @@ class MainActivity : ComponentActivity() { } intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconInfo.id) setResult(RESULT_OK, intent) + finish() } else { setResult(RESULT_CANCELED, intent) } From 6bc2a9e4ad1422cd5b4e80432565fd2eb6e2d706 Mon Sep 17 00:00:00 2001 From: SuperDragonXD <70206496+SuperDragonXD@users.noreply.github.com> Date: Fri, 26 Jul 2024 22:46:33 +0800 Subject: [PATCH 3/3] Revert app module gradle changes --- app/build.gradle.kts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 92451330b7b..619a15f5915 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,6 @@ import app.cash.licensee.LicenseeTask import com.android.build.gradle.internal.api.ApkVariantOutputImpl +import com.android.build.gradle.tasks.MergeResources import java.io.FileInputStream import java.util.Locale import java.util.Properties @@ -100,7 +101,7 @@ android { applicationVariants.all { outputs.all { (this as? ApkVariantOutputImpl)?.outputFileName = - "Lawnicons $versionName v$versionCode ${buildType.name}.apk".replace(" ", ".") + "Lawnicons $versionName v${versionCode}_${buildType.name}.apk" } } } @@ -121,9 +122,9 @@ androidComponents.onVariants { variant -> } // Process SVGs before every build. -// tasks.withType().configureEach { -// dependsOn(projects.svgProcessor.dependencyProject.tasks.named("run")) -// } +tasks.withType().configureEach { + dependsOn(projects.svgProcessor.dependencyProject.tasks.named("run")) +} licensee { allow("Apache-2.0") @@ -135,10 +136,10 @@ dependencies { implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.activity:activity-compose:1.9.1") implementation(platform("androidx.compose:compose-bom:2024.06.00")) - implementation("androidx.compose.ui:ui:1.6.6") - implementation("androidx.compose.ui:ui-tooling-preview:1.6.6") - implementation("androidx.compose.ui:ui-util:1.6.6") - debugImplementation("androidx.compose.ui:ui-tooling:1.6.6") + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.ui:ui-util") + debugImplementation("androidx.compose.ui:ui-tooling") implementation("androidx.compose.animation:animation") implementation("androidx.compose.material:material-icons-core-android:1.6.8") implementation("androidx.compose.material3:material3:1.3.0-beta05")