diff options
10 files changed, 89 insertions, 41 deletions
diff --git a/app/build.gradle b/app/build.gradle index b4017ed1..ad0355ac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,6 +80,7 @@ dependencies { implementation deps.androidx.core_ktx implementation deps.androidx.documentfile implementation deps.androidx.fragment_ktx + implementation deps.androidx.lifecycle_common implementation deps.androidx.lifecycle_livedata_ktx implementation deps.androidx.lifecycle_viewmodel_ktx implementation deps.androidx.local_broadcast_manager diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt index 8cef5682..2f3a3a7b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt @@ -33,6 +33,7 @@ import com.zeapo.pwdstore.ui.adapters.PasswordItemRecyclerAdapter import com.zeapo.pwdstore.ui.dialogs.ItemCreationBottomSheet import com.zeapo.pwdstore.utils.PasswordItem import com.zeapo.pwdstore.utils.PasswordRepository +import com.zeapo.pwdstore.utils.viewBinding import me.zhanghai.android.fastscroll.FastScrollerBuilder import java.io.File import java.util.Stack @@ -44,10 +45,9 @@ class PasswordFragment : Fragment() { private var recyclerViewStateToRestore: Parcelable? = null private var actionMode: ActionMode? = null - private var _binding: PasswordRecyclerViewBinding? = null private val model: SearchableRepositoryViewModel by activityViewModels() - private val binding get() = _binding!! + private val binding by viewBinding(PasswordRecyclerViewBinding::bind) private fun requireStore() = requireActivity() as PasswordStore @@ -56,7 +56,6 @@ class PasswordFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = PasswordRecyclerViewBinding.inflate(inflater, container, false) settings = PreferenceManager.getDefaultSharedPreferences(requireContext()) initializePasswordList() binding.fab.setOnClickListener { @@ -149,11 +148,6 @@ class PasswordFragment : Fragment() { } } - override fun onDestroyView() { - _binding = null - super.onDestroyView() - } - private val actionModeCallback = object : ActionMode.Callback { // Called when the action mode is created; startActionMode() was called override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { diff --git a/app/src/main/java/com/zeapo/pwdstore/ToCloneOrNot.kt b/app/src/main/java/com/zeapo/pwdstore/ToCloneOrNot.kt index 511e978e..49a680cf 100644 --- a/app/src/main/java/com/zeapo/pwdstore/ToCloneOrNot.kt +++ b/app/src/main/java/com/zeapo/pwdstore/ToCloneOrNot.kt @@ -10,25 +10,19 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import kotlinx.android.synthetic.main.fragment_to_clone_or_not.clone_from_server_button -import kotlinx.android.synthetic.main.fragment_to_clone_or_not.local_directory_button -import kotlinx.android.synthetic.main.fragment_to_clone_or_not.settings_button +import com.zeapo.pwdstore.databinding.FragmentToCloneOrNotBinding +import com.zeapo.pwdstore.utils.viewBinding class ToCloneOrNot : Fragment() { - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_to_clone_or_not, container, false) - } + private val binding by viewBinding(FragmentToCloneOrNotBinding::bind) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = binding.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - settings_button.setOnClickListener { startActivity(Intent(requireContext(), UserPreference::class.java)) } - local_directory_button.setOnClickListener { (requireActivity() as PasswordStore).initRepository(PasswordStore.NEW_REPO_BUTTON) } - clone_from_server_button.setOnClickListener { (requireActivity() as PasswordStore).initRepository(PasswordStore.CLONE_REPO_BUTTON) } + binding.settingsButton.setOnClickListener { startActivity(Intent(requireContext(), UserPreference::class.java)) } + binding.localDirectoryButton.setOnClickListener { (requireActivity() as PasswordStore).initRepository(PasswordStore.NEW_REPO_BUTTON) } + binding.cloneFromServerButton.setOnClickListener { (requireActivity() as PasswordStore).initRepository(PasswordStore.CLONE_REPO_BUTTON) } } } diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt index 57a1f21a..1a969b51 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt @@ -36,6 +36,7 @@ import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure import com.zeapo.pwdstore.autofill.oreo.FormOrigin import com.zeapo.pwdstore.databinding.ActivityOreoAutofillFilterBinding import com.zeapo.pwdstore.utils.PasswordItem +import com.zeapo.pwdstore.utils.viewBinding @TargetApi(Build.VERSION_CODES.O) class AutofillFilterView : AppCompatActivity() { @@ -72,7 +73,7 @@ class AutofillFilterView : AppCompatActivity() { private lateinit var formOrigin: FormOrigin private lateinit var directoryStructure: DirectoryStructure - private lateinit var binding: ActivityOreoAutofillFilterBinding + private val binding by viewBinding(ActivityOreoAutofillFilterBinding::inflate) private val model: SearchableRepositoryViewModel by viewModels { ViewModelProvider.AndroidViewModelFactory(application) @@ -80,7 +81,6 @@ class AutofillFilterView : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityOreoAutofillFilterBinding.inflate(layoutInflater) setContentView(binding.root) setFinishOnTouchOutside(true) diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt index 5ea3e66b..8ac980cc 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt @@ -22,6 +22,7 @@ import com.zeapo.pwdstore.autofill.oreo.AutofillPublisherChangedException import com.zeapo.pwdstore.autofill.oreo.FormOrigin import com.zeapo.pwdstore.autofill.oreo.computeCertificatesHash import com.zeapo.pwdstore.databinding.ActivityOreoAutofillPublisherChangedBinding +import com.zeapo.pwdstore.utils.viewBinding @TargetApi(Build.VERSION_CODES.O) class AutofillPublisherChangedActivity : AppCompatActivity() { @@ -45,11 +46,10 @@ class AutofillPublisherChangedActivity : AppCompatActivity() { } private lateinit var appPackage: String - private lateinit var binding: ActivityOreoAutofillPublisherChangedBinding + private val binding by viewBinding(ActivityOreoAutofillPublisherChangedBinding::inflate) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityOreoAutofillPublisherChangedBinding.inflate(layoutInflater) setContentView(binding.root) setFinishOnTouchOutside(true) diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt index be1f016b..be911a3a 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt @@ -14,15 +14,15 @@ import com.google.android.material.snackbar.Snackbar import com.zeapo.pwdstore.R import com.zeapo.pwdstore.databinding.ActivityGitConfigBinding import com.zeapo.pwdstore.utils.PasswordRepository +import com.zeapo.pwdstore.utils.viewBinding import org.eclipse.jgit.lib.Constants class GitConfigActivity : BaseGitActivity() { - private lateinit var binding: ActivityGitConfigBinding + private val binding by viewBinding(ActivityGitConfigBinding::inflate) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityGitConfigBinding.inflate(layoutInflater) setContentView(binding.root) supportActionBar?.setDisplayHomeAsUpEnabled(true) diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt index f1d195ac..1eac569b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt @@ -17,6 +17,7 @@ import com.zeapo.pwdstore.databinding.ActivityGitCloneBinding import com.zeapo.pwdstore.git.config.ConnectionMode import com.zeapo.pwdstore.git.config.Protocol import com.zeapo.pwdstore.utils.PasswordRepository +import com.zeapo.pwdstore.utils.viewBinding import java.io.IOException /** @@ -25,11 +26,10 @@ import java.io.IOException */ class GitServerConfigActivity : BaseGitActivity() { - private lateinit var binding: ActivityGitCloneBinding + private val binding by viewBinding(ActivityGitCloneBinding::inflate) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityGitCloneBinding.inflate(layoutInflater) val isClone = intent?.extras?.getInt(REQUEST_ARG_OP) ?: -1 == REQUEST_CLONE if (isClone) { binding.saveButton.text = getString(R.string.clone_button) diff --git a/app/src/main/java/com/zeapo/pwdstore/sshkeygen/SshKeyGenFragment.kt b/app/src/main/java/com/zeapo/pwdstore/sshkeygen/SshKeyGenFragment.kt index 923b53a4..510f5ce2 100644 --- a/app/src/main/java/com/zeapo/pwdstore/sshkeygen/SshKeyGenFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/sshkeygen/SshKeyGenFragment.kt @@ -19,6 +19,7 @@ import com.jcraft.jsch.JSch import com.jcraft.jsch.KeyPair import com.zeapo.pwdstore.R import com.zeapo.pwdstore.databinding.FragmentSshKeygenBinding +import com.zeapo.pwdstore.utils.viewBinding import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -28,17 +29,9 @@ import java.io.FileOutputStream class SshKeyGenFragment : Fragment() { private var keyLength = 4096 - private var _binding: FragmentSshKeygenBinding? = null - private val binding get() = _binding!! + private val binding by viewBinding(FragmentSshKeygenBinding::bind) - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - _binding = FragmentSshKeygenBinding.inflate(inflater, container, false) - return binding.root - } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = binding.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -60,7 +53,6 @@ class SshKeyGenFragment : Fragment() { override fun onDestroyView() { super.onDestroyView() - _binding = null } // Invoked when 'Generate' button of SshKeyGenFragment clicked. Generates a diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt b/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt new file mode 100644 index 00000000..35eb7ae3 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt @@ -0,0 +1,66 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package com.zeapo.pwdstore.utils + + +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.observe +import androidx.viewbinding.ViewBinding +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Imported from https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c + */ +class FragmentViewBindingDelegate<T : ViewBinding>( + val fragment: Fragment, + val viewBindingFactory: (View) -> T +) : ReadOnlyProperty<Fragment, T> { + + private var binding: T? = null + + init { + fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner -> + viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + binding = null + } + }) + } + } + }) + } + + override fun getValue(thisRef: Fragment, property: KProperty<*>): T { + val binding = binding + if (binding != null) { + return binding + } + + val lifecycle = fragment.viewLifecycleOwner.lifecycle + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { + throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") + } + + return viewBindingFactory(thisRef.requireView()).also { this.binding = it } + } +} + +fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) = + FragmentViewBindingDelegate(this, viewBindingFactory) + +inline fun <T : ViewBinding> AppCompatActivity.viewBinding(crossinline bindingInflater: (LayoutInflater) -> T) = + lazy(LazyThreadSafetyMode.NONE) { + bindingInflater.invoke(layoutInflater) + } diff --git a/dependencies.gradle b/dependencies.gradle index b0b4dd80..72ab6757 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -32,6 +32,7 @@ ext.deps = [ core_ktx: 'androidx.core:core-ktx:1.3.0-rc01', documentfile: 'androidx.documentfile:documentfile:1.0.1', fragment_ktx: 'androidx.fragment:fragment-ktx:1.3.0-alpha03', + lifecycle_common: 'androidx.lifecycle:lifecycle-common-java8:2.3.0-alpha01', lifecycle_livedata_ktx: 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-alpha01', lifecycle_viewmodel_ktx: 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-alpha01', local_broadcast_manager: 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0-alpha01', |