Day28 - 储存帐密及自动登入

今天来做储存帐密和自动登入的功能。

提醒:今天的内容缺少了加密储存密码,是极度危险的功能,这部份预计会放到明天处理。

Layout的部份我一样使用Chip取代Checkbox,在Login页面中加入:

<!-- ... -->
<com.google.android.material.chip.ChipGroup
    android:id="@+id/chipGroup"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:alpha="0"
    app:layout_constraintBottom_toTopOf="@id/login"
    app:layout_constraintEnd_toEndOf="@id/pwdLayout"
    app:layout_constraintStart_toStartOf="@id/pwdLayout"
    app:layout_constraintTop_toBottomOf="@id/pwdLayout">

    <com.google.android.material.chip.Chip
        android:id="@+id/saveId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkable="true"
        android:fontFamily="sans-serif"
        android:text="记住ID"
        android:textSize="16sp"
        android:textStyle="bold" />

    <com.google.android.material.chip.Chip
        android:id="@+id/savePwd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkable="true"
        android:fontFamily="sans-serif"
        android:text="记住密码"
        android:textSize="16sp"
        android:textStyle="bold" />

    <com.google.android.material.chip.Chip
        android:id="@+id/auto_login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkable="true"
        android:fontFamily="sans-serif"
        android:text="自动登入"
        android:textSize="16sp"
        android:textStyle="bold"
        android:visibility="gone" />
</com.google.android.material.chip.ChipGroup>
<!-- ... -->

SharePreferences

储存的内容会存到SharePreferences中,先做一些前置作业:

const val PREF_NAME = "preferences"
const val PREF_FIELD_ID = "id"
const val PREF_FIELD_PWD = "pwd"
const val PREF_FIELD_AES_KEY = "aes_key"
const val PREF_FIELD_AUTO_LOGIN = "auto_login"

class LoginFragment : Fragment() {
    // ...
    private lateinit var preferences: SharedPreferences
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        preferences = view.context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
    }
}

PREF_NAME是在Day21中已加入的值,其余是这两天会用到的。

储存帐密及是否自动登入

储存的时机点我是放在登入成功时,以下片段节自Day07

// ...
4 -> { // "【主功能表】"
    withContext(Dispatchers.Main) {
        val editor = preferences.edit()
        if (binding.saveId.isChecked) {
            editor.putString(PREF_FIELD_ID, id)
        }

        if (binding.savePwd.isChecked) {
            editor.putString(PREF_FIELD_PWD, pwd)
        }
        editor.putBoolean(
            PREF_FIELD_AUTO_LOGIN,
            binding.autoLogin.isChecked
        )
        editor.apply()
        (requireActivity() as MainActivity).dismissLoading()
        NavHostFragment.findNavController(this@LoginFragment)
            .navigate(R.id.action_loginFragment_to_searchArticleFragment)
    }
    break
}
// ...

提取帐密和是否自动登入

提取的位置我是放在开始执行进入动画前:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // ...
    binding.chipGroup.postDelayed({
        val id = preferences.getString(PREF_FIELD_ID, null)
        val pwd = preferences.getString(PREF_FIELD_PWD, null)
        if (!id.isNullOrBlank()) {
            binding.idInput.setText(id)
            binding.saveId.isChecked = true
        }
        if (!pwd.isNullOrBlank()) {
            binding.pwdInput.setText(pwd)
            binding.savePwd.isChecked = true
        }
        if (binding.saveId.isChecked && binding.savePwd.isChecked) {
            binding.autoLogin.visibility = View.VISIBLE
            binding.autoLogin.isChecked =
                preferences.getBoolean(PREF_FIELD_AUTO_LOGIN, false)
        }

        enterAnimate(binding.chipGroup)
    }, 267)
    // ...
}

autoLogin的检查条件除了本身是否纪录有勾选外,还需要多判断是否有已储存的帐密,此外autoLogin的按钮我预设是隐藏的,只有在saveIdsavePwd都被打勾的状态下才能勾选。

Chip点击动作

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // ...
    binding.saveId.setOnCheckedChangeListener { _, isChecked ->
        checkShowAutoLogin()
        if (!isChecked) {
            preferences.edit().remove(PREF_FIELD_ID).apply()
        }
    }
    binding.savePwd.setOnCheckedChangeListener { _, isChecked ->
        checkShowAutoLogin()
        if (!isChecked) {
            preferences.edit().remove(PREF_FIELD_PWD).apply()
        }
    }
    binding.autoLogin.setOnCheckedChangeListener { _, isChecked ->
        preferences.edit().putBoolean(PREF_FIELD_AUTO_LOGIN, isChecked).apply()
    }
    // ...
}

private fun checkShowAutoLogin() {
    if (binding.saveId.isChecked && binding.savePwd.isChecked) {
        binding.autoLogin.visibility = View.VISIBLE
    } else {
        binding.autoLogin.visibility = View.GONE
        binding.autoLogin.isChecked = false
    }
}

如前所述autoLogin只有在saveIdsavePwd都被打勾的状态下才能勾选,因此这两个按钮在点击时会去呼叫checkShowAutoLogin来判断目前状态,其余内容就是取消勾选时移除目前已储存的资料。

自动登入

在处理自动登入时,除了autoLogin的勾选状态外,还需要考虑到登出断线重连的状态。在这两个状态中我们不该执行自动登入的功能。

为了处理上述两个状态,我分别在loginFragment的fragment tag和welcome_to_login的action tag中加入Argument。

fragment tag

<argument
    android:name="canAutoLogin"
    android:defaultValue="false"
    app:argType="boolean"
    app:nullable="false" />

action tag

<argument
    android:name="canAutoLogin"
    android:defaultValue="true"
    app:argType="boolean"
    app:nullable="false" />

可以看到两者的差异只差在defaultValue,只有在从WelcomeFragment到LoginFragment的action中才会将canAutoLogin设为true

有了这个参数值,就可以做以下判断:

// ...
if (preferences.getBoolean(PREF_FIELD_AUTO_LOGIN, false)
    && LoginFragmentArgs.fromBundle(requireArguments()).canAutoLogin
) {
    binding.login.performClick()
}
// ...

至此自动登入功能也就完成了。

今日功能画面

https://i.imgur.com/IeNR2li.gif

最後再次提醒:今天的内容缺少了加密储存密码,是极度危险的功能,这部份预计会放到明天处理。


<<:  DAY28 linebot message api-Template 介绍-1

>>:  D33 - 用 Swift 和公开资讯,打造投资理财的 Apps { 台股申购功能扩充.4 }

【Day15】:STM32辗压Arduino的功能—TIM(下)

TIMER+NVIC中断 今天我们来使用Timer的中断功能吧! 设定与昨天大致相同,只是我们现在需...

Day25 建立角色功能

首先建立装载角色资料的 ViewModel,因为接下来的权限会以角色判断,ASP.NET Core ...

【第十五天 - Flutter 官方 CodeLab Get-To-Know 活动报名教学(下)】

前言 我很喜欢这篇 CodeLab,我自己认为,如果这篇的内容看得懂那 Provider 基本上都会...

Day6-"while、do-while"

Day4有跟大家提到for回圈,但并非所有条件都必须用for回圈来写,这个时候我们就可以利用whil...

Day7 甘特图的特性与关键路径

再来要讲到管理工具中耳熟能详的工具之一,甘特图。Gantt Chart在规划专案时,几乎就是所以人第...