mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-27 23:40:33 +05:30
refactor: refactor WelcomeActivity and associated logic (#6996)
This commit is contained in:
parent
abc8e49878
commit
3a09869eb6
@ -6,27 +6,26 @@ import com.github.libretube.api.obj.PipedInstance
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object InstanceHelper {
|
||||
private const val PIPED_INSTANCES_URL = "https://piped-instances.kavin.rocks"
|
||||
class InstanceRepository(private val context: Context) {
|
||||
|
||||
/**
|
||||
* Fetch official public instances from kavin.rocks
|
||||
*/
|
||||
suspend fun getInstances(context: Context): List<PipedInstance> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
RetrofitInstance.externalApi.getInstances(PIPED_INSTANCES_URL)
|
||||
}.getOrNull() ?: run {
|
||||
throw Exception(context.getString(R.string.failed_fetching_instances))
|
||||
}
|
||||
suspend fun getInstances(): Result<List<PipedInstance>> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
RetrofitInstance.externalApi.getInstances(PIPED_INSTANCES_URL)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstancesFallback(context: Context): List<PipedInstance> {
|
||||
fun getInstancesFallback(): List<PipedInstance> {
|
||||
val instanceNames = context.resources.getStringArray(R.array.instances)
|
||||
return context.resources.getStringArray(R.array.instancesValue)
|
||||
.mapIndexed { index, instanceValue ->
|
||||
PipedInstance(instanceNames[index], instanceValue)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PIPED_INSTANCES_URL = "https://piped-instances.kavin.rocks"
|
||||
}
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
package com.github.libretube.api.obj
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@Parcelize
|
||||
data class PipedInstance(
|
||||
val name: String,
|
||||
@SerialName("api_url") val apiUrl: String,
|
||||
@ -21,4 +24,4 @@ data class PipedInstance(
|
||||
@SerialName("uptime_7d") val uptimeWeek: Float? = null,
|
||||
@SerialName("uptime_30d") val uptimeMonth: Float? = null,
|
||||
val isCurrentlyDown: Boolean = false
|
||||
)
|
||||
) : Parcelable
|
||||
|
@ -12,6 +12,8 @@ import com.github.libretube.db.DatabaseHolder.Database
|
||||
import com.github.libretube.extensions.TAG
|
||||
import com.github.libretube.obj.BackupFile
|
||||
import com.github.libretube.obj.PreferenceItem
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
@ -42,10 +44,10 @@ object BackupHelper {
|
||||
* Restore data from a [BackupFile]
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun restoreAdvancedBackup(context: Context, uri: Uri) {
|
||||
suspend fun restoreAdvancedBackup(context: Context, uri: Uri) = withContext(Dispatchers.IO) {
|
||||
val backupFile = context.contentResolver.openInputStream(uri)?.use {
|
||||
JsonHelper.json.decodeFromStream<BackupFile>(it)
|
||||
} ?: return
|
||||
} ?: return@withContext
|
||||
|
||||
Database.watchHistoryDao().insertAll(backupFile.watchHistory.orEmpty())
|
||||
Database.searchHistoryDao().insertAll(backupFile.searchHistory.orEmpty())
|
||||
|
@ -7,38 +7,20 @@ import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.isGone
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.api.RetrofitInstance
|
||||
import com.github.libretube.constants.PreferenceKeys
|
||||
import com.github.libretube.databinding.ActivityWelcomeBinding
|
||||
import com.github.libretube.helpers.BackupHelper
|
||||
import com.github.libretube.helpers.PreferenceHelper
|
||||
import com.github.libretube.ui.adapters.InstancesAdapter
|
||||
import com.github.libretube.ui.base.BaseActivity
|
||||
import com.github.libretube.ui.models.WelcomeModel
|
||||
import com.github.libretube.ui.models.WelcomeViewModel
|
||||
import com.github.libretube.ui.preferences.BackupRestoreSettings
|
||||
import com.google.common.collect.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class WelcomeActivity : BaseActivity() {
|
||||
private val viewModel: WelcomeModel by viewModels()
|
||||
|
||||
private val viewModel by viewModels<WelcomeViewModel> { WelcomeViewModel.Factory }
|
||||
|
||||
private val restoreFilePicker =
|
||||
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
if (uri == null) return@registerForActivityResult
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
BackupHelper.restoreAdvancedBackup(this@WelcomeActivity, uri)
|
||||
|
||||
// only skip the welcome activity if the restored backup contains an instance
|
||||
val instancePref = PreferenceHelper.getString(PreferenceKeys.FETCH_INSTANCE, "")
|
||||
if (instancePref.isNotEmpty()) {
|
||||
withContext(Dispatchers.Main) { startMainActivity() }
|
||||
}
|
||||
}
|
||||
viewModel.restoreAdvancedBackup(this, uri)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -47,43 +29,38 @@ class WelcomeActivity : BaseActivity() {
|
||||
val binding = ActivityWelcomeBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
binding.instancesRecycler.layoutManager = LinearLayoutManager(this@WelcomeActivity)
|
||||
val adapter = InstancesAdapter(viewModel.selectedInstanceIndex.value) { index ->
|
||||
viewModel.selectedInstanceIndex.value = index
|
||||
binding.okay.alpha = 1f
|
||||
}
|
||||
val adapter = InstancesAdapter(
|
||||
viewModel.uiState.value?.selectedInstanceIndex,
|
||||
viewModel::setSelectedInstanceIndex,
|
||||
)
|
||||
binding.instancesRecycler.adapter = adapter
|
||||
|
||||
// ALl the binding values are optional due to two different possible layouts (normal, landscape)
|
||||
viewModel.instances.observe(this) { instances ->
|
||||
adapter.submitList(ImmutableList.copyOf(instances))
|
||||
binding.progress.isGone = true
|
||||
}
|
||||
viewModel.fetchInstances()
|
||||
|
||||
binding.okay.alpha = if (viewModel.selectedInstanceIndex.value != null) 1f else 0.5f
|
||||
binding.okay.setOnClickListener {
|
||||
if (viewModel.selectedInstanceIndex.value != null) {
|
||||
val selectedInstance =
|
||||
viewModel.instances.value!![viewModel.selectedInstanceIndex.value!!]
|
||||
PreferenceHelper.putString(PreferenceKeys.FETCH_INSTANCE, selectedInstance.apiUrl)
|
||||
startMainActivity()
|
||||
} else {
|
||||
Toast.makeText(this, R.string.choose_instance, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
viewModel.saveSelectedInstance()
|
||||
}
|
||||
|
||||
binding.restore.setOnClickListener {
|
||||
restoreFilePicker.launch(BackupRestoreSettings.JSON)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startMainActivity() {
|
||||
// refresh the api urls since they have changed likely
|
||||
RetrofitInstance.lazyMgr.reset()
|
||||
val mainActivityIntent = Intent(this@WelcomeActivity, MainActivity::class.java)
|
||||
startActivity(mainActivityIntent)
|
||||
finish()
|
||||
viewModel.uiState.observe(this) { (selectedIndex, instances, error, navigateToMain) ->
|
||||
binding.okay.isEnabled = selectedIndex != null
|
||||
binding.progress.isGone = instances.isNotEmpty()
|
||||
|
||||
adapter.submitList(instances)
|
||||
|
||||
error?.let {
|
||||
Toast.makeText(this, it, Toast.LENGTH_LONG).show()
|
||||
viewModel.onErrorShown()
|
||||
}
|
||||
|
||||
navigateToMain?.let {
|
||||
val mainActivityIntent = Intent(this, MainActivity::class.java)
|
||||
startActivity(mainActivityIntent)
|
||||
finish()
|
||||
viewModel.onNavigated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestOrientationChange() {
|
||||
|
@ -1,30 +0,0 @@
|
||||
package com.github.libretube.ui.models
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.libretube.api.InstanceHelper
|
||||
import com.github.libretube.api.obj.PipedInstance
|
||||
import com.github.libretube.extensions.toastFromMainDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class WelcomeModel(private val application: Application) : AndroidViewModel(application) {
|
||||
val selectedInstanceIndex = MutableLiveData<Int>()
|
||||
|
||||
var instances = MutableLiveData<List<PipedInstance>>()
|
||||
|
||||
fun fetchInstances() {
|
||||
if (!instances.value.isNullOrEmpty()) return
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val instances = try {
|
||||
InstanceHelper.getInstances(application)
|
||||
} catch (e: Exception) {
|
||||
application.toastFromMainDispatcher(e.message.orEmpty())
|
||||
InstanceHelper.getInstancesFallback(application)
|
||||
}
|
||||
this@WelcomeModel.instances.postValue(instances)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package com.github.libretube.ui.models
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.createSavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.api.InstanceRepository
|
||||
import com.github.libretube.api.RetrofitInstance
|
||||
import com.github.libretube.api.obj.PipedInstance
|
||||
import com.github.libretube.constants.PreferenceKeys
|
||||
import com.github.libretube.helpers.BackupHelper
|
||||
import com.github.libretube.helpers.PreferenceHelper
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class WelcomeViewModel(
|
||||
private val instanceRepository: InstanceRepository,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = savedStateHandle.getStateFlow(UI_STATE, UiState())
|
||||
val uiState = _uiState.asLiveData()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
instanceRepository.getInstances()
|
||||
.onSuccess { instances ->
|
||||
savedStateHandle[UI_STATE] = _uiState.value.copy(instances = instances)
|
||||
}
|
||||
.onFailure {
|
||||
savedStateHandle[UI_STATE] = _uiState.value.copy(
|
||||
instances = instanceRepository.getInstancesFallback(),
|
||||
error = R.string.failed_fetching_instances,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedInstanceIndex(index: Int) {
|
||||
savedStateHandle[UI_STATE] = _uiState.value.copy(selectedInstanceIndex = index)
|
||||
}
|
||||
|
||||
fun saveSelectedInstance() {
|
||||
val selectedInstanceIndex = _uiState.value.selectedInstanceIndex
|
||||
if (selectedInstanceIndex == null) {
|
||||
savedStateHandle[UI_STATE] = _uiState.value.copy(error = R.string.choose_instance)
|
||||
} else {
|
||||
PreferenceHelper.putString(
|
||||
PreferenceKeys.FETCH_INSTANCE,
|
||||
_uiState.value.instances[selectedInstanceIndex].apiUrl
|
||||
)
|
||||
refreshAndNavigate()
|
||||
}
|
||||
}
|
||||
|
||||
fun restoreAdvancedBackup(context: Context, uri: Uri) {
|
||||
viewModelScope.launch {
|
||||
BackupHelper.restoreAdvancedBackup(context, uri)
|
||||
|
||||
// only skip the welcome activity if the restored backup contains an instance
|
||||
val instancePref = PreferenceHelper.getString(PreferenceKeys.FETCH_INSTANCE, "")
|
||||
if (instancePref.isNotEmpty()) {
|
||||
refreshAndNavigate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshAndNavigate() {
|
||||
// refresh the api urls since they have changed likely
|
||||
RetrofitInstance.lazyMgr.reset()
|
||||
savedStateHandle[UI_STATE] = _uiState.value.copy(navigateToMain = Unit)
|
||||
}
|
||||
|
||||
fun onErrorShown() {
|
||||
savedStateHandle[UI_STATE] = _uiState.value.copy(error = null)
|
||||
}
|
||||
|
||||
fun onNavigated() {
|
||||
savedStateHandle[UI_STATE] = _uiState.value.copy(navigateToMain = null)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class UiState(
|
||||
val selectedInstanceIndex: Int? = null,
|
||||
val instances: List<PipedInstance> = emptyList(),
|
||||
@StringRes val error: Int? = null,
|
||||
val navigateToMain: Unit? = null,
|
||||
) : Parcelable
|
||||
|
||||
companion object {
|
||||
private const val UI_STATE = "ui_state"
|
||||
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
WelcomeViewModel(
|
||||
instanceRepository = InstanceRepository(this[APPLICATION_KEY]!!),
|
||||
savedStateHandle = createSavedStateHandle(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import androidx.preference.Preference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.api.InstanceHelper
|
||||
import com.github.libretube.api.InstanceRepository
|
||||
import com.github.libretube.api.RetrofitInstance
|
||||
import com.github.libretube.api.obj.PipedInstance
|
||||
import com.github.libretube.constants.IntentData
|
||||
@ -53,17 +53,18 @@ class InstanceSettings : BasePreferenceFragment() {
|
||||
|
||||
lifecycleScope.launch {
|
||||
// update the instances to also show custom ones
|
||||
initInstancesPref(instancePrefs, InstanceHelper.getInstancesFallback(appContext))
|
||||
initInstancesPref(instancePrefs, InstanceRepository(appContext).getInstancesFallback())
|
||||
|
||||
// try to fetch the public list of instances async
|
||||
try {
|
||||
val instances = withContext(Dispatchers.IO) {
|
||||
InstanceHelper.getInstances(appContext)
|
||||
val instanceRepo = InstanceRepository(appContext)
|
||||
val instances = instanceRepo.getInstances()
|
||||
.onFailure {
|
||||
appContext.toastFromMainDispatcher(it.message.orEmpty())
|
||||
}
|
||||
initInstancesPref(instancePrefs, instances)
|
||||
} catch (e: Exception) {
|
||||
appContext.toastFromMainDispatcher(e.message.orEmpty())
|
||||
}
|
||||
initInstancesPref(
|
||||
instancePrefs,
|
||||
instances.getOrDefault(instanceRepo.getInstancesFallback())
|
||||
)
|
||||
}
|
||||
|
||||
authInstance.setOnPreferenceChangeListener { _, _ ->
|
||||
@ -189,9 +190,7 @@ class InstanceSettings : BasePreferenceFragment() {
|
||||
val instances = ImmutableList.copyOf(this.instances)
|
||||
binding.optionsRecycler.adapter = InstancesAdapter(selectedIndex) {
|
||||
selectedInstance = instances[it].apiUrl
|
||||
}.also {
|
||||
it.submitList(instances)
|
||||
}
|
||||
}.also { it.submitList(instances) }
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(preference.title)
|
||||
|
@ -73,7 +73,9 @@
|
||||
android:fadeScrollbars="false"
|
||||
android:paddingBottom="70dp"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:listitem="@layout/instance_row" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/progress"
|
||||
@ -109,7 +111,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:alpha="0.5"
|
||||
android:text="@string/okay" />
|
||||
|
||||
</FrameLayout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user