Merge branch '2.0' into 'master'

2.0 Changelogs

See merge request fdroid/fdroidclient!1611
This commit is contained in:
Torsten Grote
2026-01-23 18:08:15 +00:00
23 changed files with 86 additions and 147 deletions

View File

@@ -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(

View File

@@ -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()

View File

@@ -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())
}
}
}
}

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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}'