new update notification cards in home screen and notifications background job checking app update and package update

pull/1274/head
deniscerri 2 months ago
parent b5cf907dc6
commit 37df34ebc5
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -21,6 +21,7 @@ import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
@ -63,6 +64,7 @@ import com.deniscerri.ytdl.util.NavbarUtil.applyNavBarStyle
import com.deniscerri.ytdl.util.ThemeUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.UpdateUtil
import com.deniscerri.ytdl.work.background.UpdateCheckWorker
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
@ -537,6 +539,11 @@ class MainActivity : BaseActivity() {
putBoolean("auto_update_ytdlp", updateYTDLSwitch.isChecked)
putBoolean("asked_auto_update_preferences", true)
}
if (updateAppSwitch.isChecked) {
UpdateCheckWorker.schedule(context)
}
callAutoUpdates()
}
@ -544,6 +551,10 @@ class MainActivity : BaseActivity() {
dialog.show()
}
private var installPackageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
RuntimeManager.reInit(this)
}
private fun callAutoUpdates() {
if (BuildConfig.FLAVOR == "github" && preferences.getBoolean("update_app", false)) {
val updateUtil = UpdateUtil(this)
@ -554,10 +565,50 @@ class MainActivity : BaseActivity() {
settingsViewModel.backup()
}
withContext(Dispatchers.Main) {
UiUtil.showNewAppUpdateDialog(res.getOrNull()!!, this@MainActivity, updateUtil, this@MainActivity, preferences)
UiUtil.showNewAppUpdateSnackBar(
res.getOrNull()!!,
this@MainActivity,
findViewById<LinearLayout>(R.id.notification_container),
findViewById(R.id.frame_layout),
navigationBarView,
layoutInflater,
updateUtil,
this@MainActivity,
preferences
)
}
}
val skipRemindingPackageUpdate = preferences.getStringSet("skip_reminding_package_update", setOf())!!.toMutableSet()
RuntimeManager.packages.forEach { pkg ->
val instance = pkg.plugin.getInstance()
if (instance.bundledVersion.isNullOrBlank() && instance.downloadedVersion.isNullOrBlank()) return@forEach
instance.getReleases().apply {
val releases = this.getOrElse { listOf() }
if (releases.isEmpty()) return@apply
val latestRelease = releases.first()
if (latestRelease.isBundled || latestRelease.isInstalled) return@apply
if (skipRemindingPackageUpdate.contains(latestRelease.tag_name)) return@apply
skipRemindingPackageUpdate.add(latestRelease.tag_name)
preferences.edit().putStringSet("skip_reminding_package_update", skipRemindingPackageUpdate).apply()
withContext(Dispatchers.Main) {
UiUtil.showNewPackageUpdateSnackBar(
latestRelease,
pkg,
this@MainActivity,
findViewById<LinearLayout>(R.id.notification_container),
findViewById(R.id.frame_layout),
navigationBarView,
layoutInflater,
this@MainActivity,
installPackageLauncher
)
}
}
}
}
}
if (preferences.getBoolean("auto_update_ytdlp", false)){

@ -15,6 +15,7 @@ import com.deniscerri.ytdl.core.packages.Python
import com.deniscerri.ytdl.core.packages.QuickJS
import com.deniscerri.ytdl.core.stream.StreamGobbler
import com.deniscerri.ytdl.core.stream.StreamProcessExtractor
import com.deniscerri.ytdl.database.models.PackageItem
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
@ -61,6 +62,14 @@ object RuntimeManager {
private var ENV_PYTHONHOME: String? = null
private var TMPDIR: String = ""
val packages: List<PackageItem> = listOf(
PackageItem("Python", Python),
PackageItem("FFmpeg", FFmpeg),
PackageItem("NodeJS", NodeJS),
PackageItem("Deno", Deno),
PackageItem("Aria2c", Aria2c)
)
fun init(appContext: Context) {
if (initialized) return

@ -17,13 +17,12 @@ import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.dao.DownloadDao
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple
import com.deniscerri.ytdl.database.models.DownloadItemSimple
import com.deniscerri.ytdl.database.models.DownloadSizeMetadata
import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.DownloadWorker
import com.deniscerri.ytdl.util.AlarmScheduler
import com.deniscerri.ytdl.work.download.DownloadWorker
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import java.io.File

@ -10,7 +10,7 @@ import androidx.work.WorkManager
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.dao.ObserveSourcesDao
import com.deniscerri.ytdl.database.models.observeSources.ObserveSourcesItem
import com.deniscerri.ytdl.work.ObserveSourceWorker
import com.deniscerri.ytdl.work.background.ObserveSourceWorker
import kotlinx.coroutines.flow.Flow
import java.util.Calendar
import java.util.concurrent.TimeUnit

@ -45,9 +45,9 @@ import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.FormatUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil
import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker
import com.deniscerri.ytdl.util.AlarmScheduler
import com.deniscerri.ytdl.work.background.UpdateMultipleDownloadsDataWorker
import com.deniscerri.ytdl.work.background.UpdateMultipleDownloadsFormatsWorker
import com.google.gson.Gson
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope

@ -11,7 +11,7 @@ import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.dao.TerminalDao
import com.deniscerri.ytdl.database.models.TerminalItem
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.work.TerminalDownloadWorker
import com.deniscerri.ytdl.work.download.TerminalDownloadWorker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow

@ -9,7 +9,7 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.deniscerri.ytdl.work.DownloadWorker
import com.deniscerri.ytdl.work.download.DownloadWorker
import java.util.concurrent.TimeUnit
class ScheduleAlarmReceiver : BroadcastReceiver() {

@ -54,7 +54,6 @@ import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.VideoPlayerUtil
import com.deniscerri.ytdl.util.WorkerEventBus
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment

@ -31,7 +31,6 @@ import com.deniscerri.ytdl.ui.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.WorkerEventBus
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.badge.ExperimentalBadgeUtils
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.progressindicator.LinearProgressIndicator

@ -30,7 +30,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.ObserveSourcesViewModel
import com.deniscerri.ytdl.ui.adapter.ObserveSourcesAdapter
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.work.ObserveSourceWorker
import com.deniscerri.ytdl.work.background.ObserveSourceWorker
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder

@ -24,17 +24,14 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.viewmodel.LogViewModel
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.WorkerEventBus
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton

@ -16,9 +16,9 @@ import androidx.work.WorkManager
import com.deniscerri.ytdl.ui.more.settings.SettingModule
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads
import com.deniscerri.ytdl.work.DownloadWorker
import com.deniscerri.ytdl.util.AlarmScheduler
import com.deniscerri.ytdl.work.background.CleanUpLeftoverDownloads
import com.deniscerri.ytdl.work.download.DownloadWorker
import java.util.Calendar
import java.util.concurrent.TimeUnit
import androidx.core.content.edit

@ -89,13 +89,7 @@ class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, Packag
recyclerView.adapter = listAdapter
recyclerView.enableFastScroll()
packages = listOf(
PackageItem("Python", Python),
PackageItem("FFmpeg", FFmpeg),
PackageItem("NodeJS", NodeJS),
PackageItem("Deno", Deno),
PackageItem("Aria2c", Aria2c)
)
packages = RuntimeManager.packages
RuntimeManager.getInstance().assertInit()
listAdapter.submitList(packages)
}
@ -197,79 +191,6 @@ class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, Packag
override fun onDownloadReleaseClick(item: PackageBase.PackageRelease) {
bottomSheet?.dismiss()
var positiveButton: Button? = null
val updateDialog = MaterialAlertDialogBuilder(requireContext())
.setTitle("${item.tag_name} (${FileUtil.convertFileSize(item.downloadSize)})")
.setMessage(item.body)
.setIcon(R.drawable.ic_update_app)
.setNegativeButton(requireContext().getString(R.string.cancel)) { _: DialogInterface?, _: Int ->
tmpDownloadJob?.cancel()
}
.setPositiveButton(requireContext().getString(R.string.download), null)
val view = updateDialog.show()
val textView = view.findViewById<TextView>(android.R.id.message)
textView!!.movementMethod = LinkMovementMethod.getInstance()
val mw = Markwon.builder(requireContext()).usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
requireContext().startActivity(browserIntent)
}
}
}).build()
mw.setMarkdown(textView, item.body)
positiveButton = view.getButton(AlertDialog.BUTTON_POSITIVE)
positiveButton?.setOnClickListener {
positiveButton.isEnabled = false
positiveButton.text = "0%"
tmpDownloadJob = lifecycleScope.launch {
val instance = tmpItem!!.getInstance()
val fileResp = instance.downloadReleaseApk(item) { progress ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
positiveButton.text = "$progress%"
}
}
}
fileResp.onFailure {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
Snackbar.make(requireView(), it.message ?: getString(R.string.errored), Snackbar.LENGTH_LONG).show()
}
}
}
fileResp.onSuccess { file ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
val canRequestPackageInstalls = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
android.Manifest.permission.REQUEST_INSTALL_PACKAGES.hasPermission(requireContext())
} else {
true
}
if (canRequestPackageInstalls) {
val contentUri = FileProvider.getUriForFile(requireContext(), requireContext().packageName + ".fileprovider", file)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(contentUri, "application/vnd.android.package-archive")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
installLauncher.launch(intent)
} else {
Snackbar.make(requireView(), getString(R.string.install_downloaded_file), Snackbar.LENGTH_LONG).show()
}
}
}
}
}
}
UiUtil.showNewReleaseUpdateDialog(item, tmpItem!!, requireActivity(), viewLifecycleOwner, requireView(), null, installLauncher)
}
}

@ -23,7 +23,6 @@ import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel
import com.deniscerri.ytdl.ui.adapter.TerminalDownloadsAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.WorkerEventBus
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.progressindicator.LinearProgressIndicator
import kotlinx.coroutines.flow.collectLatest

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.work
package com.deniscerri.ytdl.util
import android.annotation.SuppressLint
import android.app.AlarmManager
@ -85,7 +85,7 @@ class AlarmScheduler(private val context: Context) {
val cancelPendingIntent = PendingIntent.getService(context, 0, cancelIntent,
PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)
kotlin.runCatching {
runCatching {
alarmManager?.cancel(pendingIntent)
alarmManager?.cancel(cancelPendingIntent)
}

@ -1,5 +1,6 @@
package com.deniscerri.ytdl.util
import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
@ -15,6 +16,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import androidx.annotation.RequiresPermission
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.FileProvider
@ -820,6 +822,58 @@ class NotificationUtil(var context: Context) {
notificationManager.notify(QUERY_PROCESS_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
}
@SuppressLint("MissingPermission")
fun showNewAppUpdate(version: String) {
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
val intent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.homeFragment)
.createPendingIntent()
notificationBuilder
.setContentTitle(resources.getString(R.string.version_ready_to_download, version))
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
.setLargeIcon(
BitmapFactory.decodeResource(
resources,
R.drawable.ic_launcher_foreground_large
)
)
.setContentIntent(intent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.clearActions()
notificationManager.notify(NEW_APP_UPDATE_NOTIFICATION_ID, notificationBuilder.build())
}
@SuppressLint("MissingPermission")
fun showNewPackageUpdate(packageName: String, version: String) {
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
val intent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.homeFragment)
.createPendingIntent()
notificationBuilder
.setContentTitle(resources.getString(R.string.package_version_ready_to_download, packageName, version))
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
.setLargeIcon(
BitmapFactory.decodeResource(
resources,
R.drawable.ic_launcher_foreground_large
)
)
.setContentIntent(intent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.clearActions()
notificationManager.notify(NEW_PACKAGE_UPDATE_NOTIFICATION_ID, notificationBuilder.build())
}
private fun isNotificationChannelEnabled(channelId: String?): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!TextUtils.isEmpty(channelId)) {
@ -850,6 +904,8 @@ class NotificationUtil(var context: Context) {
const val QUERY_PROCESS_FINISHED_NOTIFICATION_ID = 80000
const val DOWNLOAD_RUNNING_NOTIFICATION_ID = 90000
const val DOWNLOAD_TERMINAL_RUNNING_NOTIFICATION_ID = 99000
const val NEW_APP_UPDATE_NOTIFICATION_ID = 99900
const val NEW_PACKAGE_UPDATE_NOTIFICATION_ID = 99990
private const val PROGRESS_MAX = 100
private const val PROGRESS_CURR = 0

@ -35,6 +35,7 @@ import android.widget.ProgressBar
import android.widget.RadioButton
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.DimenRes
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
@ -53,12 +54,14 @@ import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.afollestad.materialdialogs.utils.MDUtil.textChanged
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.core.packages.PackageBase
import com.deniscerri.ytdl.database.enums.DownloadType
import com.deniscerri.ytdl.database.models.CommandTemplate
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.models.GithubRelease
import com.deniscerri.ytdl.database.models.HistoryItem
import com.deniscerri.ytdl.database.models.PackageItem
import com.deniscerri.ytdl.database.models.TemplateShortcut
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
@ -2741,6 +2744,31 @@ object UiUtil {
)
}
fun showNewAppUpdateSnackBar(
v: GithubRelease,
context: Activity,
container: LinearLayout,
frameLayout: View,
anchorView: View?,
layoutInflater: LayoutInflater,
updateUtil: UpdateUtil,
lifecycleOwner: LifecycleOwner,
preferences: SharedPreferences
) {
val customView = layoutInflater.inflate(R.layout.layout_update_snackbar, null)
val triggerAction = View.OnClickListener {
container.removeView(customView)
showNewAppUpdateDialog(v, context, updateUtil, lifecycleOwner, preferences)
}
customView.setOnClickListener(triggerAction)
customView.findViewById<View>(R.id.btn_close_update).setOnClickListener {
container.removeView(customView)
}
container.addView(customView)
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
fun showNewAppUpdateDialog(v: GithubRelease, context: Activity, updateUtil: UpdateUtil, lifecycleOwner: LifecycleOwner, preferences: SharedPreferences) {
if (context.isFinishing || context.isDestroyed) return
@ -2834,4 +2862,123 @@ object UiUtil {
}
}
}
fun showNewPackageUpdateSnackBar(
v: PackageBase.PackageRelease,
packageItem: PackageItem,
context: Activity,
container: LinearLayout,
frameLayout: View,
anchorView: View?,
layoutInflater: LayoutInflater,
lifecycleOwner: LifecycleOwner,
installLauncher: ActivityResultLauncher<Intent>
) {
val customView = layoutInflater.inflate(R.layout.layout_package_update_snackbar, null)
val triggerAction = View.OnClickListener {
container.removeView(customView)
showNewReleaseUpdateDialog(v, packageItem, context, lifecycleOwner, frameLayout, anchorView, installLauncher)
}
customView.setOnClickListener(triggerAction)
customView.findViewById<View>(R.id.btn_close_update).setOnClickListener {
container.removeView(customView)
}
customView.findViewById<TextView>(R.id.newVersionTitle).text = context.getString(R.string.package_version_ready_to_download, packageItem.title, v.tag_name)
container.addView(customView)
}
fun showNewReleaseUpdateDialog(
item: PackageBase.PackageRelease,
packageItem: PackageItem,
context: Activity,
lifecycleOwner: LifecycleOwner,
activityView: View,
snackbarAnchorView: View?,
installLauncher: ActivityResultLauncher<Intent>
) {
var tmpDownloadJob : Job? = null
var positiveButton: Button? = null
val updateDialog = MaterialAlertDialogBuilder(context)
.setTitle("${item.tag_name} (${FileUtil.convertFileSize(item.downloadSize)})")
.setMessage(item.body)
.setIcon(R.drawable.ic_update_app)
.setNegativeButton(context.getString(R.string.cancel)) { _: DialogInterface?, _: Int ->
tmpDownloadJob?.cancel()
}
.setPositiveButton(context.getString(R.string.download), null)
val view = updateDialog.show()
val textView = view.findViewById<TextView>(android.R.id.message)
textView!!.movementMethod = LinkMovementMethod.getInstance()
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
context.startActivity(browserIntent)
}
}
}).build()
mw.setMarkdown(textView, item.body)
val lifecycleScope = lifecycleOwner.lifecycleScope
positiveButton = view.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
positiveButton?.setOnClickListener {
positiveButton.isEnabled = false
positiveButton.text = "0%"
tmpDownloadJob = lifecycleScope.launch {
val instance = packageItem.getInstance()
val fileResp = instance.downloadReleaseApk(item) { progress ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
positiveButton.text = "$progress%"
}
}
}
fileResp.onFailure {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
val snackbar = Snackbar.make(activityView, it.message ?: context.getString(R.string.errored), Snackbar.LENGTH_LONG)
snackbar.anchorView = snackbarAnchorView
snackbar.show()
}
}
}
fileResp.onSuccess { file ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
val canRequestPackageInstalls = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
android.Manifest.permission.REQUEST_INSTALL_PACKAGES.hasPermission(context)
} else {
true
}
if (canRequestPackageInstalls) {
val contentUri = FileProvider.getUriForFile(context, context.packageName + ".fileprovider", file)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(contentUri, "application/vnd.android.package-archive")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
installLauncher.launch(intent)
} else {
val snackbar = Snackbar.make(activityView, context.getString(R.string.install_downloaded_file), Snackbar.LENGTH_LONG)
snackbar.anchorView = snackbarAnchorView
snackbar.show()
}
}
}
}
}
}
}
}

@ -1,6 +1,6 @@
package com.deniscerri.ytdl.util
import com.deniscerri.ytdl.work.DownloadWorker
import com.deniscerri.ytdl.work.download.DownloadWorker
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow

@ -1,20 +0,0 @@
package com.deniscerri.ytdl.work
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
class AutoGenerateWebPoTokenWorker(
private val context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
override fun doWork(): Result {
return Result.success()
}
companion object {
const val TAG = "AutoGenerateWebPoTokenWorker"
}
}

@ -9,7 +9,6 @@ import com.deniscerri.ytdl.core.RuntimeManager
import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.repository.DownloadRepository
class CancelScheduledDownloadWorker(
private val context: Context,
workerParams: WorkerParameters
@ -17,11 +16,11 @@ class CancelScheduledDownloadWorker(
@SuppressLint("RestrictedApi")
override suspend fun doWork(): Result {
if (isStopped) return Result.success()
val dbManager = DBManager.getInstance(context)
val dbManager = DBManager.Companion.getInstance(context)
val dao = dbManager.downloadDao
val runningDownloads = dao.getActiveDownloadsList()
WorkManager.getInstance(context).cancelAllWorkByTag("download")
WorkManager.Companion.getInstance(context).cancelAllWorkByTag("download")
runningDownloads.forEach {
RuntimeManager.getInstance().destroyProcessById(it.id.toString())
it.status = DownloadRepository.Status.Queued.toString()

@ -1,18 +0,0 @@
package com.deniscerri.ytdl.work
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.deniscerri.ytdl.util.UpdateUtil
class UpdateYTDLWorker(
private val context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
UpdateUtil(context).updateYTDL()
return Result.success()
}
}

@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work
package com.deniscerri.ytdl.work.background
import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
@ -13,23 +13,28 @@ import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NotificationUtil
import java.io.File
class CleanUpLeftoverDownloads(
private val context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val notificationUtil = NotificationUtil(App.instance)
val notificationUtil = NotificationUtil(App.Companion.instance)
val id = System.currentTimeMillis().toInt()
val notification = notificationUtil.createDeletingLeftoverDownloadsNotification()
if (Build.VERSION.SDK_INT >= 33) {
setForegroundAsync(ForegroundInfo(id, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
setForegroundAsync(
ForegroundInfo(
id,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
)
}else{
setForegroundAsync(ForegroundInfo(id, notification))
}
val dbManager = DBManager.getInstance(context)
val dbManager = DBManager.Companion.getInstance(context)
val downloadRepo = DownloadRepository(dbManager.downloadDao)
downloadRepo.deleteCancelled()
downloadRepo.deleteErrored()

@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work
package com.deniscerri.ytdl.work.background
import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.content.pm.ServiceInfo
import android.os.Build
import android.util.Log
import androidx.preference.PreferenceManager
@ -22,6 +22,7 @@ import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.repository.HistoryRepository
import com.deniscerri.ytdl.database.repository.ObserveSourcesRepository
import com.deniscerri.ytdl.database.repository.ResultRepository
import com.deniscerri.ytdl.util.AlarmScheduler
import com.deniscerri.ytdl.util.Extensions.calculateNextTimeForObserving
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NotificationUtil
@ -31,7 +32,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
class ObserveSourceWorker(
private val context: Context,
workerParams: WorkerParameters
@ -40,11 +40,12 @@ class ObserveSourceWorker(
val sourceID = inputData.getLong("id", 0)
if (sourceID == 0L) return Result.success()
val notificationUtil = NotificationUtil(App.instance)
val dbManager = DBManager.getInstance(context)
val workManager = WorkManager.getInstance(context)
val notificationUtil = NotificationUtil(App.Companion.instance)
val dbManager = DBManager.Companion.getInstance(context)
val workManager = WorkManager.Companion.getInstance(context)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao, workManager, sharedPreferences)
val repo =
ObserveSourcesRepository(dbManager.observeSourcesDao, workManager, sharedPreferences)
val historyRepo = HistoryRepository(dbManager.historyDao)
val downloadRepo = DownloadRepository(dbManager.downloadDao)
val commandTemplateDao = dbManager.commandTemplateDao
@ -60,12 +61,18 @@ class ObserveSourceWorker(
val workerID = System.currentTimeMillis().toInt()
val notification = notificationUtil.createObserveSourcesNotification(item.name)
if (Build.VERSION.SDK_INT >= 33) {
setForegroundAsync(ForegroundInfo(workerID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
setForegroundAsync(
ForegroundInfo(
workerID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
)
}else{
setForegroundAsync(ForegroundInfo(workerID, notification))
}
val list = kotlin.runCatching {
val list = runCatching {
resultRepository.getResultsFromSource(item.url, resetResults = false, addToResults = false, singleItem = false)
}.onFailure {
Log.e("observe", it.toString())
@ -140,7 +147,7 @@ class ObserveSourceWorker(
if (downloadItems.isNotEmpty()){
//QUEUE DOWNLOADS
//COPY OF QUEUE DOWNLOADS IN DOWNLOAD VIEW MODEL. NEEDS TO BE UPDATED IF THAT IS UPDATED
val context = App.instance
val context = App.Companion.instance
val alarmScheduler = AlarmScheduler(context)
val activeAndQueuedDownloads = downloadRepo.getActiveAndQueuedDownloads()
val queuedItems = mutableListOf<DownloadItem>()
@ -171,8 +178,9 @@ class ObserveSourceWorker(
existing.add(it)
}else{
//check if downloaded and file exists
val history = withContext(Dispatchers.IO){
historyRepo.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
val history = withContext(Dispatchers.IO) {
historyRepo.getAllByURL(it.url)
.filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
}
val existingHistory = history.firstOrNull {
@ -211,13 +219,13 @@ class ObserveSourceWorker(
if (isFinished) {
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
withContext(Dispatchers.IO){
withContext(Dispatchers.IO) {
repo.update(item)
}
return Result.success()
}
withContext(Dispatchers.IO){
withContext(Dispatchers.IO) {
repo.update(item)
}
@ -237,7 +245,7 @@ class ObserveSourceWorker(
.setInitialDelay(item.calculateNextTimeForObserving() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
.setInputData(Data.Builder().putLong("id", sourceID).build())
WorkManager.getInstance(context).enqueueUniqueWork(
WorkManager.Companion.getInstance(context).enqueueUniqueWork(
"OBSERVE$sourceID",
ExistingWorkPolicy.REPLACE,
workRequest.build()

@ -0,0 +1,79 @@
package com.deniscerri.ytdl.work.background
import android.content.Context
import androidx.preference.PreferenceManager
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.core.RuntimeManager
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UpdateUtil
import java.util.concurrent.TimeUnit
class UpdateCheckWorker(
private val context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val updateUtil = UpdateUtil(App.instance)
val notificationUtil = NotificationUtil(App.instance)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val newRelease = updateUtil.tryGetNewVersion()
if (newRelease.isSuccess && BuildConfig.FLAVOR == "github") {
notificationUtil.showNewAppUpdate(newRelease.getOrNull()!!.tag_name)
}
val skipRemindingPackageUpdate = sharedPreferences.getStringSet("skip_reminding_package_update", setOf())!!.toMutableSet()
RuntimeManager.packages.forEach { pkg ->
val instance = pkg.plugin.getInstance()
if (instance.bundledVersion.isNullOrBlank() && instance.downloadedVersion.isNullOrBlank()) return@forEach
instance.getReleases().apply {
val releases = this.getOrElse { listOf() }
if (releases.isEmpty()) return@apply
val latestRelease = releases.first()
if (latestRelease.isBundled || latestRelease.isInstalled) return@apply
if (skipRemindingPackageUpdate.contains(latestRelease.tag_name)) return@apply
notificationUtil.showNewPackageUpdate(pkg.title, latestRelease.tag_name)
skipRemindingPackageUpdate.add(latestRelease.tag_name)
sharedPreferences.edit().putStringSet("skip_reminding_package_update", skipRemindingPackageUpdate).apply()
}
}
return Result.success()
}
companion object {
const val WORK_NAME = "ytdlnis_update_check"
fun schedule(context: Context, intervalMinutes: Long = 24 * 60L) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = PeriodicWorkRequestBuilder<UpdateCheckWorker>(
intervalMinutes, TimeUnit.MINUTES
)
.setConstraints(constraints)
.setInitialDelay(intervalMinutes, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
WORK_NAME,
ExistingPeriodicWorkPolicy.UPDATE,
request
)
}
}
}

@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work
package com.deniscerri.ytdl.work.background
import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
@ -10,19 +10,19 @@ import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.repository.ResultRepository
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.work.setForegroundSafely
import kotlinx.coroutines.runBlocking
class UpdateMultipleDownloadsDataWorker(private val context: Context,workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
class UpdateMultipleDownloadsDataWorker(private val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
override suspend fun getForegroundInfo(): ForegroundInfo {
val workNotif = NotificationUtil(App.instance).createDataUpdateNotification()
val workNotif = NotificationUtil(App.Companion.instance).createDataUpdateNotification()
return ForegroundInfo(
2000000000,
workNotif,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
FOREGROUND_SERVICE_TYPE_DATA_SYNC
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
} else {
0
},
@ -31,11 +31,11 @@ class UpdateMultipleDownloadsDataWorker(private val context: Context,workerParam
override suspend fun doWork(): Result {
val dbManager = DBManager.getInstance(context)
val dbManager = DBManager.Companion.getInstance(context)
val dao = dbManager.downloadDao
val resDao = dbManager.resultDao
val commandTemplateDao = dbManager.commandTemplateDao
val resultRepo = ResultRepository(resDao,commandTemplateDao, context)
val resultRepo = ResultRepository(resDao, commandTemplateDao, context)
val ids = inputData.getLongArray("ids")!!.toMutableList()
setForegroundSafely()

@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work
package com.deniscerri.ytdl.work.background
import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.work.ForegroundInfo
import androidx.work.Worker
@ -13,18 +13,17 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.util.NotificationUtil
import kotlinx.coroutines.runBlocking
class UpdateMultipleDownloadsFormatsWorker(
private val context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
override fun doWork(): Result {
val dbManager = DBManager.getInstance(context)
val dbManager = DBManager.Companion.getInstance(context)
val dao = dbManager.downloadDao
val resDao = dbManager.resultDao
val commandTemplateDao = dbManager.commandTemplateDao
val resultRepo = ResultRepository(resDao, commandTemplateDao, context)
val vm = DownloadViewModel(App.instance)
val vm = DownloadViewModel(App.Companion.instance)
val notificationUtil = NotificationUtil(context)
val ids = inputData.getLongArray("ids")!!.toMutableList()
val otherIdsInBundle = inputData.getLongArray("other_ids_in_bundle")!!.toMutableList()
@ -34,7 +33,13 @@ class UpdateMultipleDownloadsFormatsWorker(
val notification = notificationUtil.createFormatsUpdateNotification()
if (Build.VERSION.SDK_INT >= 33) {
setForegroundAsync(ForegroundInfo(workID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
setForegroundAsync(
ForegroundInfo(
workID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
)
}else{
setForegroundAsync(ForegroundInfo(workID, notification))
}

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.work
package com.deniscerri.ytdl.work.download
import android.annotation.SuppressLint
import android.app.PendingIntent
@ -29,12 +29,15 @@ import com.deniscerri.ytdl.database.models.LogItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.repository.LogRepository
import com.deniscerri.ytdl.database.repository.ResultRepository
import com.deniscerri.ytdl.util.AlarmScheduler
import com.deniscerri.ytdl.util.Extensions.getMediaDuration
import com.deniscerri.ytdl.util.Extensions.toStringDuration
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.WorkerEventBus
import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil
import com.deniscerri.ytdl.work.isRunning
import com.deniscerri.ytdl.work.setForegroundSafely
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.work
package com.deniscerri.ytdl.work.download
import android.app.PendingIntent
import android.content.Context
@ -112,7 +112,7 @@ class TerminalDownloadWorker(
)
kotlin.runCatching {
runCatching {
if (logDownloads){
runBlocking {
logItem.id = logRepo.insert(logItem)

@ -43,6 +43,20 @@
app:layout_constraintTop_toTopOf="parent"
/>
<LinearLayout
android:id="@+id/notification_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:animateLayoutChanges="true"
app:layout_constraintEnd_toEndOf="parent"
android:divider="@drawable/divider_10dp"
android:showDividers="middle"
android:layout_marginHorizontal="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/bottomNavigationView" />
<com.google.android.material.navigationrail.NavigationRailView
android:id="@+id/bottomNavigationView"
android:layout_width="wrap_content"

@ -43,6 +43,20 @@
app:layout_constraintTop_toTopOf="parent"
/>
<LinearLayout
android:id="@+id/notification_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:animateLayoutChanges="true"
android:divider="@drawable/divider_10dp"
android:showDividers="middle"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/navigationView" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="wrap_content"

@ -43,6 +43,20 @@
app:layout_constraintTop_toTopOf="parent"
/>
<LinearLayout
android:id="@+id/notification_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:animateLayoutChanges="true"
android:divider="@drawable/divider_10dp"
android:showDividers="middle"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/bottomNavigationView" />
<com.google.android.material.navigationrail.NavigationRailView
android:id="@+id/bottomNavigationView"
android:layout_width="wrap_content"

@ -34,6 +34,18 @@
app:layout_constraintTop_toBottomOf="@+id/incognito_header"
/>
<LinearLayout
android:id="@+id/notification_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:divider="@drawable/divider_10dp"
android:showDividers="middle"
android:layout_marginHorizontal="20dp"
android:layout_marginBottom="20dp"
android:animateLayoutChanges="true"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="10dp"
app:cardCornerRadius="20dp"
app:cardElevation="0dp"
app:strokeWidth="1dp"
app:strokeColor="?attr/colorTertiaryContainer"
app:cardBackgroundColor="?attr/colorTertiaryContainer">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingVertical="12dp"
android:paddingHorizontal="16dp"
android:gravity="center_vertical">
<!-- Circular Indicator Container matching App Palette -->
<com.google.android.material.card.MaterialCardView
android:layout_width="36dp"
android:layout_height="36dp"
app:cardCornerRadius="18dp"
app:cardElevation="0dp"
app:strokeWidth="0dp"
app:cardBackgroundColor="?attr/colorTertiary"
android:layout_marginEnd="14dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center"
android:src="@android:drawable/stat_sys_download_done"
app:tint="?attr/colorPrimaryContainer" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/newVersionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/package_version_ready_to_download"
android:textStyle="bold"
android:fontFamily="sans-serif-medium"
android:textColor="?attr/colorOnSurface"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_update_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tap_to_see_changes_and_install"
android:textColor="?attr/colorOnSurfaceVariant"
android:textSize="12sp"
android:layout_marginTop="1dp" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_close_update"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13sp"
app:icon="@drawable/baseline_close_24"
app:iconTint="?attr/colorTertiary"
android:fontFamily="sans-serif-medium"
android:paddingHorizontal="12dp"
app:rippleColor="?attr/colorTertiaryContainer" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="20dp"
app:cardElevation="0dp"
app:strokeWidth="1dp"
app:strokeColor="?attr/colorPrimaryContainer"
app:cardBackgroundColor="?attr/colorPrimaryContainer">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingVertical="12dp"
android:paddingHorizontal="16dp"
android:gravity="center_vertical">
<!-- Circular Indicator Container matching App Palette -->
<com.google.android.material.card.MaterialCardView
android:layout_width="36dp"
android:layout_height="36dp"
app:cardCornerRadius="18dp"
app:cardElevation="0dp"
android:clickable="false"
android:focusable="false"
app:strokeWidth="0dp"
app:cardBackgroundColor="?attr/colorSecondary"
android:layout_marginEnd="14dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:clickable="false"
android:focusable="false"
android:layout_gravity="center"
android:src="@android:drawable/stat_sys_download_done"
app:tint="?attr/colorPrimaryContainer" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/newVersionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/version_ready_to_download"
android:textStyle="bold"
android:clickable="false"
android:focusable="false"
android:fontFamily="sans-serif-medium"
android:textColor="?attr/colorOnSurface"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_update_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:text="@string/tap_to_see_changes_and_install"
android:textColor="?attr/colorOnSurfaceVariant"
android:textSize="12sp"
android:layout_marginTop="1dp" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_close_update"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13sp"
app:icon="@drawable/baseline_close_24"
app:iconTint="?attr/colorTertiary"
android:fontFamily="sans-serif-medium"
android:paddingHorizontal="12dp"
app:rippleColor="?attr/colorTertiaryContainer" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

@ -537,4 +537,8 @@
<string name="player_client_not_empty">Player Client tag shouldn\'t be empty</string>
<string name="player_client_already_created">Player Client is already created</string>
<string name="write_atleast_one_potoken">You need to write at least one PO Token</string>
<string name="version_ready_to_download">Version %s is ready to download</string>
<string name="package_version_ready_to_download">Package %1$s with version %2$s is ready to download</string>
<string name="tap_to_see_changes_and_install">Tap to see changes and install</string>
<string name="view">View</string>
</resources>

Loading…
Cancel
Save