From da09cfcdf5d94a3250add37897b20bd3c75b6853 Mon Sep 17 00:00:00 2001 From: Anton Weber Date: Fri, 30 Aug 2024 14:19:14 +0200 Subject: [PATCH 1/2] Use question identifiers as keys in QuestionsDialog Previously, we used references to the question instance. Now that we fetch fresh instances more often through SQLDelight, we might end up with multiple instances of the same question being passed into the dialog. To work around this, we can simply use QuestionLike identifier. --- .../android/questions/QuestionsDialog.kt | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/libpretixui-android/src/main/java/eu/pretix/libpretixui/android/questions/QuestionsDialog.kt b/libpretixui-android/src/main/java/eu/pretix/libpretixui/android/questions/QuestionsDialog.kt index 7dcd0f3..dd16807 100644 --- a/libpretixui-android/src/main/java/eu/pretix/libpretixui/android/questions/QuestionsDialog.kt +++ b/libpretixui-android/src/main/java/eu/pretix/libpretixui/android/questions/QuestionsDialog.kt @@ -105,11 +105,11 @@ interface QuestionsDialogInterface : DialogInterface { class QuestionsDialog( val ctx: Activity, val questions: List, - val values: Map? = null, + val values: Map? = null, val defaultCountry: String?, val glideLoader: ((String) -> GlideUrl)? = null, val retryHandler: ((MutableList) -> Unit), - val copyFrom: Map? = null, + val copyFrom: Map? = null, val attendeeName: String? = null, val attendeeDOB: String? = null, val ticketId: String? = null, @@ -128,7 +128,7 @@ class QuestionsDialog( private val fieldViews = HashMap() private val labels = HashMap() private val warnings = HashMap() - private val setters = HashMap Unit)>() + private val setters = HashMap Unit)>() private var v: View = LayoutInflater.from(context).inflate(R.layout.dialog_questions, null) private var waitingForAnswerFor: QuestionLike? = null @@ -232,13 +232,13 @@ class QuestionsDialog( when (question.type) { QuestionType.TEL -> { val fieldS = PhoneEditText(ctx) - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - fieldS.setPhoneNumber(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + fieldS.setPhoneNumber(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { fieldS.setPhoneNumber(question.default) } fieldViews[question] = fieldS - setters[question] = { fieldS.setPhoneNumber(it) } + setters[question.identifier] = { fieldS.setPhoneNumber(it) } if (defaultCountry != null) { fieldS.setDefaultCountry(defaultCountry) } @@ -251,12 +251,12 @@ class QuestionsDialog( } QuestionType.EMAIL -> { val fieldS = EditText(ctx) - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - fieldS.setText(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + fieldS.setText(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { fieldS.setText(question.default) } - setters[question] = { fieldS.setText(it) } + setters[question.identifier] = { fieldS.setText(it) } fieldS.setLines(1) fieldS.isSingleLine = true fieldS.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS @@ -269,12 +269,12 @@ class QuestionsDialog( } QuestionType.S -> { val fieldS = EditText(ctx) - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - fieldS.setText(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + fieldS.setText(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { fieldS.setText(question.default) } - setters[question] = { fieldS.setText(it) } + setters[question.identifier] = { fieldS.setText(it) } fieldS.setLines(1) fieldS.isSingleLine = true fieldS.setOnKeyListener(ctrlEnterListener) @@ -286,12 +286,12 @@ class QuestionsDialog( } QuestionType.T -> { val fieldT = EditText(ctx) - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - fieldT.setText(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + fieldT.setText(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { fieldT.setText(question.default) } - setters[question] = { fieldT.setText(it) } + setters[question.identifier] = { fieldT.setText(it) } fieldT.setLines(2) fieldT.setOnKeyListener(ctrlEnterListener) fieldT.doAfterTextChanged { @@ -302,12 +302,12 @@ class QuestionsDialog( } QuestionType.N -> { val fieldN = EditText(ctx) - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - fieldN.setText(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + fieldN.setText(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { fieldN.setText(question.default) } - setters[question] = { fieldN.setText(it) } + setters[question.identifier] = { fieldN.setText(it) } fieldN.inputType = InputType.TYPE_CLASS_NUMBER.or(InputType.TYPE_NUMBER_FLAG_DECIMAL).or(InputType.TYPE_NUMBER_FLAG_SIGNED) fieldN.isSingleLine = true fieldN.setLines(1) @@ -322,12 +322,12 @@ class QuestionsDialog( QuestionType.B -> { val fieldB = CheckBox(ctx) fieldB.setText(R.string.yes) - if (values?.containsKey(question) == true) { - fieldB.isChecked = "True" == values[question] + if (values?.containsKey(question.identifier) == true) { + fieldB.isChecked = "True" == values[question.identifier] } else if (!question.default.isNullOrBlank()) { fieldB.isChecked = "True" == question.default } - setters[question] = { fieldB.isChecked = "True" == it } + setters[question.identifier] = { fieldB.isChecked = "True" == it } fieldB.setOnKeyListener(ctrlEnterListener) fieldB.setOnCheckedChangeListener { buttonView, isChecked -> updateDependencyVisibilities() @@ -356,7 +356,7 @@ class QuestionsDialog( val imgF = ImageView(ctx) val btnFD = Button(ctx) - setters[question] = { + setters[question.identifier] = { if (it.isNullOrBlank()) { imgF.visibility = View.GONE btnFD.visibility = View.GONE @@ -415,7 +415,7 @@ class QuestionsDialog( } } } - setters[question]!!(values?.get(question)) + setters[question.identifier]!!(values?.get(question.identifier)) imgF.layoutParams = LinearLayout.LayoutParams(160, 120) fieldsF.add(imgF) @@ -455,8 +455,8 @@ class QuestionsDialog( } QuestionType.M -> { val fields = ArrayList() - val selected = if (values?.containsKey(question) == true) { - values[question]!!.split(",") + val selected = if (values?.containsKey(question.identifier) == true) { + values[question.identifier]!!.split(",") } else if (!question.default.isNullOrBlank()) { question.default.split(",") } else { @@ -477,7 +477,7 @@ class QuestionsDialog( fields.add(field) llFormFields.addView(field) } - setters[question] = { + setters[question.identifier] = { for (f in fields) { if (it != null && it.contains((f.tag as QuestionOption).server_id.toString())) { f.isChecked = true @@ -490,17 +490,17 @@ class QuestionsDialog( val fieldC = Spinner(ctx) fieldC.adapter = CountryAdapter(ctx) val defaultcc = CountryCode.getByAlpha2Code(Locale.getDefault().country) - setters[question] = { + setters[question.identifier] = { val cc = CountryCode.getByAlpha2Code(it) fieldC.setSelection((fieldC.adapter as CountryAdapter).getIndex(cc ?: defaultcc)) } - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - setters[question]!!(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + setters[question.identifier]!!(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { - setters[question]!!(question.default) + setters[question.identifier]!!(question.default) } else { - setters[question]!!(defaultcc.alpha2) + setters[question.identifier]!!(defaultcc.alpha2) } fieldC.setOnKeyListener(ctrlEnterListener) fieldC.onItemSelectedListener = object : OnItemSelectedListener { @@ -527,7 +527,7 @@ class QuestionsDialog( opts.add(0, emptyOpt) fieldC.adapter = OptionAdapter(ctx, opts.filter { it != null } as MutableList) - setters[question] = { + setters[question.identifier] = { var i = 1 // 0 = empty opt for (opt in question.options) { if (opt.server_id.toString() == it) { @@ -538,10 +538,10 @@ class QuestionsDialog( } } - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - setters[question]!!(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + setters[question.identifier]!!(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { - setters[question]!!(question.default) + setters[question.identifier]!!(question.default) } fieldC.setOnKeyListener(ctrlEnterListener) fieldC.onItemSelectedListener = object : OnItemSelectedListener { @@ -565,17 +565,17 @@ class QuestionsDialog( } QuestionType.D -> { val fieldD = DatePickerField(ctx, question.valid_date_min, question.valid_date_max) - setters[question] = { + setters[question.identifier] = { try { fieldD.setValue(df.parse(it)) } catch (e: ParseException) { e.printStackTrace() } } - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - setters[question]!!(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + setters[question.identifier]!!(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { - setters[question]!!(question.default) + setters[question.identifier]!!(question.default) } fieldD.setOnKeyListener(ctrlEnterListener) fieldD.doAfterTextChanged { @@ -586,17 +586,17 @@ class QuestionsDialog( } QuestionType.H -> { val fieldH = TimePickerField(ctx) - setters[question] = { + setters[question.identifier] = { try { fieldH.value = LocalTime.fromDateFields(hf.parse(it)) } catch (e: ParseException) { e.printStackTrace() } } - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - setters[question]!!(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + setters[question.identifier]!!(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { - setters[question]!!(question.default) + setters[question.identifier]!!(question.default) } fieldH.setOnKeyListener(ctrlEnterListener) fieldH.doAfterTextChanged { @@ -630,7 +630,7 @@ class QuestionsDialog( fieldsW.add(fieldWH) llInner.addView(fieldWH) - setters[question] = { + setters[question.identifier] = { try { fieldWD.setValue(wf.parse(it)) fieldWH.value = LocalTime.fromDateFields(wf.parse(it)) @@ -639,10 +639,10 @@ class QuestionsDialog( } } - if (values?.containsKey(question) == true && !values[question].isNullOrBlank()) { - setters[question]!!(values[question]) + if (values?.containsKey(question.identifier) == true && !values[question.identifier].isNullOrBlank()) { + setters[question.identifier]!!(values[question.identifier]) } else if (!question.default.isNullOrBlank()) { - setters[question]!!(question.default) + setters[question.identifier]!!(question.default) } fieldViews[question] = fieldsW llFormFields.addView(llInner) @@ -942,11 +942,11 @@ class QuestionsDialog( fun showQuestionsDialog( ctx: Activity, questions: List, - values: Map? = null, + values: Map? = null, defaultCountry: String?, glideLoader: ((String) -> GlideUrl)? = null, retryHandler: ((MutableList) -> Unit), - copyFrom: Map? = null, + copyFrom: Map? = null, attendeeName: String? = null, attendeeDOB: String? = null, ticketId: String? = null, From 17e7a38ee194695efe072bcdf044c523e2e81fcd Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Thu, 21 Nov 2024 18:44:04 +0100 Subject: [PATCH 2/2] Move common setup ui into libpretixui --- .../android/setup/SetupFragment.kt | 369 ++++++++++++++++++ .../main/res/layout/dialog_setup_manual.xml | 38 ++ .../src/main/res/layout/fragment_setup.xml | 82 ++++ .../src/main/res/menu/menu_setup.xml | 8 + .../src/main/res/values-de/strings.xml | 19 +- .../src/main/res/values/strings.xml | 19 +- 6 files changed, 533 insertions(+), 2 deletions(-) create mode 100644 libpretixui-android/src/main/java/eu/pretix/libpretixui/android/setup/SetupFragment.kt create mode 100644 libpretixui-android/src/main/res/layout/dialog_setup_manual.xml create mode 100644 libpretixui-android/src/main/res/layout/fragment_setup.xml create mode 100644 libpretixui-android/src/main/res/menu/menu_setup.xml diff --git a/libpretixui-android/src/main/java/eu/pretix/libpretixui/android/setup/SetupFragment.kt b/libpretixui-android/src/main/java/eu/pretix/libpretixui/android/setup/SetupFragment.kt new file mode 100644 index 0000000..65560b2 --- /dev/null +++ b/libpretixui-android/src/main/java/eu/pretix/libpretixui/android/setup/SetupFragment.kt @@ -0,0 +1,369 @@ +package eu.pretix.libpretixui.android.setup + +import android.Manifest +import android.app.Dialog +import android.app.ProgressDialog +import android.content.pm.PackageManager +import android.os.Bundle +import android.text.Editable +import android.text.method.TextKeyListener +import android.text.method.TextKeyListener.Capitalize +import android.util.Log +import android.view.KeyEvent +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatDialog +import androidx.core.content.ContextCompat +import androidx.core.os.bundleOf +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.Lifecycle +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import eu.pretix.libpretixsync.setup.SetupBadRequestException +import eu.pretix.libpretixsync.setup.SetupBadResponseException +import eu.pretix.libpretixsync.setup.SetupException +import eu.pretix.libpretixsync.setup.SetupServerErrorException +import eu.pretix.libpretixui.android.R +import eu.pretix.libpretixui.android.databinding.FragmentSetupBinding +import eu.pretix.libpretixui.android.scanning.HardwareScanner +import eu.pretix.libpretixui.android.scanning.ScanReceiver +import eu.pretix.libpretixui.android.scanning.ScannerView +import eu.pretix.libpretixui.android.scanning.ScannerView.ResultHandler +import eu.pretix.libpretixui.android.scanning.defaultToScanner +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import org.json.JSONException +import org.json.JSONObject +import java.io.IOException +import java.lang.Exception +import javax.net.ssl.SSLException + +interface SetupCallable { + fun setup(url: String, token: String) + fun onSucessfulSetup() + fun onGenericSetupException(e: Exception) +} + +class SetupFragment : Fragment() { + companion object { + const val ARG_DEFAULT_HOST = "default_host" + } + + private var _binding: FragmentSetupBinding? = null + private val binding get() = _binding!! + private var defaultHost: String? = null + private var useCamera = true + var lastScanTime = 0L + var lastScanValue = "" + private var currentOpenAlert: AppCompatDialog? = null + private var tkl = TextKeyListener(Capitalize.NONE, false) + private var keyboardEditable = Editable.Factory.getInstance().newEditable("") + private var ongoingSetup = false + private val LOG_TAG = this::class.java.name + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _binding = FragmentSetupBinding.inflate(inflater, container, false) + return binding.root + } + + private val hardwareScanner = HardwareScanner(object : ScanReceiver { + override fun scanResult(result: String) { + handleScan(result) + } + }) + + private val scannerResultHandler = object : ResultHandler { + override fun handleResult(rawResult: ScannerView.Result) { + if (lastScanValue == rawResult.text && lastScanTime > System.currentTimeMillis() - 3000) { + return + } + lastScanValue = rawResult.text + lastScanTime = System.currentTimeMillis() + handleScan(rawResult.text) + } + } + + fun handleScan(result: String) { + try { + val jd = JSONObject(result) + if (jd.has("version")) { + alert(R.string.setup_error_legacy_qr_code) + return + } + if (!jd.has("handshake_version")) { + alert(R.string.setup_error_invalid_qr_code) + return + } + if (jd.getInt("handshake_version") > 1) { + alert(R.string.setup_error_version_too_high) + return + } + if (!jd.has("url") || !jd.has("token")) { + alert(R.string.setup_error_invalid_qr_code) + return + } + initialize(jd.getString("url"), jd.getString("token")) + } catch (e: JSONException) { + alert(R.string.setup_error_invalid_qr_code) + return + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + defaultHost = requireArguments().getString(ARG_DEFAULT_HOST, "") + useCamera = !defaultToScanner() + + binding.btSwitchCamera.setOnClickListener { + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + hardwareScanner.stop(requireContext()) + useCamera = true + binding.scannerView.setResultHandler(scannerResultHandler) + binding.scannerView.startCamera() + binding.llHardwareScan.visibility = if (useCamera) View.GONE else View.VISIBLE + } + } + + val menuHost: MenuHost = requireActivity() as MenuHost + menuHost.addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.menu_setup, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + return when (menuItem.itemId) { + R.id.action_manual -> { + showManualSetupDialog() + return true + } + else -> false + } + } + }, viewLifecycleOwner, Lifecycle.State.RESUMED) + + val requestPermissionLauncher = + registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (!isGranted) { + Toast.makeText(requireContext(), getString(R.string.setup_grant_camera_permission), Toast.LENGTH_SHORT).show() + } else { + binding.scannerView.startCamera() + if (useCamera) { + binding.scannerView.setResultHandler(scannerResultHandler) + binding.scannerView.startCamera() + } + } + } + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + requestPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } + + override fun onResume() { + super.onResume() + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + if (useCamera) { + binding.scannerView.setResultHandler(scannerResultHandler) + binding.scannerView.startCamera() + } + } + binding.llHardwareScan.visibility = if (useCamera) View.GONE else View.VISIBLE + hardwareScanner.start(requireContext()) + } + + override fun onPause() { + super.onPause() + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + if (useCamera) { + binding.scannerView.stopCamera() + } + } + hardwareScanner.stop(requireContext()) + } + + fun showManualSetupDialog() { + childFragmentManager.setFragmentResultListener( + ManualSetupDialogFragment.KEY, + viewLifecycleOwner, + { _, bundle -> + val url = bundle.getString(ManualSetupDialogFragment.RESULT_URL) + val token = bundle.getString(ManualSetupDialogFragment.RESULT_TOKEN) + if (url != null && token != null) { + initialize(url, token) + } + childFragmentManager.clearFragmentResultListener(ManualSetupDialogFragment.KEY) + }) + + val args = bundleOf(ManualSetupDialogFragment.ARG_DEFAULT_URL to defaultHost) + childFragmentManager.beginTransaction() + .add(ManualSetupDialogFragment::class.java, args, "MANUAL_SETUP_DIALOG") + .commit() + } + + // NOTE: this needs manually be called in the Activity + fun dispatchKeyEvent(event: KeyEvent): Boolean { + if (event.keyCode == KeyEvent.KEYCODE_ENTER) { + if (event.action == KeyEvent.ACTION_UP) { + handleScan(keyboardEditable.toString()) + keyboardEditable.clear() + } + return true + } + val processed = when (event.action) { + KeyEvent.ACTION_DOWN -> tkl.onKeyDown(null, keyboardEditable, event.keyCode, event) + KeyEvent.ACTION_UP -> tkl.onKeyUp(null, keyboardEditable, event.keyCode, event) + else -> tkl.onKeyOther(null, keyboardEditable, event) + } + if (processed) { + return true + } + return false + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + private fun initialize(url: String, token: String) { + if (ongoingSetup) { + Log.w(LOG_TAG, "Ongoing setup. Discarding initialize with ${url} / ${token}.") + return + } + ongoingSetup = true + + val pdialog = ProgressDialog(requireContext()).apply { + isIndeterminate = true + setMessage(getString(R.string.setup_progress)) + setTitle(R.string.setup_progress) + setCanceledOnTouchOutside(false) + setCancelable(false) + } + + fun resume() { + pdialog.dismiss() + ongoingSetup = false + } + + pdialog.show() + + runBlocking { + val bgScope = CoroutineScope(Dispatchers.IO) + try { + bgScope.async { + // FIXME: propagate useCamera back to appconfig + (requireActivity() as SetupCallable).setup(url, token) + }.await() + (requireActivity() as SetupCallable).onSucessfulSetup() + } catch (e: SetupBadRequestException) { + e.printStackTrace() + if (parentFragmentManager.isDestroyed) { + return@runBlocking + } + resume() + alert(R.string.setup_error_request) + } catch (e: SSLException) { + e.printStackTrace() + if (parentFragmentManager.isDestroyed) { + return@runBlocking + } + resume() + alert(R.string.setup_error_ssl) + } catch (e: IOException) { + e.printStackTrace() + if (parentFragmentManager.isDestroyed) { + return@runBlocking + } + resume() + alert(R.string.setup_error_io) + } catch (e: SetupServerErrorException) { + if (parentFragmentManager.isDestroyed) { + return@runBlocking + } + resume() + alert(R.string.setup_error_server) + } catch (e: SetupBadResponseException) { + e.printStackTrace() + if (parentFragmentManager.isDestroyed) { + return@runBlocking + } + resume() + alert(R.string.setup_error_response) + } catch (e: SetupException) { + e.printStackTrace() + if (parentFragmentManager.isDestroyed) { + return@runBlocking + } + resume() + alert(e.message ?: "Unknown error") + } catch (e: Exception) { + e.printStackTrace() + (requireActivity() as SetupCallable).onGenericSetupException(e) + if (parentFragmentManager.isDestroyed) { + return@runBlocking + } + resume() + alert(e.message ?: "Unknown error") + } + } + } + + fun alert(id: Int) { alert(getString(id)) } + fun alert(message: CharSequence) { + if (currentOpenAlert != null) { + currentOpenAlert!!.dismiss() + } + currentOpenAlert = MaterialAlertDialogBuilder(requireContext()).setMessage(message).create() + currentOpenAlert!!.show() + } +} + +// having the manual setup alert as a dialog fragment +// allows it to keep its input after rotating the device +class ManualSetupDialogFragment : DialogFragment() { + companion object { + const val KEY = "ManualSetupDialogFragment" + const val ARG_DEFAULT_URL = "default_url" + const val RESULT_URL = "url" + const val RESULT_TOKEN = "token" + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val defaultUrl = arguments?.getString(ARG_DEFAULT_URL, "") + + val builder = MaterialAlertDialogBuilder(requireActivity()) + val view = layoutInflater.inflate(R.layout.dialog_setup_manual, null) + val inputUri = view.findViewById(R.id.input_uri) + if (!defaultUrl.isNullOrEmpty()) { + inputUri.setText(defaultUrl) + } + val inputToken = view.findViewById(R.id.input_token) + builder.setView(view) + builder.setPositiveButton(R.string.ok) { dialog, _ -> + dialog.dismiss() + parentFragmentManager.setFragmentResult( + KEY, + bundleOf( + RESULT_URL to inputUri.text.toString(), + RESULT_TOKEN to inputToken.text.toString() + ) + ) + } + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + } + return builder.create() + } +} \ No newline at end of file diff --git a/libpretixui-android/src/main/res/layout/dialog_setup_manual.xml b/libpretixui-android/src/main/res/layout/dialog_setup_manual.xml new file mode 100644 index 0000000..06ea0c2 --- /dev/null +++ b/libpretixui-android/src/main/res/layout/dialog_setup_manual.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libpretixui-android/src/main/res/layout/fragment_setup.xml b/libpretixui-android/src/main/res/layout/fragment_setup.xml new file mode 100644 index 0000000..2d0d228 --- /dev/null +++ b/libpretixui-android/src/main/res/layout/fragment_setup.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + +