From bd686739fc2d2c4061df2f8a3d90acdaf8324c15 Mon Sep 17 00:00:00 2001 From: Henrik Lepson Date: Sat, 16 Oct 2021 13:20:55 +0300 Subject: [PATCH] MOB-40 changed nav logic, refactored Home, Pin and Can fragments --- .../mobileauthapp/CanFragment.kt | 124 +++++++++++------- .../mobileauthapp/HomeFragment.kt | 72 +++++++++- .../mobileauthapp/MainActivity.kt | 21 ++- .../mobileauthapp/PinFragment.kt | 101 +++++++++----- .../mobileauthapp/menu/SettingsFragment.kt | 12 +- .../src/main/res/drawable/ic_check_logo.xml | 10 ++ .../src/main/res/drawable/ic_info_logo.xml | 10 ++ .../app/src/main/res/layout/fragment_home.xml | 80 +++++++++-- .../src/main/res/layout/fragment_settings.xml | 13 ++ .../app/src/main/res/navigation/nav_graph.xml | 13 ++ .../app/src/main/res/values-en/strings.xml | 10 +- .../app/src/main/res/values-et/strings.xml | 10 +- .../app/src/main/res/values/strings.xml | 10 +- 13 files changed, 382 insertions(+), 104 deletions(-) create mode 100644 MobileAuthApp/app/src/main/res/drawable/ic_check_logo.xml create mode 100644 MobileAuthApp/app/src/main/res/drawable/ic_info_logo.xml diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt index 8c028af..cdf9e4e 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt @@ -1,7 +1,6 @@ package com.tarkvaraprojekt.mobileauthapp import android.app.AlertDialog -import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,7 +14,9 @@ import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentCanBinding import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel /** - * Fragment that deals with asking the user for six digit CAN + * Fragment that deals with asking the user for a six digit CAN. If the CAN is already saved + * then the fragment is skipped immediately and if the CAN is not saved then the user + * is asked whether it should be saved for the future or not before continuing. */ class CanFragment : Fragment() { @@ -23,7 +24,10 @@ class CanFragment : Fragment() { private var binding: FragmentCanBinding? = null - // Navigation arguments. saving = true means that we are navigating here from the settings menu and must return to the settings + // Navigation arguments: + // saving = true means that we are navigating here from the settings menu and must return to the settings menu. + // reading = true means that we are only reading the information from the ID card that does not need PIN 1, + // this information is passed on to the next PinFragment. private val args: CanFragmentArgs by navArgs() override fun onCreateView( @@ -37,62 +41,90 @@ class CanFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if (viewModel.userCan.length == 6) { - skip() - } + checkIfSkip() + // If the user arrives from the settings menu then the button should say + // save instead of continue. if (args.saving) { binding!!.nextButton.text = getString(R.string.save_text) } - binding!!.nextButton.setOnClickListener { goToNextFragment() } + binding!!.nextButton.setOnClickListener { checkEnteredCan() } binding!!.cancelButton.setOnClickListener { goToTheStart() } } - // If CAN is already set - private fun skip() { - findNavController().navigate(R.id.action_canFragment_to_pinFragment) - } - - // Might need some rework, must break it up and make logic better. - private fun goToNextFragment() { - val enteredCan = binding!!.canEditText.editText?.text.toString() - if (enteredCan.length != 6) { - Toast.makeText(requireContext(), getString(R.string.length_can), Toast.LENGTH_SHORT) - .show() - } else { - viewModel.setUserCan( - binding!!.canEditText.editText?.text.toString() - ) - if (args.saving) { - viewModel.storeCan(requireContext()) - findNavController().navigate(R.id.action_canFragment_to_settingsFragment) - } else { - val canStoreQuestion: AlertDialog? = activity?.let { frag -> - val builder = AlertDialog.Builder(frag) - builder.apply { - setPositiveButton(R.string.save_text) { _, _ -> - viewModel.storeCan( - requireContext() - ) - findNavController().navigate(R.id.action_canFragment_to_pinFragment) - } - setNegativeButton(R.string.deny_text) { _, _ -> - findNavController().navigate(R.id.action_canFragment_to_pinFragment) - } - } - builder.setMessage(R.string.can_save_request) - builder.setTitle(R.string.save_can_title) - builder.create() - } - canStoreQuestion?.show() - } + /** + * Checks if the current fragment can be skipped or not. + * If the user has CAN saved on the device there is no need to ask it again. + */ + private fun checkIfSkip() { + if (viewModel.userCan.length == 6) { + goToTheNextFragment() } } + /** + * Takes user to the next fragment, which is PinFragment. + */ + private fun goToTheNextFragment() { + val action = CanFragmentDirections.actionCanFragmentToPinFragment(reading = args.reading) + findNavController().navigate(action) + } + + /** + * Checks whether the user has entered a 6 digit can to the input field. + * If yes then the user is allowed to continue otherwise the user is + * allowed to modify the entered can. + */ + private fun checkEnteredCan() { + val enteredCan = binding!!.canEditText.editText?.text.toString() + if (enteredCan.length == 6) { + viewModel.setUserCan(enteredCan) + if (args.saving) { + viewModel.storeCan(requireContext()) + goToTheStart() + } else { + val storeCanQuestion = getDialog() + storeCanQuestion?.show() + } + } else { + Toast.makeText(requireContext(), getString(R.string.length_can), Toast.LENGTH_SHORT) + .show() + } + } + + /** + * Builds a dialog that asks the user whether the entered CAN should be saved + * on the device or not. + */ + private fun getDialog(): AlertDialog? { + return activity?.let { frag -> + val builder = AlertDialog.Builder(frag) + builder.apply { + // If response is positive then save the CAN on the device. + setPositiveButton(R.string.save_text) { _, _ -> + viewModel.storeCan( + requireContext() + ) + goToTheNextFragment() + } + setNegativeButton(R.string.deny_text) { _, _ -> + goToTheNextFragment() + } + } + builder.setMessage(R.string.can_save_request) + builder.setTitle(R.string.save_can_title) + builder.create() + } + } + + /** + * Navigates the user back to the start depending on where the user arrived. + * If the user arrived from the settings menu then the start is the settings menu + * not the HomeFragment. + */ private fun goToTheStart() { if (args.saving) { findNavController().navigate(R.id.action_canFragment_to_settingsFragment) } else { - viewModel.clearUserInfo() findNavController().navigate(R.id.action_canFragment_to_homeFragment) } } diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/HomeFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/HomeFragment.kt index 75a9ebf..77a2552 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/HomeFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/HomeFragment.kt @@ -1,7 +1,6 @@ package com.tarkvaraprojekt.mobileauthapp import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -11,6 +10,13 @@ import androidx.navigation.fragment.findNavController import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentHomeBinding import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel +/** + * HomeFragment is only shown to the user when then the user launches the application. When the application + * is launched by another application or a website then this Fragment will be skipped. + * This fragment uses the fields from the MainActivity by casting the activity to MainActivity. + * This might not be the best practice, but the application uses a single activity design so it should + * always work. + */ class HomeFragment : Fragment() { private val viewModel: SmartCardViewModel by activityViewModels() @@ -23,18 +29,72 @@ class HomeFragment : Fragment() { savedInstanceState: Bundle? ): View? { binding = FragmentHomeBinding.inflate(inflater, container, false) + // Making settings menu active again + (activity as MainActivity).menuAvailable = true return binding!!.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.checkCan(requireContext()) - viewModel.checkPin(requireContext()) - binding!!.beginButton.setOnClickListener { goToNextFragment() } + initialChecks() + binding!!.beginButton.setOnClickListener { goToTheNextFragment() } } - private fun goToNextFragment() { - findNavController().navigate(R.id.action_homeFragment_to_canFragment) + /** + * Method where all the initial checks that should be done before any user input is accepted should be added. + */ + private fun initialChecks() { + viewModel.checkCan(requireContext()) + viewModel.checkPin(requireContext()) + displayStates() + } + + /** + * Starts the process of interacting with the ID card by sending user to the CAN fragment. + */ + private fun goToTheNextFragment() { + // Making settings menu inactive + (activity as MainActivity).menuAvailable = false + // Currently saving is true because the application is not yet integrated with + // other applications or websites. + val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(reading = true) + findNavController().navigate(action) + } + + /** + * Displays texts that inform the user whether the CAN and PIN 1 are saved on the device or not. + * This might help the user to save some time as checking menu is not necessary unless the user + * wishes to make changes to the saved CAN or PIN 1. + */ + private fun displayStates() { + canState() + pinState() + } + + /** + * Checks the state of the CAN, saved or not saved. Updates the text and logo. + */ + private fun canState() { + if (viewModel.userCan.length == 6) { + binding!!.canStatusText.text = getString(R.string.can_status_saved) + binding!!.canStatusLogo.setImageResource(R.drawable.ic_check_logo) + } else { + binding!!.canStatusText.text = getString(R.string.can_status_negative) + binding!!.canStatusLogo.setImageResource(R.drawable.ic_info_logo) + } + } + + /** + * Checks the state of the PIN 1, saved or not saved. Updates the text and logo. + */ + private fun pinState() { + if (viewModel.userPin.length in 4..12) { + binding!!.pinStatusText.text = getString(R.string.pin_status_saved) + binding!!.pinStatusLogo.setImageResource(R.drawable.ic_check_logo) + } else { + binding!!.pinStatusText.text = getString(R.string.pin_status_negative) + binding!!.pinStatusLogo.setImageResource(R.drawable.ic_info_logo) + } } override fun onDestroyView() { diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/MainActivity.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/MainActivity.kt index f1e4353..638d88e 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/MainActivity.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/MainActivity.kt @@ -1,22 +1,25 @@ package com.tarkvaraprojekt.mobileauthapp -import android.nfc.NfcAdapter import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import android.util.Log import android.view.Menu import android.view.MenuItem import android.widget.Toast import androidx.navigation.NavController -import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import com.tarkvaraprojekt.mobileauthapp.databinding.ActivityMainBinding -import java.util.* + +/** + * The only activity of the application (single activity design). + */ class MainActivity : AppCompatActivity() { private lateinit var navigationController: NavController + // If true the settings menu can be accessed from the toolbar in the upper part of the screen. + var menuAvailable: Boolean = true + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = ActivityMainBinding.inflate(layoutInflater) @@ -35,10 +38,14 @@ class MainActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.menu_settings_option -> { - navigationController.navigate(R.id.action_homeFragment_to_settingsFragment) - true + if (menuAvailable) { + navigationController.navigate(R.id.action_homeFragment_to_settingsFragment) + true + } else { + Toast.makeText(this, getString(R.string.unavailable), Toast.LENGTH_SHORT).show() + false + } } else -> super.onOptionsItemSelected(item) } - } \ No newline at end of file diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/PinFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/PinFragment.kt index cc9dd30..68b6355 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/PinFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/PinFragment.kt @@ -14,7 +14,9 @@ import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentPinBinding import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel /** - * Fragment that deals with asking the user for PIN1 + * Fragment that deals with asking the user for PIN 1. If the user has already saved the PIN 1 then it is not asked again + * and the fragment is skipped and if the PIN 1 is not saved then the user is asked whether it should be saved or + * not before continuing. */ class PinFragment : Fragment() { @@ -22,7 +24,10 @@ class PinFragment : Fragment() { private var binding: FragmentPinBinding? = null - // Navigation arguments. saving = true means that we are navigating here from the settings menu and must return to the settings + // Navigation arguments: + // saving = true means that the user must be returned to the settings menu + // reading = true means that we are reading information from the ID card that does + // not require PIN 1 so it is not necessary to ask it. private val args: CanFragmentArgs by navArgs() override fun onCreateView( @@ -36,48 +41,51 @@ class PinFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if (viewModel.userPin.length in 4..12) { - skip() - } + checkIfSkip() + // If the user arrives from the settings menu then the button says + // save instead of continue. if (args.saving) { binding!!.nextButton.text = getString(R.string.save_text) } - binding!!.nextButton.setOnClickListener { goToNextFragment() } + binding!!.nextButton.setOnClickListener { checkEnteredPin() } binding!!.cancelButton.setOnClickListener { goToTheStart() } } - private fun skip() { + /** + * Checks if the current fragment can be skipped or not. + * If the user has PIN 1 saved on the device or PIN 1 is not required + * then the PIN 1 won't be asked. + */ + private fun checkIfSkip() { + if (args.reading) { + goToTheNextFragment() + } else if (viewModel.userPin.length in 4..12) { + goToTheNextFragment() + } + } + + /** + * Takes user to the next fragment, which is AuthFragment. + */ + private fun goToTheNextFragment() { findNavController().navigate(R.id.action_pinFragment_to_authFragment) } - private fun goToNextFragment() { - val enteredPin1 = binding!!.pinEditText.editText?.text.toString() - if (enteredPin1.length in 4..12) { - viewModel.setUserPin( - binding!!.pinEditText.editText?.text.toString() - ) + /** + * Checks whether the user has entered a PIN 1 with length between [4, 12] in the + * input field. If yes then the user is allowed to continue otherwise the user is + * allowed to modify the entered PIN 1. + */ + private fun checkEnteredPin() { + val enteredPin = binding!!.pinEditText.editText?.text.toString() + if (enteredPin.length in 4..12) { + viewModel.setUserPin(enteredPin) if (args.saving) { viewModel.storePin(requireContext()) - findNavController().navigate(R.id.action_pinFragment_to_settingsFragment) + goToTheStart() } else { - val canStoreQuestion: AlertDialog? = activity?.let { frag -> - val builder = AlertDialog.Builder(frag) - builder.apply { - setPositiveButton(R.string.save_text) { _, _ -> - viewModel.storePin( - requireContext() - ) - findNavController().navigate(R.id.action_pinFragment_to_authFragment) - } - setNegativeButton(R.string.deny_text) { _, _ -> - findNavController().navigate(R.id.action_pinFragment_to_authFragment) - } - } - builder.setMessage(R.string.pin_save_request) - builder.setTitle(R.string.save_pin_title) - builder.create() - } - canStoreQuestion?.show() + val storePinQuestion = getDialog() + storePinQuestion?.show() } } else { Toast.makeText(requireContext(), getString(R.string.length_pin), Toast.LENGTH_SHORT) @@ -85,6 +93,35 @@ class PinFragment : Fragment() { } } + /** + * Builds a dialog that asks the user whether the entered PIN 1 should be saved + * on the device or not. + */ + private fun getDialog(): AlertDialog? { + return activity?.let { frag -> + val builder = AlertDialog.Builder(frag) + builder.apply { + // If response is positive save the PIN 1 on the device. + setPositiveButton(R.string.save_text) { _, _ -> + viewModel.storePin( + requireContext() + ) + goToTheNextFragment() + } + setNegativeButton(R.string.deny_text) { _, _ -> + goToTheNextFragment() + } + } + builder.setMessage(R.string.pin_save_request) + builder.setTitle(R.string.save_pin_title) + builder.create() + } + } + + /** + * Returns user to the start. If the user arrived from the settings menu then the start is + * settings menu not the HomeFragment. + */ private fun goToTheStart() { if (args.saving) { findNavController().navigate(R.id.action_pinFragment_to_settingsFragment) diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/menu/SettingsFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/menu/SettingsFragment.kt index 7410c3f..87e822c 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/menu/SettingsFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/menu/SettingsFragment.kt @@ -42,6 +42,7 @@ class SettingsFragment : Fragment() { binding!!.canMenuAction.setOnClickListener { canAction() } binding!!.pinMenuAction.setOnClickListener { pinAction() } binding!!.pinMenuShow.setOnClickListener { togglePin() } + binding!!.returnButton.setOnClickListener { backToHome() } } /** @@ -66,7 +67,7 @@ class SettingsFragment : Fragment() { viewModel.deleteCan(requireContext()) showCanField() } else { - val action = SettingsFragmentDirections.actionSettingsFragmentToCanFragment(true) + val action = SettingsFragmentDirections.actionSettingsFragmentToCanFragment(saving = true) findNavController().navigate(action) } } @@ -100,7 +101,7 @@ class SettingsFragment : Fragment() { viewModel.deletePin(requireContext()) showPinField() } else { - val action = SettingsFragmentDirections.actionSettingsFragmentToPinFragment(true) + val action = SettingsFragmentDirections.actionSettingsFragmentToPinFragment(saving = true) findNavController().navigate(action) } } @@ -125,6 +126,13 @@ class SettingsFragment : Fragment() { } } + /** + * Navigates back to home fragment. + */ + private fun backToHome() { + findNavController().navigate(R.id.action_settingsFragment_to_homeFragment) + } + override fun onDestroy() { super.onDestroy() binding = null diff --git a/MobileAuthApp/app/src/main/res/drawable/ic_check_logo.xml b/MobileAuthApp/app/src/main/res/drawable/ic_check_logo.xml new file mode 100644 index 0000000..5e111ca --- /dev/null +++ b/MobileAuthApp/app/src/main/res/drawable/ic_check_logo.xml @@ -0,0 +1,10 @@ + + + diff --git a/MobileAuthApp/app/src/main/res/drawable/ic_info_logo.xml b/MobileAuthApp/app/src/main/res/drawable/ic_info_logo.xml new file mode 100644 index 0000000..17255b7 --- /dev/null +++ b/MobileAuthApp/app/src/main/res/drawable/ic_info_logo.xml @@ -0,0 +1,10 @@ + + + diff --git a/MobileAuthApp/app/src/main/res/layout/fragment_home.xml b/MobileAuthApp/app/src/main/res/layout/fragment_home.xml index dc30b64..47396aa 100644 --- a/MobileAuthApp/app/src/main/res/layout/fragment_home.xml +++ b/MobileAuthApp/app/src/main/res/layout/fragment_home.xml @@ -7,23 +7,87 @@ android:padding="24dp" tools:context=".HomeFragment"> - + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + + + + + + + + + + + + + + + + + + + + + + + + + + -