From aff0eca22a95166e359b38f5d9c28efc6f7806a2 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 23 Jan 2026 09:51:24 -0300 Subject: [PATCH 1/3] update start-alpha-cycle.py for 2.0 --- tools/start-alpha-cycle.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/start-alpha-cycle.py b/tools/start-alpha-cycle.py index 2d3e0b1d8..1a7c3eac7 100755 --- a/tools/start-alpha-cycle.py +++ b/tools/start-alpha-cycle.py @@ -19,8 +19,8 @@ if not re.match(r'[0-9]+\.[0-9]+\.[0-9]+\Z', tag): sys.exit(1) print(f'Working off of {tag} release') -vc_pat = re.compile(r"versionCode +([1-9][0-9]{6,})") -versionCode = int(vc_pat.search(repo.git.show(f"{tag}:app/build.gradle")).group(1)) +vc_pat = re.compile(r"versionCode = +([1-9][0-9]{6,})") +versionCode = int(vc_pat.search(repo.git.show(f"{tag}:app/build.gradle.kts")).group(1)) print(f"Working off of {tag} release (versionCode {versionCode}).") if versionCode % 1000 == 0: @@ -44,8 +44,8 @@ if not Path(default_file).exists(): newvc = ((versionCode // 1000) + 1) * 1000 print(f'New alpha versionCode: {newvc}') -build_gradle = Path('app/build.gradle') -build_gradle.write_text(vc_pat.sub(f'versionCode {newvc}', build_gradle.read_text())) +build_gradle = Path('app/build.gradle.kts') +build_gradle.write_text(vc_pat.sub(f'versionCode = {newvc}', build_gradle.read_text())) for f in projectdir.glob('metadata/*/changelogs/default.txt'): vcf = f.parent / f'{versionCode}{f.suffix}' From ed5995a16fd0ed2cc220b85107f17e75949dded1 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 23 Jan 2026 09:55:46 -0300 Subject: [PATCH 2/3] Add changelog --- metadata/ar/changelogs/{default.txt => 1023051.txt} | 0 metadata/ca/changelogs/{default.txt => 1023051.txt} | 0 metadata/cs/changelogs/{default.txt => 1023051.txt} | 0 metadata/de/changelogs/{default.txt => 1023051.txt} | 0 metadata/en-US/changelogs/{default.txt => 1023051.txt} | 0 metadata/eo/changelogs/{default.txt => 1023051.txt} | 0 metadata/fr/changelogs/{default.txt => 1023051.txt} | 0 metadata/ga/changelogs/{default.txt => 1023051.txt} | 0 metadata/id/changelogs/{default.txt => 1023051.txt} | 0 metadata/ja/changelogs/{default.txt => 1023051.txt} | 0 metadata/lv/changelogs/{default.txt => 1023051.txt} | 0 metadata/pt-BR/changelogs/{default.txt => 1023051.txt} | 0 metadata/tr/changelogs/{default.txt => 1023051.txt} | 0 metadata/uk/changelogs/{default.txt => 1023051.txt} | 0 metadata/zh-CN/changelogs/{default.txt => 1023051.txt} | 0 metadata/zh-TW/changelogs/{default.txt => 1023051.txt} | 0 .../metadata/android/en-US/changelogs/default.txt | 8 ++++++++ 17 files changed, 8 insertions(+) rename metadata/ar/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/ca/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/cs/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/de/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/en-US/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/eo/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/fr/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/ga/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/id/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/ja/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/lv/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/pt-BR/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/tr/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/uk/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/zh-CN/changelogs/{default.txt => 1023051.txt} (100%) rename metadata/zh-TW/changelogs/{default.txt => 1023051.txt} (100%) create mode 100644 src/basic/fastlane/metadata/android/en-US/changelogs/default.txt diff --git a/metadata/ar/changelogs/default.txt b/metadata/ar/changelogs/1023051.txt similarity index 100% rename from metadata/ar/changelogs/default.txt rename to metadata/ar/changelogs/1023051.txt diff --git a/metadata/ca/changelogs/default.txt b/metadata/ca/changelogs/1023051.txt similarity index 100% rename from metadata/ca/changelogs/default.txt rename to metadata/ca/changelogs/1023051.txt diff --git a/metadata/cs/changelogs/default.txt b/metadata/cs/changelogs/1023051.txt similarity index 100% rename from metadata/cs/changelogs/default.txt rename to metadata/cs/changelogs/1023051.txt diff --git a/metadata/de/changelogs/default.txt b/metadata/de/changelogs/1023051.txt similarity index 100% rename from metadata/de/changelogs/default.txt rename to metadata/de/changelogs/1023051.txt diff --git a/metadata/en-US/changelogs/default.txt b/metadata/en-US/changelogs/1023051.txt similarity index 100% rename from metadata/en-US/changelogs/default.txt rename to metadata/en-US/changelogs/1023051.txt diff --git a/metadata/eo/changelogs/default.txt b/metadata/eo/changelogs/1023051.txt similarity index 100% rename from metadata/eo/changelogs/default.txt rename to metadata/eo/changelogs/1023051.txt diff --git a/metadata/fr/changelogs/default.txt b/metadata/fr/changelogs/1023051.txt similarity index 100% rename from metadata/fr/changelogs/default.txt rename to metadata/fr/changelogs/1023051.txt diff --git a/metadata/ga/changelogs/default.txt b/metadata/ga/changelogs/1023051.txt similarity index 100% rename from metadata/ga/changelogs/default.txt rename to metadata/ga/changelogs/1023051.txt diff --git a/metadata/id/changelogs/default.txt b/metadata/id/changelogs/1023051.txt similarity index 100% rename from metadata/id/changelogs/default.txt rename to metadata/id/changelogs/1023051.txt diff --git a/metadata/ja/changelogs/default.txt b/metadata/ja/changelogs/1023051.txt similarity index 100% rename from metadata/ja/changelogs/default.txt rename to metadata/ja/changelogs/1023051.txt diff --git a/metadata/lv/changelogs/default.txt b/metadata/lv/changelogs/1023051.txt similarity index 100% rename from metadata/lv/changelogs/default.txt rename to metadata/lv/changelogs/1023051.txt diff --git a/metadata/pt-BR/changelogs/default.txt b/metadata/pt-BR/changelogs/1023051.txt similarity index 100% rename from metadata/pt-BR/changelogs/default.txt rename to metadata/pt-BR/changelogs/1023051.txt diff --git a/metadata/tr/changelogs/default.txt b/metadata/tr/changelogs/1023051.txt similarity index 100% rename from metadata/tr/changelogs/default.txt rename to metadata/tr/changelogs/1023051.txt diff --git a/metadata/uk/changelogs/default.txt b/metadata/uk/changelogs/1023051.txt similarity index 100% rename from metadata/uk/changelogs/default.txt rename to metadata/uk/changelogs/1023051.txt diff --git a/metadata/zh-CN/changelogs/default.txt b/metadata/zh-CN/changelogs/1023051.txt similarity index 100% rename from metadata/zh-CN/changelogs/default.txt rename to metadata/zh-CN/changelogs/1023051.txt diff --git a/metadata/zh-TW/changelogs/default.txt b/metadata/zh-TW/changelogs/1023051.txt similarity index 100% rename from metadata/zh-TW/changelogs/default.txt rename to metadata/zh-TW/changelogs/1023051.txt diff --git a/src/basic/fastlane/metadata/android/en-US/changelogs/default.txt b/src/basic/fastlane/metadata/android/en-US/changelogs/default.txt new file mode 100644 index 000000000..c6952e820 --- /dev/null +++ b/src/basic/fastlane/metadata/android/en-US/changelogs/default.txt @@ -0,0 +1,8 @@ +* UI rewritten from scratch with Kotlin compose +* improved search, also searching in descriptions and translations +* easier to discover new apps, also highlighting the most downloaded ones +* installation approval *before* downloading +* multiple updates/downloads at the same time +* notifying user of issues with apps (e.g. signing key changed) +* optional Material You color theme +* improved filtering of lists From 28db65f1088a8f3db1e8122d5100697b432eeb3a Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 23 Jan 2026 11:25:46 -0300 Subject: [PATCH 3/3] Clean up top app bar search bar --- .../main/kotlin/org/fdroid/ui/apps/MyApps.kt | 4 +- .../kotlin/org/fdroid/ui/lists/AppList.kt | 9 +- .../org/fdroid/ui/lists/TopSearchBar.kt | 85 ------------------- .../org/fdroid/ui/search/ExpandedSearch.kt | 55 +----------- .../org/fdroid/ui/search/TopSearchBar.kt | 64 ++++++++++++++ 5 files changed, 74 insertions(+), 143 deletions(-) delete mode 100644 app/src/main/kotlin/org/fdroid/ui/lists/TopSearchBar.kt create mode 100644 app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt diff --git a/app/src/main/kotlin/org/fdroid/ui/apps/MyApps.kt b/app/src/main/kotlin/org/fdroid/ui/apps/MyApps.kt index d4950cc9d..da1e4f9b4 100644 --- a/app/src/main/kotlin/org/fdroid/ui/apps/MyApps.kt +++ b/app/src/main/kotlin/org/fdroid/ui/apps/MyApps.kt @@ -44,7 +44,7 @@ import org.fdroid.database.AppListSortOrder.LAST_UPDATED import org.fdroid.download.NetworkState import org.fdroid.install.InstallConfirmationState import org.fdroid.ui.FDroidContent -import org.fdroid.ui.lists.TopSearchBar +import org.fdroid.ui.search.TopSearchBar import org.fdroid.ui.utils.BigLoadingIndicator import org.fdroid.ui.utils.getMyAppsInfo import org.fdroid.ui.utils.myAppsModel @@ -84,7 +84,7 @@ fun MyApps( Scaffold( topBar = { if (searchActive) { - TopSearchBar(onSearch = myAppsInfo::search, onSearchCleared) { + TopSearchBar(onSearch = myAppsInfo::search, onSearchCleared = onSearchCleared) { onBackPressedDispatcher?.onBackPressed() } } else TopAppBar( diff --git a/app/src/main/kotlin/org/fdroid/ui/lists/AppList.kt b/app/src/main/kotlin/org/fdroid/ui/lists/AppList.kt index 0655c50d8..b46cec95d 100644 --- a/app/src/main/kotlin/org/fdroid/ui/lists/AppList.kt +++ b/app/src/main/kotlin/org/fdroid/ui/lists/AppList.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.plus import androidx.compose.foundation.lazy.LazyColumn @@ -52,6 +53,7 @@ import com.viktormykhailiv.compose.hints.rememberHintController import org.fdroid.R import org.fdroid.database.AppListSortOrder import org.fdroid.ui.FDroidContent +import org.fdroid.ui.search.TopSearchBar import org.fdroid.ui.utils.BigLoadingIndicator import org.fdroid.ui.utils.OnboardingCard import org.fdroid.ui.utils.getAppListInfo @@ -95,7 +97,10 @@ fun AppList( topBar = { if (searchActive) { val onSearchCleared = { appListInfo.actions.onSearch("") } - TopSearchBar(onSearch = appListInfo.actions::onSearch, onSearchCleared) { + TopSearchBar( + onSearch = appListInfo.actions::onSearch, + onSearchCleared = onSearchCleared, + ) { searchActive = false onSearchCleared() } @@ -155,7 +160,7 @@ fun AppList( LazyListState() } Column( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize().imePadding() ) { val apps = appListInfo.model.apps if (apps == null) BigLoadingIndicator() diff --git a/app/src/main/kotlin/org/fdroid/ui/lists/TopSearchBar.kt b/app/src/main/kotlin/org/fdroid/ui/lists/TopSearchBar.kt deleted file mode 100644 index 53c4ab7d6..000000000 --- a/app/src/main/kotlin/org/fdroid/ui/lists/TopSearchBar.kt +++ /dev/null @@ -1,85 +0,0 @@ -package org.fdroid.ui.lists - -import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Clear -import androidx.compose.material3.AppBarWithSearch -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.rememberSearchBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.res.stringResource -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.debounce -import org.fdroid.R -import org.fdroid.ui.search.AppSearchInputField -import org.fdroid.ui.search.SEARCH_THRESHOLD - -/** - * This is a top app bar that isn't mean to ever expand with results, but for in-list filtering. - * There may still be potential to factor out common code with [AppSearchInputField]. - */ -@Composable -@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) -fun TopSearchBar( - onSearch: (String) -> Unit, - onSearchCleared: () -> Unit, - onHideSearch: () -> Unit, -) { - val searchFieldState = rememberTextFieldState() - val focusRequester = remember { FocusRequester() } - AppBarWithSearch( - state = rememberSearchBarState(), - inputField = { - SearchBarDefaults.InputField( - state = searchFieldState, - leadingIcon = { - IconButton(onClick = onHideSearch) { - Icon( - imageVector = Icons.AutoMirrored.Default.ArrowBack, - contentDescription = stringResource(R.string.back), - ) - } - }, - trailingIcon = { - if (searchFieldState.text.isNotEmpty()) { - IconButton(onClick = { - searchFieldState.setTextAndPlaceCursorAtEnd("") - onSearchCleared() - }) { - Icon( - imageVector = Icons.Filled.Clear, - contentDescription = stringResource(R.string.clear_search), - ) - } - } - }, - onSearch = onSearch, - expanded = false, - onExpandedChange = {}, - modifier = Modifier.focusRequester(focusRequester) - ) - }, - ) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - snapshotFlow { searchFieldState.text } - .debounce(500) - .collectLatest { - if (it.length >= SEARCH_THRESHOLD || it.isEmpty()) { - onSearch(searchFieldState.text.toString()) - } - } - } -} diff --git a/app/src/main/kotlin/org/fdroid/ui/search/ExpandedSearch.kt b/app/src/main/kotlin/org/fdroid/ui/search/ExpandedSearch.kt index 160ac7111..4ff10659b 100644 --- a/app/src/main/kotlin/org/fdroid/ui/search/ExpandedSearch.kt +++ b/app/src/main/kotlin/org/fdroid/ui/search/ExpandedSearch.kt @@ -5,30 +5,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.SearchBarValue -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.rememberSearchBarState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import kotlinx.coroutines.launch -import org.fdroid.R import org.fdroid.ui.FDroidContent import org.fdroid.ui.categories.CategoryItem import org.fdroid.ui.lists.AppListItem @@ -44,41 +27,9 @@ fun ExpandedSearch( onBack: () -> Unit, onSearchCleared: () -> Unit, ) { - val scope = rememberCoroutineScope() - val searchBarState = rememberSearchBarState() - val focusRequester = remember { FocusRequester() } - val keyboardController = LocalSoftwareKeyboardController.current Scaffold( topBar = { - TopAppBar( - navigationIcon = { - IconButton(onClick = { - if (searchBarState.currentValue == SearchBarValue.Expanded) { - keyboardController?.hide() - scope.launch { searchBarState.animateToCollapsed() } - } else { - onBack() - } - }) { - Icon( - imageVector = Icons.AutoMirrored.Default.ArrowBack, - contentDescription = stringResource(R.string.back), - ) - } - }, - title = { - AppSearchInputField( - searchBarState = searchBarState, - textFieldState = textFieldState, - onSearch = onSearch, - onSearchCleared = { - textFieldState.setTextAndPlaceCursorAtEnd("") - onSearchCleared() - }, - modifier = Modifier.focusRequester(focusRequester) - ) - } - ) + TopSearchBar(textFieldState, onSearch, onSearchCleared, onBack) } ) { paddingValues -> HorizontalDivider( @@ -93,10 +44,6 @@ fun ExpandedSearch( modifier = Modifier ) } - LaunchedEffect(Unit) { - focusRequester.requestFocus() - keyboardController?.show() - } } @Preview diff --git a/app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt b/app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt new file mode 100644 index 000000000..1e886d84e --- /dev/null +++ b/app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt @@ -0,0 +1,64 @@ +package org.fdroid.ui.search + +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.rememberSearchBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import kotlinx.coroutines.FlowPreview +import org.fdroid.R + +/** + * This is a top app bar that isn't mean to ever expand with results, but for in-list filtering. + * There may still be potential to factor out common code with [AppSearchInputField]. + */ +@Composable +@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) +fun TopSearchBar( + searchFieldState: TextFieldState = rememberTextFieldState(), + onSearch: suspend (String) -> Unit, + onSearchCleared: () -> Unit, + onHideSearch: () -> Unit, +) { + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + TopAppBar( + navigationIcon = { + IconButton(onClick = onHideSearch) { + Icon( + imageVector = Icons.AutoMirrored.Default.ArrowBack, + contentDescription = stringResource(R.string.back), + ) + } + }, + title = { + AppSearchInputField( + searchBarState = rememberSearchBarState(), + textFieldState = searchFieldState, + onSearch = onSearch, + onSearchCleared = { + searchFieldState.setTextAndPlaceCursorAtEnd("") + onSearchCleared() + }, + modifier = Modifier.focusRequester(focusRequester) + ) + } + ) + LaunchedEffect(Unit) { + focusRequester.requestFocus() + keyboardController?.show() + } +}