inject SharedPreferences (#4441)

(this one is for @charlag)

Calling `PreferenceManager.getDefaultSharedPreferences()` will read the
preference file from disk every time. This PR makes `SharedPreferences`
a singleton so they will only be created once at appstart (with a few
exceptions where it is hard to inject, e.g. in the `openLink` helper)
which should help getting our ANRs down.

```
StrictMode policy violation; ~duration=285 ms: android.os.strictmode.DiskReadViolation
    at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1666)
    at libcore.io.BlockGuardOs.access(BlockGuardOs.java:74)
    at libcore.io.ForwardingOs.access(ForwardingOs.java:128)
    at android.app.ActivityThread$AndroidOs.access(ActivityThread.java:8054)
    at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:313)
    at java.io.File.exists(File.java:813)
    at android.app.ContextImpl.ensurePrivateDirExists(ContextImpl.java:790)
    at android.app.ContextImpl.ensurePrivateDirExists(ContextImpl.java:781)
    at android.app.ContextImpl.getPreferencesDir(ContextImpl.java:737)
    at android.app.ContextImpl.getSharedPreferencesPath(ContextImpl.java:962)
    at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:583)
    at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:221)
    at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:221)
    at androidx.preference.PreferenceManager.getDefaultSharedPreferences(PreferenceManager.java:119)
    at com.keylesspalace.tusky.BaseActivity.onCreate(BaseActivity.java:96)
   ...
```
This commit is contained in:
Konrad Pozniak 2024-05-24 08:05:09 +02:00 committed by GitHub
commit d554d71958
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 159 additions and 135 deletions

View file

@ -16,6 +16,7 @@
package com.keylesspalace.tusky
import android.content.SharedPreferences
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -25,7 +26,6 @@ import androidx.appcompat.widget.SearchView
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
@ -44,6 +44,7 @@ import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
import com.keylesspalace.tusky.viewmodel.State
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch
private typealias AccountInfo = Pair<TimelineAccount, Boolean>
@ -51,6 +52,9 @@ private typealias AccountInfo = Pair<TimelineAccount, Boolean>
@AndroidEntryPoint
class AccountsInListFragment : DialogFragment() {
@Inject
lateinit var preferences: SharedPreferences
private val viewModel: AccountsInListViewModel by viewModels()
private val binding by viewBinding(FragmentAccountsInListBinding::bind)
@ -60,9 +64,6 @@ class AccountsInListFragment : DialogFragment() {
private val searchAdapter = SearchAdapter()
private val radius by unsafeLazy { resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp) }
private val pm by unsafeLazy { PreferenceManager.getDefaultSharedPreferences(requireContext()) }
private val animateAvatar by unsafeLazy { pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false) }
private val animateEmojis by unsafeLazy { pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -206,6 +207,8 @@ class AccountsInListFragment : DialogFragment() {
position: Int
) {
val account = getItem(position)
val animateAvatar = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
holder.binding.displayNameTextView.text = account.name.emojify(account.emojis, holder.binding.displayNameTextView, animateEmojis)
holder.binding.usernameTextView.text = account.username
loadAvatar(account.avatar, holder.binding.avatar, radius, animateAvatar)
@ -257,6 +260,9 @@ class AccountsInListFragment : DialogFragment() {
) {
val (account, inAList) = getItem(position)
val animateAvatar = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
holder.binding.displayNameTextView.text = account.name.emojify(account.emojis, holder.binding.displayNameTextView, animateEmojis)
holder.binding.usernameTextView.text = account.username
loadAvatar(account.avatar, holder.binding.avatar, radius, animateAvatar)

View file

@ -42,6 +42,7 @@ import com.keylesspalace.tusky.adapter.AccountSelectionAdapter;
import com.keylesspalace.tusky.components.login.LoginActivity;
import com.keylesspalace.tusky.db.entity.AccountEntity;
import com.keylesspalace.tusky.db.AccountManager;
import com.keylesspalace.tusky.di.PreferencesEntryPoint;
import com.keylesspalace.tusky.interfaces.AccountSelectionListener;
import com.keylesspalace.tusky.settings.AppTheme;
import com.keylesspalace.tusky.settings.PrefKeys;
@ -55,6 +56,8 @@ import javax.inject.Inject;
import static com.keylesspalace.tusky.settings.PrefKeys.APP_THEME;
import dagger.hilt.EntryPoints;
/**
* All activities inheriting from BaseActivity must be annotated with @AndroidEntryPoint
*/
@ -68,6 +71,10 @@ public abstract class BaseActivity extends AppCompatActivity {
@NonNull
public AccountManager accountManager;
@Inject
@NonNull
public SharedPreferences preferences;
/**
* Allows overriding the default ViewModelProvider.Factory for testing purposes.
*/
@ -93,8 +100,6 @@ public abstract class BaseActivity extends AppCompatActivity {
);
}
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
/* There isn't presently a way to globally change the theme of a whole application at
* runtime, just individual activities. So, each activity has to set its theme before any
* views are created. */
@ -127,7 +132,8 @@ public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context newBase) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(newBase);
// injected preferences not yet available at this point of the lifecycle
SharedPreferences preferences = EntryPoints.get(newBase.getApplicationContext(), PreferencesEntryPoint.class).preferences();
// Scale text in the UI from PrefKeys.UI_TEXT_SCALE_RATIO
float uiScaleRatio = preferences.getFloat(PrefKeys.UI_TEXT_SCALE_RATIO, 100F);
@ -247,7 +253,11 @@ public abstract class BaseActivity extends AppCompatActivity {
if (!showActiveAccount && activeAccount != null) {
accounts.remove(activeAccount);
}
AccountSelectionAdapter adapter = new AccountSelectionAdapter(this);
AccountSelectionAdapter adapter = new AccountSelectionAdapter(
this,
preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
);
adapter.addAll(accounts);
new AlertDialog.Builder(this)

View file

@ -51,7 +51,6 @@ import androidx.core.view.MenuProvider
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.MarginPageTransformer
import at.connyduck.calladapter.networkresult.fold
import com.bumptech.glide.Glide
@ -112,7 +111,6 @@ import com.keylesspalace.tusky.util.overrideActivityTransitionCompat
import com.keylesspalace.tusky.util.reduceSwipeSensitivity
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation
import com.keylesspalace.tusky.util.unsafeLazy
import com.keylesspalace.tusky.util.viewBinding
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
@ -183,8 +181,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
private var unreadAnnouncementsCount = 0
private val preferences by unsafeLazy { PreferenceManager.getDefaultSharedPreferences(this) }
// We need to know if the emoji pack has been changed
private var selectedEmojiPack: String? = null
@ -1188,7 +1184,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
header.clear()
header.profiles = profiles
header.setActiveProfile(accountManager.activeAccount!!.id)
binding.mainToolbar.subtitle = if (accountManager.shouldDisplaySelfUsername(this)) {
binding.mainToolbar.subtitle = if (accountManager.shouldDisplaySelfUsername()) {
accountManager.activeAccount!!.fullName
} else {
null

View file

@ -52,7 +52,7 @@ class TuskyApplication : Application(), Configuration.Provider {
lateinit var localeManager: LocaleManager
@Inject
lateinit var sharedPreferences: SharedPreferences
lateinit var preferences: SharedPreferences
override fun onCreate() {
// Uncomment me to get StrictMode violation logs
@ -70,7 +70,7 @@ class TuskyApplication : Application(), Configuration.Provider {
Security.insertProviderAt(Conscrypt.newProvider(), 1)
// Migrate shared preference keys and defaults from version to version.
val oldVersion = sharedPreferences.getInt(
val oldVersion = preferences.getInt(
PrefKeys.SCHEMA_VERSION,
NEW_INSTALL_SCHEMA_VERSION
)
@ -84,7 +84,7 @@ class TuskyApplication : Application(), Configuration.Provider {
EmojiPackHelper.init(this, DefaultEmojiPackList.get(this), allowPackImports = false)
// init night mode
val theme = sharedPreferences.getString(APP_THEME, AppTheme.DEFAULT.value)
val theme = preferences.getString(APP_THEME, AppTheme.DEFAULT.value)
setAppNightMode(theme)
localeManager.setLocale()
@ -109,7 +109,7 @@ class TuskyApplication : Application(), Configuration.Provider {
private fun upgradeSharedPreferences(oldVersion: Int, newVersion: Int) {
Log.d(TAG, "Upgrading shared preferences: $oldVersion -> $newVersion")
val editor = sharedPreferences.edit()
val editor = preferences.edit()
if (oldVersion < 2023022701) {
// These preferences are (now) handled in AccountPreferenceHandler. Remove them from shared for clarity.
@ -123,7 +123,7 @@ class TuskyApplication : Application(), Configuration.Provider {
// Default value for appTheme is now THEME_SYSTEM. If the user is upgrading and
// didn't have an explicit preference set use the previous default, so the
// theme does not unexpectedly change.
if (!sharedPreferences.contains(APP_THEME)) {
if (!preferences.contains(APP_THEME)) {
editor.putString(APP_THEME, AppTheme.NIGHT.value)
}
}

View file

@ -20,15 +20,17 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAutocompleteAccountBinding
import com.keylesspalace.tusky.db.entity.AccountEntity
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.loadAvatar
class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(
class AccountSelectionAdapter(
context: Context,
private val animateAvatars: Boolean,
private val animateEmojis: Boolean
) : ArrayAdapter<AccountEntity>(
context,
R.layout.item_autocomplete_account
) {
@ -42,17 +44,13 @@ class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(
val account = getItem(position)
if (account != null) {
val pm = PreferenceManager.getDefaultSharedPreferences(binding.avatar.context)
val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
binding.username.text = account.fullName
binding.displayName.text = account.displayName.emojify(account.emojis, binding.displayName, animateEmojis)
binding.avatarBadge.visibility = View.GONE // We never want to display the bot badge here
val avatarRadius = context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp)
val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatar)
loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatars)
}
return binding.root

View file

@ -48,7 +48,6 @@ import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.updatePadding
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.MarginPageTransformer
import com.bumptech.glide.Glide
@ -92,7 +91,6 @@ import com.keylesspalace.tusky.util.reduceSwipeSensitivity
import com.keylesspalace.tusky.util.setClickableText
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation
import com.keylesspalace.tusky.util.unsafeLazy
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.view.showMuteAccountDialog
@ -121,8 +119,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
private lateinit var accountFieldAdapter: AccountFieldAdapter
private val preferences by unsafeLazy { PreferenceManager.getDefaultSharedPreferences(this) }
private var followState: FollowState = FollowState.NOT_FOLLOWING
private var blocking: Boolean = false
private var muting: Boolean = false
@ -173,10 +169,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
// Obtain information to fill out the profile.
viewModel.setAccountInfo(intent.getStringExtra(KEY_ACCOUNT_ID)!!)
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
animateAvatar = sharedPrefs.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
animateEmojis = sharedPrefs.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
hideFab = sharedPrefs.getBoolean(PrefKeys.FAB_HIDE, false)
animateAvatar = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
handleWindowInsets()
setupToolbar()
@ -241,7 +236,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
}
// If wellbeing mode is enabled, follow stats and posts count should be hidden
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_PROFILE, false)
if (wellbeingEnabled) {
@ -675,7 +669,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
binding.accountFloatingActionButton.setOnClickListener { mention() }
binding.accountFollowButton.setOnClickListener {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
val confirmFollows = preferences.getBoolean(PrefKeys.CONFIRM_FOLLOWS, false)
if (viewModel.isSelf) {
val intent = Intent(this@AccountActivity, EditProfileActivity::class.java)
@ -721,7 +714,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
showingReblogs = relation.showingReblogs
// If wellbeing mode is enabled, "follows you" text should not be visible
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_PROFILE, false)
binding.accountFollowsYouTextView.visible(relation.followedBy && !wellbeingEnabled)

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.components.account.media
import android.content.SharedPreferences
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
@ -28,7 +29,6 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.color.MaterialColors
import com.keylesspalace.tusky.R
@ -64,6 +64,9 @@ class AccountMediaFragment :
@Inject
lateinit var accountManager: AccountManager
@Inject
lateinit var preferences: SharedPreferences
private val binding by viewBinding(FragmentTimelineBinding::bind)
private val viewModel: AccountMediaViewModel by viewModels()
@ -78,7 +81,6 @@ class AccountMediaFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
val preferences = PreferenceManager.getDefaultSharedPreferences(view.context)
val useBlurhash = preferences.getBoolean(PrefKeys.USE_BLURHASH, true)
adapter = AccountMediaGridAdapter(

View file

@ -15,12 +15,12 @@
package com.keylesspalace.tusky.components.accountlist
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
@ -73,6 +73,9 @@ class AccountListFragment :
@Inject
lateinit var accountManager: AccountManager
@Inject
lateinit var preferences: SharedPreferences
private val binding by viewBinding(FragmentAccountListBinding::bind)
private lateinit var type: Type
@ -101,10 +104,9 @@ class AccountListFragment :
binding.swipeRefreshLayout.setOnRefreshListener { fetchAccounts() }
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
val pm = PreferenceManager.getDefaultSharedPreferences(view.context)
val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
val showBotOverlay = pm.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true)
val animateAvatar = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
val showBotOverlay = preferences.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true)
val activeAccount = accountManager.activeAccount!!

View file

@ -17,7 +17,6 @@ package com.keylesspalace.tusky.components.announcements
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
@ -27,7 +26,6 @@ import android.widget.PopupWindow
import androidx.activity.viewModels
import androidx.core.view.MenuProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.color.MaterialColors
@ -100,7 +98,6 @@ class AnnouncementsActivity :
val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
binding.announcementsList.addItemDecoration(divider)
val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)

View file

@ -56,7 +56,6 @@ import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionManager
import com.canhub.cropper.CropImage
@ -104,7 +103,6 @@ import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.modernLanguageCode
import com.keylesspalace.tusky.util.setDrawableTint
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.unsafeLazy
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import com.mikepenz.iconics.IconicsDrawable
@ -146,8 +144,6 @@ class ComposeActivity :
private var photoUploadUri: Uri? = null
private val preferences by unsafeLazy { PreferenceManager.getDefaultSharedPreferences(this) }
@VisibleForTesting
var maximumTootCharacters = InstanceInfoRepository.DEFAULT_CHARACTER_LIMIT
var charactersReservedPerUrl = InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL
@ -290,7 +286,7 @@ class ComposeActivity :
setupButtons()
subscribeToUpdates(mediaAdapter)
if (accountManager.shouldDisplaySelfUsername(this)) {
if (accountManager.shouldDisplaySelfUsername()) {
binding.composeUsernameView.text = getString(
R.string.compose_active_account_description,
activeAccount.fullName

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.components.conversation
import android.content.SharedPreferences
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
@ -30,7 +31,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.paging.LoadState
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -79,6 +79,9 @@ class ConversationsFragment :
@Inject
lateinit var eventHub: EventHub
@Inject
lateinit var preferences: SharedPreferences
private val viewModel: ConversationsViewModel by viewModels()
private val binding by viewBinding(FragmentTimelineBinding::bind)
@ -98,8 +101,6 @@ class ConversationsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
val preferences = PreferenceManager.getDefaultSharedPreferences(view.context)
val statusDisplayOptions = StatusDisplayOptions(
animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true,
@ -407,10 +408,9 @@ class ConversationsFragment :
}
private fun onPreferenceChanged(key: String) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
when (key) {
PrefKeys.FAB_HIDE -> {
hideFab = sharedPreferences.getBoolean(PrefKeys.FAB_HIDE, false)
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
}
PrefKeys.MEDIA_PREVIEW_ENABLED -> {

View file

@ -17,7 +17,6 @@ package com.keylesspalace.tusky.components.login
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.util.Log
@ -55,8 +54,6 @@ class LoginActivity : BaseActivity() {
private val binding by viewBinding(ActivityLoginBinding::inflate)
private lateinit var preferences: SharedPreferences
private val oauthRedirectUri: String
get() {
val scheme = getString(R.string.oauth_scheme)
@ -74,6 +71,10 @@ class LoginActivity : BaseActivity() {
}
}
private var domain: String = ""
private var clientId: String = ""
private var clientSecret: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -87,6 +88,12 @@ class LoginActivity : BaseActivity() {
binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
}
if (savedInstanceState != null) {
domain = savedInstanceState.getString(DOMAIN, "")
clientId = savedInstanceState.getString(CLIENT_ID, "")
clientSecret = savedInstanceState.getString(CLIENT_SECRET, "")
}
if (isAccountMigration()) {
binding.domainEditText.setText(accountManager.activeAccount!!.domain)
binding.domainEditText.isEnabled = false
@ -99,11 +106,6 @@ class LoginActivity : BaseActivity() {
.into(binding.loginLogo)
}
preferences = getSharedPreferences(
getString(R.string.preferences_file_key),
Context.MODE_PRIVATE
)
binding.loginButton.setOnClickListener { onLoginClick(true) }
binding.whatsAnInstanceTextView.setOnClickListener {
@ -135,16 +137,18 @@ class LoginActivity : BaseActivity() {
return super.onCreateOptionsMenu(menu)
}
/**
* Obtain the oauth client credentials for this app. This is only necessary the first time the
* app is run on a given server instance. So, after the first authentication, they are
* saved in SharedPreferences and every subsequent run they are simply fetched from there.
*/
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(DOMAIN, domain)
outState.putString(CLIENT_ID, clientId)
outState.putString(CLIENT_SECRET, clientSecret)
}
private fun onLoginClick(openInWebView: Boolean) {
binding.loginButton.isEnabled = false
binding.domainTextInputLayout.error = null
val domain = canonicalizeDomain(binding.domainEditText.text.toString())
domain = canonicalizeDomain(binding.domainEditText.text.toString())
try {
HttpUrl.Builder().host(domain).scheme("https").build()
@ -170,16 +174,9 @@ class LoginActivity : BaseActivity() {
getString(R.string.tusky_website)
).fold(
{ credentials ->
// Before we open browser page we save the data.
// Even if we don't open other apps user may go to password manager or somewhere else
// and we will need to pick up the process where we left off.
// Alternatively we could pass it all as part of the intent and receive it back
// but it is a bit of a workaround.
preferences.edit()
.putString(DOMAIN, domain)
.putString(CLIENT_ID, credentials.clientId)
.putString(CLIENT_SECRET, credentials.clientSecret)
.apply()
// Save credentials. These will be put into the savedInstanceState so they get restored after activity recreation.
clientId = credentials.clientId
clientSecret = credentials.clientSecret
redirectUserToAuthorizeAndLogin(domain, credentials.clientId, openInWebView)
},
@ -267,11 +264,6 @@ class LoginActivity : BaseActivity() {
}
private suspend fun fetchOauthToken(code: String) {
/* restore variables from SharedPreferences */
val domain = preferences.getNonNullString(DOMAIN, "")
val clientId = preferences.getNonNullString(CLIENT_ID, "")
val clientSecret = preferences.getNonNullString(CLIENT_SECRET, "")
setLoading(true)
mastodonApi.fetchOAuthToken(

View file

@ -26,7 +26,6 @@ import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.MainActivity
import com.keylesspalace.tusky.R
@ -123,16 +122,12 @@ class PreferencesActivity :
override fun onResume() {
super.onResume()
PreferenceManager.getDefaultSharedPreferences(
this
).registerOnSharedPreferenceChangeListener(this)
preferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
PreferenceManager.getDefaultSharedPreferences(
this
).unregisterOnSharedPreferenceChangeListener(this)
preferences.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSaveInstanceState(outState: Bundle) {

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.components.report.fragments
import android.content.SharedPreferences
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
@ -28,7 +29,6 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
@ -72,6 +72,9 @@ class ReportStatusesFragment :
@Inject
lateinit var accountManager: AccountManager
@Inject
lateinit var preferences: SharedPreferences
private val viewModel: ReportViewModel by activityViewModels()
private val binding by viewBinding(FragmentReportStatusesBinding::bind)
@ -145,7 +148,6 @@ class ReportStatusesFragment :
}
private fun initStatusesView() {
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val statusDisplayOptions = StatusDisplayOptions(
animateAvatars = false,
mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true,

View file

@ -25,7 +25,6 @@ import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import androidx.preference.PreferenceManager
import com.google.android.material.tabs.TabLayoutMediator
import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.R
@ -33,7 +32,6 @@ import com.keylesspalace.tusky.components.search.adapter.SearchPagerAdapter
import com.keylesspalace.tusky.databinding.ActivitySearchBinding
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.reduceSwipeSensitivity
import com.keylesspalace.tusky.util.unsafeLazy
import com.keylesspalace.tusky.util.viewBinding
import dagger.hilt.android.AndroidEntryPoint
@ -44,8 +42,6 @@ class SearchActivity : BottomSheetActivity(), MenuProvider, SearchView.OnQueryTe
private val binding by viewBinding(ActivitySearchBinding::inflate)
private val preferences by unsafeLazy { PreferenceManager.getDefaultSharedPreferences(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)

View file

@ -15,20 +15,25 @@
package com.keylesspalace.tusky.components.search.fragments
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.paging.PagingData
import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import com.keylesspalace.tusky.components.search.adapter.SearchAccountsAdapter
import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.settings.PrefKeys
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@AndroidEntryPoint
class SearchAccountsFragment : SearchFragment<TimelineAccount>() {
@Inject
lateinit var preferences: SharedPreferences
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.searchRecyclerView.addItemDecoration(
@ -40,10 +45,6 @@ class SearchAccountsFragment : SearchFragment<TimelineAccount>() {
}
override fun createAdapter(): PagingDataAdapter<TimelineAccount, *> {
val preferences = PreferenceManager.getDefaultSharedPreferences(
binding.searchRecyclerView.context
)
return SearchAccountsAdapter(
this,
preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),

View file

@ -21,6 +21,7 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -37,7 +38,6 @@ import androidx.core.view.ViewCompat
import androidx.lifecycle.lifecycleScope
import androidx.paging.PagingData
import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import at.connyduck.calladapter.networkresult.fold
@ -76,6 +76,9 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
@Inject
lateinit var accountManager: AccountManager
@Inject
lateinit var preferences: SharedPreferences
override val data: Flow<PagingData<StatusViewData.Concrete>>
get() = viewModel.statusesFlow
@ -111,9 +114,6 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
}
override fun createAdapter(): PagingDataAdapter<StatusViewData.Concrete, *> {
val preferences = PreferenceManager.getDefaultSharedPreferences(
binding.searchRecyclerView.context
)
val statusDisplayOptions = StatusDisplayOptions(
animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
mediaPreviewEnabled = viewModel.mediaPreviewEnabled,

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.components.timeline
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@ -30,7 +31,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -92,6 +92,9 @@ class TimelineFragment :
@Inject
lateinit var eventHub: EventHub
@Inject
lateinit var preferences: SharedPreferences
private val viewModel: TimelineViewModel by unsafeLazy {
val viewModelProvider = ViewModelProvider(viewModelStore, defaultViewModelProviderFactory, defaultViewModelCreationExtras)
if (kind == TimelineViewModel.Kind.HOME) {
@ -170,7 +173,6 @@ class TimelineFragment :
isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true)
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
readingOrder = ReadingOrder.from(preferences.getString(PrefKeys.READING_ORDER, null))
val statusDisplayOptions = StatusDisplayOptions(
@ -282,7 +284,6 @@ class TimelineFragment :
}
if (actionButtonPresent()) {
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
@ -317,7 +318,7 @@ class TimelineFragment :
}
}
updateRelativeTimePeriodically {
updateRelativeTimePeriodically(preferences) {
adapter.notifyItemRangeChanged(
0,
adapter.itemCount,
@ -568,10 +569,9 @@ class TimelineFragment :
}
private fun onPreferenceChanged(key: String) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
when (key) {
PrefKeys.FAB_HIDE -> {
hideFab = sharedPreferences.getBoolean(PrefKeys.FAB_HIDE, false)
hideFab = preferences.getBoolean(PrefKeys.FAB_HIDE, false)
}
PrefKeys.MEDIA_PREVIEW_ENABLED -> {
@ -585,7 +585,7 @@ class TimelineFragment :
PrefKeys.READING_ORDER -> {
readingOrder = ReadingOrder.from(
sharedPreferences.getString(PrefKeys.READING_ORDER, null)
preferences.getString(PrefKeys.READING_ORDER, null)
)
}
}

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.components.viewthread
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@ -30,7 +31,6 @@ import androidx.fragment.app.commit
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
@ -57,6 +57,7 @@ import com.keylesspalace.tusky.viewdata.AttachmentViewData.Companion.list
import com.keylesspalace.tusky.viewdata.StatusViewData
import com.keylesspalace.tusky.viewdata.TranslationViewData
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
@ -69,6 +70,9 @@ class ViewThreadFragment :
StatusActionListener,
MenuProvider {
@Inject
lateinit var preferences: SharedPreferences
private val viewModel: ViewThreadViewModel by viewModels()
private val binding by viewBinding(FragmentViewThreadBinding::bind)
@ -92,7 +96,6 @@ class ViewThreadFragment :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
thisThreadsStatusId = requireArguments().getString(ID_EXTRA)!!
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val statusDisplayOptions = StatusDisplayOptions(
animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.components.viewthread.edits
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.Menu
@ -27,7 +28,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
@ -52,6 +52,7 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch
@AndroidEntryPoint
@ -61,6 +62,9 @@ class ViewEditsFragment :
OnRefreshListener,
MenuProvider {
@Inject
lateinit var preferences: SharedPreferences
private val viewModel: ViewEditsViewModel by viewModels()
private val binding by viewBinding(FragmentViewEditsBinding::bind)
@ -81,7 +85,6 @@ class ViewEditsFragment :
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
statusId = requireArguments().getString(STATUS_ID_EXTRA)!!
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)

View file

@ -15,9 +15,8 @@
package com.keylesspalace.tusky.db
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.db.dao.AccountDao
import com.keylesspalace.tusky.db.entity.AccountEntity
import com.keylesspalace.tusky.entity.Account
@ -35,7 +34,10 @@ import javax.inject.Singleton
private const val TAG = "AccountManager"
@Singleton
class AccountManager @Inject constructor(db: AppDatabase) {
class AccountManager @Inject constructor(
db: AppDatabase,
private val preferences: SharedPreferences
) {
@Volatile
var activeAccount: AccountEntity? = null
@ -236,9 +238,8 @@ class AccountManager @Inject constructor(db: AppDatabase) {
/**
* @return true if the name of the currently-selected account should be displayed in UIs
*/
fun shouldDisplaySelfUsername(context: Context): Boolean {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val showUsernamePreference = sharedPreferences.getString(
fun shouldDisplaySelfUsername(): Boolean {
val showUsernamePreference = preferences.getString(
PrefKeys.SHOW_SELF_USERNAME,
"disambiguate"
)

View file

@ -0,0 +1,12 @@
package com.keylesspalace.tusky.di
import android.content.SharedPreferences
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@EntryPoint
@InstallIn(SingletonComponent::class)
interface PreferencesEntryPoint {
fun preferences(): SharedPreferences
}

View file

@ -21,7 +21,6 @@ import android.os.Build
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.preference.PreferenceDataStore
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.settings.PrefKeys
import dagger.hilt.android.qualifiers.ApplicationContext
@ -33,10 +32,11 @@ class LocaleManager @Inject constructor(
@ApplicationContext val context: Context
) : PreferenceDataStore() {
private var prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@Inject
lateinit var preferences: SharedPreferences
fun setLocale() {
val language = prefs.getNonNullString(PrefKeys.LANGUAGE, DEFAULT)
val language = preferences.getNonNullString(PrefKeys.LANGUAGE, DEFAULT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (language != HANDLED_BY_SYSTEM) {
@ -44,7 +44,7 @@ class LocaleManager @Inject constructor(
// hand over the old setting to the system and save a dummy value in Shared Preferences
applyLanguageToApp(language)
prefs.edit()
preferences.edit()
.putString(PrefKeys.LANGUAGE, HANDLED_BY_SYSTEM)
.apply()
}
@ -58,7 +58,7 @@ class LocaleManager @Inject constructor(
// if we are on Android < 13 we have to save the selected language so we can apply it at appstart
// on Android 13+ the system handles it for us
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
prefs.edit()
preferences.edit()
.putString(PrefKeys.LANGUAGE, value)
.apply()
}
@ -84,7 +84,7 @@ class LocaleManager @Inject constructor(
}
}
} else {
prefs.getNonNullString(PrefKeys.LANGUAGE, DEFAULT)
preferences.getNonNullString(PrefKeys.LANGUAGE, DEFAULT)
}
}

View file

@ -2,11 +2,11 @@
package com.keylesspalace.tusky.util
import android.content.SharedPreferences
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.settings.PrefKeys
import kotlin.time.Duration.Companion.minutes
import kotlinx.coroutines.delay
@ -19,8 +19,7 @@ private val UPDATE_INTERVAL = 1.minutes
* if setting absoluteTimeView is false.
* Start updates when the Fragment becomes visible and stop when it is hidden.
*/
fun Fragment.updateRelativeTimePeriodically(callback: Runnable) {
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
fun Fragment.updateRelativeTimePeriodically(preferences: SharedPreferences, callback: Runnable) {
val lifecycle = viewLifecycleOwner.lifecycle
lifecycle.coroutineScope.launch {
// This child coroutine will launch each time the Fragment moves to the STARTED state