mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-05-19 14:10:38 -04:00
Merge branch '2.0' into 'master'
2.0 Changelogs See merge request fdroid/fdroidclient!1611
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
64
app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt
Normal file
64
app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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}'
|
||||
|
||||
Reference in New Issue
Block a user