From 50a695a9069be5cf6cbf49e2536629831b5d5b56 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 3 Jun 2023 21:30:46 +0200 Subject: [PATCH 1/3] Add Welcome Activity to show on first app startup --- app/src/main/AndroidManifest.xml | 4 ++ .../github/libretube/api/InstanceHelper.kt | 36 ++++++++++ .../libretube/helpers/PreferenceHelper.kt | 5 -- .../libretube/ui/activities/MainActivity.kt | 7 +- .../ui/activities/WelcomeActivity.kt | 62 +++++++++++++++++ .../libretube/ui/adapters/InstancesAdapter.kt | 41 ++++++++++++ .../ui/preferences/InstanceSettings.kt | 30 +++------ .../ui/viewholders/InstancesViewHolder.kt | 8 +++ app/src/main/res/layout/activity_welcome.xml | 66 +++++++++++++++++++ app/src/main/res/layout/instance_row.xml | 15 +++++ app/src/main/res/values/strings.xml | 2 + 11 files changed, 248 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/github/libretube/api/InstanceHelper.kt create mode 100644 app/src/main/java/com/github/libretube/ui/activities/WelcomeActivity.kt create mode 100644 app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt create mode 100644 app/src/main/java/com/github/libretube/ui/viewholders/InstancesViewHolder.kt create mode 100644 app/src/main/res/layout/activity_welcome.xml create mode 100644 app/src/main/res/layout/instance_row.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 849d4de35..7d6695a27 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,6 +31,10 @@ android:theme="@style/StartupTheme" tools:targetApi="n"> + + diff --git a/app/src/main/java/com/github/libretube/api/InstanceHelper.kt b/app/src/main/java/com/github/libretube/api/InstanceHelper.kt new file mode 100644 index 000000000..b058973c3 --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/InstanceHelper.kt @@ -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 { + 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 { + val instanceNames = context.resources.getStringArray(R.array.instances) + return context.resources.getStringArray(R.array.instancesValue) + .mapIndexed { index, instanceValue -> + Instances(instanceNames[index], instanceValue) + } + } +} diff --git a/app/src/main/java/com/github/libretube/helpers/PreferenceHelper.kt b/app/src/main/java/com/github/libretube/helpers/PreferenceHelper.kt index 60a4a8d59..2d46aa00f 100644 --- a/app/src/main/java/com/github/libretube/helpers/PreferenceHelper.kt +++ b/app/src/main/java/com/github/libretube/helpers/PreferenceHelper.kt @@ -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) { diff --git a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt index 5b1e54611..aa61d458e 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt @@ -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 diff --git a/app/src/main/java/com/github/libretube/ui/activities/WelcomeActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/WelcomeActivity.kt new file mode 100644 index 000000000..06ae802e9 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/activities/WelcomeActivity.kt @@ -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() + } + } + } +} diff --git a/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt new file mode 100644 index 000000000..cb590342e --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt @@ -0,0 +1,41 @@ +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, + private val onSelectInstance: (index: Int) -> Unit +): RecyclerView.Adapter() { + 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 { + radioButton.text = "${instance.name} ${instance.locations}" + 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) + } + } + } +} diff --git a/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt b/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt index 243fc4104..9cf2ac1ed 100644 --- a/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt +++ b/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt @@ -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) }) diff --git a/app/src/main/java/com/github/libretube/ui/viewholders/InstancesViewHolder.kt b/app/src/main/java/com/github/libretube/ui/viewholders/InstancesViewHolder.kt new file mode 100644 index 000000000..3f5084b4a --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/viewholders/InstancesViewHolder.kt @@ -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) diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml new file mode 100644 index 000000000..f6d7ae551 --- /dev/null +++ b/app/src/main/res/layout/activity_welcome.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/instance_row.xml b/app/src/main/res/layout/instance_row.xml new file mode 100644 index 000000000..a91e7d8f5 --- /dev/null +++ b/app/src/main/res/layout/instance_row.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 35448a110..e36756da0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -412,6 +412,8 @@ Creation date (reversed) Alphabetic Alphabetic (reversed) + Welcome to LibreTube + Please choose a Piped instance to use below. The Piped instance will act as a middle man between you and YouTube. You can still change it later in the instance settings. Import subscriptions from From b4e3c977fd33962c48da5e139d1b7801b1fd21aa Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 4 Jun 2023 16:42:23 +0200 Subject: [PATCH 2/3] [Welcome Activity] Scrollbars, CDN indicator, note about instance regions --- .../com/github/libretube/ui/adapters/InstancesAdapter.kt | 3 ++- app/src/main/res/layout/activity_welcome.xml | 6 ++++-- app/src/main/res/layout/instance_row.xml | 6 ++++-- app/src/main/res/values/strings.xml | 4 +++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt index cb590342e..49215a247 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt @@ -26,7 +26,8 @@ class InstancesAdapter( override fun onBindViewHolder(holder: InstancesViewHolder, position: Int) { val instance = instances[position] holder.binding.apply { - radioButton.text = "${instance.name} ${instance.locations}" + 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 -> diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml index f6d7ae551..56b1d15ee 100644 --- a/app/src/main/res/layout/activity_welcome.xml +++ b/app/src/main/res/layout/activity_welcome.xml @@ -31,7 +31,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginHorizontal="20dp" - android:text="@string/choose_instance" /> + android:text="@string/choose_instance_long" /> + android:layout_height="wrap_content" + android:fadeScrollbars="false" + android:scrollbars="vertical" /> + android:paddingHorizontal="12dp"> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e36756da0..1fa2b98e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -413,7 +413,9 @@ Alphabetic Alphabetic (reversed) Welcome to LibreTube - Please choose a Piped instance to use below. The Piped instance will act as a middle man between you and YouTube. You can still change it later in the instance settings. + Please choose an instance first! + Please choose a Piped instance to use below. The Piped instance will act as a middle man between you and YouTube. These servers are located at different physical places (indicated with flags). Instances that are close to where you live, are likely faster than others. You can still change it later in the instance settings. + Registration disabled Import subscriptions from From 3a224c263267160b39d9bb91f346568863543ed6 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 5 Jun 2023 10:07:12 +0200 Subject: [PATCH 3/3] Improve message/explaination in welcome activity --- app/src/main/res/values/strings.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1fa2b98e8..44ff1e12c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -414,8 +414,7 @@ Alphabetic (reversed) Welcome to LibreTube Please choose an instance first! - Please choose a Piped instance to use below. The Piped instance will act as a middle man between you and YouTube. These servers are located at different physical places (indicated with flags). Instances that are close to where you live, are likely faster than others. You can still change it later in the instance settings. - Registration disabled + 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. Import subscriptions from