Skip to content

Commit

Permalink
Merge pull request #1850 from dedis/work-fe2-maxime-enhanced-join-lao…
Browse files Browse the repository at this point in the history
…-manually

Improvements to Manual Input in Scanning Actions
  • Loading branch information
matteosz authored May 16, 2024
2 parents defee58 + e8bf549 commit 4dd9f0e
Show file tree
Hide file tree
Showing 16 changed files with 738 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.github.dedis.popstellar.model.Role
import com.github.dedis.popstellar.model.qrcode.ConnectToLao
import com.github.dedis.popstellar.repository.remote.GlobalNetworkManager
import com.github.dedis.popstellar.utility.ActivityUtils.getQRCodeColor
import com.github.dedis.popstellar.utility.GeneralUtils
import com.github.dedis.popstellar.utility.UIUtils
import com.github.dedis.popstellar.utility.error.ErrorUtils.logAndShow
import com.github.dedis.popstellar.utility.error.UnknownLaoException
import com.google.gson.Gson
Expand All @@ -27,7 +27,7 @@ class InviteFragment : Fragment() {

private lateinit var laoViewModel: LaoViewModel
private lateinit var binding: InviteFragmentBinding
private lateinit var clipboardManager: GeneralUtils.ClipboardUtil
private lateinit var clipboardManager: UIUtils.ClipboardUtil

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -36,7 +36,7 @@ class InviteFragment : Fragment() {
): View? {
binding = InviteFragmentBinding.inflate(inflater, container, false)
laoViewModel = LaoActivity.obtainViewModel(requireActivity())
clipboardManager = GeneralUtils.ClipboardUtil(requireActivity())
clipboardManager = UIUtils.ClipboardUtil(requireActivity())

// Display the LAO identifier, not the device public key
binding.laoPropertiesIdentifierText.text = laoViewModel.laoId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import com.github.dedis.popstellar.ui.lao.LaoViewModel
import com.github.dedis.popstellar.utility.ActivityUtils.buildBackButtonCallback
import com.github.dedis.popstellar.utility.ActivityUtils.getQRCodeColor
import com.github.dedis.popstellar.utility.Constants
import com.github.dedis.popstellar.utility.GeneralUtils
import com.github.dedis.popstellar.utility.UIUtils
import com.github.dedis.popstellar.utility.error.ErrorUtils.logAndShow
import com.github.dedis.popstellar.utility.error.UnknownRollCallException
import com.github.dedis.popstellar.utility.error.keys.KeyException
Expand All @@ -39,7 +39,7 @@ class TokenFragment : Fragment() {

private lateinit var laoViewModel: LaoViewModel

private lateinit var clipboardManager: GeneralUtils.ClipboardUtil
private lateinit var clipboardManager: UIUtils.ClipboardUtil

override fun onResume() {
super.onResume()
Expand All @@ -54,7 +54,7 @@ class TokenFragment : Fragment() {
): View? {
val binding = TokenFragmentBinding.inflate(inflater, container, false)
laoViewModel = obtainViewModel(requireActivity())
clipboardManager = GeneralUtils.ClipboardUtil(requireActivity())
clipboardManager = UIUtils.ClipboardUtil(requireActivity())

try {
val laoId = laoViewModel.laoId!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@ import androidx.camera.view.CameraController
import androidx.camera.view.LifecycleCameraController
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.github.dedis.popstellar.R
import com.github.dedis.popstellar.databinding.QrScannerFragmentBinding
import com.github.dedis.popstellar.ui.PopViewModel
import com.github.dedis.popstellar.utility.UIUtils
import com.github.dedis.popstellar.utility.UIUtils.hideKeyboard
import com.github.dedis.popstellar.utility.error.ErrorUtils.logAndShow
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import java.util.Objects
import timber.log.Timber

class QrScannerFragment : Fragment() {
private lateinit var binding: QrScannerFragmentBinding
private var barcodeScanner: BarcodeScanner? = null
private lateinit var scanningViewModel: QRCodeScanningViewModel
private lateinit var popViewModel: PopViewModel
private lateinit var clipboardManager: UIUtils.ClipboardUtil

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -41,10 +45,16 @@ class QrScannerFragment : Fragment() {
if (scanningAction.displayCounter) {
displayCounter()
}
clipboardManager = UIUtils.ClipboardUtil(requireActivity())

UIUtils.setupInputFields(
recyclerView = binding.dynamicInputsContainer,
inputFields = scanningAction.getInputFields().toList(),
context = requireContext(),
clipboardManager = clipboardManager)

binding.scannedTitle.setText(scanningAction.scanTitle)
binding.addManualTitle.setText(scanningAction.manualAddTitle)
binding.manualAddEditText.setHint(scanningAction.hint)
binding.scannerInstructionText.setText(scanningAction.instruction)

setupNbScanned()
Expand Down Expand Up @@ -150,21 +160,6 @@ class QrScannerFragment : Fragment() {
binding.scannerCamera.controller = cameraController
}

private fun setupManualAdd() {
binding.scannerEnterManually.setOnClickListener { _: View? ->
binding.scannerBottomTexts.visibility = View.GONE
binding.enterManuallyCard.visibility = View.VISIBLE
}
binding.addManualClose.setOnClickListener { _: View? ->
binding.scannerBottomTexts.visibility = View.VISIBLE
binding.enterManuallyCard.visibility = View.GONE
}
binding.manualAddButton.setOnClickListener { _: View? ->
val input = Objects.requireNonNull(binding.manualAddEditText.text).toString()
onResult(input)
}
}

private fun displayCounter() {
binding.scannedTitle.visibility = View.VISIBLE
binding.scannedNumber.visibility = View.VISIBLE
Expand All @@ -178,6 +173,36 @@ class QrScannerFragment : Fragment() {
scanningViewModel.handleData(data)
}

private fun setupManualAdd() {
binding.scannerEnterManually.setOnClickListener {
binding.scannerBottomTexts.visibility = View.GONE
binding.enterManuallyCard.visibility = View.VISIBLE
}

binding.addManualClose.setOnClickListener {
hideKeyboard(requireContext(), binding.root)
binding.scannerBottomTexts.visibility = View.VISIBLE
binding.enterManuallyCard.visibility = View.GONE
}

binding.manualAddButton.setOnClickListener {
val adapter = binding.dynamicInputsContainer.adapter as UIUtils.InputFieldsAdapter
val inputs = adapter.getCurrentInputData()

val json = scanningAction.formatJson(inputs)
Timber.tag(TAG).d("Manual add json: %s", json)
if (json.isNotBlank()) {
onResult(json)
} else {
logAndShow(
requireContext(),
TAG,
IllegalArgumentException("Required fields are empty"),
R.string.qrcode_scanning_manual_entry_error)
}
}
}

companion object {
val TAG: String = QrScannerFragment::class.java.simpleName
const val SCANNING_KEY = "scanning_action_key"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,33 @@ import com.github.dedis.popstellar.ui.home.HomeFragment
import com.github.dedis.popstellar.ui.home.LaoCreateFragment
import com.github.dedis.popstellar.ui.lao.LaoActivity
import com.github.dedis.popstellar.ui.lao.event.rollcall.RollCallFragment
import com.github.dedis.popstellar.utility.UIUtils.InputFieldConfig
import java.util.function.BiConsumer
import java.util.function.Function

/**
* Enum class modeling the the action we want to do when using the QR code fragment. It provides
* strings to display and functions, i.e. to obtain view models and on back press behaviour
* strings to display, UI, Input elements and functions, i.e. to obtain view models and on back
* press behaviour
*
* @param instruction the instruction to display to the user
* @param scanTitle the title to display when scanning
* @param pageTitle the title to display on the page
* @param manualAddTitle the title to display when manually adding
* @param inputFields the input fields to display
* @param jsonFormatter the function to format the input fields into a JSON string
* @param scannerViewModelProvider the function to obtain the scanner view model
* @param popViewModelProvider the function to obtain the PoP view model
* @param onBackPressed the function to call when the back button is pressed
* @param displayCounter whether to display the counter (i.e. number of scanned items)
*/
enum class ScanningAction(
@StringRes val instruction: Int,
@StringRes val scanTitle: Int,
@StringRes val pageTitle: Int,
@StringRes val hint: Int,
@StringRes val manualAddTitle: Int,
private val inputFields: Array<InputFieldConfig>,
private val jsonFormatter: (Map<Int, String?>) -> String,
private val scannerViewModelProvider:
BiFunction<FragmentActivity, String?, QRCodeScanningViewModel>,
private val popViewModelProvider: Function<FragmentActivity, PopViewModel>,
Expand All @@ -34,27 +48,25 @@ enum class ScanningAction(
R.string.qrcode_scanning_add_witness,
R.string.scanned_witness,
R.string.add_witness_title,
R.string.manual_witness_hint,
R.string.add_witness_title,
{ activity: FragmentActivity, _: String? ->
HomeActivity.obtainWitnessingViewModel(activity)
},
{ activity: FragmentActivity -> HomeActivity.obtainViewModel(activity) },
{ manager: FragmentManager, _: Array<String> ->
R.string.manual_add_witness_title,
arrayOf(InputFieldConfig(R.string.manual_add_witness_hint, true)),
{ inputs -> "{\"main_public_key\":\"${inputs[R.string.manual_add_witness_hint]}\"}" },
{ activity, _ -> HomeActivity.obtainWitnessingViewModel(activity) },
{ activity -> HomeActivity.obtainViewModel(activity) },
{ manager, _ ->
HomeActivity.setCurrentFragment(manager, R.id.fragment_lao_create) { LaoCreateFragment() }
},
true),
ADD_WITNESS(
R.string.qrcode_scanning_add_witness,
R.string.scanned_witness,
R.string.add_witness_title,
R.string.manual_witness_hint,
R.string.add_witness_title,
{ activity: FragmentActivity, laoId: String? ->
LaoActivity.obtainWitnessingViewModel(activity, laoId)
},
{ activity: FragmentActivity -> LaoActivity.obtainViewModel(activity) },
{ manager: FragmentManager, _: Array<String> ->
R.string.manual_add_witness_title,
arrayOf(InputFieldConfig(R.string.manual_add_witness_hint, true)),
{ inputs -> "{\"main_public_key\":\"${inputs[R.string.manual_add_witness_hint]}\"}" },
{ activity, laoId -> LaoActivity.obtainWitnessingViewModel(activity, laoId) },
{ activity -> LaoActivity.obtainViewModel(activity) },
{ manager, _ ->
LaoActivity.setCurrentFragment(manager, R.id.fragment_witnessing) {
com.github.dedis.popstellar.ui.lao.witness.WitnessingFragment()
}
Expand All @@ -64,42 +76,44 @@ enum class ScanningAction(
R.string.qrcode_scanning_add_attendee,
R.string.scanned_tokens,
R.string.add_attendee_title,
R.string.rc_manual_hint,
R.string.add_attendee_title,
{ activity: FragmentActivity, laoId: String? ->
LaoActivity.obtainRollCallViewModel(activity, laoId)
},
{ activity: FragmentActivity -> LaoActivity.obtainViewModel(activity) },
{ manager: FragmentManager, stringArray: Array<String> ->
R.string.manual_add_rc_title,
arrayOf(InputFieldConfig(R.string.manual_add_rc_add_hint, true)),
{ inputs -> "{\"pop_token\":\"${inputs[R.string.manual_add_rc_add_hint]}\"}" },
{ activity, laoId -> LaoActivity.obtainRollCallViewModel(activity, laoId) },
{ activity -> LaoActivity.obtainViewModel(activity) },
{ manager, args ->
LaoActivity.setCurrentFragment(manager, R.id.fragment_roll_call) {
RollCallFragment.newInstance(stringArray[0])
RollCallFragment.newInstance(args[0])
}
},
// We only need the first arg (rc id)
true),
ADD_LAO_PARTICIPANT(
R.string.qrcode_scanning_connect_lao,
R.string.scanned_tokens,
R.string.join_lao_title,
R.string.join_manual_hint,
R.string.add_lao_participant_title,
{ activity: FragmentActivity, _: String? -> HomeActivity.obtainViewModel(activity) },
{ activity: FragmentActivity -> HomeActivity.obtainViewModel(activity) },
{ manager: FragmentManager, _: Array<String> ->
R.string.manual_add_lao_join_title,
arrayOf(
InputFieldConfig(R.string.manual_add_server_url_hint, true),
InputFieldConfig(R.string.manual_add_lao_id_hint, true)),
{ inputs ->
"{\"server\":\"${inputs[R.string.manual_add_server_url_hint]}\", \"lao\":\"${inputs[R.string.manual_add_lao_id_hint]}\"}"
},
{ activity, _ -> HomeActivity.obtainViewModel(activity) },
{ activity -> HomeActivity.obtainViewModel(activity) },
{ manager, _ ->
HomeActivity.setCurrentFragment(manager, R.id.fragment_home) { HomeFragment() }
},
false),
ADD_POPCHA(
R.string.qrcode_scanning_add_popcha,
R.string.scanned_tokens,
R.string.popcha_add,
R.string.manual_popcha_hint,
R.string.popcha_scan_title,
{ activity: FragmentActivity, laoId: String? ->
LaoActivity.obtainPoPCHAViewModel(activity, laoId)
},
{ activity: FragmentActivity -> LaoActivity.obtainViewModel(activity) },
{ manager: FragmentManager, _: Array<String> ->
R.string.popcha_add_title,
R.string.manual_add_popcha_title,
arrayOf(InputFieldConfig(R.string.manual_add_popcha_hint, true)),
{ inputs -> inputs[R.string.manual_add_popcha_hint] ?: "" },
{ activity, laoId -> LaoActivity.obtainPoPCHAViewModel(activity, laoId) },
{ activity -> LaoActivity.obtainViewModel(activity) },
{ manager, _ ->
LaoActivity.setCurrentFragment(manager, R.id.fragment_popcha_home) {
com.github.dedis.popstellar.ui.lao.popcha.PoPCHAHomeFragment()
}
Expand Down Expand Up @@ -140,4 +154,17 @@ enum class ScanningAction(
internal fun interface BiFunction<T, U, V> {
fun apply(t: T, u: U): V
}

/** Returns the input fields for the scanning action */
fun getInputFields(): Array<InputFieldConfig> = inputFields

/**
* Formats the input fields into a JSON string
*
* @param inputs the input fields
* @return the formatted JSON string, or an empty string if any input is null or blank
*/
fun formatJson(inputs: Map<Int, String?>): String {
return if (inputs.values.any { it.isNullOrBlank() }) "" else jsonFormatter(inputs)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@ package com.github.dedis.popstellar.utility

import android.app.Activity
import android.app.Application
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.lifecycle.Lifecycle
import com.github.dedis.popstellar.R
import com.github.dedis.popstellar.model.objects.security.Base64URLData
import io.github.novacrypto.bip39.MnemonicGenerator
import io.github.novacrypto.bip39.wordlists.English
Expand Down Expand Up @@ -72,29 +65,6 @@ object GeneralUtils {
}
}

/**
* This class is a utility to setup a copy button that copies the text of a TextView to the
* clipboard.
*
* @param context the context of the application
*/
class ClipboardUtil(val context: Context) {

fun setupCopyButton(button: View, textView: TextView, label: String) {
button.setOnClickListener {
val text = textView.text.toString()
copyTextToClipboard(label, text)
Toast.makeText(context, R.string.successful_copy, Toast.LENGTH_SHORT).show()
}
}

private fun copyTextToClipboard(label: String, content: String) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(label, content)
clipboard.setPrimaryClip(clip)
}
}

/**
* This function converts a base64 string into some mnemonic words.
*
Expand Down
Loading

0 comments on commit 4dd9f0e

Please sign in to comment.