Merge pull request #818 from Bnyro/master

In-app updates
This commit is contained in:
Bnyro 2022-07-18 19:30:43 +02:00 committed by GitHub
commit 286f61db8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 253 additions and 199 deletions

View File

@ -277,8 +277,12 @@
<service <service
android:name=".services.ClosingService" android:name=".services.ClosingService"
android:enabled="true" android:enabled="true"
android:exported="false" android:exported="false" />
android:stopWithTask="false" />
<service
android:name=".services.UpdateService"
android:enabled="true"
android:exported="false" />
</application> </application>
</manifest> </manifest>

View File

@ -5,9 +5,12 @@ package com.github.libretube
*/ */
object Globals { object Globals {
// for the player fragment // for the player fragment
var isFullScreen = false var IS_FULL_SCREEN = false
var isMiniPlayerVisible = false var MINI_PLAYER_VISIBLE = false
// for the data saver mode // for the data saver mode
var dataSaverModeEnabled = false var DATA_SAVER_MODE_ENABLED = false
// for downloads
var IS_DOWNLOAD_RUNNING = false
} }

View File

@ -4,6 +4,8 @@ import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.os.Build import android.os.Build
import android.os.StrictMode
import android.os.StrictMode.VmPolicy
import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
class MyApp : Application() { class MyApp : Application() {
@ -19,6 +21,12 @@ class MyApp : Application() {
* set the applicationContext as context for the [PreferenceHelper] * set the applicationContext as context for the [PreferenceHelper]
*/ */
PreferenceHelper.setContext(applicationContext) PreferenceHelper.setContext(applicationContext)
/**
* bypassing fileUriExposedException, see https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed
*/
val builder = VmPolicy.Builder()
StrictMode.setVmPolicy(builder.build())
} }
/** /**

View File

@ -76,7 +76,7 @@ class MainActivity : AppCompatActivity() {
} }
// save whether the data saver mode is enabled // save whether the data saver mode is enabled
Globals.dataSaverModeEnabled = PreferenceHelper.getBoolean( Globals.DATA_SAVER_MODE_ENABLED = PreferenceHelper.getBoolean(
PreferenceKeys.DATA_SAVER_MODE, PreferenceKeys.DATA_SAVER_MODE,
false false
) )
@ -320,7 +320,7 @@ class MainActivity : AppCompatActivity() {
enableTransition(R.id.yt_transition, true) enableTransition(R.id.yt_transition, true)
} }
findViewById<LinearLayout>(R.id.linLayout).visibility = View.VISIBLE findViewById<LinearLayout>(R.id.linLayout).visibility = View.VISIBLE
Globals.isFullScreen = false Globals.IS_FULL_SCREEN = false
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {

View File

@ -1,24 +1,20 @@
package com.github.libretube.dialogs package com.github.libretube.dialogs
import android.Manifest
import android.app.Dialog import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.util.Log import android.util.Log
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.size import androidx.core.view.size
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.DialogDownloadBinding import com.github.libretube.databinding.DialogDownloadBinding
import com.github.libretube.obj.Streams import com.github.libretube.obj.Streams
import com.github.libretube.services.DownloadService import com.github.libretube.services.DownloadService
import com.github.libretube.util.PermissionHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -36,45 +32,12 @@ class DownloadDialog : DialogFragment() {
return activity?.let { return activity?.let {
videoId = arguments?.getString("video_id")!! videoId = arguments?.getString("video_id")!!
val mainActivity = activity as MainActivity
val builder = MaterialAlertDialogBuilder(it) val builder = MaterialAlertDialogBuilder(it)
binding = DialogDownloadBinding.inflate(layoutInflater) binding = DialogDownloadBinding.inflate(layoutInflater)
fetchAvailableSources() fetchAvailableSources()
// request storage permissions if not granted yet PermissionHelper.requestReadWrite(activity as AppCompatActivity)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.d("myz", "" + Build.VERSION.SDK_INT)
if (!Environment.isExternalStorageManager()) {
ActivityCompat.requestPermissions(
mainActivity,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
),
1
) // permission request code is just an int
}
} else {
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
mainActivity,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1
)
}
}
binding.title.text = ThemeHelper.getStyledAppName(requireContext()) binding.title.text = ThemeHelper.getStyledAppName(requireContext())

View File

@ -3,43 +3,51 @@ package com.github.libretube.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.services.UpdateService
import com.github.libretube.update.UpdateInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
class UpdateAvailableDialog( class UpdateDialog(
private val versionTag: String, private val updateInfo: UpdateInfo
private val updateLink: String
) : DialogFragment() { ) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(context?.getString(R.string.update_available, versionTag)) .setTitle(context?.getString(R.string.update_available, updateInfo.name))
.setMessage(context?.getString(R.string.update_available_text)) .setMessage(context?.getString(R.string.update_now))
.setNegativeButton(context?.getString(R.string.cancel)) { _, _ -> .setNegativeButton(context?.getString(R.string.cancel)) { _, _ ->
dismiss() dismiss()
} }
.setPositiveButton(context?.getString(R.string.okay)) { _, _ -> .setPositiveButton(context?.getString(R.string.okay)) { _, _ ->
val uri = Uri.parse(updateLink) val downloadUrl = getDownloadUrl(updateInfo)
Log.i("downloadUrl", downloadUrl.toString())
if (downloadUrl != null) {
val intent = Intent(context, UpdateService::class.java)
intent.putExtra("downloadUrl", downloadUrl)
context?.startService(intent)
} else {
val uri = Uri.parse(updateInfo.html_url)
val intent = Intent(Intent.ACTION_VIEW).setData(uri) val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent) startActivity(intent)
} }
}
.create() .create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
}
class NoUpdateAvailableDialog : DialogFragment() { private fun getDownloadUrl(updateInfo: UpdateInfo): String? {
val supportedArchitectures = Build.SUPPORTED_ABIS
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { supportedArchitectures.forEach { arch ->
return activity?.let { updateInfo.assets.forEach {
MaterialAlertDialogBuilder(requireContext()) if (it.browser_download_url.contains(arch)) return it.browser_download_url
.setTitle(context?.getString(R.string.app_uptodate)) }
.setMessage(context?.getString(R.string.no_update_available)) }
.setPositiveButton(context?.getString(R.string.okay)) { _, _ -> } return null
.create()
} ?: throw IllegalStateException("Activity cannot be null")
} }
} }

View File

@ -79,7 +79,7 @@ class LibraryFragment : Fragment() {
override fun onResume() { override fun onResume() {
// optimize CreatePlaylistFab bottom margin if miniPlayer active // optimize CreatePlaylistFab bottom margin if miniPlayer active
val layoutParams = binding.createPlaylist.layoutParams as ViewGroup.MarginLayoutParams val layoutParams = binding.createPlaylist.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.bottomMargin = if (Globals.isMiniPlayerVisible) 180 else 64 layoutParams.bottomMargin = if (Globals.MINI_PLAYER_VISIBLE) 180 else 64
binding.createPlaylist.layoutParams = layoutParams binding.createPlaylist.layoutParams = layoutParams
super.onResume() super.onResume()
} }

View File

@ -358,11 +358,11 @@ class PlayerFragment : Fragment() {
val mainMotionLayout = val mainMotionLayout =
mainActivity.binding.mainMotionLayout mainActivity.binding.mainMotionLayout
if (currentId == eId) { if (currentId == eId) {
Globals.isMiniPlayerVisible = true Globals.MINI_PLAYER_VISIBLE = true
exoPlayerView.useController = false exoPlayerView.useController = false
mainMotionLayout.progress = 1F mainMotionLayout.progress = 1F
} else if (currentId == sId) { } else if (currentId == sId) {
Globals.isMiniPlayerVisible = false Globals.MINI_PLAYER_VISIBLE = false
exoPlayerView.useController = true exoPlayerView.useController = true
mainMotionLayout.progress = 0F mainMotionLayout.progress = 0F
} }
@ -384,7 +384,7 @@ class PlayerFragment : Fragment() {
// actions that don't depend on video information // actions that don't depend on video information
private fun initializeOnClickActions() { private fun initializeOnClickActions() {
binding.closeImageView.setOnClickListener { binding.closeImageView.setOnClickListener {
Globals.isMiniPlayerVisible = false Globals.MINI_PLAYER_VISIBLE = false
binding.playerMotionLayout.transitionToEnd() binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
mainActivity.supportFragmentManager.beginTransaction() mainActivity.supportFragmentManager.beginTransaction()
@ -392,7 +392,7 @@ class PlayerFragment : Fragment() {
.commit() .commit()
} }
playerBinding.closeImageButton.setOnClickListener { playerBinding.closeImageButton.setOnClickListener {
Globals.isMiniPlayerVisible = false Globals.MINI_PLAYER_VISIBLE = false
binding.playerMotionLayout.transitionToEnd() binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
mainActivity.supportFragmentManager.beginTransaction() mainActivity.supportFragmentManager.beginTransaction()
@ -436,7 +436,7 @@ class PlayerFragment : Fragment() {
playerBinding.fullscreen.setOnClickListener { playerBinding.fullscreen.setOnClickListener {
// hide player controller // hide player controller
exoPlayerView.hideController() exoPlayerView.hideController()
if (!Globals.isFullScreen) { if (!Globals.IS_FULL_SCREEN) {
// go to fullscreen mode // go to fullscreen mode
setFullscreen() setFullscreen()
} else { } else {
@ -578,7 +578,7 @@ class PlayerFragment : Fragment() {
mainActivity.requestedOrientation = orientation mainActivity.requestedOrientation = orientation
} }
Globals.isFullScreen = true Globals.IS_FULL_SCREEN = true
} }
private fun unsetFullscreen() { private fun unsetFullscreen() {
@ -602,7 +602,7 @@ class PlayerFragment : Fragment() {
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
} }
Globals.isFullScreen = false Globals.IS_FULL_SCREEN = false
} }
private fun scaleControls(scaleFactor: Float) { private fun scaleControls(scaleFactor: Float) {
@ -983,7 +983,7 @@ class PlayerFragment : Fragment() {
if (response.duration!! > 0) { if (response.duration!! > 0) {
// download clicked // download clicked
binding.relPlayerDownload.setOnClickListener { binding.relPlayerDownload.setOnClickListener {
if (!IS_DOWNLOAD_RUNNING) { if (!Globals.IS_DOWNLOAD_RUNNING) {
val newFragment = DownloadDialog() val newFragment = DownloadDialog()
val bundle = Bundle() val bundle = Bundle()
bundle.putString("video_id", videoId) bundle.putString("video_id", videoId)
@ -1214,7 +1214,7 @@ class PlayerFragment : Fragment() {
} }
playerBinding.chapterLL.visibility = View.VISIBLE playerBinding.chapterLL.visibility = View.VISIBLE
playerBinding.chapterLL.setOnClickListener { playerBinding.chapterLL.setOnClickListener {
if (Globals.isFullScreen) { if (Globals.IS_FULL_SCREEN) {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.chapters) .setTitle(R.string.chapters)
.setItems(titles.toTypedArray()) { _, index -> .setItems(titles.toTypedArray()) { _, index ->
@ -1505,13 +1505,13 @@ class PlayerFragment : Fragment() {
playerBinding.closeImageButton.visibility = visibility playerBinding.closeImageButton.visibility = visibility
playerBinding.exoTitle.visibility = playerBinding.exoTitle.visibility =
if (isLocked && if (isLocked &&
Globals.isFullScreen Globals.IS_FULL_SCREEN
) View.VISIBLE else View.INVISIBLE ) View.VISIBLE else View.INVISIBLE
// hide the close image button // hide the close image button
playerBinding.closeImageButton.visibility = playerBinding.closeImageButton.visibility =
if (isLocked && if (isLocked &&
!(Globals.isFullScreen && !autoRotationEnabled) !(Globals.IS_FULL_SCREEN && !autoRotationEnabled)
) View.VISIBLE else View.GONE ) View.VISIBLE else View.GONE
// disable double tap to seek when the player is locked // disable double tap to seek when the player is locked
@ -1674,7 +1674,7 @@ class PlayerFragment : Fragment() {
} }
binding.linLayout.visibility = View.GONE binding.linLayout.visibility = View.GONE
Globals.isFullScreen = false Globals.IS_FULL_SCREEN = false
} else { } else {
// enable exoPlayer controls again // enable exoPlayer controls again
exoPlayerView.useController = true exoPlayerView.useController = true
@ -1692,7 +1692,7 @@ class PlayerFragment : Fragment() {
binding.playerScrollView.getHitRect(bounds) binding.playerScrollView.getHitRect(bounds)
if (SDK_INT >= Build.VERSION_CODES.O && if (SDK_INT >= Build.VERSION_CODES.O &&
(binding.playerScrollView.getLocalVisibleRect(bounds) || Globals.isFullScreen) (binding.playerScrollView.getLocalVisibleRect(bounds) || Globals.IS_FULL_SCREEN)
) { ) {
activity?.enterPictureInPictureMode(updatePipParams()) activity?.enterPictureInPictureMode(updatePipParams())
} }

View File

@ -1,7 +0,0 @@
package com.github.libretube.obj
// data class for the update info, required to return the data
data class VersionInfo(
val updateUrl: String,
val tagName: String
)

View File

@ -1,18 +1,14 @@
package com.github.libretube.preferences package com.github.libretube.preferences
import android.Manifest
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference import androidx.preference.ListPreference
@ -26,6 +22,7 @@ import com.github.libretube.dialogs.DeleteAccountDialog
import com.github.libretube.dialogs.LoginDialog import com.github.libretube.dialogs.LoginDialog
import com.github.libretube.dialogs.LogoutDialog import com.github.libretube.dialogs.LogoutDialog
import com.github.libretube.dialogs.RequireRestartDialog import com.github.libretube.dialogs.RequireRestartDialog
import com.github.libretube.util.PermissionHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import org.json.JSONObject import org.json.JSONObject
import org.json.JSONTokener import org.json.JSONTokener
@ -288,51 +285,7 @@ class InstanceSettings : PreferenceFragmentCompat() {
private fun importSubscriptions() { private fun importSubscriptions() {
val token = PreferenceHelper.getToken() val token = PreferenceHelper.getToken()
// check StorageAccess // check StorageAccess
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { PermissionHelper.requestReadWrite(activity as AppCompatActivity)
Log.d("myz", "" + Build.VERSION.SDK_INT)
if (ContextCompat.checkSelfPermission(
this.requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
),
1
) // permission request code is just an int
} else if (token != "") {
MainSettings.getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
} else {
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1
)
} else if (token != "") {
MainSettings.getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
} }
private fun subscribe(channels: List<String>) { private fun subscribe(channels: List<String>) {

View File

@ -10,9 +10,9 @@ import com.github.libretube.BuildConfig
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.activities.SettingsActivity import com.github.libretube.activities.SettingsActivity
import com.github.libretube.dialogs.RequireRestartDialog import com.github.libretube.dialogs.RequireRestartDialog
import com.github.libretube.dialogs.UpdateAvailableDialog import com.github.libretube.dialogs.UpdateDialog
import com.github.libretube.update.UpdateChecker
import com.github.libretube.util.ThemeHelper import com.github.libretube.util.ThemeHelper
import com.github.libretube.util.checkUpdate
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -93,13 +93,10 @@ class MainSettings : PreferenceFragmentCompat() {
update?.setOnPreferenceClickListener { update?.setOnPreferenceClickListener {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// check for update // check for update
val versionInfo = checkUpdate() val updateInfo = UpdateChecker.checkUpdate()
if (versionInfo?.tagName != "" && BuildConfig.VERSION_NAME != versionInfo?.tagName) { if (updateInfo?.name != "" && BuildConfig.VERSION_NAME != updateInfo?.name) {
// show the UpdateAvailableDialog if there's an update available // show the UpdateAvailableDialog if there's an update available
val updateAvailableDialog = UpdateAvailableDialog( val updateAvailableDialog = UpdateDialog(updateInfo!!)
versionInfo!!.tagName,
versionInfo.updateUrl
)
updateAvailableDialog.show(childFragmentManager, "UpdateAvailableDialog") updateAvailableDialog.show(childFragmentManager, "UpdateAvailableDialog")
} else { } else {
// otherwise show the no update available snackBar // otherwise show the no update available snackBar

View File

@ -18,14 +18,13 @@ import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.FFmpegKit
import com.github.libretube.Globals
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.obj.DownloadType import com.github.libretube.obj.DownloadType
import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys import com.github.libretube.preferences.PreferenceKeys
import java.io.File import java.io.File
var IS_DOWNLOAD_RUNNING = false
class DownloadService : Service() { class DownloadService : Service() {
val TAG = "DownloadService" val TAG = "DownloadService"
@ -45,7 +44,7 @@ class DownloadService : Service() {
private lateinit var tempDir: File private lateinit var tempDir: File
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
IS_DOWNLOAD_RUNNING = true Globals.IS_DOWNLOAD_RUNNING = true
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -164,7 +163,10 @@ class DownloadService : Service() {
onDestroy() onDestroy()
} }
} else { } else {
try {
muxDownloadedMedia() muxDownloadedMedia()
} catch (e: Exception) {
}
} }
} }
} }
@ -280,7 +282,7 @@ class DownloadService : Service() {
} catch (e: Exception) { } catch (e: Exception) {
} }
IS_DOWNLOAD_RUNNING = false Globals.IS_DOWNLOAD_RUNNING = false
Log.d(TAG, "dl finished!") Log.d(TAG, "dl finished!")
stopForeground(true) stopForeground(true)
stopService(Intent(this@DownloadService, DownloadService::class.java)) stopService(Intent(this@DownloadService, DownloadService::class.java))

View File

@ -0,0 +1,79 @@
package com.github.libretube.services
import android.app.DownloadManager
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Environment
import android.os.IBinder
import java.io.File
class UpdateService : Service() {
private val TAG = "UpdateService"
private lateinit var downloadUrl: String
private var downloadId: Long = -1
private lateinit var file: File
private lateinit var downloadManager: DownloadManager
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
downloadUrl = intent?.getStringExtra("downloadUrl")!!
downloadApk(downloadUrl)
return super.onStartCommand(intent, flags, startId)
}
private fun downloadApk(downloadUrl: String) {
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
// val dir = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
file = File(dir, "release.apk")
val request: DownloadManager.Request =
DownloadManager.Request(Uri.parse(downloadUrl))
.setTitle("Downloading APK ...")
.setDescription("")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setDestinationUri(Uri.fromFile(file))
.setAllowedOverMetered(true)
.setAllowedOverRoaming(true)
downloadManager =
applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadId = downloadManager.enqueue(request)
// listener for the download to end
registerReceiver(
onDownloadComplete,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
}
private val onDownloadComplete: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (downloadId == id) {
// install the apk after download finished
val installIntent = Intent(Intent.ACTION_VIEW)
installIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
installIntent.setDataAndType(
Uri.fromFile(file),
downloadManager.getMimeTypeForDownloadedFile(downloadId)
)
startActivity(installIntent)
}
}
}
override fun onDestroy() {
unregisterReceiver(onDownloadComplete)
super.onDestroy()
}
override fun onBind(p0: Intent?): IBinder? {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,44 @@
package com.github.libretube.update
import com.github.libretube.GITHUB_API_URL
import com.google.gson.Gson
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
import javax.net.ssl.HttpsURLConnection
object UpdateChecker {
fun checkUpdate(): UpdateInfo? {
var versionInfo: UpdateInfo? = null
// run http request as thread to make it async
val thread = Thread {
// otherwise crashes without internet
try {
versionInfo = getUpdateInfo()
} catch (e: Exception) {
}
}
thread.start()
// wait for the thread to finish
thread.join()
// return the information about the latest version
return versionInfo
}
fun getUpdateInfo(): UpdateInfo? {
val latest = URL(GITHUB_API_URL)
val json = StringBuilder()
val urlConnection: HttpsURLConnection?
urlConnection = latest.openConnection() as HttpsURLConnection
// read json
val br = BufferedReader(InputStreamReader(urlConnection.inputStream))
var line: String?
while (br.readLine().also { line = it } != null) json.append(line)
// Parse and return the json data
val gson = Gson()
return gson.fromJson(json.toString(), UpdateInfo::class.java)
}
}

View File

@ -40,7 +40,7 @@ object ConnectionHelper {
// load an image from a url into an imageView // load an image from a url into an imageView
fun loadImage(url: String?, target: ImageView) { fun loadImage(url: String?, target: ImageView) {
// only load the image if the data saver mode is disabled // only load the image if the data saver mode is disabled
if (!Globals.dataSaverModeEnabled) { if (!Globals.DATA_SAVER_MODE_ENABLED) {
Picasso.get().load(url).fit().centerCrop().into(target) Picasso.get().load(url).fit().centerCrop().into(target)
} }
} }

View File

@ -0,0 +1,47 @@
package com.github.libretube.util
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
object PermissionHelper {
fun requestReadWrite(activity: AppCompatActivity) {
// request storage permissions if not granted yet
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.d("myz", "" + Build.VERSION.SDK_INT)
if (!Environment.isExternalStorageManager()) {
ActivityCompat.requestPermissions(
activity,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
),
1
) // permission request code is just an int
}
} else {
if (ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
activity,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1
)
}
}
}
}

View File

@ -1,48 +0,0 @@
package com.github.libretube.util
import com.github.libretube.GITHUB_API_URL
import com.github.libretube.obj.VersionInfo
import com.github.libretube.update.UpdateInfo
import com.google.gson.Gson
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
import javax.net.ssl.HttpsURLConnection
fun checkUpdate(): VersionInfo? {
var versionInfo: VersionInfo? = null
// run http request as thread to make it async
val thread = Thread {
// otherwise crashes without internet
try {
versionInfo = getUpdateInfo()
} catch (e: Exception) {
}
}
thread.start()
// wait for the thread to finish
thread.join()
// return the information about the latest version
return versionInfo
}
fun getUpdateInfo(): VersionInfo? {
val latest = URL(GITHUB_API_URL)
val json = StringBuilder()
val urlConnection: HttpsURLConnection?
urlConnection = latest.openConnection() as HttpsURLConnection
val br = BufferedReader(InputStreamReader(urlConnection.inputStream))
var line: String?
while (br.readLine().also { line = it } != null) json.append(line)
// Parse and return the json data
val gson = Gson()
val updateInfo = gson.fromJson(json.toString(), UpdateInfo::class.java)
return VersionInfo(
updateInfo.html_url,
updateInfo.name
)
}

View File

@ -252,4 +252,5 @@
<string name="system_caption_style">System caption style</string> <string name="system_caption_style">System caption style</string>
<string name="captions">Captions</string> <string name="captions">Captions</string>
<string name="none">None</string> <string name="none">None</string>
<string name="update_now">Do you want to update the app now?</string>
</resources> </resources>