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
android:name=".services.ClosingService"
android:enabled="true"
android:exported="false"
android:stopWithTask="false" />
android:exported="false" />
<service
android:name=".services.UpdateService"
android:enabled="true"
android:exported="false" />
</application>
</manifest>

View File

@ -5,9 +5,12 @@ package com.github.libretube
*/
object Globals {
// for the player fragment
var isFullScreen = false
var isMiniPlayerVisible = false
var IS_FULL_SCREEN = false
var MINI_PLAYER_VISIBLE = false
// 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.NotificationManager
import android.os.Build
import android.os.StrictMode
import android.os.StrictMode.VmPolicy
import com.github.libretube.preferences.PreferenceHelper
class MyApp : Application() {
@ -19,6 +21,12 @@ class MyApp : Application() {
* set the applicationContext as context for the [PreferenceHelper]
*/
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
Globals.dataSaverModeEnabled = PreferenceHelper.getBoolean(
Globals.DATA_SAVER_MODE_ENABLED = PreferenceHelper.getBoolean(
PreferenceKeys.DATA_SAVER_MODE,
false
)
@ -320,7 +320,7 @@ class MainActivity : AppCompatActivity() {
enableTransition(R.id.yt_transition, true)
}
findViewById<LinearLayout>(R.id.linLayout).visibility = View.VISIBLE
Globals.isFullScreen = false
Globals.IS_FULL_SCREEN = false
}
override fun onConfigurationChanged(newConfig: Configuration) {

View File

@ -1,24 +1,20 @@
package com.github.libretube.dialogs
import android.Manifest
import android.app.Dialog
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.size
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.DialogDownloadBinding
import com.github.libretube.obj.Streams
import com.github.libretube.services.DownloadService
import com.github.libretube.util.PermissionHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -36,45 +32,12 @@ class DownloadDialog : DialogFragment() {
return activity?.let {
videoId = arguments?.getString("video_id")!!
val mainActivity = activity as MainActivity
val builder = MaterialAlertDialogBuilder(it)
binding = DialogDownloadBinding.inflate(layoutInflater)
fetchAvailableSources()
// 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(
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
)
}
}
PermissionHelper.requestReadWrite(activity as AppCompatActivity)
binding.title.text = ThemeHelper.getStyledAppName(requireContext())

View File

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

View File

@ -79,7 +79,7 @@ class LibraryFragment : Fragment() {
override fun onResume() {
// optimize CreatePlaylistFab bottom margin if miniPlayer active
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
super.onResume()
}

View File

@ -358,11 +358,11 @@ class PlayerFragment : Fragment() {
val mainMotionLayout =
mainActivity.binding.mainMotionLayout
if (currentId == eId) {
Globals.isMiniPlayerVisible = true
Globals.MINI_PLAYER_VISIBLE = true
exoPlayerView.useController = false
mainMotionLayout.progress = 1F
} else if (currentId == sId) {
Globals.isMiniPlayerVisible = false
Globals.MINI_PLAYER_VISIBLE = false
exoPlayerView.useController = true
mainMotionLayout.progress = 0F
}
@ -384,7 +384,7 @@ class PlayerFragment : Fragment() {
// actions that don't depend on video information
private fun initializeOnClickActions() {
binding.closeImageView.setOnClickListener {
Globals.isMiniPlayerVisible = false
Globals.MINI_PLAYER_VISIBLE = false
binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity
mainActivity.supportFragmentManager.beginTransaction()
@ -392,7 +392,7 @@ class PlayerFragment : Fragment() {
.commit()
}
playerBinding.closeImageButton.setOnClickListener {
Globals.isMiniPlayerVisible = false
Globals.MINI_PLAYER_VISIBLE = false
binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity
mainActivity.supportFragmentManager.beginTransaction()
@ -436,7 +436,7 @@ class PlayerFragment : Fragment() {
playerBinding.fullscreen.setOnClickListener {
// hide player controller
exoPlayerView.hideController()
if (!Globals.isFullScreen) {
if (!Globals.IS_FULL_SCREEN) {
// go to fullscreen mode
setFullscreen()
} else {
@ -578,7 +578,7 @@ class PlayerFragment : Fragment() {
mainActivity.requestedOrientation = orientation
}
Globals.isFullScreen = true
Globals.IS_FULL_SCREEN = true
}
private fun unsetFullscreen() {
@ -602,7 +602,7 @@ class PlayerFragment : Fragment() {
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
}
Globals.isFullScreen = false
Globals.IS_FULL_SCREEN = false
}
private fun scaleControls(scaleFactor: Float) {
@ -983,7 +983,7 @@ class PlayerFragment : Fragment() {
if (response.duration!! > 0) {
// download clicked
binding.relPlayerDownload.setOnClickListener {
if (!IS_DOWNLOAD_RUNNING) {
if (!Globals.IS_DOWNLOAD_RUNNING) {
val newFragment = DownloadDialog()
val bundle = Bundle()
bundle.putString("video_id", videoId)
@ -1214,7 +1214,7 @@ class PlayerFragment : Fragment() {
}
playerBinding.chapterLL.visibility = View.VISIBLE
playerBinding.chapterLL.setOnClickListener {
if (Globals.isFullScreen) {
if (Globals.IS_FULL_SCREEN) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.chapters)
.setItems(titles.toTypedArray()) { _, index ->
@ -1505,13 +1505,13 @@ class PlayerFragment : Fragment() {
playerBinding.closeImageButton.visibility = visibility
playerBinding.exoTitle.visibility =
if (isLocked &&
Globals.isFullScreen
Globals.IS_FULL_SCREEN
) View.VISIBLE else View.INVISIBLE
// hide the close image button
playerBinding.closeImageButton.visibility =
if (isLocked &&
!(Globals.isFullScreen && !autoRotationEnabled)
!(Globals.IS_FULL_SCREEN && !autoRotationEnabled)
) View.VISIBLE else View.GONE
// disable double tap to seek when the player is locked
@ -1674,7 +1674,7 @@ class PlayerFragment : Fragment() {
}
binding.linLayout.visibility = View.GONE
Globals.isFullScreen = false
Globals.IS_FULL_SCREEN = false
} else {
// enable exoPlayer controls again
exoPlayerView.useController = true
@ -1692,7 +1692,7 @@ class PlayerFragment : Fragment() {
binding.playerScrollView.getHitRect(bounds)
if (SDK_INT >= Build.VERSION_CODES.O &&
(binding.playerScrollView.getLocalVisibleRect(bounds) || Globals.isFullScreen)
(binding.playerScrollView.getLocalVisibleRect(bounds) || Globals.IS_FULL_SCREEN)
) {
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
import android.Manifest
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
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.LogoutDialog
import com.github.libretube.dialogs.RequireRestartDialog
import com.github.libretube.util.PermissionHelper
import com.github.libretube.util.RetrofitInstance
import org.json.JSONObject
import org.json.JSONTokener
@ -288,51 +285,7 @@ class InstanceSettings : PreferenceFragmentCompat() {
private fun importSubscriptions() {
val token = PreferenceHelper.getToken()
// check StorageAccess
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
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()
}
}
PermissionHelper.requestReadWrite(activity as AppCompatActivity)
}
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.activities.SettingsActivity
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.checkUpdate
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -93,13 +93,10 @@ class MainSettings : PreferenceFragmentCompat() {
update?.setOnPreferenceClickListener {
CoroutineScope(Dispatchers.IO).launch {
// check for update
val versionInfo = checkUpdate()
if (versionInfo?.tagName != "" && BuildConfig.VERSION_NAME != versionInfo?.tagName) {
val updateInfo = UpdateChecker.checkUpdate()
if (updateInfo?.name != "" && BuildConfig.VERSION_NAME != updateInfo?.name) {
// show the UpdateAvailableDialog if there's an update available
val updateAvailableDialog = UpdateAvailableDialog(
versionInfo!!.tagName,
versionInfo.updateUrl
)
val updateAvailableDialog = UpdateDialog(updateInfo!!)
updateAvailableDialog.show(childFragmentManager, "UpdateAvailableDialog")
} else {
// 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.NotificationManagerCompat
import com.arthenica.ffmpegkit.FFmpegKit
import com.github.libretube.Globals
import com.github.libretube.R
import com.github.libretube.obj.DownloadType
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys
import java.io.File
var IS_DOWNLOAD_RUNNING = false
class DownloadService : Service() {
val TAG = "DownloadService"
@ -45,7 +44,7 @@ class DownloadService : Service() {
private lateinit var tempDir: File
override fun onCreate() {
super.onCreate()
IS_DOWNLOAD_RUNNING = true
Globals.IS_DOWNLOAD_RUNNING = true
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -164,7 +163,10 @@ class DownloadService : Service() {
onDestroy()
}
} else {
try {
muxDownloadedMedia()
} catch (e: Exception) {
}
}
}
}
@ -280,7 +282,7 @@ class DownloadService : Service() {
} catch (e: Exception) {
}
IS_DOWNLOAD_RUNNING = false
Globals.IS_DOWNLOAD_RUNNING = false
Log.d(TAG, "dl finished!")
stopForeground(true)
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
fun loadImage(url: String?, target: ImageView) {
// 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)
}
}

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="captions">Captions</string>
<string name="none">None</string>
<string name="update_now">Do you want to update the app now?</string>
</resources>