diff --git a/app/src/main/java/org/fedorahosted/freeotp/token/TokenLayout.kt b/app/src/main/java/org/fedorahosted/freeotp/token/TokenLayout.kt index b13392c7..d75a8c7b 100644 --- a/app/src/main/java/org/fedorahosted/freeotp/token/TokenLayout.kt +++ b/app/src/main/java/org/fedorahosted/freeotp/token/TokenLayout.kt @@ -6,6 +6,7 @@ import android.util.Log import android.view.View import android.view.animation.AnimationUtils import android.widget.ImageView +import android.widget.LinearLayout import android.widget.PopupMenu import android.widget.TextView @@ -27,6 +28,7 @@ class TokenLayout : MaterialCardView, View.OnClickListener, Runnable { private lateinit var mIssuer: TextView private lateinit var mLabel: TextView private lateinit var mCategory: TextView + private lateinit var mTitlesContainer: LinearLayout private lateinit var mMenu: ImageView private lateinit var mPopupMenu: PopupMenu @@ -51,6 +53,7 @@ class TokenLayout : MaterialCardView, View.OnClickListener, Runnable { mIssuer = findViewById(R.id.issuer) as TextView mLabel = findViewById(R.id.label) as TextView mCategory = findViewById(R.id.category) as TextView + mTitlesContainer = findViewById(R.id.titles_container) as LinearLayout mMenu = findViewById(R.id.menu) as ImageView mPopupMenu = PopupMenu(context, mMenu) @@ -73,6 +76,7 @@ class TokenLayout : MaterialCardView, View.OnClickListener, Runnable { mProgressOuter.clearAnimation() mProgressInner.visibility = View.GONE mProgressOuter.visibility = View.GONE + setCodeOverlayVisible(false) // Get the code placeholder. val placeholder = CharArray(token.digits) @@ -84,22 +88,26 @@ class TokenLayout : MaterialCardView, View.OnClickListener, Runnable { mImage.setTokenImage(token) // Set the labels. - mLabel.text = token.label - mIssuer.text = token.issuer - mCode.text = mPlaceholder + val category = token.category?.trim()?.takeIf { it.isNotEmpty() } + val accountLabel = if (category == null) { + token.label + } else { + "${token.label} ($category)" + } + val issuerLabel = token.alias?.trim()?.takeIf { it.isNotEmpty() } + ?: token.issuer + mLabel.text = accountLabel + mIssuer.text = issuerLabel + mCode.text = "" if (mIssuer.text.isEmpty()) { - mIssuer.text = token.label + mIssuer.text = accountLabel mLabel.visibility = View.GONE } else { mLabel.visibility = View.VISIBLE } - if (token.category.isNullOrBlank()) { - mCategory.visibility = View.GONE - } else { - mCategory.visibility = View.VISIBLE - mCategory.text = token.category - } + mCategory.visibility = View.GONE + mCategory.text = "" } private fun animate(view: View, anim: Int, animate: Boolean) { @@ -109,6 +117,11 @@ class TokenLayout : MaterialCardView, View.OnClickListener, Runnable { view.startAnimation(a) } + private fun setCodeOverlayVisible(visible: Boolean) { + mCode.visibility = if (visible) View.VISIBLE else View.GONE + mTitlesContainer.alpha = if (visible) 0.28f else 1.0f + } + fun start(type: OtpTokenType, codes: TokenCode, animate: Boolean) { mCodes = codes mType = type @@ -128,6 +141,7 @@ class TokenLayout : MaterialCardView, View.OnClickListener, Runnable { } mStartTime = System.currentTimeMillis() + setCodeOverlayVisible(true) post(this) } @@ -138,7 +152,8 @@ class TokenLayout : MaterialCardView, View.OnClickListener, Runnable { override fun run() { // Get the current data val code = mCodes?.currentCode?: run { - mCode.text = mPlaceholder + mCode.text = "" + setCodeOverlayVisible(false) mProgressInner.visibility = View.GONE mProgressOuter.visibility = View.GONE animate(mImage, R.anim.token_image_fadein, true) diff --git a/app/src/main/java/org/fedorahosted/freeotp/ui/AddActivity.kt b/app/src/main/java/org/fedorahosted/freeotp/ui/AddActivity.kt index f48a88c7..1acab600 100644 --- a/app/src/main/java/org/fedorahosted/freeotp/ui/AddActivity.kt +++ b/app/src/main/java/org/fedorahosted/freeotp/ui/AddActivity.kt @@ -44,7 +44,10 @@ import kotlinx.coroutines.launch import org.fedorahosted.freeotp.R import org.fedorahosted.freeotp.data.OtpTokenDatabase import org.fedorahosted.freeotp.data.OtpTokenFactory +import org.fedorahosted.freeotp.util.blankToNull import org.fedorahosted.freeotp.util.ImageUtil +import org.liberty.android.freeotp.token_images.TokenImage +import org.liberty.android.freeotp.token_images.displayKey import java.io.UnsupportedEncodingException import java.net.URLEncoder import java.util.* @@ -60,7 +63,9 @@ class AddActivity : AppCompatActivity(), View.OnClickListener, CompoundButton.On private lateinit var mImage: ImageButton private lateinit var mIssuer: EditText private lateinit var mLabel: EditText + private lateinit var mAlias: EditText private lateinit var mCategory: AutoCompleteTextView + private lateinit var mIconKey: AutoCompleteTextView private lateinit var mSecret: EditText private lateinit var mInterval: EditText private lateinit var mCounter: EditText @@ -91,7 +96,9 @@ class AddActivity : AppCompatActivity(), View.OnClickListener, CompoundButton.On mImage = findViewById(R.id.image) mIssuer = findViewById(R.id.issuer) mLabel = findViewById(R.id.label) + mAlias = findViewById(R.id.alias) mCategory = findViewById(R.id.category) + mIconKey = findViewById(R.id.icon_key) mSecret = findViewById(R.id.secret) mInterval = findViewById(R.id.interval) mCounter = findViewById(R.id.counter) @@ -114,10 +121,14 @@ class AddActivity : AppCompatActivity(), View.OnClickListener, CompoundButton.On val tw = AddTextWatcher(this) mIssuer.addTextChangedListener(tw) mLabel.addTextChangedListener(tw) + mAlias.addTextChangedListener(tw) mCategory.addTextChangedListener(tw) + mIconKey.addTextChangedListener(tw) mSecret.addTextChangedListener(AddSecretTextWatcher(this)) mInterval.addTextChangedListener(tw) + setupIconKeyAdapter() + lifecycleScope.launch { val categories = otpTokenDatabase.otpTokenDao().getAllCategories().first().toMutableList() categories.add(0, getString(R.string.uncategorized)) @@ -185,7 +196,9 @@ class AddActivity : AppCompatActivity(), View.OnClickListener, CompoundButton.On // Add the token lifecycleScope.launch { val token = OtpTokenFactory.createFromUri(Uri.parse(uri)).copy( - category = mCategory.text.toString() + alias = mAlias.text.toString().blankToNull(), + category = mCategory.text.toString().blankToNull(), + iconKey = mIconKey.text.toString().blankToNull() ) otpTokenDatabase.otpTokenDao().insert(token) setResult(Activity.RESULT_OK) @@ -216,4 +229,19 @@ class AddActivity : AppCompatActivity(), View.OnClickListener, CompoundButton.On } } } + + private fun setupIconKeyAdapter() { + val iconKeys = TokenImage.values().map { it.displayKey() } + val adapter = ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, iconKeys) + mIconKey.setAdapter(adapter) + mIconKey.threshold = 0 + mIconKey.setOnClickListener { + mIconKey.showDropDown() + } + mIconKey.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus) { + mIconKey.showDropDown() + } + } + } } diff --git a/app/src/main/java/org/fedorahosted/freeotp/ui/DeleteActivity.kt b/app/src/main/java/org/fedorahosted/freeotp/ui/DeleteActivity.kt index 21ed44ac..cf9ee782 100644 --- a/app/src/main/java/org/fedorahosted/freeotp/ui/DeleteActivity.kt +++ b/app/src/main/java/org/fedorahosted/freeotp/ui/DeleteActivity.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.launch import org.fedorahosted.freeotp.R import org.fedorahosted.freeotp.data.OtpTokenDatabase import org.fedorahosted.freeotp.databinding.DeleteBinding +import org.fedorahosted.freeotp.util.displayName import org.fedorahosted.freeotp.util.setTokenImage import javax.inject.Inject @@ -65,7 +66,7 @@ class DeleteActivity : AppCompatActivity() { return@collect } - (findViewById(R.id.issuer) as TextView).text = token.issuer + (findViewById(R.id.issuer) as TextView).text = token.displayName() (findViewById(R.id.label) as TextView).text = token.label binding.imageView.setTokenImage(token) } diff --git a/app/src/main/java/org/fedorahosted/freeotp/ui/EditActivity.kt b/app/src/main/java/org/fedorahosted/freeotp/ui/EditActivity.kt index 682889e1..3c8fe439 100644 --- a/app/src/main/java/org/fedorahosted/freeotp/ui/EditActivity.kt +++ b/app/src/main/java/org/fedorahosted/freeotp/ui/EditActivity.kt @@ -48,8 +48,11 @@ import kotlinx.coroutines.launch import org.fedorahosted.freeotp.R import org.fedorahosted.freeotp.data.OtpTokenDatabase import org.fedorahosted.freeotp.databinding.EditBinding +import org.fedorahosted.freeotp.util.blankToNull import org.fedorahosted.freeotp.util.ImageUtil import org.fedorahosted.freeotp.util.Settings +import org.liberty.android.freeotp.token_images.TokenImage +import org.liberty.android.freeotp.token_images.displayKey import javax.inject.Inject @AndroidEntryPoint @@ -62,7 +65,9 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { private lateinit var binding: EditBinding private lateinit var mIssuer: EditText private lateinit var mLabel: EditText + private lateinit var mAlias: EditText private lateinit var mCategory: AutoCompleteTextView + private lateinit var mIconKey: AutoCompleteTextView private lateinit var mImage: ImageButton private lateinit var mRestore: Button private lateinit var mSave: Button @@ -71,8 +76,12 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { private var mIssuerDefault: String? = null private var mLabelCurrent: String? = null private var mLabelDefault: String? = null + private var mAliasCurrent: String? = null + private var mAliasDefault: String? = null private var mCategoryCurrent: String? = null private var mCategoryDefault: String? = null + private var mIconKeyCurrent: String? = null + private var mIconKeyDefault: String? = null private var mImageCurrent: Uri? = null private var mImageDefault: Uri? = null @@ -123,17 +132,23 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { val token = otpTokenDatabase.otpTokenDao().get(tokenId).first() ?: return@launch mIssuerCurrent = token.issuer mLabelCurrent = token.label + mAliasCurrent = token.alias ?: "" mCategoryCurrent = token.category ?: "" + mIconKeyCurrent = token.iconKey ?: "" mImageCurrent = if (token.imagePath != null) Uri.parse(token.imagePath) else null mIssuerDefault = token.issuer mLabelDefault = token.label + mAliasDefault = mAliasCurrent mCategoryDefault = mCategoryCurrent + mIconKeyDefault = mIconKeyCurrent mImageDefault = mImageCurrent // Get references to widgets. mIssuer = findViewById(R.id.issuer) as EditText mLabel = findViewById(R.id.label) as EditText + mAlias = findViewById(R.id.alias) as EditText mCategory = findViewById(R.id.category) as AutoCompleteTextView + mIconKey = findViewById(R.id.icon_key) as AutoCompleteTextView mImage = findViewById(R.id.image) as ImageButton mRestore = findViewById(R.id.restore) as Button mSave = findViewById(R.id.save) as Button @@ -142,7 +157,9 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { // Setup text changed listeners. mIssuer.addTextChangedListener(this@EditActivity) mLabel.addTextChangedListener(this@EditActivity) + mAlias.addTextChangedListener(this@EditActivity) mCategory.addTextChangedListener(this@EditActivity) + mIconKey.addTextChangedListener(this@EditActivity) // Setup click callbacks. findViewById(R.id.cancel).setOnClickListener(this@EditActivity) @@ -157,7 +174,9 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { mLabel.setText(mLabelCurrent) mIssuer.setText(mIssuerCurrent) + mAlias.setText(mAliasCurrent) mCategory.setText(mCategoryCurrent) + mIconKey.setText(mIconKeyCurrent) mIssuer.setSelection(mIssuer.text.length) // Token details @@ -233,6 +252,8 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { mCategory.setText("") } } + + setupIconKeyAdapter() } } @@ -261,9 +282,11 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { val label = mLabel.text.toString() val issuer = mIssuer.text.toString() + val alias = mAlias.text.toString() val category = mCategory.text.toString() - mSave.isEnabled = label != mLabelCurrent || issuer != mIssuerCurrent || category != mCategoryCurrent || !imageIs(mImageCurrent) - mRestore.isEnabled = label != mLabelDefault || issuer != mIssuerDefault || category != mCategoryDefault || !imageIs(mImageDefault) + val iconKey = mIconKey.text.toString() + mSave.isEnabled = label != mLabelCurrent || issuer != mIssuerCurrent || alias != mAliasCurrent || category != mCategoryCurrent || iconKey != mIconKeyCurrent || !imageIs(mImageCurrent) + mRestore.isEnabled = label != mLabelDefault || issuer != mIssuerDefault || alias != mAliasDefault || category != mCategoryDefault || iconKey != mIconKeyDefault || !imageIs(mImageDefault) } override fun afterTextChanged(s: Editable) { @@ -281,11 +304,11 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { R.id.restore -> { mLabel.setText(mLabelDefault) mIssuer.setText(mIssuerDefault) + mAlias.setText(mAliasDefault) mCategory.setText(mCategoryDefault) + mIconKey.setText(mIconKeyDefault) mIssuer.setSelection(mIssuer.text.length) - mImageDefault?.let { - showImage(it) - } + showImage(mImageDefault) } R.id.save -> { @@ -294,7 +317,9 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { val newToken = token.copy( issuer = mIssuer.text.toString(), label = mLabel.text.toString(), - category = mCategory.text.toString(), + alias = mAlias.text.toString().blankToNull(), + category = mCategory.text.toString().blankToNull(), + iconKey = mIconKey.text.toString().blankToNull(), imagePath = mImageDisplay?.toString() ) @@ -312,8 +337,23 @@ class EditActivity : AppCompatActivity(), TextWatcher, View.OnClickListener { } } + private fun setupIconKeyAdapter() { + val iconKeys = TokenImage.values().map { it.displayKey() } + val adapter = ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, iconKeys) + mIconKey.setAdapter(adapter) + mIconKey.threshold = 0 + mIconKey.setOnClickListener { + mIconKey.showDropDown() + } + mIconKey.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus) { + mIconKey.showDropDown() + } + } + } + companion object { const val EXTRA_TOKEN_ID = "token_id" const val IMAGE_REQUEST_CODE = 60 } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/fedorahosted/freeotp/ui/MainActivity.kt b/app/src/main/java/org/fedorahosted/freeotp/ui/MainActivity.kt index 66db5b92..65207781 100644 --- a/app/src/main/java/org/fedorahosted/freeotp/ui/MainActivity.kt +++ b/app/src/main/java/org/fedorahosted/freeotp/ui/MainActivity.kt @@ -65,6 +65,7 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar +import com.google.android.material.tabs.TabLayout import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.first @@ -96,6 +97,9 @@ class MainActivity : AppCompatActivity() { private var menu: Menu? = null private var lastSessionEndTimestamp = 0L; private var categories: List = emptyList() + private var hasUncategorizedTokens = false + private var selectedCategoryFilter: String? = null + private var updatingCategoryTabs = false private val tokenListObserver: AdapterDataObserver = object: AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { @@ -181,6 +185,15 @@ class MainActivity : AppCompatActivity() { launch { viewModel.getAllCategories().collect { categories = it + updateCategoryTabs() + invalidateOptionsMenu() + } + } + + launch { + viewModel.hasUncategorizedTokens().collect { + hasUncategorizedTokens = it + updateCategoryTabs() invalidateOptionsMenu() } } @@ -203,6 +216,20 @@ class MainActivity : AppCompatActivity() { }) + binding.categoryTabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + if (!updatingCategoryTabs) { + selectCategoryFilter(tab.tag as? String, updateTabs = false) + } + } + + override fun onTabUnselected(tab: TabLayout.Tab) { + } + + override fun onTabReselected(tab: TabLayout.Tab) { + } + }) + binding.addTokenFab.setOnClickListener { startActivity(Intent(this, ScanTokenActivity::class.java)) } @@ -245,14 +272,19 @@ class MainActivity : AppCompatActivity() { override fun onPrepareOptionsMenu(menu: Menu): Boolean { val filterItem = menu.findItem(R.id.action_filter) val filterMenu = filterItem?.subMenu ?: return super.onPrepareOptionsMenu(menu) + filterItem.isVisible = categories.isNotEmpty() || hasUncategorizedTokens filterMenu.clear() - filterMenu.add(Menu.NONE, R.id.action_filter_all, Menu.NONE, R.string.all_categories) - filterMenu.add(Menu.NONE, R.id.action_filter_uncategorized, Menu.NONE, R.string.uncategorized) + if (categories.isNotEmpty()) { + filterMenu.add(Menu.NONE, R.id.action_filter_all, Menu.NONE, R.string.all_categories) + } + if (hasUncategorizedTokens) { + filterMenu.add(Menu.NONE, R.id.action_filter_uncategorized, Menu.NONE, R.string.uncategorized) + } for (category in categories) { filterMenu.add(Menu.NONE, Menu.NONE, Menu.NONE, category).setOnMenuItemClickListener { - viewModel.setCategoryFilter(category) + selectCategoryFilter(category) true } } @@ -273,12 +305,12 @@ class MainActivity : AppCompatActivity() { } R.id.action_filter_all -> { - viewModel.setCategoryFilter(null) + selectCategoryFilter(null) return true } R.id.action_filter_uncategorized -> { - viewModel.setCategoryFilter(MainViewModel.CATEGORY_UNCATEGORIZED) + selectCategoryFilter(MainViewModel.CATEGORY_UNCATEGORIZED) return true } @@ -478,6 +510,74 @@ class MainActivity : AppCompatActivity() { this.menu?.findItem(R.id.require_authentication)?.isChecked = settings.requireAuthentication } + private fun selectCategoryFilter(category: String?, updateTabs: Boolean = true) { + selectedCategoryFilter = category + viewModel.setCategoryFilter(category) + + if (updateTabs) { + selectCategoryTab(category) + } + } + + private fun updateCategoryTabs() { + normalizeCategoryFilter() + + updatingCategoryTabs = true + binding.categoryTabs.removeAllTabs() + if (categories.isEmpty() && !hasUncategorizedTokens) { + binding.categoryTabs.visibility = View.GONE + updatingCategoryTabs = false + return + } + + binding.categoryTabs.visibility = View.VISIBLE + if (categories.isNotEmpty()) { + addCategoryTab(getString(R.string.category_tab_all), null) + } + if (hasUncategorizedTokens) { + addCategoryTab(getString(R.string.category_tab_none), MainViewModel.CATEGORY_UNCATEGORIZED) + } + categories.forEach { category -> + addCategoryTab(category, category) + } + selectCategoryTab(selectedCategoryFilter) + updatingCategoryTabs = false + } + + private fun normalizeCategoryFilter() { + val normalizedCategory = when { + categories.isEmpty() && !hasUncategorizedTokens -> null + categories.isEmpty() && hasUncategorizedTokens -> MainViewModel.CATEGORY_UNCATEGORIZED + selectedCategoryFilter == MainViewModel.CATEGORY_UNCATEGORIZED && !hasUncategorizedTokens -> null + selectedCategoryFilter != null && + selectedCategoryFilter != MainViewModel.CATEGORY_UNCATEGORIZED && + !categories.contains(selectedCategoryFilter) -> null + else -> selectedCategoryFilter + } + + if (selectedCategoryFilter != normalizedCategory) { + selectedCategoryFilter = normalizedCategory + viewModel.setCategoryFilter(normalizedCategory) + } + } + + private fun addCategoryTab(title: String, category: String?) { + val tab = binding.categoryTabs.newTab() + .setText(title) + tab.tag = category + binding.categoryTabs.addTab(tab, category == selectedCategoryFilter) + } + + private fun selectCategoryTab(category: String?) { + for (index in 0 until binding.categoryTabs.tabCount) { + val tab = binding.categoryTabs.getTabAt(index) ?: continue + if (tab.tag == category) { + tab.select() + return + } + } + } + private fun verifyAuthentication(isEnabling: Boolean = false, isDisabling: Boolean = false) { val executor = ContextCompat.getMainExecutor(this) val biometricPrompt = BiometricPrompt(this, executor, diff --git a/app/src/main/java/org/fedorahosted/freeotp/ui/MainViewModel.kt b/app/src/main/java/org/fedorahosted/freeotp/ui/MainViewModel.kt index fea1603e..f530e794 100644 --- a/app/src/main/java/org/fedorahosted/freeotp/ui/MainViewModel.kt +++ b/app/src/main/java/org/fedorahosted/freeotp/ui/MainViewModel.kt @@ -56,6 +56,8 @@ class MainViewModel @Inject constructor( fun getAllCategories(): Flow> = otpTokenDatabase.otpTokenDao().getAllCategories() + fun hasUncategorizedTokens(): Flow = otpTokenDatabase.otpTokenDao().hasUncategorizedTokens() + fun getTokenList(): Flow> { return combine(authState, tokenSearchQuery, categoryFilter, otpTokenDatabase.otpTokenDao().getAll()) {auth, searchQuery, category, tokens -> if (auth != AuthState.AUTHENTICATED) { @@ -66,7 +68,9 @@ class MainViewModel @Inject constructor( if (searchQuery.isNotEmpty()) { filteredTokens = filteredTokens.filter { token -> token.label.contains(searchQuery, true) - || token.issuer?.contains(searchQuery, true) ?: false + || (token.issuer?.contains(searchQuery, true) ?: false) + || (token.alias?.contains(searchQuery, true) ?: false) + || (token.iconKey?.contains(searchQuery, true) ?: false) } } diff --git a/app/src/main/java/org/fedorahosted/freeotp/ui/ShareQrCodeActivity.kt b/app/src/main/java/org/fedorahosted/freeotp/ui/ShareQrCodeActivity.kt index eb17be1c..0eeed0a2 100644 --- a/app/src/main/java/org/fedorahosted/freeotp/ui/ShareQrCodeActivity.kt +++ b/app/src/main/java/org/fedorahosted/freeotp/ui/ShareQrCodeActivity.kt @@ -37,6 +37,7 @@ import org.fedorahosted.freeotp.R import org.fedorahosted.freeotp.data.OtpTokenDatabase import org.fedorahosted.freeotp.data.OtpTokenFactory import org.fedorahosted.freeotp.databinding.ShareQrcodeBinding +import org.fedorahosted.freeotp.util.displayName import javax.inject.Inject @AndroidEntryPoint @@ -68,7 +69,7 @@ class ShareQrCodeActivity : AppCompatActivity() { // Get token values. val token = otpTokenDatabase.otpTokenDao().get(tokenId).first() ?: return@launch - title = token.issuer + title = token.displayName() // Get references to widgets. val qrcodeWriter = QRCodeWriter(); diff --git a/app/src/main/java/org/fedorahosted/freeotp/util/TokenDisplayExtensions.kt b/app/src/main/java/org/fedorahosted/freeotp/util/TokenDisplayExtensions.kt new file mode 100644 index 00000000..b3060f5a --- /dev/null +++ b/app/src/main/java/org/fedorahosted/freeotp/util/TokenDisplayExtensions.kt @@ -0,0 +1,13 @@ +package org.fedorahosted.freeotp.util + +import org.fedorahosted.freeotp.data.OtpToken + +fun OtpToken.displayName(): String { + return alias?.takeIf { it.isNotBlank() } + ?: issuer?.takeIf { it.isNotBlank() } + ?: label +} + +fun String?.blankToNull(): String? { + return this?.trim()?.takeIf { it.isNotEmpty() } +} diff --git a/app/src/main/java/org/fedorahosted/freeotp/util/TokenImageViewExtensions.kt b/app/src/main/java/org/fedorahosted/freeotp/util/TokenImageViewExtensions.kt index d54ec999..5fca728b 100644 --- a/app/src/main/java/org/fedorahosted/freeotp/util/TokenImageViewExtensions.kt +++ b/app/src/main/java/org/fedorahosted/freeotp/util/TokenImageViewExtensions.kt @@ -6,8 +6,8 @@ import com.amulyakhare.textdrawable.util.ColorGenerator import com.bumptech.glide.Glide import org.fedorahosted.freeotp.R import org.fedorahosted.freeotp.data.OtpToken -import org.liberty.android.freeotp.token_images.TokenImage import org.liberty.android.freeotp.token_images.matchToken +import org.liberty.android.freeotp.token_images.matchTokenImage fun ImageView.setTokenImage(token: OtpToken) { when { @@ -17,24 +17,31 @@ fun ImageView.setTokenImage(token: OtpToken) { .placeholder(R.drawable.logo) .into(this) } + !token.iconKey.isNullOrBlank() -> { + matchTokenImage(token.iconKey)?.let { + setImageResource(it.resource) + } ?: setFallbackTextImage(token) + } !token.issuer.isNullOrBlank() -> { matchIssuerWithTokenThumbnail(token)?.let { setImageResource(it) - } ?: run { - val tokenText = token.issuer?.substring(0, 1) ?: "" - val color = ColorGenerator.MATERIAL.getColor(tokenText) - val tokenTextDrawable = TextDrawable.builder().buildRoundRect(tokenText, color, 10) - setImageDrawable(tokenTextDrawable) - } + } ?: setFallbackTextImage(token) } else -> { - setImageResource(R.drawable.logo) + setFallbackTextImage(token) } } } +private fun ImageView.setFallbackTextImage(token: OtpToken) { + val tokenText = token.displayName().substring(0, 1) + val color = ColorGenerator.MATERIAL.getColor(tokenText) + val tokenTextDrawable = TextDrawable.builder().buildRoundRect(tokenText, color, 10) + setImageDrawable(tokenTextDrawable) +} + private fun matchIssuerWithTokenThumbnail(token: OtpToken): Int? { - return TokenImage.values().firstOrNull { + return org.liberty.android.freeotp.token_images.TokenImage.values().firstOrNull { it.matchToken(token.issuer, token.label) }?.resource -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index ff01516d..ff783ea3 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -39,6 +39,12 @@ android:layout_marginEnd="@dimen/space_2" android:iconifiedByDefault="true"/> + + + + + + + + @@ -87,5 +109,28 @@ android:completionThreshold="1" android:textAppearance="?android:attr/textAppearanceSmallInverse" /> + + + + + + + diff --git a/app/src/main/res/layout/token.xml b/app/src/main/res/layout/token.xml index 5a351146..f5a8a9b6 100644 --- a/app/src/main/res/layout/token.xml +++ b/app/src/main/res/layout/token.xml @@ -31,11 +31,11 @@ + android:minHeight="72dp"> - - - - - + app:layout_constraintEnd_toEndOf="parent"> + + + + + + + + app:layout_constraintBottom_toBottomOf="@+id/token_content" + app:layout_constraintTop_toTopOf="@+id/token_content" /> - \ No newline at end of file + diff --git a/app/src/main/res/values-af-rZA/strings.xml b/app/src/main/res/values-af-rZA/strings.xml index b6ab5084..2ccbf875 100644 --- a/app/src/main/res/values-af-rZA/strings.xml +++ b/app/src/main/res/values-af-rZA/strings.xml @@ -12,6 +12,8 @@ Teller Uitreiker Rekening + Alias + Ikoon ID Geheim Tipe @@ -65,4 +67,6 @@ Launch with screenshot enabled Token details Share to other apps + Alles + Geen diff --git a/app/src/main/res/values-ar-rSA/strings.xml b/app/src/main/res/values-ar-rSA/strings.xml index 9f132e8f..50443291 100644 --- a/app/src/main/res/values-ar-rSA/strings.xml +++ b/app/src/main/res/values-ar-rSA/strings.xml @@ -12,6 +12,8 @@ أداة العد وحدة اصدار الحساب + الاسم المستعار + الأيقونة الكود السر النوع @@ -65,4 +67,6 @@ إطلاق مع تمكين لقطة الشاشة تفاصيل الرمز Share to other apps + الكل + لا شيء diff --git a/app/src/main/res/values-ca-rES/strings.xml b/app/src/main/res/values-ca-rES/strings.xml index 3b973102..682f6a42 100644 --- a/app/src/main/res/values-ca-rES/strings.xml +++ b/app/src/main/res/values-ca-rES/strings.xml @@ -12,6 +12,8 @@ Comptador Emissor Compte + Àlies + Icona Identificador Clau secreta Tipus @@ -65,4 +67,6 @@ Inicia amb la captura de pantalla activada Detalls de l\'OTP Envia a una altra app + Tot + Cap diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 7dd134e3..2251adb1 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -12,6 +12,8 @@ Počítadlo Vydavatel Účet + Alias + Ikona ID Tajný Typ @@ -65,4 +67,6 @@ Spustit s povoleným snímkem obrazovky Detaily tokenu Share to other apps + Vše + Žádné diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 30313a93..4f9db30b 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -12,6 +12,8 @@ Zähler Aussteller Konto + Alias + Symbol ID Geheimnis Art @@ -65,4 +67,6 @@ Starte mit aktiviertem Screenshot Token-Details Mit anderen Apps teilen + Alle + Keine diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index e464721d..6601011e 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -12,6 +12,8 @@ Contador Emisor Cuenta + Alias + Icono ID Secreto Tipo @@ -65,4 +67,6 @@ Iniciar con captura de pantalla activada Detalles del token Enviar a otras aplicaciones + Todo + Ninguna diff --git a/app/src/main/res/values-eu-rES/strings.xml b/app/src/main/res/values-eu-rES/strings.xml index f6221ba2..e3eba285 100644 --- a/app/src/main/res/values-eu-rES/strings.xml +++ b/app/src/main/res/values-eu-rES/strings.xml @@ -12,6 +12,8 @@ Kontagailua Igorlea Kontua + Aliasa + Ikonoa ID Sekretua Mota @@ -65,4 +67,6 @@ Abiarazi pantaila-argazkiak gaituz Token xehetasunak Partekatu beste aplikazioekin + Denak + Bat ere ez diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index de84eba9..97e4e573 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -12,6 +12,8 @@ Compteur Émetteur Compte + Alias + Icône ID Secret Type @@ -65,4 +67,6 @@ Lancer avec capture d\'écran activée Détails du jeton Partager avec d\'autres applications + Tout + Aucune diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index 7c05bc05..16f1cd7d 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -12,6 +12,8 @@ Számláló Kibocsátó Fiók + Álnév + Ikon Azonosító Titok Típus @@ -65,4 +67,6 @@ Launch with screenshot enabled Token details Share to other apps + Mind + Nincs diff --git a/app/src/main/res/values-id-rID/strings.xml b/app/src/main/res/values-id-rID/strings.xml index 298ca398..39f4fcaf 100644 --- a/app/src/main/res/values-id-rID/strings.xml +++ b/app/src/main/res/values-id-rID/strings.xml @@ -12,6 +12,8 @@ Counter Penerbit Akun + Alias + Ikon ID Kunci Rahasia Tipe @@ -76,4 +78,6 @@ Luncurkan dengan tangkapan layar aktif Detail Token Bagikan ke aplikasi lain + Semua + Tidak ada diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 02a7a196..c82e14e0 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -12,6 +12,8 @@ Contatore Emittente Conto + Alias + Icona ID Segreto Tipo @@ -65,4 +67,6 @@ Usa con gli screenshot abilitati Dettagli del token Condividi con altre app + Tutti + Nessuna diff --git a/app/src/main/res/values-iw-rHE/strings.xml b/app/src/main/res/values-iw-rHE/strings.xml index c035d775..d63cd93b 100644 --- a/app/src/main/res/values-iw-rHE/strings.xml +++ b/app/src/main/res/values-iw-rHE/strings.xml @@ -12,6 +12,8 @@ מונה מנפיק חשבון + כינוי + סמל מזהה סוד סוג @@ -65,4 +67,6 @@ הפעלה כאשר צילום מסך מופעל פרטי אסימון שיתוף עם יישומים אחרים + הכל + ללא diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 3b7f587c..592c066d 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -12,6 +12,8 @@ カウンター 発行者 アカウント + エイリアス + アイコン ID 秘密鍵 タイプ @@ -65,4 +67,6 @@ スクリーンショットを有効にして起動 トークンの詳細 他のアプリと共有する + すべて + なし diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index b6297f3b..92a17904 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -12,6 +12,8 @@ Licznik Wydawca Konto + Alias + Ikona ID Sekret Typ @@ -65,4 +67,6 @@ Uruchom z włączonym zrzutem ekranu Szczegóły tokenu Udostępnij innym aplikacjom + Wszystkie + Brak diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 9908301c..b0234eb4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -12,6 +12,8 @@ Contador Emissor Conta + Apelido + Ícone ID Segredo Tipo @@ -65,4 +67,6 @@ Iniciar com captura de tela ativada Detalhes do token Compartilhar com outros apps + Todos + Nenhuma diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 178c0cab..2430ffe4 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -12,6 +12,8 @@ Счетчик Издатель Аккаунт + Псевдоним + Значок ID Секретный ключ Тип @@ -65,4 +67,6 @@ Запуск с включённым режимом скриншота Детали токена Поделиться с другими приложениями + Все + Нет diff --git a/app/src/main/res/values-si-rLK/strings.xml b/app/src/main/res/values-si-rLK/strings.xml index 3042f4ce..bed26b13 100644 --- a/app/src/main/res/values-si-rLK/strings.xml +++ b/app/src/main/res/values-si-rLK/strings.xml @@ -12,6 +12,8 @@ කවුන්ටරය නිකුත් කරන්නා ගැන + අන්වර්ථ නාමය + නිරූපකය හැඳුනුම්පත රහස ටයිප් කරන්න @@ -65,4 +67,6 @@ තිර රුවක් සබල කර දියත් කරන්න ටෝකන් විස්තර Share to other apps + සියල්ල + නැත diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 1e48a5b2..b617517c 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -12,6 +12,8 @@ Räknare Utfärdare Konto + Alias + Ikon ID Hemlighet Typ @@ -65,4 +67,6 @@ Starta med skärmdump aktiverad Tokendetaljer Dela med andra appar + Alla + Ingen diff --git a/app/src/main/res/values-ta-rIN/strings.xml b/app/src/main/res/values-ta-rIN/strings.xml index 5d70b50c..2fbd86fc 100644 --- a/app/src/main/res/values-ta-rIN/strings.xml +++ b/app/src/main/res/values-ta-rIN/strings.xml @@ -12,6 +12,8 @@ Counter வழங்குபவர் கணக்கு + மாற்றுப்பெயர் + ஐகான் ID இரகசிய கடவு வகை @@ -65,4 +67,6 @@ Launch with screenshot enabled Token details Share to other apps + அனைத்தும் + எதுவுமில்லை diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index 79c46693..f2a4de26 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -12,6 +12,8 @@ Sayaç Yayınlayan Hesap + Takma ad + Simge Kimlik Gizli Anahtar Tip @@ -65,4 +67,6 @@ Ekran görüntüsü aktifken başlat Token ayrıntıları Diğer uygulamalarla paylaş + Tümü + Yok diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 2ec9fe7c..25afc903 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -12,6 +12,8 @@ Лічильник Видавець Обліковий запис + Псевдонім + Значок ІД Секрет Тип @@ -65,4 +67,6 @@ Запустити з дозволеним знімком екрана Деталі ключа Share to other apps + Усі + Немає diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 08df180c..fb2abb59 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -12,6 +12,8 @@ 计数器 发布人 帐户 + 别名 + 图标 ID 密钥 类型 @@ -65,4 +67,6 @@ 启动时允许屏幕截图 令牌详细信息 分享到其他应用 + 全部 + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 506a565b..324caf36 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -12,6 +12,8 @@ 計數器 發行者 帳號 + 別名 + 圖示 ID 金鑰 類型 @@ -65,4 +67,6 @@ 啟用螢幕截圖啟動 權杖詳細資訊 分享給其他應用程式 + 全部 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 450cf297..19b63df1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,6 +12,8 @@ Counter Issuer Account + Alias + Icon ID Secret Type @@ -80,4 +82,6 @@ Category All categories Uncategorized + All + None diff --git a/token-data/src/main/java/org/fedorahosted/freeotp/data/MigrationUtil.kt b/token-data/src/main/java/org/fedorahosted/freeotp/data/MigrationUtil.kt index 1c6deb7b..2b4822f3 100644 --- a/token-data/src/main/java/org/fedorahosted/freeotp/data/MigrationUtil.kt +++ b/token-data/src/main/java/org/fedorahosted/freeotp/data/MigrationUtil.kt @@ -38,6 +38,8 @@ class MigrationUtil @Inject constructor( ordinal = index.toLong() + 1, issuer = legacyToken.issuer, label = legacyToken.label, + alias = legacyToken.alias, + iconKey = legacyToken.iconKey, imagePath = legacyToken.image?.toString(), tokenType = if (legacyToken.type == Token.TokenType.HOTP) OtpTokenType.HOTP else OtpTokenType.TOTP, algorithm = legacyToken.algorithm ?: "sha1", @@ -56,6 +58,8 @@ class MigrationUtil @Inject constructor( val uri = OtpTokenFactory.toUri(otpToken) Token(uri).apply { category = otpToken.category + alias = otpToken.alias + iconKey = otpToken.iconKey } } } diff --git a/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpToken.kt b/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpToken.kt index 060baa6b..6538a257 100644 --- a/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpToken.kt +++ b/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpToken.kt @@ -9,6 +9,8 @@ data class OtpToken ( val ordinal: Long, val issuer: String?, val label: String, + val alias: String? = null, + val iconKey: String? = null, val imagePath: String?, val tokenType: OtpTokenType, val algorithm: String, @@ -18,4 +20,4 @@ data class OtpToken ( val period: Int, val encryptionType: EncryptionType, val category: String? = null -) \ No newline at end of file +) diff --git a/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenDao.kt b/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenDao.kt index 15f3fc81..08cf96bc 100644 --- a/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenDao.kt +++ b/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenDao.kt @@ -29,6 +29,9 @@ interface OtpTokenDao { @Query("select distinct category from otp_tokens where category is not null and category != ''") fun getAllCategories(): Flow> + @Query("select exists(select 1 from otp_tokens where category is null or category = '')") + fun hasUncategorizedTokens(): Flow + @Query("delete from otp_tokens where id = :id") suspend fun deleteById(id: Long) @@ -73,4 +76,4 @@ interface OtpTokenDao { } } } -} \ No newline at end of file +} diff --git a/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenDatabase.kt b/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenDatabase.kt index 89f84f16..f2efdd09 100644 --- a/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenDatabase.kt +++ b/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenDatabase.kt @@ -7,16 +7,23 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import org.fedorahosted.freeotp.data.util.Converters -@Database(entities = [OtpToken::class], version = 2, exportSchema = false) +@Database(entities = [OtpToken::class], version = 3, exportSchema = false) @TypeConverters(Converters::class) abstract class OtpTokenDatabase : RoomDatabase() { abstract fun otpTokenDao(): OtpTokenDao companion object { val MIGRATION_1_2 = object : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE otp_tokens ADD COLUMN category TEXT") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE otp_tokens ADD COLUMN category TEXT") + } + } + + val MIGRATION_2_3 = object : Migration(2, 3) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE otp_tokens ADD COLUMN alias TEXT") + db.execSQL("ALTER TABLE otp_tokens ADD COLUMN iconKey TEXT") } } } -} \ No newline at end of file +} diff --git a/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenFactory.kt b/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenFactory.kt index e2becb80..93170315 100644 --- a/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenFactory.kt +++ b/token-data/src/main/java/org/fedorahosted/freeotp/data/OtpTokenFactory.kt @@ -109,4 +109,4 @@ object OtpTokenFactory { return builder.build() } -} \ No newline at end of file +} diff --git a/token-data/src/main/java/org/fedorahosted/freeotp/data/legacy/Token.java b/token-data/src/main/java/org/fedorahosted/freeotp/data/legacy/Token.java index 4c3a6673..3404a8ff 100644 --- a/token-data/src/main/java/org/fedorahosted/freeotp/data/legacy/Token.java +++ b/token-data/src/main/java/org/fedorahosted/freeotp/data/legacy/Token.java @@ -56,6 +56,8 @@ public static enum TokenType { private String labelAlt; private String image; private String imageAlt; + private String alias; + private String iconKey; private TokenType type; private String algo; private byte[] secret; @@ -353,6 +355,22 @@ public void setCategory(String category) { this.category = category; } + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getIconKey() { + return iconKey; + } + + public void setIconKey(String iconKey) { + this.iconKey = iconKey; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -368,6 +386,8 @@ public boolean equals(Object o) { Objects.equals(labelAlt, token.labelAlt) && Objects.equals(image, token.image) && Objects.equals(imageAlt, token.imageAlt) && + Objects.equals(alias, token.alias) && + Objects.equals(iconKey, token.iconKey) && type == token.type && Objects.equals(algo, token.algo) && Arrays.equals(secret, token.secret) && @@ -376,7 +396,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = Objects.hash(issuerInt, issuerExt, issuerAlt, label, labelAlt, image, imageAlt, type, algo, digits, counter, period, category); + int result = Objects.hash(issuerInt, issuerExt, issuerAlt, label, labelAlt, image, imageAlt, alias, iconKey, type, algo, digits, counter, period, category); result = 31 * result + Arrays.hashCode(secret); return result; } diff --git a/token-data/src/main/java/org/fedorahosted/freeotp/data/module/DataModule.kt b/token-data/src/main/java/org/fedorahosted/freeotp/data/module/DataModule.kt index c08a7c76..671ba84d 100644 --- a/token-data/src/main/java/org/fedorahosted/freeotp/data/module/DataModule.kt +++ b/token-data/src/main/java/org/fedorahosted/freeotp/data/module/DataModule.kt @@ -18,7 +18,7 @@ object DataModule { @Provides fun database(@ApplicationContext context:Context) = Room.databaseBuilder(context, OtpTokenDatabase::class.java, "otp-token-db") - .addMigrations(OtpTokenDatabase.MIGRATION_1_2) + .addMigrations(OtpTokenDatabase.MIGRATION_1_2, OtpTokenDatabase.MIGRATION_2_3) .build() @Singleton diff --git a/token-images/src/main/java/org/liberty/android/freeotp/token_images/TokenImage.kt b/token-images/src/main/java/org/liberty/android/freeotp/token_images/TokenImage.kt index 06f34acd..f9247bca 100644 --- a/token-images/src/main/java/org/liberty/android/freeotp/token_images/TokenImage.kt +++ b/token-images/src/main/java/org/liberty/android/freeotp/token_images/TokenImage.kt @@ -279,3 +279,11 @@ fun TokenImage.matchToken(issuer: String?, label: String?): Boolean { issuerMatched } } + +fun TokenImage.displayKey(): String = issuer ?: name + +fun matchTokenImage(query: String?, label: String? = null): TokenImage? { + return TokenImage.values().firstOrNull { + it.matchToken(query, label) + } +}