-
Notifications
You must be signed in to change notification settings - Fork 0
/
SettingsFragment.kt
383 lines (322 loc) · 16.6 KB
/
SettingsFragment.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
package com.example.hakonsreader.fragments
import android.app.Dialog
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.preference.*
import com.example.hakonsreader.R
import com.example.hakonsreader.api.RedditApi
import com.example.hakonsreader.broadcastreceivers.InboxWorkerStartReceiver
import com.example.hakonsreader.interfaces.LanguageListener
import com.example.hakonsreader.interfaces.OnUnreadMessagesBadgeSettingChanged
import com.example.hakonsreader.views.preferences.multicolor.MultiColorFragCompat
import com.example.hakonsreader.views.preferences.multicolor.MultiColorPreference
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.installations.FirebaseInstallations
import com.google.firebase.ktx.Firebase
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* Fragment for displaying user settings
*/
@AndroidEntryPoint
class SettingsFragment : PreferenceFragmentCompat() {
companion object {
@Suppress("UNUSED")
private const val TAG = "SettingsFragment"
/**
* The String.format() format used for formatting the summary for the link scale
*/
private const val LINK_SCALE_SUMMARY_FORMAT = "%.2f"
/**
* @return A new instance of this fragment
*/
fun newInstance() = SettingsFragment()
}
// The API won't be used to make any calls here, but is needed to adjust third party options
@Inject
lateinit var api: RedditApi
private lateinit var settings: SharedPreferences
var unreadMessagesBadgeSettingChanged: OnUnreadMessagesBadgeSettingChanged? = null
var languageListener: LanguageListener? = null
// TODO the filtered subreddits dont update until the fragment is recreated, if you go to settings
// then filter a subreddit then go back it wont appear since the fragment isn't recreated
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
settings = PreferenceManager.getDefaultSharedPreferences(requireContext())
val themePreference: SwitchPreference? = findPreference(getString(R.string.prefs_key_theme))
themePreference?.onPreferenceChangeListener = themeChangeListener
// Set that the auto hide comments preference is only a number (with sign)
val hideComments: EditTextPreference? = findPreference(getString(R.string.prefs_key_hide_comments_threshold))
hideComments?.let {
it.setOnBindEditTextListener { editText: EditText -> editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_SIGNED }
it.onPreferenceChangeListener = hideCommentsChangeListener
setHideCommentsSummary(it, null)
}
val language: ListPreference? = findPreference(getString(R.string.prefs_key_language))
language?.let {
it.onPreferenceChangeListener = languageChangeListener
}
// The enabled state isn't stored, so if auto playing videos is disabled then disable the nsfw auto play
// (this is the same functionality as is done in autoPlayVideosChangeListener)
val autoPlayNsfwVideos: SwitchPreference? = findPreference(getString(R.string.prefs_key_auto_play_nsfw_videos))
val autoPlayVideos: SwitchPreference? = findPreference(getString(R.string.prefs_key_auto_play_videos_switch))
autoPlayVideos?.let {
it.onPreferenceChangeListener = autoPlayVideosChangeListener
autoPlayNsfwVideos?.isEnabled = !it.isChecked
if (it.isChecked) {
autoPlayNsfwVideos?.isEnabled = true
} else {
autoPlayNsfwVideos?.isEnabled = false
autoPlayNsfwVideos?.isChecked = false
}
}
val filteredSubreddits: EditTextPreference? = findPreference(getString(R.string.prefs_key_filter_posts_from_default_subreddits))
filteredSubreddits?.let {
it.onPreferenceChangeListener = filteredSubredditsChangeListener
setFilteredSubredditsSummary(it, null)
}
val linkScalePreference: SeekBarPreference? = findPreference(getString(R.string.prefs_key_link_scale))
linkScalePreference?.let {
it.onPreferenceChangeListener = linkScaleChangeListener
it.summary = String.format(LINK_SCALE_SUMMARY_FORMAT, linkScalePreference.value / 100f)
}
val unreadMessagesBadgePreference: SwitchPreference? = findPreference(getString(R.string.prefs_key_inbox_show_badge))
unreadMessagesBadgePreference?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue: Any? ->
unreadMessagesBadgeSettingChanged?.showUnreadMessagesBadge(newValue as Boolean)
true
}
val crashReportPreference: SwitchPreference? = findPreference(getString(R.string.prefs_key_send_crash_reports))
crashReportPreference?.let {
it.onPreferenceChangeListener = crashReportsChangeListener
}
// Third party options all use the same listener
findPreference<SwitchPreference>(getString(R.string.prefs_key_third_party_load_gfycat_gifs))?.let {
it.onPreferenceChangeListener = thirdPartyOptionsListener
}
findPreference<SwitchPreference>(getString(R.string.prefs_key_third_party_load_imgur_gifs))?.let {
it.onPreferenceChangeListener = thirdPartyOptionsListener
}
findPreference<SwitchPreference>(getString(R.string.prefs_key_third_party_load_imgur_albums))?.let {
it.onPreferenceChangeListener = thirdPartyOptionsListener
}
findPreference<ListPreference>(getString(R.string.prefs_key_inbox_update_frequency))?.let {
it.onPreferenceChangeListener = inboxFrequencyListener
}
// This setting shouldn't be changed when gestures is enabled (can we actually check if it is enabled?)
if (Build.VERSION.SDK_INT >= 29) {
findPreference<SwitchPreference>(getString(R.string.prefs_key_show_subreddit_info_button))?.isEnabled = false
}
}
override fun onDisplayPreferenceDialog(preference: Preference?) {
if (preference is MultiColorPreference) {
val dialog = MultiColorFragCompat.newInstance(preference.key)
dialog.setTargetFragment(this, 0)
dialog.show(parentFragmentManager, null)
} else {
super.onDisplayPreferenceDialog(preference)
}
}
/**
* Listener for light/dark mode. Automatically updates the theme
*/
private val themeChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
newValue as Boolean
if (newValue) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
true
}
/**
* Listener for the threshold for hiding comments. This ensures that if nothing is input into the input
* field then the default is set
*/
private val hideCommentsChangeListener = Preference.OnPreferenceChangeListener { preference: Preference?, newValue: Any? ->
preference as EditTextPreference
// The value is stored as a string
val value = newValue as String?
// If no value is set, set to default
if (value == null || value.isEmpty()) {
val defaultHideComments = resources.getInteger(R.integer.prefs_default_hide_comments_threshold)
val defaultAsString = defaultHideComments.toString()
settings.edit()
.putString(getString(R.string.prefs_key_hide_comments_threshold), defaultAsString)
.apply()
// TODO going into the setting again still shows the old value, even though it is actually updated
// going out of the fragment and then going back shows the correct value
setHideCommentsSummary(preference, defaultAsString)
// return false = don't update since we're manually updating the value ourselves
return@OnPreferenceChangeListener false
}
setHideCommentsSummary(preference, value)
true
}
/**
* Listener for language changes. Language is updated automatically
*/
private val languageChangeListener = Preference.OnPreferenceChangeListener { _, newValue: Any ->
newValue as String
// We have to pass the language here since the value stored in SharedPreferences isn't updated until
// this function returns true, so trying to retrieve the value in updateLanguage() would retrieve the old value
languageListener?.updateLanguage(newValue)
true
}
/**
* Listener for when auto playing videos changes. This will change the auto play NSFW videos preference
* as it should be dependant on the normal auto play.
*
* If normal auto play is set to "Never", NSFW auto play should be disabled as well
*/
private val autoPlayVideosChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
val autoPlayNsfw: SwitchPreference = findPreference(getString(R.string.prefs_key_auto_play_nsfw_videos))
?: return@OnPreferenceChangeListener true
// TODO this isnt updated when going into the settings, only when changing. Have to enable this in onCreatePreferences as well
if (newValue as Boolean) {
autoPlayNsfw.isEnabled = true
} else {
// Ideally I wouldn't have to manually call setChecked(false) as it would be ideal if the actual value
// would be saved, but when retrieving the value and the preference is disabled it would always return false
// But this works fine enough
autoPlayNsfw.isEnabled = false
autoPlayNsfw.isChecked = false
}
true
}
private val filteredSubredditsChangeListener = Preference.OnPreferenceChangeListener { preference: Preference, value: Any? ->
setFilteredSubredditsSummary(preference as EditTextPreference, value as String?)
true
}
/**
* Listener that updates the summary on the preference to the actual value the new value represents
*/
private val linkScaleChangeListener = Preference.OnPreferenceChangeListener { preference: Preference, newValue: Any ->
val value = newValue as Int
val actualValue = value / 100f
preference.summary = String.format(LINK_SCALE_SUMMARY_FORMAT, actualValue)
true
}
/**
* Listener that updates if Firebase crashlytics is enabled or disabled
*/
private val crashReportsChangeListener = Preference.OnPreferenceChangeListener { preference: Preference, newValue: Any ->
newValue as Boolean
preference as SwitchPreference
// Delete unsent reports. If this goes from true to false it makes sense to delete them, if it goes from false to true
// it also makes sense as the user might not want the previous reports to be sent
Firebase.crashlytics.deleteUnsentReports()
return@OnPreferenceChangeListener if (newValue) {
CrashReportsConfirmDialog.newInstance(preference.key).show(childFragmentManager, "crash_reports_dialog")
// The setting should only be actually enabled by the user confirming in the dialog
false
} else {
// https://firebase.google.com/docs/projects/manage-installations#delete-fid
FirebaseInstallations.getInstance().delete()
Firebase.crashlytics.setCrashlyticsCollectionEnabled(false)
// When turning the setting off we should keep the new value
true
}
}
/**
* Generic listener for third party options changes (only for boolean options)
*/
private val thirdPartyOptionsListener = Preference.OnPreferenceChangeListener { preference, newValue ->
// It is an error to use this listener on anything else than a SwitchPreference
newValue as Boolean
when (preference.key) {
getString(R.string.prefs_key_third_party_load_gfycat_gifs) -> api.thirdPartyOptions.loadGfycatGifs = newValue
getString(R.string.prefs_key_third_party_load_imgur_gifs) -> api.thirdPartyOptions.loadImgurGifs = newValue
getString(R.string.prefs_key_third_party_load_imgur_albums) -> api.thirdPartyOptions.loadImgurAlbums = newValue
}
true
}
private val inboxFrequencyListener = Preference.OnPreferenceChangeListener { _, newValue ->
val freq = when (newValue as String) {
getString(R.string.prefs_key_inbox_update_frequency_15_min) -> 15
getString(R.string.prefs_key_inbox_update_frequency_30_min) -> 30
getString(R.string.prefs_key_inbox_update_frequency_60_min) -> 60
else -> -1
}
InboxWorkerStartReceiver.startInboxWorker(requireContext(), freq, replace = true)
true
}
/**
* Sets the summary for the hide comments threshold preference
*
* @param preference The preference
* @param value The value to use directly. If this is `null` then the value stored
* in the preference is used
*/
private fun setHideCommentsSummary(preference: EditTextPreference, value: String?) {
preference.summary = value ?: preference.text
}
/**
* Sets the summary for the filtered subreddits preference
*
* @param preference The preference
* @param value The value to use directly. If this is `null` then the value stored
* in the preference is used
*/
private fun setFilteredSubredditsSummary(preference: EditTextPreference, value: String?) {
// The value must be passed since when it is used in the changeListener the value in
// the preference wont be updated until the change listener returns
// This won't update before you go in/out of the settings, since changing the summary provider
// when it already has been is apparently not allowed
val filteredSubs = (value ?: preference.text).split("\n".toRegex()).toTypedArray()
var count = 0
for (filteredSub in filteredSubs) {
// In case there are empty lines, don't count them
if (filteredSub.isNotBlank()) {
count++
}
}
val filteredSummary = resources.getQuantityString(R.plurals.filteredSubredditsSummary, count, count)
preference.summary = filteredSummary
}
/**
* DialogFragment that ensures the user confirms enabling Firebase crash reports
*
* Note this fragment MUST be a child fragment of a [PreferenceFragmentCompat], otherwise it will crash
*/
class CrashReportsConfirmDialog : DialogFragment() {
companion object {
private const val ARGS_PREFERENCE_KEY = "args_preferenceKey"
fun newInstance(preferenceKey: String) = CrashReportsConfirmDialog().apply {
arguments = bundleOf(ARGS_PREFERENCE_KEY to preferenceKey)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
return Dialog(context).apply {
setContentView(R.layout.dialog_enable_crashlytics)
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
setCancelable(false)
setCanceledOnTouchOutside(false)
findViewById<Button>(R.id.btnEnable).setOnClickListener {
val parent = requireParentFragment() as PreferenceFragmentCompat
parent.findPreference<SwitchPreference>(requireArguments().getString(ARGS_PREFERENCE_KEY)!!)?.let {
it.sharedPreferences.edit()
.putBoolean(it.key, true)
.apply()
it.isChecked = true
}
Firebase.crashlytics.setCrashlyticsCollectionEnabled(true)
dismiss()
}
findViewById<Button>(R.id.btnCancel).setOnClickListener {
// This should already be false, but just to be sure
Firebase.crashlytics.setCrashlyticsCollectionEnabled(false)
dismiss()
}
}
}
}
}