diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt index ea4b2e1..9c233c4 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt @@ -194,8 +194,7 @@ class HomeActivity : AppCompatActivity() { autoRefresh(store) } - // hideAmounts is always false on launch; eye feature just needs to be enabled - viewModel.hideAmounts.value = false + viewModel.hideAmounts.value = getSharedPreferences("prefs", MODE_PRIVATE).getBoolean("hide_amounts", false) // Show dashboard on first create if (savedInstanceState == null) { @@ -434,6 +433,7 @@ fun applyNavLabelVisibility() { if (item.itemId == R.id.action_hide_amounts) { val newHidden = !(viewModel.hideAmounts.value ?: false) viewModel.hideAmounts.value = newHidden + getSharedPreferences("prefs", MODE_PRIVATE).edit().putBoolean("hide_amounts", newHidden).apply() invalidateOptionsMenu() return true } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsLoginsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsLoginsFragment.kt index 3b28281..fa16ed5 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsLoginsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsLoginsFragment.kt @@ -11,6 +11,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.materialswitch.MaterialSwitch import sh.sar.basedbank.BasedBankApp @@ -27,11 +28,21 @@ import sh.sar.basedbank.util.CredentialStore import sh.sar.basedbank.util.FinancingCache import sh.sar.basedbank.util.ForeignLimitsCache import sh.sar.basedbank.util.RecentsCache +import androidx.lifecycle.lifecycleScope +import kotlin.coroutines.resume +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import sh.sar.basedbank.api.bml.BmlActivationResult +import sh.sar.basedbank.api.bml.BmlLoginFlow +import sh.sar.basedbank.api.bml.BmlOtpChannel class SettingsLoginsFragment : Fragment() { private var _binding: FragmentSettingsLoginsBinding? = null private val binding get() = _binding!! + private val viewModel: HomeViewModel by activityViewModels() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentSettingsLoginsBinding.inflate(inflater, container, false) @@ -89,13 +100,15 @@ class SettingsLoginsFragment : Fragment() { val profile = store.loadFahipayUserProfile(loginId) val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.fahipay_name) addLoginRow(container, R.drawable.fahipay_logo, displayName) { + val hide = viewModel.hideAmounts.value ?: false + val masked = "••••••" showLoginDetails( title = getString(R.string.fahipay_name), details = buildString { if (!profile?.fullName.isNullOrBlank()) appendLine("${getString(R.string.login_detail_name)}: ${profile!!.fullName}") - if (!profile?.email.isNullOrBlank()) appendLine("${getString(R.string.login_detail_email)}: ${profile!!.email}") - if (!profile?.mobile.isNullOrBlank()) appendLine("${getString(R.string.login_detail_mobile)}: ${profile!!.mobile}") - if (!profile?.nid.isNullOrBlank()) appendLine("${getString(R.string.login_detail_id_card)}: ${profile!!.nid}") + if (!profile?.email.isNullOrBlank()) appendLine("${getString(R.string.login_detail_email)}: ${if (hide) masked else profile!!.email}") + if (!profile?.mobile.isNullOrBlank()) appendLine("${getString(R.string.login_detail_mobile)}: ${if (hide) masked else profile!!.mobile}") + if (!profile?.nid.isNullOrBlank()) appendLine("${getString(R.string.login_detail_id_card)}: ${if (hide) masked else profile!!.nid}") }.trim(), onLogout = { confirmLogout(getString(R.string.fahipay_name)) { logoutFahipay(store, loginId) } } ) @@ -258,8 +271,16 @@ class SettingsLoginsFragment : Fragment() { ) { val ctx = requireContext() val dp = ctx.resources.displayMetrics.density - val originalHidden = store.getHiddenBmlProfileIds(loginId) - val hidden = originalHidden.toMutableSet() + val hidden = store.getHiddenBmlProfileIds(loginId).toMutableSet() + // Business profiles with no saved session were skipped during login — ensure they start hidden + val needsActivation = bmlProfiles + .filter { it.profileType == "business" && store.loadBmlProfileSession(it.profileId) == null } + .map { it.profileId } + .toMutableSet() + for (id in needsActivation) { + if (hidden.add(id)) store.setHiddenBmlProfileIds(loginId, hidden) + } + val originalHidden = hidden.toSet() val scroll = android.widget.ScrollView(ctx) val container = LinearLayout(ctx).apply { @@ -269,12 +290,14 @@ class SettingsLoginsFragment : Fragment() { } scroll.addView(container) + val hide = viewModel.hideAmounts.value ?: false + val masked = "••••••" listOfNotNull( profile?.fullName?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_name)}: $it" }, - profile?.email?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_email)}: $it" }, - profile?.mobile?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_mobile)}: $it" }, - profile?.customerId?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_customer_id)}: $it" }, - profile?.idCard?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_id_card)}: $it" } + profile?.email?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_email)}: ${if (hide) masked else it}" }, + profile?.mobile?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_mobile)}: ${if (hide) masked else it}" }, + profile?.customerId?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_customer_id)}: ${if (hide) masked else it}" }, + profile?.idCard?.takeIf { it.isNotBlank() }?.let { "${getString(R.string.login_detail_id_card)}: ${if (hide) masked else it}" } ).forEach { line -> container.addView(TextView(ctx).apply { text = line @@ -357,8 +380,19 @@ class SettingsLoginsFragment : Fragment() { toggleRows.forEach { (p, toggle) -> toggle.setOnCheckedChangeListener { _, checked -> - if (checked) hidden.remove(p.profileId) else hidden.add(p.profileId) - updateToggleStates(saveBtn) + if (checked && p.profileId in needsActivation) { + toggle.isChecked = false // revert — enabling requires OTP + viewLifecycleOwner.lifecycleScope.launch { + val success = activateBmlBusinessProfile(store, loginId, p) + if (success) { + needsActivation.remove(p.profileId) + toggle.isChecked = true // listener re-fires, removes from hidden + } + } + } else { + if (checked) hidden.remove(p.profileId) else hidden.add(p.profileId) + updateToggleStates(saveBtn) + } } } @@ -366,7 +400,209 @@ class SettingsLoginsFragment : Fragment() { store.setHiddenBmlProfileIds(loginId, hidden) clearAllCaches(ctx) dialog.dismiss() - (activity as? HomeActivity)?.applyProfileVisibility() + (activity as? HomeActivity)?.relogin() + } + } + + private suspend fun activateBmlBusinessProfile( + store: CredentialStore, + loginId: String, + profile: BmlProfile + ): Boolean { + val creds = store.loadBmlCredentials(loginId) ?: run { + showSimpleError("Credentials not found — please log out and log in again") + return false + } + val progressDialog = MaterialAlertDialogBuilder(requireContext()) + .setMessage("Connecting to BML\u2026") + .setCancelable(false) + .show() + val flow = BmlLoginFlow() + val loginTag = "bml_$loginId" + val activationResult = try { + withContext(Dispatchers.IO) { + flow.login(creds.username, creds.password, creds.otpSeed) + flow.activateProfile(profile, loginTag) + } + } catch (e: Exception) { + progressDialog.dismiss() + showSimpleError(e.message ?: "Authentication failed") + return false + } + progressDialog.dismiss() + return when (activationResult) { + is BmlActivationResult.Success -> { + store.saveBmlProfileSession(profile.profileId, activationResult.session.accessToken, activationResult.session.deviceId) + true + } + is BmlActivationResult.NeedsBusinessOtp -> + continueBmlBusinessOtpFlow(store, loginId, profile, flow, loginTag, activationResult.channels) + } + } + + private suspend fun continueBmlBusinessOtpFlow( + store: CredentialStore, + loginId: String, + profile: BmlProfile, + flow: BmlLoginFlow, + loginTag: String, + channels: List + ): Boolean { + val selectedChannel = showBmlChannelSelectionDialog(profile.name, channels) ?: return false + val channelObj = channels.first { it.channel == selectedChannel } + val sendProgress = MaterialAlertDialogBuilder(requireContext()) + .setMessage("Sending OTP\u2026") + .setCancelable(false) + .show() + try { + withContext(Dispatchers.IO) { flow.requestBusinessOtp(selectedChannel) } + } catch (e: Exception) { + sendProgress.dismiss() + showSimpleError(e.message ?: "Failed to send OTP") + return false + } + sendProgress.dismiss() + var otpError: String? = null + while (true) { + val code = showBmlOtpInputDialog(profile.name, channelObj, otpError) ?: return false + val verifyProgress = MaterialAlertDialogBuilder(requireContext()) + .setMessage("Verifying\u2026") + .setCancelable(false) + .show() + try { + val (session, _) = withContext(Dispatchers.IO) { + flow.submitBusinessOtp(selectedChannel, code, profile, loginTag) + } + verifyProgress.dismiss() + store.saveBmlProfileSession(profile.profileId, session.accessToken, session.deviceId) + return true + } catch (e: Exception) { + verifyProgress.dismiss() + if (e.message?.contains("Invalid OTP") == true) { + otpError = e.message + } else { + showSimpleError(e.message ?: "Verification failed") + return false + } + } + } + } + + private fun showSimpleError(message: String) { + MaterialAlertDialogBuilder(requireContext()) + .setMessage(message) + .setPositiveButton(R.string.close, null) + .show() + } + + private suspend fun showBmlChannelSelectionDialog(profileName: String, channels: List): String? = + suspendCancellableCoroutine { cont -> + val ctx = requireContext() + val dp = ctx.resources.displayMetrics.density + + val list = LinearLayout(ctx).apply { + orientation = LinearLayout.VERTICAL + val vp = (8 * dp).toInt() + setPadding(0, vp, 0, vp) + } + + for (channel in channels) { + val iconRes = when (channel.channel) { + "Email" -> R.drawable.ic_channel_email + "Mobile" -> R.drawable.ic_channel_sms + "WhatsApp" -> R.drawable.ic_channel_whatsapp + else -> R.drawable.ic_channel_sms + } + val iconSize = (24 * dp).toInt() + + val iconView = ImageView(ctx).apply { + setImageResource(iconRes) + } + + val textCol = LinearLayout(ctx).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER_VERTICAL + layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply { + marginStart = (12 * dp).toInt() + } + } + textCol.addView(TextView(ctx).apply { + text = channel.description + setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) + }) + textCol.addView(TextView(ctx).apply { + text = channel.masked + setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall) + alpha = 0.6f + }) + + val row = LinearLayout(ctx).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + val ta = ctx.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground)) + background = ta.getDrawable(0); ta.recycle() + isClickable = true; isFocusable = true + val hp = (24 * dp).toInt(); val vp = (12 * dp).toInt() + setPadding(hp, vp, hp, vp) + } + row.addView(iconView, LinearLayout.LayoutParams(iconSize, iconSize)) + row.addView(textCol) + list.addView(row) + } + + val d = MaterialAlertDialogBuilder(ctx) + .setTitle("Send verification code") + .setView(list) + .setNegativeButton(R.string.cancel) { _, _ -> if (cont.isActive) cont.resume(null) } + .setCancelable(false) + .show() + d.setOnCancelListener { if (cont.isActive) cont.resume(null) } + + // Wire up row clicks after dialog is created so we can dismiss it first + val rows = list.run { (0 until childCount).map { getChildAt(it) } } + rows.forEachIndexed { i, row -> + row.setOnClickListener { + d.dismiss() + if (cont.isActive) cont.resume(channels[i].channel) + } + } + } + + private suspend fun showBmlOtpInputDialog( + profileName: String, + channel: BmlOtpChannel, + errorMsg: String? = null + ): String? = suspendCancellableCoroutine { cont -> + val ctx = requireContext() + val dp = ctx.resources.displayMetrics.density + val input = android.widget.EditText(ctx).apply { + hint = "Enter OTP" + inputType = android.text.InputType.TYPE_CLASS_NUMBER + filters = arrayOf(android.text.InputFilter.LengthFilter(6)) + setPadding((24 * dp).toInt(), (8 * dp).toInt(), (24 * dp).toInt(), (8 * dp).toInt()) + } + val msg = buildString { + append(getString(R.string.bml_business_otp_sent, channel.description)) + append(" (${channel.masked})") + if (errorMsg != null) append("\n\n$errorMsg") + } + val d = MaterialAlertDialogBuilder(ctx) + .setTitle("Enter verification code") + .setMessage(msg) + .setView(input) + .setPositiveButton(R.string.verify, null) + .setNegativeButton(R.string.cancel) { _, _ -> if (cont.isActive) cont.resume(null) } + .setCancelable(false) + .show() + d.setOnCancelListener { if (cont.isActive) cont.resume(null) } + d.getButton(android.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val code = input.text.toString().trim() + if (code.length != 6) { + input.error = "Enter 6 digits" + } else { + d.dismiss() + if (cont.isActive) cont.resume(code) + } } } diff --git a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt index 1260af5..452b657 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt @@ -21,8 +21,6 @@ import sh.sar.basedbank.R import sh.sar.basedbank.api.bml.BmlAccountClient import sh.sar.basedbank.api.bml.BmlActivationResult import sh.sar.basedbank.api.bml.BmlLoginFlow -import sh.sar.basedbank.api.bml.BmlOtpChannel -import sh.sar.basedbank.api.bml.BmlProfile import sh.sar.basedbank.api.fahipay.FahipayAccountClient import sh.sar.basedbank.api.fahipay.FahipayLoginFlow import sh.sar.basedbank.api.fahipay.FahipaySession @@ -34,8 +32,6 @@ import sh.sar.basedbank.util.CredentialStore import sh.sar.basedbank.databinding.FragmentCredentialsBinding import sh.sar.basedbank.ui.home.HomeActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlin.coroutines.resume -import kotlinx.coroutines.suspendCancellableCoroutine class CredentialsFragment : Fragment() { @@ -60,10 +56,6 @@ class CredentialsFragment : Fragment() { private var bmlFlow: BmlLoginFlow? = null private var bmlLoginId: String = "" private var bmlAccumulatedAccounts = mutableListOf() - private var bmlPendingBusinessProfiles = ArrayDeque>>() - private var bmlCurrentBusinessProfile: BmlProfile? = null - private var bmlSelectedChannel: String? = null - private var bmlAwaitingBusinessOtp = false override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentCredentialsBinding.inflate(inflater, container, false) @@ -170,11 +162,7 @@ class CredentialsFragment : Fragment() { private fun attemptLogin() { when (bankType) { - "BML" -> { - if (bmlAwaitingBusinessOtp) submitBmlBusinessOtp() - else attemptBmlLogin() - return - } + "BML" -> { attemptBmlLogin(); return } "FAHIPAY" -> { attemptFahipayLogin(); return } } @@ -257,10 +245,6 @@ class CredentialsFragment : Fragment() { bmlLoginId = username bmlAccumulatedAccounts.clear() - bmlPendingBusinessProfiles.clear() - bmlCurrentBusinessProfile = null - bmlSelectedChannel = null - bmlAwaitingBusinessOtp = false val flow = BmlLoginFlow().also { bmlFlow = it } val loginTag = "bml_$username" @@ -272,24 +256,22 @@ class CredentialsFragment : Fragment() { } if (profiles.isEmpty()) throw Exception("No profiles found for this account") - // Activate each profile; personal profiles are immediate, business ones need OTP + var hasBusinessProfiles = false for (profile in profiles) { + if (profile.profileType == "business") { + hasBusinessProfiles = true + continue // skip — user can enable in Settings → Logins + } val result = withContext(Dispatchers.IO) { flow.activateProfile(profile, loginTag) } - when (result) { - is BmlActivationResult.Success -> { - bmlAccumulatedAccounts += result.accounts - val store = CredentialStore(requireContext()) - store.saveBmlProfileSession(profile.profileId, result.session.accessToken, result.session.deviceId) - val app = requireActivity().application as BasedBankApp - app.bmlSessions[profile.profileId] = result.session - } - is BmlActivationResult.NeedsBusinessOtp -> { - bmlPendingBusinessProfiles.addLast(Pair(profile, result.channels)) - } + if (result is BmlActivationResult.Success) { + bmlAccumulatedAccounts += result.accounts + val store = CredentialStore(requireContext()) + store.saveBmlProfileSession(profile.profileId, result.session.accessToken, result.session.deviceId) + val app = requireActivity().application as BasedBankApp + app.bmlSessions[profile.profileId] = result.session } } - // Save credentials and profile list now (before business OTP prompts) val store = CredentialStore(requireContext()) store.saveBmlCredentials(bmlLoginId, username, password, otpSeed) store.saveBmlProfiles(bmlLoginId, profiles) @@ -300,11 +282,7 @@ class CredentialsFragment : Fragment() { binding.progressBar.visibility = View.GONE binding.btnLogin.isEnabled = true - if (bmlPendingBusinessProfiles.isNotEmpty()) { - processNextBmlBusinessProfile() - } else { - finishBmlLogin() - } + finishBmlLogin(hasBusinessProfiles) } catch (e: Exception) { binding.tvError.text = e.message ?: "Login failed" binding.tvError.visibility = View.VISIBLE @@ -314,115 +292,7 @@ class CredentialsFragment : Fragment() { } } - private suspend fun processNextBmlBusinessProfile() { - val (profile, channels) = bmlPendingBusinessProfiles.removeFirstOrNull() - ?: run { finishBmlLogin(); return } - - bmlCurrentBusinessProfile = profile - - // Show channel selection dialog - val selectedChannel = showBmlChannelDialog(profile.name, channels) ?: run { - // User skipped this profile — move on - processNextBmlBusinessProfile() - return - } - bmlSelectedChannel = selectedChannel - - // Request OTP - binding.progressBar.visibility = View.VISIBLE - binding.btnLogin.isEnabled = false - try { - withContext(Dispatchers.IO) { - bmlFlow!!.requestBusinessOtp(selectedChannel) - } - } catch (e: Exception) { - binding.progressBar.visibility = View.GONE - binding.btnLogin.isEnabled = true - binding.tvError.text = e.message ?: "Failed to send OTP" - binding.tvError.visibility = View.VISIBLE - processNextBmlBusinessProfile() - return - } - - // Show OTP input — disable credential fields (same pattern as Fahipay TOTP step) - bmlAwaitingBusinessOtp = true - binding.etUsername.isEnabled = false - binding.etPassword.isEnabled = false - binding.etOtpSeed.isEnabled = false - binding.progressBar.visibility = View.GONE - binding.tilTotpCode.hint = getString(R.string.bml_business_otp_hint, profile.name) - binding.tilTotpCode.helperText = getString(R.string.bml_business_otp_sent, selectedChannel) - binding.tilTotpCode.visibility = View.VISIBLE - binding.etTotpCode.text?.clear() - binding.btnLogin.text = getString(R.string.verify) - binding.btnLogin.isEnabled = true - binding.tvError.visibility = View.GONE - } - - private fun submitBmlBusinessOtp() { - val code = binding.etTotpCode.text.toString().trim() - if (code.length != 6) { - binding.tvError.text = getString(R.string.fahipay_totp_hint) - binding.tvError.visibility = View.VISIBLE - return - } - - binding.tvError.visibility = View.GONE - binding.progressBar.visibility = View.VISIBLE - binding.btnLogin.isEnabled = false - - val profile = bmlCurrentBusinessProfile ?: return - val channel = bmlSelectedChannel ?: return - val loginTag = "bml_$bmlLoginId" - - viewLifecycleOwner.lifecycleScope.launch { - try { - val (session, accounts) = withContext(Dispatchers.IO) { - bmlFlow!!.submitBusinessOtp(channel, code, profile, loginTag) - } - bmlAccumulatedAccounts += accounts - val store = CredentialStore(requireContext()) - store.saveBmlProfileSession(profile.profileId, session.accessToken, session.deviceId) - val app = requireActivity().application as BasedBankApp - app.bmlSessions[profile.profileId] = session - - bmlAwaitingBusinessOtp = false - binding.tilTotpCode.visibility = View.GONE - binding.btnLogin.text = getString(R.string.login) - - binding.progressBar.visibility = View.GONE - binding.btnLogin.isEnabled = true - - if (bmlPendingBusinessProfiles.isNotEmpty()) { - processNextBmlBusinessProfile() - } else { - finishBmlLogin() - } - } catch (e: Exception) { - binding.tvError.text = e.message ?: "OTP verification failed" - binding.tvError.visibility = View.VISIBLE - binding.progressBar.visibility = View.GONE - binding.btnLogin.isEnabled = true - } - } - } - - private suspend fun showBmlChannelDialog(profileName: String, channels: List): String? = - suspendCancellableCoroutine { cont -> - val options = channels.map { "${it.description} (${it.masked})" }.toTypedArray() - val dialog = MaterialAlertDialogBuilder(requireContext()) - .setTitle(getString(R.string.bml_business_otp_title, profileName)) - .setItems(options) { _, which -> - if (cont.isActive) cont.resume(channels[which].channel) - } - .setNegativeButton(R.string.bml_business_otp_skip) { _: android.content.DialogInterface, _: Int -> - if (cont.isActive) cont.resume(null as String?) - } - .show() - dialog.setOnCancelListener { if (cont.isActive) cont.resume(null as String?) } - } - - private suspend fun finishBmlLogin() { + private suspend fun finishBmlLogin(hasBusinessProfiles: Boolean = false) { val store = CredentialStore(requireContext()) val accounts = bmlAccumulatedAccounts.toList() @@ -479,6 +349,9 @@ class CredentialsFragment : Fragment() { app.bmlAccounts = app.bmlAccounts.filter { it.loginTag != "bml_$bmlLoginId" } + accounts app.accounts = app.accounts.filter { it.loginTag != "bml_$bmlLoginId" } + accounts + if (hasBusinessProfiles) { + Toast.makeText(requireContext(), "Business profiles can be enabled in Settings → Logins", Toast.LENGTH_LONG).show() + } val intent = Intent(requireContext(), HomeActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK startActivity(intent) diff --git a/app/src/main/res/drawable/ic_channel_email.xml b/app/src/main/res/drawable/ic_channel_email.xml new file mode 100644 index 0000000..e5d35b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_channel_email.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_channel_sms.xml b/app/src/main/res/drawable/ic_channel_sms.xml new file mode 100644 index 0000000..3d9b3a5 --- /dev/null +++ b/app/src/main/res/drawable/ic_channel_sms.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_channel_whatsapp.xml b/app/src/main/res/drawable/ic_channel_whatsapp.xml new file mode 100644 index 0000000..35cebb4 --- /dev/null +++ b/app/src/main/res/drawable/ic_channel_whatsapp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f248011..1b9a806 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -175,10 +175,7 @@ Verify - Activate %s profile - %s OTP code OTP sent via %s - Skip This is your source account diff --git a/whatsapp-svgrepo-com.svg b/whatsapp-svgrepo-com.svg new file mode 100644 index 0000000..a677047 --- /dev/null +++ b/whatsapp-svgrepo-com.svg @@ -0,0 +1,6 @@ + + + +whatsapp + + \ No newline at end of file diff --git a/whatsapp.svg b/whatsapp.svg new file mode 100644 index 0000000..2a6c69f --- /dev/null +++ b/whatsapp.svg @@ -0,0 +1 @@ +WhatsApp \ No newline at end of file