Skip to content

Commit

Permalink
settings and database backups
Browse files Browse the repository at this point in the history
  • Loading branch information
crackededed committed Sep 19, 2024
1 parent e5ad533 commit 274f68f
Show file tree
Hide file tree
Showing 28 changed files with 357 additions and 20 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ android {
minSdk = 16
targetSdk = 35
versionCode = 121
versionName = "2.34.4"
versionName = "2.35.0"
}

buildTypes {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<application
android:name=".XtraApp"
android:allowBackup="false"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_rules"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import android.content.Context
import androidx.core.content.edit
import com.github.andreyasadchy.xtra.util.C
import com.github.andreyasadchy.xtra.util.TwitchApiHelper
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.tokenPrefs

sealed class Account(val id: String?,
val login: String?) {
Expand All @@ -13,7 +13,7 @@ sealed class Account(val id: String?,
private var account: Account? = null

fun get(context: Context): Account {
return account ?: with(context.prefs()) {
return account ?: with(context.tokenPrefs()) {
val helixToken = TwitchApiHelper.getHelixHeaders(context)[C.HEADER_TOKEN].takeUnless { it.isNullOrBlank() }
val gqlToken = TwitchApiHelper.getGQLHeaders(context, true)[C.HEADER_TOKEN].takeUnless { it.isNullOrBlank() }
if (!helixToken.isNullOrBlank() || !gqlToken.isNullOrBlank()) {
Expand All @@ -32,7 +32,7 @@ sealed class Account(val id: String?,

fun set(context: Context, account: Account?) {
this.account = account
context.prefs().edit {
context.tokenPrefs().edit {
if (account != null) {
putString(C.USER_ID, account.id)
putString(C.USERNAME, account.login)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.github.andreyasadchy.xtra.util.isLightTheme
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.shortToast
import com.github.andreyasadchy.xtra.util.toast
import com.github.andreyasadchy.xtra.util.tokenPrefs
import com.github.andreyasadchy.xtra.util.visible
import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint
Expand Down Expand Up @@ -94,7 +95,7 @@ class LoginActivity : AppCompatActivity() {
val gqlToken = gqlHeaders[C.HEADER_TOKEN]?.removePrefix("OAuth ")
TwitchApiHelper.checkedValidation = false
Account.set(this, null)
prefs().edit {
tokenPrefs().edit {
putString(C.TOKEN, null)
putString(C.GQL_HEADERS, null)
putLong(C.INTEGRITY_EXPIRATION, 0)
Expand Down Expand Up @@ -319,7 +320,7 @@ class LoginActivity : AppCompatActivity() {
if (!helixToken.isNullOrBlank()) {
TwitchApiHelper.checkedValidation = true
Account.set(this@LoginActivity, LoggedIn(userId, userLogin))
prefs().edit {
tokenPrefs().edit {
putString(C.TOKEN, helixToken)
}
}
Expand Down Expand Up @@ -350,7 +351,7 @@ class LoginActivity : AppCompatActivity() {
if (!helixToken.isNullOrBlank()) {
TwitchApiHelper.checkedValidation = true
Account.set(this@LoginActivity, LoggedIn(userId, userLogin))
prefs().edit {
tokenPrefs().edit {
putString(C.TOKEN, helixToken)
}
}
Expand Down Expand Up @@ -394,7 +395,7 @@ class LoginActivity : AppCompatActivity() {
TwitchApiHelper.checkedValidation = true
Account.set(this@LoginActivity, LoggedIn(userId, userLogin))
if (!gqlToken.isNullOrBlank()) {
prefs().edit {
tokenPrefs().edit {
if (prefs().getBoolean(C.ENABLE_INTEGRITY, false)) {
putLong(C.INTEGRITY_EXPIRATION, 0)
putString(C.GQL_HEADERS, JSONObject(mapOf(
Expand All @@ -407,7 +408,7 @@ class LoginActivity : AppCompatActivity() {
}
}
if (!helixToken.isNullOrBlank()) {
prefs().edit {
tokenPrefs().edit {
putString(C.TOKEN, helixToken)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.github.andreyasadchy.xtra.util.C
import com.github.andreyasadchy.xtra.util.TwitchApiHelper
import com.github.andreyasadchy.xtra.util.getAlertDialogBuilder
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.tokenPrefs
import org.json.JSONObject

class IntegrityDialog : DialogFragment() {
Expand Down Expand Up @@ -66,7 +67,7 @@ class IntegrityDialog : DialogFragment() {

override fun shouldInterceptRequest(view: WebView, webViewRequest: WebResourceRequest): WebResourceResponse? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !webViewRequest.requestHeaders.entries.firstOrNull { it.key.equals("Client-Integrity", true) }?.value.isNullOrBlank()) {
context.prefs().edit {
context.tokenPrefs().edit {
putLong(C.INTEGRITY_EXPIRATION, System.currentTimeMillis() + 57600000)
putString(C.GQL_HEADERS, JSONObject(
if (context.prefs().getBoolean(C.GET_ALL_GQL_HEADERS, false)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import com.github.andreyasadchy.xtra.util.isNetworkAvailable
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.shortToast
import com.github.andreyasadchy.xtra.util.toast
import com.github.andreyasadchy.xtra.util.tokenPrefs
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
Expand Down Expand Up @@ -193,6 +194,26 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener {
putBoolean(C.FIRST_LAUNCH8, false)
}
}
if (prefs.getBoolean(C.FIRST_LAUNCH9, true)) {
tokenPrefs().edit {
putString(C.USER_ID, prefs.getString(C.USER_ID, null))
putString(C.USERNAME, prefs.getString(C.USERNAME, null))
putString(C.TOKEN, prefs.getString(C.TOKEN, null))
putString(C.GQL_TOKEN2, prefs.getString(C.GQL_TOKEN2, null))
putString(C.GQL_HEADERS, prefs.getString(C.GQL_HEADERS, null))
putLong(C.INTEGRITY_EXPIRATION, prefs.getLong(C.INTEGRITY_EXPIRATION, 0))
}
prefs.edit {
remove(C.USER_ID)
remove(C.USERNAME)
remove(C.TOKEN)
remove(C.GQL_TOKEN)
remove(C.GQL_TOKEN2)
remove(C.GQL_HEADERS)
remove(C.INTEGRITY_EXPIRATION)
putBoolean(C.FIRST_LAUNCH9, false)
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.integrity.collectLatest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.github.andreyasadchy.xtra.ui.settings

import android.app.Activity
import android.content.ComponentName
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
Expand All @@ -12,6 +14,8 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
Expand All @@ -29,6 +33,7 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.SeekBarPreference
Expand All @@ -40,11 +45,14 @@ import com.github.andreyasadchy.xtra.databinding.ActivitySettingsBinding
import com.github.andreyasadchy.xtra.ui.main.IntegrityDialog
import com.github.andreyasadchy.xtra.util.C
import com.github.andreyasadchy.xtra.util.DisplayUtils
import com.github.andreyasadchy.xtra.util.DownloadUtils
import com.github.andreyasadchy.xtra.util.TwitchApiHelper
import com.github.andreyasadchy.xtra.util.applyTheme
import com.github.andreyasadchy.xtra.util.convertDpToPixels
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.shortToast
import com.github.andreyasadchy.xtra.util.toast
import com.github.andreyasadchy.xtra.util.tokenPrefs
import com.google.android.material.appbar.AppBarLayout
import com.woxthebox.draglistview.DragItemAdapter
import com.woxthebox.draglistview.DragListView
Expand Down Expand Up @@ -93,15 +101,87 @@ class SettingsActivity : AppCompatActivity() {
class SettingsFragment : MaterialPreferenceFragment() {

private val viewModel: SettingsViewModel by viewModels()

private var changed = false
private var backupResultLauncher: ActivityResultLauncher<Intent>? = null
private var restoreResultLauncher: ActivityResultLauncher<Intent>? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
changed = savedInstanceState?.getBoolean(KEY_CHANGED) == true
if (changed) {
requireActivity().setResult(Activity.RESULT_OK)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
backupResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let {
viewModel.backupSettings(it.toString())
}
}
}
} else {
backupResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let {
val isShared = it.scheme == ContentResolver.SCHEME_CONTENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isShared) {
val storage = DownloadUtils.getAvailableStorage(requireContext())
val uri = Uri.decode(it.path).substringAfter("/document/")
val storageName = uri.substringBefore(":")
val storagePath = if (storageName.equals("primary", true)) {
storage.firstOrNull()
} else {
if (storage.size >= 2) {
storage.lastOrNull()
} else {
storage.firstOrNull()
}
}?.path?.substringBefore("/Android/data") ?: "/storage/emulated/0"
val path = uri.substringAfter(":").substringBeforeLast("/")
val fullUri = "$storagePath/$path"
viewModel.backupSettings(fullUri)
} else {
it.path?.substringBeforeLast("/")?.let { uri -> viewModel.backupSettings(uri) }
}
}
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
restoreResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val list = mutableListOf<String>()
result.data?.clipData?.let { clipData ->
for (i in 0 until clipData.itemCount) {
val item = clipData.getItemAt(i)
item.uri?.let {
list.add(it.toString())
}
}
} ?: result.data?.data?.let {
list.add(it.toString())
}
viewModel.restoreSettings(list)
}
}
} else {
restoreResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val list = mutableListOf<String>()
result.data?.clipData?.let { clipData ->
for (i in 0 until clipData.itemCount) {
val item = clipData.getItemAt(i)
item.uri?.path?.let {
list.add(it)
}
}
} ?: result.data?.data?.path?.let {
list.add(it)
}
viewModel.restoreSettings(list)
}
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -275,6 +355,58 @@ class SettingsActivity : AppCompatActivity() {
true
}

findPreference<Preference>("backup_settings")?.setOnPreferenceClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
backupResultLauncher?.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE))
} else {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
if (intent.resolveActivity(requireActivity().packageManager) != null) {
backupResultLauncher?.launch(intent)
} else {
requireContext().toast(R.string.no_file_manager_found)
}
}
true
}

findPreference<Preference>("restore_settings")?.setOnPreferenceClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
restoreResultLauncher?.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
})
} else {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
}
if (intent.resolveActivity(requireActivity().packageManager) != null) {
restoreResultLauncher?.launch(intent)
} else {
requireContext().toast(R.string.no_file_manager_found)
}
}
true
}

findPreference<EditTextPreference>("gql_headers")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.GQL_HEADERS, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.GQL_HEADERS, newValue.toString())
}
true
}
}

findPreference<Preference>("get_integrity_token")?.setOnPreferenceClickListener {
IntegrityDialog.show(childFragmentManager)
true
Expand Down Expand Up @@ -739,6 +871,50 @@ class SettingsActivity : AppCompatActivity() {

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.api_token_preferences, rootKey)

findPreference<EditTextPreference>("user_id")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.USER_ID, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.USER_ID, newValue.toString())
}
true
}
}

findPreference<EditTextPreference>("username")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.USERNAME, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.USERNAME, newValue.toString())
}
true
}
}

findPreference<EditTextPreference>("token")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.TOKEN, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.TOKEN, newValue.toString())
}
true
}
}

findPreference<EditTextPreference>("gql_token2")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.GQL_TOKEN2, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.GQL_TOKEN2, newValue.toString())
}
true
}
}
}
}
}
Loading

0 comments on commit 274f68f

Please sign in to comment.