taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit e5178c02dc267cc7d3fec62cbf7adbeab0bbc754
parent 453ff23088310aceec251764691c436b9c9634c8
Author: Bohdan Potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Fri,  2 May 2025 10:36:46 +0200

[pos] updating the time selection on the config page to a proper one :-D

Diffstat:
Mmerchant-terminal/build.gradle | 1+
Mmerchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mmerchant-terminal/src/main/res/layout/fragment_merchant_config.xml | 74++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mmerchant-terminal/src/main/res/values/strings.xml | 6++++++
4 files changed, 111 insertions(+), 51 deletions(-)

diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle @@ -69,6 +69,7 @@ dependencies { implementation "com.google.android.material:material:$material_version" implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version" + implementation 'androidx.compose.material3:material3:1.3.2' implementation "androidx.recyclerview:recyclerview:1.3.2" implementation "androidx.recyclerview:recyclerview-selection:1.1.0" diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt @@ -17,6 +17,8 @@ package net.taler.merchantpos.config import android.Manifest +import android.app.TimePickerDialog +import android.app.DatePickerDialog import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle @@ -27,6 +29,9 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup +import android.widget.Button +import android.widget.RadioButton +import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.OptIn @@ -43,7 +48,6 @@ import androidx.lifecycle.lifecycleScope import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG import com.google.android.material.snackbar.Snackbar -import com.google.android.material.textfield.TextInputEditText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -58,6 +62,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.common.InputImage import net.taler.merchantpos.MainActivity +import android.text.format.DateFormat +import java.util.Calendar +import java.util.Locale /** * Fragment that displays merchant settings, either by scanning a QR code @@ -92,6 +99,17 @@ class ConfigFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + // 1) Views + val neverOption = ui.root.findViewById<RadioButton>(R.id.neverExpiresOption) + val dateOption = ui.root.findViewById<RadioButton>(R.id.dateExpiresOption) + val deadlineLayout = ui.root.findViewById<View>(R.id.deadlinePickerLayout) + val selectDateButton = ui.root.findViewById<Button>(R.id.selectDateButton) + val selectTimeButton = ui.root.findViewById<Button>(R.id.selectTimeButton) + val selectedDeadline = ui.root.findViewById<TextView>(R.id.selectedDeadline) + + // 2) Shared Calendar instance for storing the deadline + val deadlineCal = Calendar.getInstance() + // set initial toggle ui.configToggle.check(R.id.newConfigButton) @@ -105,18 +123,6 @@ class ConfigFragment : Fragment() { } } - ui.timeOptionGroup.setOnCheckedChangeListener { _, checkedId -> - when (checkedId) { - R.id.foreverOption -> { - ui.customDurationLayout.visibility = GONE - } - R.id.customOption -> { - ui.customDurationLayout.visibility = VISIBLE - } - } - } - - // 1) Extract base URL and username if pasted with /instances/username // Only parse URL when user finishes editing (focus lost) ui.merchantUrlView.editText!!.setOnFocusChangeListener { v, hasFocus -> @@ -142,21 +148,11 @@ class ConfigFragment : Fragment() { // initial secret/token from user val initialSecret = ui.tokenView.editText!!.text.toString().trim() - val duration : TokenDuration = if (ui.foreverOption.isChecked) { + val duration: TokenDuration = if (neverOption.isChecked) { TokenDuration.Forever } else { - val value = ui.durationValueInput.text.toString().toLongOrNull() - ?: throw IllegalArgumentException("Please enter a number") - val unit = ui.durationUnitSpinner.selectedItem.toString() - // convert to microseconds - val factor = when (unit) { - "seconds" -> 1_000_000L - "minutes" -> 60 * 1_000_000L - "hours" -> 60 * 60 * 1_000_000L - "days" -> 24 * 60 * 60 * 1_000_000L - else -> 1_000_000L - } - TokenDuration.Micros(value * factor) + val micros = deadlineCal.timeInMillis * 1_000L + TokenDuration.Micros(micros) } // fetch limited write token @@ -187,12 +183,43 @@ class ConfigFragment : Fragment() { } } + + fun updateDeadlineText() { + val fmt = java.text.SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault()) + selectedDeadline.text = fmt.format(deadlineCal.time) + } + + + ui.expiryOptionGroup.setOnCheckedChangeListener { _, checkedId -> + deadlineLayout.visibility = if (checkedId == R.id.dateExpiresOption) VISIBLE else GONE + } + + selectDateButton.setOnClickListener { + val year = deadlineCal.get(Calendar.YEAR) + val month = deadlineCal.get(Calendar.MONTH) + val day = deadlineCal.get(Calendar.DAY_OF_MONTH) + DatePickerDialog(requireContext(), { _, y, m, d -> + deadlineCal.set(y, m, d) + updateDeadlineText() + }, year, month, day).show() + } + + selectTimeButton.setOnClickListener { + val hour = deadlineCal.get(Calendar.HOUR_OF_DAY) + val minute = deadlineCal.get(Calendar.MINUTE) + TimePickerDialog(requireContext(), { _, h, min -> + deadlineCal.set(Calendar.HOUR_OF_DAY, h) + deadlineCal.set(Calendar.MINUTE, min) + updateDeadlineText() + }, hour, minute, DateFormat.is24HourFormat(requireContext())).show() + } + updateView(savedInstanceState == null) } override fun onStart() { super.onStart() - // nothing to do here + } override fun onResume() { diff --git a/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml b/merchant-terminal/src/main/res/layout/fragment_merchant_config.xml @@ -177,9 +177,9 @@ app:layout_constraintBottom_toBottomOf="@id/tokenView" app:layout_constraintEnd_toEndOf="parent" /> - <!-- Relative Time Selection --> + <!-- ─── Expiry Section: radio buttons + conditional date/time pickers ─── --> <LinearLayout - android:id="@+id/relativeTimeGroup" + android:id="@+id/expirySection" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="16dp" @@ -188,54 +188,80 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"> + <!-- Expiry options --> <RadioGroup - android:id="@+id/timeOptionGroup" + android:id="@+id/expiryOptionGroup" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <RadioButton - android:id="@+id/foreverOption" + android:id="@+id/neverExpiresOption" android:layout_width="0dp" - android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" + android:layout_height="wrap_content" android:checked="true" - android:text="@string/forever" /> + android:text="@string/never_expires"/> <RadioButton - android:id="@+id/customOption" + android:id="@+id/dateExpiresOption" android:layout_width="0dp" - android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" - android:text="@string/custom_duration" /> + android:layout_height="wrap_content" + android:text="@string/expires_on"/> </RadioGroup> + <!-- Deadline picker (initially hidden) --> <LinearLayout - android:id="@+id/customDurationLayout" + android:id="@+id/deadlinePickerLayout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal" + android:orientation="vertical" android:visibility="gone" android:layout_marginTop="8dp"> - <EditText - android:id="@+id/durationValueInput" - android:layout_width="0dp" - android:layout_weight="1" + <TextView + android:id="@+id/deadlineLabel" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:inputType="number" - android:hint="@string/duration" /> + android:text="@string/token_validity_deadline" + style="@style/TextAppearance.Material3.BodyMedium" /> - <Spinner - android:id="@+id/durationUnitSpinner" + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginTop="8dp"> + + <com.google.android.material.button.MaterialButton + style="?attr/materialButtonOutlinedStyle" + android:id="@+id/selectDateButton" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="@string/pick_date" /> + + <com.google.android.material.button.MaterialButton + style="?attr/materialButtonOutlinedStyle" + android:id="@+id/selectTimeButton" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="@string/pick_time" /> + </LinearLayout> + + <TextView + android:id="@+id/selectedDeadline" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:entries="@array/time_units_array" /> + android:layout_marginTop="8dp" + android:text="@string/no_deadline_set" + style="@style/TextAppearance.Material3.BodyMedium"/> </LinearLayout> </LinearLayout> + + <!-- save-password checkbox --> <CheckBox android:id="@+id/saveTokenCheckBox" @@ -248,7 +274,7 @@ android:text="@string/config_save_password" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/okNewButton" - app:layout_constraintTop_toBottomOf="@id/relativeTimeGroup" + app:layout_constraintTop_toBottomOf="@id/expirySection" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_chainStyle="spread_inside" /> @@ -260,7 +286,7 @@ android:layout_margin="16dp" android:text="@string/config_ok" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/relativeTimeGroup" + app:layout_constraintTop_toBottomOf="@id/expirySection" app:layout_constraintBottom_toBottomOf="parent" /> <!-- progress spinner --> diff --git a/merchant-terminal/src/main/res/values/strings.xml b/merchant-terminal/src/main/res/values/strings.xml @@ -93,6 +93,12 @@ <string name="forever">Forever</string> <string name="custom_duration">Custom duration</string> <string name="duration">Duration</string> + <string name="token_validity_deadline">Token validity deadline:</string> + <string name="expires_on">Expires on…</string> + <string name="never_expires">Never expires</string> + <string name="no_deadline_set">No deadline set</string> + <string name="pick_date">Pick date</string> + <string name="pick_time">Pick time</string> <string-array name="time_units_array"> <item>Seconds</item> <item>Minutes</item>