Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eef85f1f authored by Sunik Kupfer's avatar Sunik Kupfer Committed by Ricki Hirner
Browse files

Provide three options for donation reminder time (#1256)

* Provide three options for donation reminder time

* Move radio buttons to top card; Change strings

* Use padding instead of height for radio elements
parent 4f2d4e3a
Loading
Loading
Loading
Loading
+79 −0
Original line number Diff line number Diff line
/*
 * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
 */

package at.bitfire.davdroid.ui.composable

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import at.bitfire.davdroid.ui.AppTheme

@Composable
fun RadioButtons(
    radioOptions: List<String> = listOf<String>(),
    onOptionSelected: (String) -> Unit = {},
    textPaddingPerOption: PaddingValues = PaddingValues(10.dp),
    modifier: Modifier = Modifier
) {
    val (selectedOption, setSelectedOption) = remember { mutableStateOf(radioOptions[0]) }
    Column(
        // Modifier.selectableGroup() is essential to ensure correct accessibility behavior
        modifier = modifier.selectableGroup()
    ) {
        radioOptions.forEach { text ->
            Row(
                Modifier
                    .fillMaxWidth()
                    .selectable(
                        selected = (text == selectedOption),
                        onClick = {
                            setSelectedOption(text)
                            onOptionSelected(text)
                        },
                        role = Role.Companion.RadioButton
                    )
                    .padding(horizontal = 16.dp),
                verticalAlignment = Alignment.CenterVertically
            ) {
                RadioButton(
                    selected = (text == selectedOption),
                    onClick = null, // null recommended for accessibility with screen readers
                )
                Text(
                    text = text,
                    style = MaterialTheme.typography.bodyLarge,
                    modifier = Modifier.padding(textPaddingPerOption)
                )
            }
        }
    }
}

@Preview
@Composable
private fun RadioButtonsPreview() {
    AppTheme {
        RadioButtons(
            radioOptions = listOf(
                "Option 1",
                "Option 2 is the longest of all the options, so we can see whether line breaks are not a problem.",
                "Option 3")
        )
    }
}
 No newline at end of file
+58 −39
Original line number Diff line number Diff line
@@ -4,37 +4,34 @@

package at.bitfire.davdroid.ui.intro

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.Constants
import at.bitfire.davdroid.Constants.withStatParams
import at.bitfire.davdroid.R
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.AppTheme
import at.bitfire.davdroid.ui.composable.CardWithImage
import at.bitfire.davdroid.ui.composable.RadioButtons
import dagger.hilt.android.lifecycle.HiltViewModel
import java.util.logging.Logger
import javax.inject.Inject

class OpenSourcePage @Inject constructor(
@@ -55,43 +52,46 @@ class OpenSourcePage @Inject constructor(

    @Composable
    private fun Page(model: Model = viewModel()) {
        val dontShow by model.dontShow.collectAsStateWithLifecycle(false)
        OpenSourcePage(
            dontShow = dontShow,
            onChangeDontShow = {
                model.setDontShow(it)
            }
            donationPopupIntervalOptions = model.donationPopupIntervalOptions,
            onChangeDontShowFor = model::setDontShowFor
        )
    }

    @HiltViewModel
    class Model @Inject constructor(
        val settings: SettingsManager
        private val settings: SettingsManager,
        private val logger: Logger
    ): ViewModel() {

        companion object {
            const val SETTING_NEXT_DONATION_POPUP = "time_nextDonationPopup"
        }

        val dontShow = settings.containsKeyFlow(SETTING_NEXT_DONATION_POPUP)
        /**
         * Possible number of months (30 days) to hide the donation popup for.
         */
        val donationPopupIntervalOptions = listOf(1, 3, 9)

        fun setDontShow(dontShowAgain: Boolean) {
            if (dontShowAgain) {
                val nextReminder = System.currentTimeMillis() + 90*86400000L     // 90 days (~ 3 months)
        /**
         * Set the next time the donation popup should be shown.
         * @param dontShowFor Number of months (30 days) to hide the donation popup for.
         */
        fun setDontShowFor(dontShowFor: Int) {
            logger.info("Setting next donation popup to $dontShowFor months")
            val month = 30*86400000L            // 30 days (~ 1 month)
            val nextReminder = month * dontShowFor + System.currentTimeMillis()
            settings.putLong(SETTING_NEXT_DONATION_POPUP, nextReminder)
            } else
                settings.remove(SETTING_NEXT_DONATION_POPUP)
        }

    }

}

@Preview
@Composable
fun OpenSourcePage(
    dontShow: Boolean = false,
    onChangeDontShow: (Boolean) -> Unit = {}
    donationPopupIntervalOptions: List<Int>,
    onChangeDontShowFor: (Int) -> Unit = {}
) {
    val uriHandler = LocalUriHandler.current

@@ -108,9 +108,11 @@ fun OpenSourcePage(
            message = stringResource(
                R.string.intro_open_source_text,
                stringResource(R.string.app_name)
            )
            ),
            modifier = Modifier.padding(vertical = 8.dp)
        ) {
            OutlinedButton(
                modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
                onClick = {
                    uriHandler.openUri(
                        Constants.HOMEPAGE_URL.buildUpon()
@@ -121,24 +123,41 @@ fun OpenSourcePage(
                    )
                }
            ) {
                Text(stringResource(R.string.intro_open_source_details))
            }
            Row(
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier.fillMaxWidth()
            ) {
                Checkbox(
                    checked = dontShow,
                    onCheckedChange = onChangeDontShow
                Text(
                    stringResource(R.string.intro_open_source_details)
                )
            }

            Text(
                text = stringResource(R.string.intro_open_source_dont_show),
                    style = MaterialTheme.typography.bodyMedium,
                    modifier = Modifier
                        .clickable { onChangeDontShow(!dontShow) }
                        .weight(1f)
                style = MaterialTheme.typography.bodyLarge
            )
            val radioOptions = donationPopupIntervalOptions.associate { numberOfMonths ->
                pluralStringResource(
                    R.plurals.intro_open_source_dont_show_months,
                    numberOfMonths,
                    numberOfMonths
                ) to numberOfMonths
            }
            RadioButtons(
                radioOptions = radioOptions.keys.toList(),
                onOptionSelected = { option ->
                    val months = radioOptions[option] ?: radioOptions.values.first()
                    onChangeDontShowFor(months)
                },
                modifier = Modifier.padding(bottom = 12.dp)
            )

        }
    }
}

@Preview
@Composable
fun OpenSourcePagePreview() {
    AppTheme {
        OpenSourcePage(
            donationPopupIntervalOptions = listOf(1, 3, 9)
        )
    }
}
 No newline at end of file
+5 −1
Original line number Diff line number Diff line
@@ -57,7 +57,11 @@
    <string name="intro_open_source_title">Open-source software</string>
    <string name="intro_open_source_text">We\'re happy that you use %s, which is open-source software. Development, maintenance and support are hard work. Please consider contributing (there are many ways) or a donation. It would be highly appreciated!</string>
    <string name="intro_open_source_details">How to contribute/donate</string>
    <string name="intro_open_source_dont_show">Don\'t show in the near future</string>
    <string name="intro_open_source_dont_show">Don\'t remind me for</string>
    <plurals name="intro_open_source_dont_show_months">
        <item quantity="one">%d month</item>
        <item quantity="other">%d months</item>
    </plurals>
    <string name="intro_next">Next</string>

    <!-- PermissionsActivity -->