commit 34168372d86a88d46d0b5da996bf079c6e02fb63 parent 493262041900da6a499e6d9157588133c46f7e3b Author: Iván Ávalos <avalos@disroot.org> Date: Thu, 31 Oct 2024 19:53:29 +0100 [wallet] Prepare app for Android 15 (edge-to-edge enforcement) Diffstat:
35 files changed, 395 insertions(+), 206 deletions(-)
diff --git a/wallet/build.gradle b/wallet/build.gradle @@ -43,7 +43,7 @@ android { defaultConfig { applicationId "net.taler.wallet" minSdkVersion 24 - targetSdkVersion 34 + targetSdkVersion 35 versionCode 48 versionName "0.13.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt @@ -27,9 +27,15 @@ import android.view.Menu import android.view.MenuItem import android.view.View.GONE import android.view.View.VISIBLE +import android.view.ViewGroup.MarginLayoutParams +import android.view.WindowInsets +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -69,16 +75,18 @@ class MainActivity : AppCompatActivity(), OnPreferenceStartFragmentCallback { } override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) ui = ActivityMainBinding.inflate(layoutInflater) setContentView(ui.root) + setupInsets() val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment nav = navHostFragment.navController - setSupportActionBar(ui.content.toolbar) - ui.content.toolbar.setupWithNavController(nav) + setSupportActionBar(ui.toolbar) + ui.toolbar.setupWithNavController(nav) // TODO: refactor and unify progress bar handling // model.showProgressBar.observe(this) { show -> @@ -116,7 +124,7 @@ class MainActivity : AppCompatActivity(), OnPreferenceStartFragmentCallback { }) model.networkManager.networkStatus.observe(this) { online -> - ui.content.offlineBanner.visibility = if (online) GONE else VISIBLE + // ui.offlineBanner.visibility = if (online) GONE else VISIBLE model.hintNetworkAvailability(online) } @@ -125,6 +133,27 @@ class MainActivity : AppCompatActivity(), OnPreferenceStartFragmentCallback { } } + private fun setupInsets() { + // We really don't want to deal with cutouts! + ViewCompat.setOnApplyWindowInsetsListener(ui.root) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + v.updateLayoutParams<MarginLayoutParams> { + leftMargin = insets.left + rightMargin = insets.right + } + windowInsets + } + + ViewCompat.setOnApplyWindowInsetsListener(ui.toolbar) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updateLayoutParams<MarginLayoutParams> { + leftMargin = insets.left + rightMargin = insets.right + } + windowInsets + } + } + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleIntents(intent) diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt b/wallet/src/main/java/net/taler/wallet/MainFragment.kt @@ -26,11 +26,12 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size @@ -153,7 +154,10 @@ class MainFragment: Fragment() { onClick = { tab = Tab.SETTINGS }, ) } - } + }, + contentWindowInsets = WindowInsets.systemBars.only( + WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom + ) ) { innerPadding -> LaunchedEffect(tab, selectedScope) { setTitle(tab, selectedScope) @@ -163,37 +167,36 @@ class MainFragment: Fragment() { model.transactionManager.selectScope(null) } - Box(Modifier.padding(innerPadding).fillMaxSize()) { - when (tab) { - Tab.BALANCES -> BalancesComposable( - state = balanceState, - txResult = txResult, - selectedScope = selectedScope, - selectedCurrencySpec = selectedSpec, - onBalanceClicked = { - model.showTransactions(it.scopeInfo) - }, - onTransactionClicked = { tx -> - if (tx.detailPageNav != 0) { - model.transactionManager.selectTransaction(tx) - findNavController().navigate(tx.detailPageNav) - } - }, - onTransactionsDelete = { txIds -> - model.transactionManager.deleteTransactions(txIds) { error -> - Toast.makeText(context, error.userFacingMsg, Toast.LENGTH_LONG).show() - } - }, - onShowBalancesClicked = { - if (model.transactionManager.selectedScope.value != null) { - model.transactionManager.selectScope(null) - } - }, - ) - Tab.SETTINGS -> SettingsView( - settingsFragmentState = settingsFragmentState, - ) - } + when (tab) { + Tab.BALANCES -> BalancesComposable( + innerPadding = innerPadding, + state = balanceState, + txResult = txResult, + selectedScope = selectedScope, + selectedCurrencySpec = selectedSpec, + onBalanceClicked = { + model.showTransactions(it.scopeInfo) + }, + onTransactionClicked = { tx -> + if (tx.detailPageNav != 0) { + model.transactionManager.selectTransaction(tx) + findNavController().navigate(tx.detailPageNav) + } + }, + onTransactionsDelete = { txIds -> + model.transactionManager.deleteTransactions(txIds) { error -> + Toast.makeText(context, error.userFacingMsg, Toast.LENGTH_LONG).show() + } + }, + onShowBalancesClicked = { + if (model.transactionManager.selectedScope.value != null) { + model.transactionManager.selectScope(null) + } + }, + ) + Tab.SETTINGS -> SettingsView( + settingsFragmentState = settingsFragmentState, + ) } } @@ -276,7 +279,9 @@ fun SettingsView( ) { AndroidFragment( SettingsFragment::class.java, - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .systemBarsPaddingAllExceptTop(), fragmentState = settingsFragmentState, ) } diff --git a/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt b/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt @@ -25,6 +25,9 @@ import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.core.content.getSystemService import androidx.core.os.bundleOf +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import net.taler.wallet.databinding.FragmentUriInputBinding @@ -43,6 +46,8 @@ class UriInputFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setupInsets() + val clipboard = requireContext().getSystemService<ClipboardManager>() ui.pasteButton.setOnClickListener { @@ -70,4 +75,11 @@ class UriInputFragment : Fragment() { } } + private fun setupInsets() { + ViewCompat.setOnApplyWindowInsetsListener(ui.root) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updatePadding(left = insets.left, right = insets.right, bottom = insets.bottom) + WindowInsetsCompat.CONSUMED + } + } } diff --git a/wallet/src/main/java/net/taler/wallet/Utils.kt b/wallet/src/main/java/net/taler/wallet/Utils.kt @@ -31,11 +31,19 @@ import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.annotation.RequiresApi import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Modifier import androidx.core.content.getSystemService import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity @@ -173,4 +181,23 @@ fun <T> T.useDebounce( } return state -} -\ No newline at end of file +} + +@Composable +fun BottomInsetsSpacer() = Spacer( + Modifier.windowInsetsBottomHeight( + WindowInsets.systemBars, + ), +) + +@Composable +fun Modifier.systemBarsPaddingBottom() = + windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) + +@Composable +fun Modifier.systemBarsPaddingHorizontal() = + windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)) + +@Composable +fun Modifier.systemBarsPaddingAllExceptTop() = + windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)) +\ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalancesComposable.kt b/wallet/src/main/java/net/taler/wallet/balances/BalancesComposable.kt @@ -21,7 +21,9 @@ import androidx.compose.animation.animateContentSize import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -60,6 +62,7 @@ import net.taler.wallet.withdraw.WithdrawalError @Composable fun BalancesComposable( + innerPadding: PaddingValues, state: BalanceState, txResult: TransactionsResult, selectedScope: ScopeInfo?, @@ -75,7 +78,12 @@ fun BalancesComposable( is BalanceState.Error -> WithdrawalError(state.error) is BalanceState.Success -> if (state.balances.isNotEmpty()) { if (selectedScope == null) { - LazyColumn(Modifier.fillMaxSize()) { + LazyColumn( + Modifier + .consumeWindowInsets(innerPadding) + .fillMaxSize(), + contentPadding = innerPadding, + ) { items(state.balances, key = { it.scopeInfo.hashCode() }) { balance -> BalanceRow(balance) { onBalanceClicked(balance) @@ -89,6 +97,7 @@ fun BalancesComposable( balance?.let { TransactionsComposable( + innerPadding = innerPadding, balance = it, currencySpec = selectedCurrencySpec, txResult = txResult, @@ -227,6 +236,7 @@ fun BalancesComposablePreview() { TalerSurface { BalancesComposable( + innerPadding = PaddingValues(0.dp), state = BalanceState.Success(balances), txResult = TransactionsResult.Success(listOf()), selectedScope = null, @@ -244,6 +254,7 @@ fun BalancesComposablePreview() { fun BalancesComposableEmptyPreview() { TalerSurface { BalancesComposable( + innerPadding = PaddingValues(0.dp), state = BalanceState.Success(listOf()), txResult = TransactionsResult.Success(listOf()), selectedScope = null, diff --git a/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt b/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt @@ -31,7 +31,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.KeyboardType diff --git a/wallet/src/main/java/net/taler/wallet/compose/LoadingScreen.kt b/wallet/src/main/java/net/taler/wallet/compose/LoadingScreen.kt @@ -22,11 +22,14 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import net.taler.wallet.systemBarsPaddingBottom @Composable fun LoadingScreen() { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .systemBarsPaddingBottom(), contentAlignment = Alignment.Center, ) { CircularProgressIndicator() diff --git a/wallet/src/main/java/net/taler/wallet/compose/Utils.kt b/wallet/src/main/java/net/taler/wallet/compose/Utils.kt @@ -21,7 +21,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.flowWithLifecycle @@ -57,7 +60,7 @@ fun <T> StateFlow<T>.collectAsStateLifecycleAware( @Composable fun TalerSurface(content: @Composable () -> Unit) { Mdc3Theme { - Surface { + Surface(Modifier.nestedScroll(rememberNestedScrollInteropConnection())) { content() } } diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import net.taler.common.Amount import net.taler.common.CurrencySpecification +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo @@ -265,6 +266,8 @@ fun MakeDepositComposable( ) { Text(stringResource(R.string.send_deposit_create_button)) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt @@ -58,6 +58,7 @@ import androidx.navigation.fragment.findNavController import net.taler.common.Amount import net.taler.common.CurrencySpecification import net.taler.wallet.AmountResult +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.MainViewModel import net.taler.wallet.R import net.taler.wallet.compose.AmountCurrencyField @@ -172,6 +173,8 @@ private fun PayToComposable( ) { Text(text = stringResource(R.string.send_deposit_check_fees_button)) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt @@ -35,6 +35,7 @@ import net.taler.common.Amount import net.taler.common.CurrencySpecification import net.taler.common.Timestamp import net.taler.common.toAbsoluteTime +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED import net.taler.wallet.backend.TalerErrorInfo @@ -100,6 +101,8 @@ fun TransactionDepositComposable( if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt @@ -23,10 +23,18 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.view.ViewGroup.MarginLayoutParams import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.core.os.bundleOf import androidx.core.view.MenuProvider +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.marginBottom +import androidx.core.view.marginLeft +import androidx.core.view.marginRight +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle.State.RESUMED @@ -36,6 +44,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.launch +import net.taler.common.Amount import net.taler.common.EventObserver import net.taler.common.fadeIn import net.taler.common.fadeOut @@ -65,6 +74,8 @@ open class ExchangeListFragment : Fragment(), ExchangeClickListener { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setupInsets() + requireActivity().addMenuProvider(object : MenuProvider { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { if (model.devMode.value == true) { @@ -206,4 +217,29 @@ open class ExchangeListFragment : Fragment(), ExchangeClickListener { } } } + + private fun setupInsets() { + ViewCompat.setOnApplyWindowInsetsListener(ui.list) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updatePadding( + bottom = insets.bottom, + left = insets.left, + right = insets.right, + ) + WindowInsetsCompat.CONSUMED + } + + val fabMarginBottom = ui.addExchangeFab.marginBottom + val fabMarginLeft = ui.addExchangeFab.marginLeft + val fabMarginRight = ui.addExchangeFab.marginRight + ViewCompat.setOnApplyWindowInsetsListener(ui.addExchangeFab) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updateLayoutParams<MarginLayoutParams> { + bottomMargin = fabMarginBottom + insets.bottom + leftMargin = fabMarginLeft + insets.left + rightMargin = fabMarginRight + insets.right + } + WindowInsetsCompat.CONSUMED + } + } } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt @@ -34,6 +34,7 @@ import net.taler.wallet.AmountResult import net.taler.wallet.R import net.taler.wallet.compose.LoadingScreen import net.taler.wallet.compose.TalerSurface +import net.taler.wallet.systemBarsPaddingBottom @Composable fun PayTemplateComposable( @@ -86,7 +87,10 @@ fun PayTemplateComposable( @Composable fun PayTemplateError(message: String) { Box( - modifier = Modifier.padding(16.dp).fillMaxSize(), + modifier = Modifier + .padding(16.dp) + .fillMaxSize() + .systemBarsPaddingBottom(), contentAlignment = Center, ) { Text( diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt @@ -42,6 +42,7 @@ import net.taler.common.Amount import net.taler.common.CurrencySpecification import net.taler.common.RelativeTime import net.taler.wallet.AmountResult +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.compose.AmountCurrencyField import net.taler.wallet.compose.TalerSurface @@ -119,6 +120,8 @@ fun PayTemplateOrderComposable( ) { Text(stringResource(R.string.payment_create_order)) } + + BottomInsetsSpacer() } LaunchedEffect(Unit) { diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt @@ -23,6 +23,10 @@ import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.ViewGroup +import android.view.ViewGroup.MarginLayoutParams +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope @@ -63,6 +67,7 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setupInsets() paymentManager.payStatus.observe(viewLifecycleOwner, ::onPaymentStatusChanged) ui.details.productsList.apply { @@ -86,6 +91,16 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { } } + private fun setupInsets() { + ViewCompat.setOnApplyWindowInsetsListener(ui.bottom.bottomLayout) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updateLayoutParams<MarginLayoutParams> { + bottomMargin = insets.bottom + } + WindowInsetsCompat.CONSUMED + } + } + private fun showLoading(show: Boolean) { model.showProgressBar.value = show if (show) { diff --git a/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt @@ -35,6 +35,7 @@ import net.taler.common.ContractMerchant import net.taler.common.CurrencySpecification import net.taler.common.Timestamp import net.taler.common.toAbsoluteTime +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo @@ -116,6 +117,8 @@ fun TransactionPaymentComposable( if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt @@ -52,6 +52,7 @@ import net.taler.wallet.backend.TalerErrorCode.WALLET_PEER_PULL_PAYMENT_INSUFFIC import net.taler.wallet.backend.TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED import net.taler.wallet.backend.TalerErrorInfo +import net.taler.wallet.systemBarsPaddingBottom data class IncomingData( val isCredit: Boolean, @@ -129,6 +130,7 @@ fun ColumnScope.PeerPullTermsComposable( modifier = Modifier .padding(8.dp) .fillMaxWidth() + .systemBarsPaddingBottom(), ) { Row( modifier = Modifier.align(End), @@ -223,7 +225,7 @@ fun PeerPullCheckingPreview() { } } -@Preview +@Preview(showSystemUi = true) @Composable fun PeerPullTermsPreview() { Surface { @@ -243,7 +245,7 @@ fun PeerPullTermsPreview() { } } -@Preview +@Preview(showSystemUi = true) @Composable fun PeerPullAcceptingPreview() { Surface { diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.unit.dp import kotlinx.serialization.json.JsonPrimitive import net.taler.common.Amount import net.taler.common.CurrencySpecification +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo @@ -227,6 +228,8 @@ fun OutgoingPullIntroComposable( Text(text = stringResource(R.string.receive_peer_create_button)) } } + + BottomInsetsSpacer() } } @@ -255,6 +258,8 @@ fun PeerErrorComposable(state: OutgoingError, onClose: () -> Unit) { ) { Text(text = stringResource(R.string.close)) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.unit.dp import kotlinx.serialization.json.JsonPrimitive import net.taler.common.Amount import net.taler.common.CurrencySpecification +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo @@ -191,6 +192,8 @@ fun OutgoingPushIntroComposable( ) { Text(text = stringResource(R.string.send_peer_create_button)) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt b/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt @@ -34,6 +34,7 @@ import net.taler.common.Amount import net.taler.common.CurrencySpecification import net.taler.common.Timestamp import net.taler.common.toAbsoluteTime +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo @@ -107,6 +108,8 @@ fun TransactionRefundComposable( if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDummyFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDummyFragment.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.unit.dp +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.compose.TalerSurface import net.taler.wallet.compose.collectAsStateLifecycleAware @@ -60,6 +61,7 @@ fun TransactionDummyComposable(t: DummyTransaction) { .verticalScroll(scrollState), horizontalAlignment = CenterHorizontally, ) { - ErrorTransactionButton(error = t.error) + ErrorTransactionButton(error = t.error) + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionLossFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionLossFragment.kt @@ -41,6 +41,7 @@ import net.taler.common.Amount import net.taler.common.CurrencySpecification import net.taler.common.Timestamp import net.taler.common.toAbsoluteTime +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo @@ -123,6 +124,8 @@ fun TransitionLossComposable( if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.unit.sp import net.taler.common.Amount import net.taler.common.CurrencySpecification import net.taler.common.toAbsoluteTime +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.compose.TalerSurface import net.taler.wallet.compose.collectAsStateLifecycleAware @@ -129,6 +130,8 @@ fun TransactionPeerComposable( if (devMode && t.error != null) { ErrorTransactionButton(error = t.error!!) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt @@ -41,6 +41,7 @@ import net.taler.common.Amount import net.taler.common.CurrencySpecification import net.taler.common.Timestamp import net.taler.common.toAbsoluteTime +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo @@ -107,6 +108,8 @@ private fun TransactionRefreshComposable( if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsComposable.kt @@ -24,8 +24,10 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize @@ -104,6 +106,7 @@ import net.taler.wallet.withdraw.WithdrawalError @Composable fun TransactionsComposable( + innerPadding: PaddingValues, balance: BalanceItem, currencySpec: CurrencySpecification?, txResult: TransactionsResult, @@ -154,63 +157,68 @@ fun TransactionsComposable( } } - Column(Modifier.fillMaxSize()) { - if (selectionMode) SelectionModeTopAppBar( - selectedItems = selectedItems, - resetSelectionMode = { - selectionMode = false - selectedItems.clear() - }, - onSelectAllClicked = { - selectedItems.clear() - selectedItems += txResult.transactions.map { it.transactionId } - }, - onDeleteClicked = { - showDeleteDialog = true - }, - ) + LazyColumn( + Modifier + .consumeWindowInsets(innerPadding) + .fillMaxHeight(), + contentPadding = innerPadding, + ) { + item { + if (selectionMode) SelectionModeTopAppBar( + selectedItems = selectedItems, + resetSelectionMode = { + selectionMode = false + selectedItems.clear() + }, + onSelectAllClicked = { + selectedItems.clear() + selectedItems += txResult.transactions.map { it.transactionId } + }, + onDeleteClicked = { + showDeleteDialog = true + }, + ) + } - LazyColumn(Modifier.fillMaxHeight()) { - item { - TransactionsHeader( - balance = balance, - spec = currencySpec, - onShowBalancesClicked = onShowBalancesClicked, - ) - } + item { + TransactionsHeader( + balance = balance, + spec = currencySpec, + onShowBalancesClicked = onShowBalancesClicked, + ) + } - items(txResult.transactions, key = { it.transactionId }) { tx -> - val isSelected = selectedItems.contains(tx.transactionId) - - TransactionRow( - tx, currencySpec, - isSelected = isSelected, - selectionMode = selectionMode, - onTransactionClick = { - if (selectionMode) { - if (isSelected) { - selectedItems.remove(tx.transactionId) - } else { - selectedItems.add(tx.transactionId) - } + items(txResult.transactions, key = { it.transactionId }) { tx -> + val isSelected = selectedItems.contains(tx.transactionId) + + TransactionRow( + tx, currencySpec, + isSelected = isSelected, + selectionMode = selectionMode, + onTransactionClick = { + if (selectionMode) { + if (isSelected) { + selectedItems.remove(tx.transactionId) } else { - onTransactionClick(tx) + selectedItems.add(tx.transactionId) } - }, - onTransactionSelect = { - if (selectionMode) { - if (isSelected) { - selectedItems.remove(tx.transactionId) - } else { - selectedItems.add(tx.transactionId) - } + } else { + onTransactionClick(tx) + } + }, + onTransactionSelect = { + if (selectionMode) { + if (isSelected) { + selectedItems.remove(tx.transactionId) } else { - selectionMode = true selectedItems.add(tx.transactionId) } - }, - ) - } + } else { + selectionMode = true + selectedItems.add(tx.transactionId) + } + }, + ) } } } @@ -467,6 +475,7 @@ fun TransactionsComposableDonePreview() { TalerSurface { TransactionsComposable( + innerPadding = PaddingValues(0.dp), balance = previewBalance, currencySpec = null, txResult = Success(transactions), @@ -496,6 +505,7 @@ fun TransactionsComposablePendingPreview() { TalerSurface { TransactionsComposable( + innerPadding = PaddingValues(0.dp), balance = previewBalance, currencySpec = null, txResult = Success(transactions), @@ -511,6 +521,7 @@ fun TransactionsComposablePendingPreview() { fun TransactionsComposableEmptyPreview() { TalerSurface { TransactionsComposable( + innerPadding = PaddingValues(0.dp), balance = previewBalance, currencySpec = null, txResult = Success(listOf()), diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt @@ -22,6 +22,14 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup +import android.view.ViewGroup.MarginLayoutParams +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.marginBottom +import androidx.core.view.marginLeft +import androidx.core.view.marginRight +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle @@ -59,6 +67,7 @@ class ReviewExchangeTosFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setupInsets() val exchangeBaseUrl = arguments?.getString("exchangeBaseUrl") ?: error("no exchangeBaseUrl passed") @@ -102,6 +111,31 @@ class ReviewExchangeTosFragment : Fragment() { } } + private fun setupInsets() { + ViewCompat.setOnApplyWindowInsetsListener(ui.tosList) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updatePadding( + left = insets.left, + right = insets.right, + bottom = insets.bottom, + ) + WindowInsetsCompat.CONSUMED + } + + val checkboxMarginLeft = ui.acceptTosCheckBox.marginLeft + val checkboxMarginRight = ui.acceptTosCheckBox.marginRight + val checkboxMarginBottom = ui.acceptTosCheckBox.marginBottom + ViewCompat.setOnApplyWindowInsetsListener(ui.acceptTosCheckBox) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updateLayoutParams<MarginLayoutParams> { + leftMargin = checkboxMarginLeft + insets.left + rightMargin = checkboxMarginRight + insets.right + bottomMargin = checkboxMarginBottom + insets.bottom + } + WindowInsetsCompat.CONSUMED + } + } + private fun onTosError(msg: String) { ui.tosList.fadeIn() ui.progressBar.fadeOut() diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt @@ -36,6 +36,7 @@ import net.taler.common.CurrencySpecification import net.taler.common.RelativeTime import net.taler.common.Timestamp import net.taler.common.toAbsoluteTime +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo @@ -119,6 +120,8 @@ fun TransactionWithdrawalComposable( if (devMode && t.error != null) { ErrorTransactionButton(error = t.error) } + + BottomInsetsSpacer() } } diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt @@ -55,6 +55,7 @@ import net.taler.wallet.CURRENCY_BTC import net.taler.wallet.R import net.taler.common.canAppHandleUri import net.taler.common.copyToClipBoard +import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.balances.ScopeInfo import net.taler.wallet.compose.ShareButton import net.taler.wallet.transactions.AmountType @@ -181,6 +182,8 @@ fun ScreenTransfer( .padding(bottom = 16.dp), ) } + + BottomInsetsSpacer() } } } diff --git a/wallet/src/main/res/layout-w550dp/payment_bottom_bar.xml b/wallet/src/main/res/layout-w550dp/payment_bottom_bar.xml @@ -24,6 +24,7 @@ tools:showIn="@layout/fragment_prompt_payment"> <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/bottomLayout" android:layout_width="match_parent" android:layout_height="wrap_content"> diff --git a/wallet/src/main/res/layout/activity_main.xml b/wallet/src/main/res/layout/activity_main.xml @@ -14,19 +14,68 @@ ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> --> -<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" - tools:openDrawer="start"> + xmlns:tools="http://schemas.android.com/tools"> - <include - android:id="@+id/content" - layout="@layout/app_content_main" + <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="wrap_content" + android:fitsSystemWindows="true"> -</androidx.drawerlayout.widget.DrawerLayout> + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </com.google.android.material.appbar.AppBarLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> + + <me.zhanghai.android.materialprogressbar.MaterialProgressBar + android:id="@+id/progress_bar" + style="@style/Widget.MaterialProgressBar.ProgressBar" + android:layout_width="match_parent" + android:layout_height="4dp" + android:elevation="4dp" + android:indeterminate="true" + android:visibility="invisible" + app:mpb_progressStyle="horizontal" + app:mpb_useIntrinsicPadding="false" + tools:visibility="visible" /> + + <FrameLayout + android:id="@+id/offline_banner" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/colorPrimary" + android:animateLayoutChanges="true" + android:visibility="gone" + tools:visibility="visible"> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:textAlignment="center" + android:textColor="?attr/colorOnPrimary" + android:text="@string/offline_banner" /> + </FrameLayout> + + <androidx.fragment.app.FragmentContainerView + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + app:defaultNavHost="true" + app:navGraph="@navigation/nav_graph" /> + + </LinearLayout> + +</androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/wallet/src/main/res/layout/app_content_main.xml b/wallet/src/main/res/layout/app_content_main.xml @@ -1,98 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ This file is part of GNU Taler - ~ (C) 2020 Taler Systems S.A. - ~ - ~ GNU Taler is free software; you can redistribute it and/or modify it under the - ~ terms of the GNU General Public License as published by the Free Software - ~ Foundation; either version 3, or (at your option) any later version. - ~ - ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. - ~ - ~ You should have received a copy of the GNU General Public License along with - ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - --> - -<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".MainActivity"> - - <com.google.android.material.appbar.AppBarLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:theme="@style/AppTheme.AppBarOverlay"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/relativeLayout" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <com.google.android.material.appbar.MaterialToolbar - android:id="@+id/toolbar" - style="@style/AppTheme.Toolbar" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:theme="@style/Widget.Material3.ActionBar.Solid" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <FrameLayout - android:id="@+id/offline_banner" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:background="?attr/colorPrimary" - app:layout_constraintTop_toBottomOf="@id/toolbar" - app:layout_constraintBottom_toTopOf="@id/progress_bar" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - android:animateLayoutChanges="true" - android:visibility="gone" - tools:visibility="visible"> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="10dp" - android:textAlignment="center" - android:textColor="?attr/colorOnPrimary" - android:text="@string/offline_banner" /> - </FrameLayout> - - <me.zhanghai.android.materialprogressbar.MaterialProgressBar - android:id="@+id/progress_bar" - style="@style/Widget.MaterialProgressBar.ProgressBar" - android:layout_width="0dp" - android:layout_height="4dp" - android:elevation="4dp" - android:indeterminate="true" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/offline_banner" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:mpb_progressStyle="horizontal" - app:mpb_useIntrinsicPadding="false" - tools:visibility="visible" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - - </com.google.android.material.appbar.AppBarLayout> - - <androidx.fragment.app.FragmentContainerView - android:id="@+id/nav_host_fragment" - android:name="androidx.navigation.fragment.NavHostFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - app:defaultNavHost="true" - app:layout_behavior="@string/appbar_scrolling_view_behavior" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:navGraph="@navigation/nav_graph" /> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/wallet/src/main/res/layout/fragment_exchange_list.xml b/wallet/src/main/res/layout/fragment_exchange_list.xml @@ -26,6 +26,7 @@ android:layout_height="match_parent" android:scrollbars="vertical" android:visibility="invisible" + android:clipToPadding="false" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:listitem="@layout/list_item_exchange" tools:visibility="visible" /> diff --git a/wallet/src/main/res/layout/fragment_review_exchange_tos.xml b/wallet/src/main/res/layout/fragment_review_exchange_tos.xml @@ -30,6 +30,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + android:clipToPadding="false" tools:listitem="@layout/list_item_tos" /> <ProgressBar diff --git a/wallet/src/main/res/layout/payment_bottom_bar.xml b/wallet/src/main/res/layout/payment_bottom_bar.xml @@ -24,6 +24,7 @@ tools:showIn="@layout/fragment_prompt_payment"> <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/bottomLayout" android:layout_width="match_parent" android:layout_height="wrap_content">