diff --git a/app/src/main/java/com/github/libretube/api/ExternalApi.kt b/app/src/main/java/com/github/libretube/api/ExternalApi.kt index 6685d75ea..1047ce5a0 100644 --- a/app/src/main/java/com/github/libretube/api/ExternalApi.kt +++ b/app/src/main/java/com/github/libretube/api/ExternalApi.kt @@ -18,7 +18,7 @@ interface ExternalApi { // fetch latest version info @GET(GITHUB_API_URL) - suspend fun getUpdateInfo(): UpdateInfo + suspend fun getLatestRelease(): UpdateInfo @POST("$SB_API_URL/api/skipSegments") suspend fun submitSegment( diff --git a/app/src/main/java/com/github/libretube/constants/IntentData.kt b/app/src/main/java/com/github/libretube/constants/IntentData.kt index ce5ec8070..43b1b5946 100644 --- a/app/src/main/java/com/github/libretube/constants/IntentData.kt +++ b/app/src/main/java/com/github/libretube/constants/IntentData.kt @@ -22,7 +22,8 @@ object IntentData { const val shareData = "shareData" const val currentPosition = "currentPosition" const val duration = "duration" - const val updateInfo = "updateInfo" + const val appUpdateChangelog = "updateChangelog" + const val appUpdateURL = "updateURL" const val backupFile = "backupFile" const val playlistTask = "playlistTask" const val loginTask = "loginTask" diff --git a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt index b1b9cc725..43eaaa26d 100644 --- a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt @@ -134,6 +134,7 @@ object PreferenceKeys { /** * Advanced */ + const val AUTOMATIC_UPDATE_CHECKS = "automatic_update_checks" const val DATA_SAVER_MODE = "data_saver_mode_key" const val MAX_IMAGE_CACHE = "image_cache_size" const val RESET_SETTINGS = "reset_settings" diff --git a/app/src/main/java/com/github/libretube/obj/update/UpdateInfo.kt b/app/src/main/java/com/github/libretube/obj/update/UpdateInfo.kt index 175d54a7e..caec9aa49 100644 --- a/app/src/main/java/com/github/libretube/obj/update/UpdateInfo.kt +++ b/app/src/main/java/com/github/libretube/obj/update/UpdateInfo.kt @@ -8,6 +8,7 @@ import kotlinx.serialization.Serializable @Serializable @Parcelize data class UpdateInfo( + val name: String, + val body: String, @SerialName("html_url") val htmlUrl: String, - val name: String ) : Parcelable 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 159df4d3b..a3a7d6307 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 @@ -18,6 +18,7 @@ import androidx.core.view.allViews import androidx.core.view.children import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment @@ -44,6 +45,9 @@ import com.github.libretube.ui.fragments.PlayerFragment import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.SearchViewModel import com.github.libretube.ui.models.SubscriptionsViewModel +import com.github.libretube.util.UpdateChecker +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class MainActivity : BaseActivity() { lateinit var binding: ActivityMainBinding @@ -82,6 +86,13 @@ class MainActivity : BaseActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + // Check update automatically + if (PreferenceHelper.getBoolean(PreferenceKeys.AUTOMATIC_UPDATE_CHECKS, false)) { + lifecycleScope.launch(Dispatchers.IO) { + UpdateChecker(this@MainActivity).checkUpdate(false) + } + } + // set the action bar for the activity setSupportActionBar(binding.toolbar) diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/UpdateAvailableDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/UpdateAvailableDialog.kt index 33594a4ef..ea945ec1c 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/UpdateAvailableDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/UpdateAvailableDialog.kt @@ -6,28 +6,33 @@ import android.os.Bundle import androidx.core.net.toUri import androidx.fragment.app.DialogFragment import com.github.libretube.R -import com.github.libretube.constants.IntentData -import com.github.libretube.extensions.parcelable -import com.github.libretube.obj.update.UpdateInfo +import com.github.libretube.constants.IntentData.appUpdateChangelog +import com.github.libretube.constants.IntentData.appUpdateURL import com.google.android.material.dialog.MaterialAlertDialogBuilder class UpdateAvailableDialog : DialogFragment() { - private lateinit var updateInfo: UpdateInfo + private var changelog: String? = null + private var releaseUrl: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - updateInfo = requireArguments().parcelable(IntentData.updateInfo)!! + arguments?.run { + changelog = getString(appUpdateChangelog) + releaseUrl = getString(appUpdateURL) + } } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return MaterialAlertDialogBuilder(requireContext()) - .setTitle(context?.getString(R.string.update_available, updateInfo.name)) - .setMessage(context?.getString(R.string.update_available_text)) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(context?.getString(R.string.okay)) { _, _ -> - val intent = Intent(Intent.ACTION_VIEW).setData(updateInfo.htmlUrl.toUri()) - startActivity(intent) + .setTitle(R.string.update_available) + .setMessage(changelog) + .setPositiveButton(R.string.download) { _, _ -> + releaseUrl?.let { + startActivity(Intent(Intent.ACTION_VIEW, it.toUri())) + } } + .setNegativeButton(R.string.tooltip_dismiss, null) + .setCancelable(false) .show() } } diff --git a/app/src/main/java/com/github/libretube/ui/preferences/MainSettings.kt b/app/src/main/java/com/github/libretube/ui/preferences/MainSettings.kt index fa95d7214..d7ea55129 100644 --- a/app/src/main/java/com/github/libretube/ui/preferences/MainSettings.kt +++ b/app/src/main/java/com/github/libretube/ui/preferences/MainSettings.kt @@ -1,21 +1,14 @@ package com.github.libretube.ui.preferences import android.os.Bundle -import androidx.annotation.StringRes -import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import com.github.libretube.BuildConfig import com.github.libretube.R -import com.github.libretube.api.RetrofitInstance -import com.github.libretube.constants.IntentData -import com.github.libretube.ui.activities.SettingsActivity import com.github.libretube.ui.base.BasePreferenceFragment -import com.github.libretube.ui.dialogs.UpdateAvailableDialog -import com.google.android.material.snackbar.Snackbar +import com.github.libretube.util.UpdateChecker import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class MainSettings : BasePreferenceFragment() { override val titleResourceId: Int = R.string.settings @@ -24,50 +17,16 @@ class MainSettings : BasePreferenceFragment() { setPreferencesFromResource(R.xml.settings, rootKey) val update = findPreference("update") + update?.summary = "v${BuildConfig.VERSION_NAME}" - // set the version of the update preference - val versionString = if (BuildConfig.DEBUG) { - "${BuildConfig.VERSION_NAME} Debug" - } else { - getString(R.string.version, BuildConfig.VERSION_NAME) - } - update?.title = versionString - - // checking for update: yes -> dialog, no -> snackBar + // check app update manually update?.setOnPreferenceClickListener { - lifecycleScope.launch { - // check for update - val updateInfo = try { - withContext(Dispatchers.IO) { - RetrofitInstance.externalApi.getUpdateInfo() - } - } catch (e: Exception) { - showSnackBar(R.string.unknown_error) - return@launch - } - if (BuildConfig.VERSION_NAME != updateInfo.name) { - // show the UpdateAvailableDialog if there's an update available - val newUpdateAvailableDialog = UpdateAvailableDialog() - newUpdateAvailableDialog.arguments = - bundleOf(IntentData.updateInfo to updateInfo) - newUpdateAvailableDialog.show( - childFragmentManager, - UpdateAvailableDialog::class.java.name - ) - } else { - // otherwise show the no update available snackBar - showSnackBar(R.string.app_uptodate) - } + lifecycleScope.launch(Dispatchers.IO) { + UpdateChecker(requireContext()).checkUpdate(true) } + true } } - - private fun showSnackBar(@StringRes text: Int) { - (activity as? SettingsActivity)?.binding?.let { - Snackbar.make(it.root, text, Snackbar.LENGTH_SHORT) - .show() - } - } } diff --git a/app/src/main/java/com/github/libretube/util/UpdateChecker.kt b/app/src/main/java/com/github/libretube/util/UpdateChecker.kt new file mode 100644 index 000000000..e93203784 --- /dev/null +++ b/app/src/main/java/com/github/libretube/util/UpdateChecker.kt @@ -0,0 +1,70 @@ +package com.github.libretube.util + +import android.content.Context +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.FragmentActivity +import com.github.libretube.BuildConfig +import com.github.libretube.R +import com.github.libretube.api.RetrofitInstance +import com.github.libretube.constants.IntentData.appUpdateChangelog +import com.github.libretube.constants.IntentData.appUpdateURL +import com.github.libretube.extensions.TAG +import com.github.libretube.extensions.toastFromMainDispatcher +import com.github.libretube.ui.dialogs.UpdateAvailableDialog +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.util.Locale + +class UpdateChecker(private val context: Context) { + suspend fun checkUpdate(isManualCheck: Boolean = false) { + val currentAppVersion = BuildConfig.VERSION_NAME.replace(".", "").toInt() + + try { + val response = RetrofitInstance.externalApi.getLatestRelease() + // version would be in the format "0.21.1" + val update = response.name.replace(".", "").toIntOrNull() + + if (update != null && currentAppVersion < update) { + withContext(Dispatchers.Main) { + showUpdateAvailableDialog(response.body, response.htmlUrl) + } + Log.i(TAG(), response.toString()) + } else if (isManualCheck) { + context.toastFromMainDispatcher(R.string.app_uptodate) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun showUpdateAvailableDialog( + changelog: String, + url: String, + ) { + val dialog = UpdateAvailableDialog() + val args = + Bundle().apply { + putString(appUpdateChangelog, sanitizeChangelog(changelog)) + putString(appUpdateURL, url) + } + dialog.arguments = args + val fragmentManager = (context as? FragmentActivity)?.supportFragmentManager + fragmentManager?.let { + dialog.show(it, UpdateAvailableDialog::class.java.simpleName) + } + } + + private fun sanitizeChangelog(changelog: String): String { + val removeBloat = changelog.substringBeforeLast("**Full Changelog**") + val removeLinks = removeBloat.replace(Regex("in https://github\\.com/\\S+"), "") + val uppercaseChangeType = + removeLinks.lines().joinToString("\n") { line -> + if (line.startsWith("##")) line.uppercase(Locale.ROOT) + " :" else line + } + val removeHashes = uppercaseChangeType.replace("## ", "") + val cleanPrefix = removeHashes.replace("*", "•") + + return cleanPrefix.trim() + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 73393b975..7c28defae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -122,7 +122,7 @@ Piped YouTube Play in the background - Version %1$s is available + Update available New update available. Click to open the GitHub releases page. Appearance Downloads @@ -154,6 +154,7 @@ Clear added Please enter a URL that works Version %1$s + Check updates automatically Related content Show similar streams alongside what you watch Preloading diff --git a/app/src/main/res/xml/advanced_settings.xml b/app/src/main/res/xml/advanced_settings.xml index 57cd0f9e2..dba5d608c 100644 --- a/app/src/main/res/xml/advanced_settings.xml +++ b/app/src/main/res/xml/advanced_settings.xml @@ -4,6 +4,12 @@ + + - + app:summary="@string/version" + app:title="@string/update_summary" />