Merge pull request #3915 from Bnyro/welcome-activity

Welcome Activity to choose instance on first app startup
This commit is contained in:
Bnyro 2023-06-05 10:07:29 +02:00 committed by GitHub
commit e8d7377242
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 254 additions and 28 deletions

View File

@ -31,6 +31,10 @@
android:theme="@style/StartupTheme"
tools:targetApi="n">
<activity
android:name=".ui.activities.WelcomeActivity"
android:label="@string/welcome" />
<activity
android:name=".ui.activities.NoInternetActivity"
android:label="@string/noInternet" />

View File

@ -0,0 +1,36 @@
package com.github.libretube.api
import android.content.Context
import com.github.libretube.R
import com.github.libretube.api.obj.Instances
import com.github.libretube.constants.FALLBACK_INSTANCES_URL
import com.github.libretube.constants.PIPED_INSTANCES_URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object InstanceHelper {
/**
* fetch official public instances from kavin.rocks as well as tokhmi.xyz as fallback
*/
suspend fun getInstances(context: Context): List<Instances> {
return withContext(Dispatchers.IO) {
runCatching {
RetrofitInstance.externalApi.getInstances(PIPED_INSTANCES_URL)
}.getOrNull() ?: runCatching {
RetrofitInstance.externalApi.getInstances(FALLBACK_INSTANCES_URL)
}.getOrNull() ?: run {
throw Exception(context.getString(R.string.failed_fetching_instances))
}
}
.sortedBy { it.name }
.toMutableList()
}
fun getInstancesFallback(context: Context): List<Instances> {
val instanceNames = context.resources.getStringArray(R.array.instances)
return context.resources.getStringArray(R.array.instancesValue)
.mapIndexed { index, instanceValue ->
Instances(instanceNames[index], instanceValue)
}
}
}

View File

@ -3,7 +3,6 @@ package com.github.libretube.helpers
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import com.github.libretube.constants.PIPED_API_URL
import com.github.libretube.constants.PreferenceKeys
import java.time.Instant
@ -29,10 +28,6 @@ object PreferenceHelper {
authSettings = getAuthenticationPreferences(context)
authEditor = authSettings.edit()
if (getString(PreferenceKeys.FETCH_INSTANCE, "").isBlank()) {
putString(PreferenceKeys.FETCH_INSTANCE, PIPED_API_URL)
}
}
fun putString(key: String, value: String) {

View File

@ -83,6 +83,11 @@ class MainActivity : BaseActivity() {
startActivity(noInternetIntent)
finish()
return
} else if (PreferenceHelper.getString(PreferenceKeys.FETCH_INSTANCE, "").isEmpty()) {
val welcomeIntent = Intent(this, WelcomeActivity::class.java)
startActivity(welcomeIntent)
finish()
return
}
binding = ActivityMainBinding.inflate(layoutInflater)
@ -189,7 +194,7 @@ class MainActivity : BaseActivity() {
/**
* Deselect all bottom bar items
*/
fun deselectBottomBarItems() {
private fun deselectBottomBarItems() {
binding.bottomNav.menu.setGroupCheckable(0, true, false)
for (child in binding.bottomNav.menu.children) {
child.isChecked = false

View File

@ -0,0 +1,62 @@
package com.github.libretube.ui.activities
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R
import com.github.libretube.api.InstanceHelper
import com.github.libretube.api.obj.Instances
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.ActivityWelcomeBinding
import com.github.libretube.extensions.toastFromMainDispatcher
import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.ui.adapters.InstancesAdapter
import com.github.libretube.ui.base.BaseActivity
import java.lang.Exception
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class WelcomeActivity: BaseActivity() {
private lateinit var binding: ActivityWelcomeBinding
private var selectedInstance: Instances? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWelcomeBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch(Dispatchers.IO) {
val instances = try {
InstanceHelper.getInstances(this@WelcomeActivity)
} catch (e: Exception) {
toastFromMainDispatcher(e.message.orEmpty())
InstanceHelper.getInstancesFallback(this@WelcomeActivity)
}
withContext(Dispatchers.Main) {
binding.instancesRecycler.layoutManager = LinearLayoutManager(this@WelcomeActivity)
binding.instancesRecycler.adapter = InstancesAdapter(instances) { index ->
selectedInstance = instances[index]
binding.okay.alpha = 1f
}
binding.progress.isGone = true
}
}
binding.okay.setOnClickListener {
if (selectedInstance != null) {
PreferenceHelper.putString(PreferenceKeys.FETCH_INSTANCE, selectedInstance!!.apiUrl)
val mainActivityIntent = Intent(this@WelcomeActivity, MainActivity::class.java)
startActivity(mainActivityIntent)
finish()
} else {
Toast.makeText(this, R.string.choose_instance, Toast.LENGTH_LONG).show()
}
}
}
}

View File

@ -0,0 +1,42 @@
package com.github.libretube.ui.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.api.obj.Instances
import com.github.libretube.databinding.InstanceRowBinding
import com.github.libretube.ui.viewholders.InstancesViewHolder
class InstancesAdapter(
private val instances: List<Instances>,
private val onSelectInstance: (index: Int) -> Unit
): RecyclerView.Adapter<InstancesViewHolder>() {
private var selectedInstanceIndex: Int? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstancesViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = InstanceRowBinding.inflate(layoutInflater)
return InstancesViewHolder(binding)
}
override fun getItemCount() = instances.size
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: InstancesViewHolder, position: Int) {
val instance = instances[position]
holder.binding.apply {
val cdnText = if (instance.cdn) " (\uD83C\uDF10 CDN)" else ""
radioButton.text = "${instance.name} ${instance.locations} $cdnText"
radioButton.setOnCheckedChangeListener(null)
radioButton.isChecked = selectedInstanceIndex == position
radioButton.setOnCheckedChangeListener { _, isChecked ->
val oldIndex = selectedInstanceIndex
selectedInstanceIndex = holder.absoluteAdapterPosition
if (isChecked) onSelectInstance(position)
oldIndex?.let { notifyItemChanged(it) }
notifyItemChanged(position)
}
}
}
}

View File

@ -10,10 +10,9 @@ import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import com.github.libretube.R
import com.github.libretube.api.InstanceHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.Instances
import com.github.libretube.constants.FALLBACK_INSTANCES_URL
import com.github.libretube.constants.PIPED_INSTANCES_URL
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.extensions.toastFromMainDispatcher
@ -23,6 +22,7 @@ import com.github.libretube.ui.dialogs.CustomInstanceDialog
import com.github.libretube.ui.dialogs.DeleteAccountDialog
import com.github.libretube.ui.dialogs.LoginDialog
import com.github.libretube.ui.dialogs.LogoutDialog
import java.lang.Exception
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -135,26 +135,12 @@ class InstanceSettings : BasePreferenceFragment() {
}
}
// fetch official public instances from kavin.rocks as well as tokhmi.xyz as
// fallback
val instances = withContext(Dispatchers.IO) {
runCatching {
RetrofitInstance.externalApi.getInstances(PIPED_INSTANCES_URL)
.toMutableList()
}.getOrNull() ?: runCatching {
RetrofitInstance.externalApi.getInstances(FALLBACK_INSTANCES_URL)
.toMutableList()
}.getOrNull() ?: run {
appContext.toastFromMainDispatcher(R.string.failed_fetching_instances)
val instanceNames = resources.getStringArray(R.array.instances)
resources.getStringArray(R.array.instancesValue)
.mapIndexed { index, instanceValue ->
Instances(instanceNames[index], instanceValue)
}
}
}
.sortedBy { it.name }
.toMutableList()
val instances = try {
InstanceHelper.getInstances(appContext)
} catch (e: Exception) {
appContext.toastFromMainDispatcher(e.message.orEmpty())
InstanceHelper.getInstancesFallback(requireContext())
}.toMutableList()
instances.addAll(customInstances.map { Instances(it.name, it.apiUrl) })

View File

@ -0,0 +1,8 @@
package com.github.libretube.ui.viewholders
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.databinding.InstanceRowBinding
class InstancesViewHolder(
val binding: InstanceRowBinding
): RecyclerView.ViewHolder(binding.root)

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/app_icon"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:layout_marginTop="50dp"
android:layout_marginBottom="15dp"
android:src="@drawable/ic_launcher_lockscreen"
app:tint="?attr/colorSecondary"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="20dp"
android:layout_marginVertical="16dp"
android:text="@string/welcome"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="20dp"
android:text="@string/choose_instance_long" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginVertical="10dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/instances_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadeScrollbars="false"
android:scrollbars="vertical" />
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/okay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="10dp"
android:layout_marginEnd="15dp"
android:layout_marginBottom="5dp"
android:alpha="0.5"
android:text="@string/okay" />
</LinearLayout>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="12dp">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radio_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
tools:ignore="RtlSymmetry"
tools:text="kavin.rocks" />
</LinearLayout>

View File

@ -412,6 +412,9 @@
<string name="creation_date_reversed">Creation date (reversed)</string>
<string name="alphabetic">Alphabetic</string>
<string name="alphabetic_reversed">Alphabetic (reversed)</string>
<string name="welcome">Welcome to LibreTube</string>
<string name="choose_instance">Please choose an instance first!</string>
<string name="choose_instance_long">Please choose a Piped instance to use from below. The Piped instance will act as the middleman between you and YouTube. These instances are located at different physical locations - indicated by their country flag(s). Instances physically closer to you are likely faster than instances that are not. You can change the instance you use in the settings at any time.</string>
<string name="mark_as_unwatched">Mark as unwatched</string>
<!-- Backup & Restore Settings -->