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