This commit is contained in:
@@ -3,6 +3,7 @@ package sh.sar.basedbank.ui.onboarding
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.os.CountDownTimer
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
@@ -17,6 +18,7 @@ class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
|
||||
|
||||
private lateinit var binding: ActivityOnboardingBinding
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private var countDownTimer: CountDownTimer? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -29,34 +31,31 @@ class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
|
||||
|
||||
TabLayoutMediator(binding.dotsIndicator, binding.viewPager) { _, _ -> }.attach()
|
||||
|
||||
// Pre-select language chip without triggering the listener
|
||||
// Pre-select language button without triggering the listener
|
||||
val savedLang = prefs.getString("language", null)
|
||||
binding.languageChipGroup.setOnCheckedStateChangeListener(null)
|
||||
binding.languageToggle.clearOnButtonCheckedListeners()
|
||||
when (savedLang) {
|
||||
"en" -> binding.chipEnglish.isChecked = true
|
||||
"dv" -> binding.chipDhivehi.isChecked = true
|
||||
"en" -> binding.btnLangEnglish.isChecked = true
|
||||
"dv" -> binding.btnLangDhivehi.isChecked = true
|
||||
}
|
||||
binding.languageChipGroup.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
if (checkedIds.isNotEmpty()) {
|
||||
selectLanguage(if (checkedIds[0] == R.id.chipEnglish) "en" else "dv")
|
||||
}
|
||||
binding.languageToggle.addOnButtonCheckedListener { _, checkedId, isChecked ->
|
||||
if (isChecked) selectLanguage(if (checkedId == R.id.btnLangEnglish) "en" else "dv")
|
||||
}
|
||||
|
||||
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
binding.languageChipGroup.visibility = if (position == 0) View.VISIBLE else View.GONE
|
||||
// Block forward swipe on slide 1 until security is set up
|
||||
if (position == 1) {
|
||||
binding.viewPager.isUserInputEnabled =
|
||||
prefs.getString("security_method", null) != null
|
||||
} else {
|
||||
binding.viewPager.isUserInputEnabled = true
|
||||
binding.languageSection.visibility = if (position == 0) View.VISIBLE else View.GONE
|
||||
binding.viewPager.isUserInputEnabled = when {
|
||||
position > 2 -> false
|
||||
position == 1 -> prefs.getString("security_method", null) != null
|
||||
else -> true
|
||||
}
|
||||
updateButtons(position, adapter.itemCount)
|
||||
if (position == adapter.itemCount - 1) startGetStartedCountdown()
|
||||
}
|
||||
})
|
||||
|
||||
binding.languageChipGroup.visibility = View.VISIBLE
|
||||
binding.languageSection.visibility = View.VISIBLE
|
||||
updateButtons(0, adapter.itemCount)
|
||||
|
||||
binding.btnNext.setOnClickListener {
|
||||
@@ -71,10 +70,21 @@ class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
countDownTimer?.cancel()
|
||||
}
|
||||
|
||||
// Called by SecuritySetupFragment when setup is complete
|
||||
override fun onSecuritySetupComplete() {
|
||||
binding.viewPager.isUserInputEnabled = true
|
||||
updateButtons(binding.viewPager.currentItem, binding.viewPager.adapter?.itemCount ?: 3)
|
||||
updateButtons(binding.viewPager.currentItem, binding.viewPager.adapter?.itemCount ?: 4)
|
||||
}
|
||||
|
||||
// Called by SecuritySetupFragment when user resets to reconfigure
|
||||
override fun onSecuritySetupReset() {
|
||||
binding.viewPager.isUserInputEnabled = false
|
||||
updateButtons(binding.viewPager.currentItem, binding.viewPager.adapter?.itemCount ?: 4)
|
||||
}
|
||||
|
||||
private fun selectLanguage(lang: String) {
|
||||
@@ -83,6 +93,21 @@ class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
|
||||
updateButtons(binding.viewPager.currentItem, binding.viewPager.adapter?.itemCount ?: 3)
|
||||
}
|
||||
|
||||
private fun startGetStartedCountdown() {
|
||||
binding.btnGetStarted.isEnabled = false
|
||||
countDownTimer?.cancel()
|
||||
countDownTimer = object : CountDownTimer(5000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
val seconds = (millisUntilFinished / 1000 + 1).toInt()
|
||||
binding.btnGetStarted.text = "${getString(R.string.get_started)} ($seconds)"
|
||||
}
|
||||
override fun onFinish() {
|
||||
binding.btnGetStarted.text = getString(R.string.get_started)
|
||||
binding.btnGetStarted.isEnabled = true
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun updateButtons(position: Int, count: Int) {
|
||||
val langSelected = prefs.getString("language", null) != null
|
||||
val securityDone = prefs.getString("security_method", null) != null
|
||||
@@ -90,16 +115,11 @@ class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
|
||||
|
||||
binding.btnGetStarted.visibility = if (isLast) View.VISIBLE else View.GONE
|
||||
|
||||
// Hide Next on slide 1 until security is done (avoids a disabled-button-with-no-explanation)
|
||||
binding.btnNext.visibility = when {
|
||||
isLast -> View.GONE
|
||||
position == 1 && !securityDone -> View.GONE
|
||||
else -> View.VISIBLE
|
||||
}
|
||||
binding.btnNext.visibility = if (isLast) View.GONE else View.VISIBLE
|
||||
binding.btnNext.isEnabled = when (position) {
|
||||
0 -> langSelected
|
||||
1 -> securityDone
|
||||
else -> true
|
||||
else -> true // position 2 (configure) has no gate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package sh.sar.basedbank.ui.onboarding
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.fragment.app.Fragment
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.databinding.FragmentOnboardingConfigureBinding
|
||||
|
||||
class OnboardingConfigureFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentOnboardingConfigureBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentOnboardingConfigureBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val prefs = requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE)
|
||||
|
||||
// Navigation — default Drawer
|
||||
val isBottom = prefs.getBoolean("bottom_nav", false)
|
||||
binding.navModeToggle.check(if (isBottom) R.id.btnNavBottom else R.id.btnNavDrawer)
|
||||
binding.navModeToggle.addOnButtonCheckedListener { _, checkedId, isChecked ->
|
||||
if (!isChecked) return@addOnButtonCheckedListener
|
||||
prefs.edit().putBoolean("bottom_nav", checkedId == R.id.btnNavBottom).apply()
|
||||
}
|
||||
|
||||
// Theme — default System
|
||||
val savedTheme = prefs.getString("theme", "system")
|
||||
binding.themeToggle.check(when (savedTheme) {
|
||||
"light" -> R.id.btnThemeLight
|
||||
"dark" -> R.id.btnThemeDark
|
||||
else -> R.id.btnThemeSystem
|
||||
})
|
||||
binding.themeToggle.addOnButtonCheckedListener { _, checkedId, isChecked ->
|
||||
if (!isChecked) return@addOnButtonCheckedListener
|
||||
val (key, mode) = when (checkedId) {
|
||||
R.id.btnThemeLight -> "light" to AppCompatDelegate.MODE_NIGHT_NO
|
||||
R.id.btnThemeDark -> "dark" to AppCompatDelegate.MODE_NIGHT_YES
|
||||
else -> "system" to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
prefs.edit().putString("theme", key).apply()
|
||||
AppCompatDelegate.setDefaultNightMode(mode)
|
||||
}
|
||||
|
||||
// Biometrics
|
||||
val canUseBiometrics = BiometricManager.from(requireContext())
|
||||
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
if (canUseBiometrics) {
|
||||
val unlockEnabled = prefs.getBoolean("biometrics_enabled", false)
|
||||
binding.switchBiometrics.isChecked = unlockEnabled
|
||||
binding.switchBiometricsTransfer.isChecked = prefs.getBoolean("biometrics_transfer_confirm", false)
|
||||
binding.switchBiometricsTransfer.isEnabled = unlockEnabled
|
||||
|
||||
binding.switchBiometrics.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.edit().putBoolean("biometrics_enabled", isChecked).apply()
|
||||
binding.switchBiometricsTransfer.isEnabled = isChecked
|
||||
if (!isChecked) {
|
||||
binding.switchBiometricsTransfer.isChecked = false
|
||||
prefs.edit().putBoolean("biometrics_transfer_confirm", false).apply()
|
||||
}
|
||||
}
|
||||
|
||||
binding.switchBiometricsTransfer.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.edit().putBoolean("biometrics_transfer_confirm", isChecked).apply()
|
||||
}
|
||||
} else {
|
||||
binding.tvBiometricsHint.visibility = View.VISIBLE
|
||||
binding.switchBiometrics.isEnabled = false
|
||||
binding.switchBiometricsTransfer.isEnabled = false
|
||||
}
|
||||
|
||||
// Block screenshots — default on
|
||||
val blockScreenshots = prefs.getBoolean("block_screenshots", true)
|
||||
binding.switchBlockScreenshots.isChecked = blockScreenshots
|
||||
applyFlagSecure(blockScreenshots)
|
||||
binding.switchBlockScreenshots.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.edit().putBoolean("block_screenshots", isChecked).apply()
|
||||
applyFlagSecure(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyFlagSecure(enabled: Boolean) {
|
||||
val win = activity?.window ?: return
|
||||
if (enabled) win.addFlags(android.view.WindowManager.LayoutParams.FLAG_SECURE)
|
||||
else win.clearFlags(android.view.WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -19,14 +19,16 @@ class OnboardingFragment : Fragment() {
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val title = requireArguments().getString(ARG_TITLE, "")
|
||||
val desc = requireArguments().getString(ARG_DESC, "")
|
||||
val titleRes = requireArguments().getInt(ARG_TITLE)
|
||||
val descRes = requireArguments().getInt(ARG_DESC)
|
||||
val icon = requireArguments().getInt(ARG_ICON, 0)
|
||||
val isFirst = requireArguments().getBoolean(ARG_IS_FIRST, false)
|
||||
val isLast = requireArguments().getBoolean(ARG_IS_LAST, false)
|
||||
|
||||
binding.icon.setImageResource(icon)
|
||||
binding.title.text = title
|
||||
binding.description.text = desc
|
||||
binding.title.text = getString(titleRes)
|
||||
binding.description.text = getString(descRes)
|
||||
binding.description.gravity = if (isLast) android.view.Gravity.START else android.view.Gravity.CENTER
|
||||
|
||||
// On the first slide, show the two placeholder cards for upcoming banks
|
||||
binding.placeholderCards.visibility = if (isFirst) View.VISIBLE else View.GONE
|
||||
@@ -42,13 +44,15 @@ class OnboardingFragment : Fragment() {
|
||||
private const val ARG_DESC = "desc"
|
||||
private const val ARG_ICON = "icon"
|
||||
private const val ARG_IS_FIRST = "is_first"
|
||||
private const val ARG_IS_LAST = "is_last"
|
||||
|
||||
fun newInstance(slide: OnboardingSlide) = OnboardingFragment().apply {
|
||||
arguments = bundleOf(
|
||||
ARG_TITLE to slide.title,
|
||||
ARG_DESC to slide.description,
|
||||
ARG_TITLE to slide.titleRes,
|
||||
ARG_DESC to slide.descRes,
|
||||
ARG_ICON to slide.iconRes,
|
||||
ARG_IS_FIRST to slide.isFirst
|
||||
ARG_IS_FIRST to slide.isFirst,
|
||||
ARG_IS_LAST to slide.isLast
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,36 +9,39 @@ class OnboardingPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(
|
||||
|
||||
private val slides = listOf(
|
||||
OnboardingSlide(
|
||||
title = activity.getString(R.string.onboarding_title_1),
|
||||
description = activity.getString(R.string.onboarding_desc_1),
|
||||
titleRes = R.string.onboarding_title_1,
|
||||
descRes = R.string.onboarding_desc_1,
|
||||
iconRes = R.drawable.ic_launcher_foreground,
|
||||
isFirst = true
|
||||
),
|
||||
OnboardingSlide(
|
||||
title = activity.getString(R.string.onboarding_title_2),
|
||||
description = activity.getString(R.string.onboarding_desc_2),
|
||||
titleRes = R.string.onboarding_title_2,
|
||||
descRes = R.string.onboarding_desc_2,
|
||||
iconRes = R.drawable.ic_launcher_foreground,
|
||||
isFirst = false
|
||||
),
|
||||
OnboardingSlide(
|
||||
title = activity.getString(R.string.onboarding_title_3),
|
||||
description = activity.getString(R.string.onboarding_desc_3),
|
||||
titleRes = R.string.onboarding_title_3,
|
||||
descRes = R.string.onboarding_desc_3,
|
||||
iconRes = R.drawable.ic_launcher_foreground,
|
||||
isFirst = false
|
||||
isFirst = false,
|
||||
isLast = true
|
||||
)
|
||||
)
|
||||
|
||||
override fun getItemCount() = slides.size
|
||||
override fun getItemCount() = slides.size + 1 // +1 for OnboardingConfigureFragment at position 2
|
||||
|
||||
override fun createFragment(position: Int): Fragment = when (position) {
|
||||
1 -> SecuritySetupFragment()
|
||||
else -> OnboardingFragment.newInstance(slides[position])
|
||||
2 -> OnboardingConfigureFragment()
|
||||
else -> OnboardingFragment.newInstance(slides[position - if (position > 2) 1 else 0])
|
||||
}
|
||||
}
|
||||
|
||||
data class OnboardingSlide(
|
||||
val title: String,
|
||||
val description: String,
|
||||
val titleRes: Int,
|
||||
val descRes: Int,
|
||||
val iconRes: Int,
|
||||
val isFirst: Boolean
|
||||
val isFirst: Boolean,
|
||||
val isLast: Boolean = false
|
||||
)
|
||||
|
||||
@@ -86,17 +86,21 @@ class PatternView @JvmOverloads constructor(
|
||||
if (errorState) return false
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
parent?.requestDisallowInterceptTouchEvent(true)
|
||||
recording = true; selected.clear()
|
||||
hit(event.x, event.y)
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
parent?.requestDisallowInterceptTouchEvent(true)
|
||||
touchX = event.x; touchY = event.y
|
||||
hit(event.x, event.y)
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
parent?.requestDisallowInterceptTouchEvent(false)
|
||||
recording = false
|
||||
invalidate()
|
||||
onPatternComplete?.invoke(selected.map { it.index })
|
||||
if (event.action == MotionEvent.ACTION_UP)
|
||||
onPatternComplete?.invoke(selected.map { it.index })
|
||||
}
|
||||
}
|
||||
invalidate()
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import sh.sar.basedbank.R
|
||||
@@ -21,6 +20,7 @@ class SecuritySetupFragment : Fragment() {
|
||||
|
||||
interface Callback {
|
||||
fun onSecuritySetupComplete()
|
||||
fun onSecuritySetupReset()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -33,7 +33,7 @@ class SecuritySetupFragment : Fragment() {
|
||||
private var _b: FragmentSecuritySetupBinding? = null
|
||||
private val b get() = _b!!
|
||||
|
||||
private enum class Step { CHOOSE, PIN_ENTER, PIN_CONFIRM, PATTERN_ENTER, PATTERN_CONFIRM, BIOMETRIC }
|
||||
private enum class Step { CONFIGURED, CHOOSE, PIN_ENTER, PIN_CONFIRM, PATTERN_ENTER, PATTERN_CONFIRM }
|
||||
|
||||
private var step = Step.CHOOSE
|
||||
private val pinDigits = mutableListOf<Int>()
|
||||
@@ -48,9 +48,6 @@ class SecuritySetupFragment : Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val prefs = requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE)
|
||||
val changeMode = arguments?.getBoolean(ARG_CHANGE_MODE, false) ?: false
|
||||
if (!changeMode && prefs.getString("security_method", null) != null) {
|
||||
(activity as? Callback)?.onSecuritySetupComplete()
|
||||
}
|
||||
|
||||
b.cardPin.setOnClickListener { goTo(Step.PIN_ENTER) }
|
||||
b.cardPattern.setOnClickListener { goTo(Step.PATTERN_ENTER) }
|
||||
@@ -62,20 +59,21 @@ class SecuritySetupFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
b.btnPatternBack.setOnClickListener { goTo(Step.CHOOSE) }
|
||||
b.btnChangeLock.setOnClickListener {
|
||||
prefs.edit().remove("security_method").apply()
|
||||
(activity as? Callback)?.onSecuritySetupReset()
|
||||
goTo(Step.CHOOSE)
|
||||
}
|
||||
|
||||
b.patternView.onPatternComplete = { pattern -> handlePattern(pattern) }
|
||||
|
||||
b.btnEnableBiometrics.setOnClickListener {
|
||||
prefs.edit().putBoolean("biometrics_enabled", true).apply()
|
||||
finishSetup()
|
||||
}
|
||||
b.btnSkipBiometrics.setOnClickListener {
|
||||
prefs.edit().putBoolean("biometrics_enabled", false).apply()
|
||||
finishSetup()
|
||||
}
|
||||
|
||||
buildNumpad()
|
||||
goTo(Step.CHOOSE)
|
||||
|
||||
if (!changeMode && prefs.getString("security_method", null) != null) {
|
||||
goTo(Step.CONFIGURED)
|
||||
} else {
|
||||
goTo(Step.CHOOSE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNumpad() {
|
||||
@@ -144,7 +142,7 @@ class SecuritySetupFragment : Fragment() {
|
||||
Step.PIN_CONFIRM -> {
|
||||
if (entered == firstPin) {
|
||||
saveCredential("pin", entered)
|
||||
goToBiometricOrFinish()
|
||||
finishSetup()
|
||||
} else {
|
||||
b.tvPinDots.text = getString(R.string.pin_no_match)
|
||||
pinDigits.clear()
|
||||
@@ -172,7 +170,7 @@ class SecuritySetupFragment : Fragment() {
|
||||
Step.PATTERN_CONFIRM -> {
|
||||
if (pattern == firstPattern) {
|
||||
saveCredential("pattern", pattern.joinToString(""))
|
||||
goToBiometricOrFinish()
|
||||
finishSetup()
|
||||
} else {
|
||||
b.patternView.showError()
|
||||
b.tvPatternStatus.text = getString(R.string.pattern_no_match)
|
||||
@@ -187,29 +185,12 @@ class SecuritySetupFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun goToBiometricOrFinish() {
|
||||
// In change mode, biometrics is managed from Settings — skip this step
|
||||
if (arguments?.getBoolean(ARG_CHANGE_MODE, false) == true) {
|
||||
finishSetup()
|
||||
return
|
||||
}
|
||||
val canAuth = BiometricManager.from(requireContext())
|
||||
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
|
||||
if (canAuth == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
goTo(Step.BIOMETRIC)
|
||||
} else {
|
||||
requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE)
|
||||
.edit().putBoolean("biometrics_enabled", false).apply()
|
||||
finishSetup()
|
||||
}
|
||||
}
|
||||
|
||||
private fun goTo(s: Step) {
|
||||
step = s
|
||||
b.viewConfigured.visibility = if (s == Step.CONFIGURED) View.VISIBLE else View.GONE
|
||||
b.viewChooseMethod.visibility = if (s == Step.CHOOSE) View.VISIBLE else View.GONE
|
||||
b.viewPinSetup.visibility = if (s == Step.PIN_ENTER || s == Step.PIN_CONFIRM) View.VISIBLE else View.GONE
|
||||
b.viewPatternSetup.visibility = if (s == Step.PATTERN_ENTER || s == Step.PATTERN_CONFIRM) View.VISIBLE else View.GONE
|
||||
b.viewBiometric.visibility = if (s == Step.BIOMETRIC) View.VISIBLE else View.GONE
|
||||
|
||||
when (s) {
|
||||
Step.PIN_ENTER -> {
|
||||
@@ -256,6 +237,7 @@ class SecuritySetupFragment : Fragment() {
|
||||
private fun finishSetup() {
|
||||
val cb = activity as? Callback
|
||||
if (cb != null) {
|
||||
goTo(Step.CONFIGURED)
|
||||
cb.onSecuritySetupComplete()
|
||||
} else {
|
||||
parentFragmentManager.popBackStack()
|
||||
|
||||
Reference in New Issue
Block a user