diff --git a/app/src/main/java/com/junkfood/seal/Downloader.kt b/app/src/main/java/com/junkfood/seal/Downloader.kt index 0c64255d6a..9ef4c10653 100644 --- a/app/src/main/java/com/junkfood/seal/Downloader.kt +++ b/app/src/main/java/com/junkfood/seal/Downloader.kt @@ -1,7 +1,9 @@ package com.junkfood.seal import android.app.PendingIntent +import android.content.Intent import android.util.Log +import androidx.core.app.NotificationCompat import androidx.annotation.CheckResult import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateMapOf @@ -412,22 +414,50 @@ object Downloader { if (it.isEmpty()) R.string.status_completed else R.string.download_finish_notification ) - FileUtil.createIntentForOpeningFile(it.firstOrNull()).run { - NotificationUtil.finishNotification( - notificationId, - title = videoInfo.title, - text = text, - intent = - if (this != null) + val openIntent = + FileUtil.createIntentForOpeningFile(it.firstOrNull())?.let { intent -> + PendingIntent.getActivity( + context, + notificationId, + intent, + PendingIntent.FLAG_IMMUTABLE, + ) + } + + val shareAction = + it.firstOrNull() + ?.takeIf { _ -> it.size == 1 } + ?.let { filePath -> + FileUtil.createIntentForSharingFile(filePath)?.let { shareIntent -> + val chooser = + Intent.createChooser( + shareIntent, + context.getString(R.string.share), + ) PendingIntent.getActivity( context, - 0, - this, - PendingIntent.FLAG_IMMUTABLE, + notificationId + 1, + chooser, + PendingIntent.FLAG_IMMUTABLE or + PendingIntent.FLAG_UPDATE_CURRENT, ) - else null, - ) - } + } + } + ?.let { pendingIntent -> + NotificationCompat.Action( + android.R.drawable.ic_menu_share, + context.getString(R.string.share), + pendingIntent, + ) + } + + NotificationUtil.finishNotification( + notificationId, + title = videoInfo.title, + text = text, + intent = openIntent, + actions = listOfNotNull(shareAction), + ) } } diff --git a/app/src/main/java/com/junkfood/seal/MainActivity.kt b/app/src/main/java/com/junkfood/seal/MainActivity.kt index 41958b19da..20283e4bf8 100644 --- a/app/src/main/java/com/junkfood/seal/MainActivity.kt +++ b/app/src/main/java/com/junkfood/seal/MainActivity.kt @@ -1,10 +1,12 @@ package com.junkfood.seal +import android.Manifest import android.content.Intent import android.os.Build import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass @@ -14,7 +16,11 @@ import com.junkfood.seal.ui.common.SettingsProvider import com.junkfood.seal.ui.page.AppEntry import com.junkfood.seal.ui.page.downloadv2.configure.DownloadDialogViewModel import com.junkfood.seal.ui.theme.SealTheme +import com.junkfood.seal.util.NotificationUtil import com.junkfood.seal.util.PreferenceUtil +import com.junkfood.seal.util.PreferenceUtil.getBoolean +import com.junkfood.seal.util.PreferenceUtil.updateBoolean +import com.junkfood.seal.util.NOTIFICATION_PERMISSION_REQUESTED import com.junkfood.seal.util.matchUrlFromSharedText import com.junkfood.seal.util.setLanguage import kotlinx.coroutines.runBlocking @@ -23,6 +29,10 @@ import org.koin.compose.KoinContext class MainActivity : AppCompatActivity() { private val dialogViewModel: DownloadDialogViewModel by viewModel() + private val notificationPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { + NOTIFICATION_PERMISSION_REQUESTED.updateBoolean(true) + } @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) override fun onCreate(savedInstanceState: Bundle?) { @@ -47,6 +57,8 @@ class MainActivity : AppCompatActivity() { } } } + + requestNotificationPermissionIfNeeded() } override fun onNewIntent(intent: Intent) { @@ -82,6 +94,14 @@ class MainActivity : AppCompatActivity() { } } + private fun requestNotificationPermissionIfNeeded() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return + if (NOTIFICATION_PERMISSION_REQUESTED.getBoolean()) return + if (NotificationUtil.areNotificationsEnabled()) return + + notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + companion object { private const val TAG = "MainActivity" private var sharedUrlCached = "" diff --git a/app/src/main/java/com/junkfood/seal/download/DownloaderV2.kt b/app/src/main/java/com/junkfood/seal/download/DownloaderV2.kt index 4713d4d3aa..4f50ffc173 100644 --- a/app/src/main/java/com/junkfood/seal/download/DownloaderV2.kt +++ b/app/src/main/java/com/junkfood/seal/download/DownloaderV2.kt @@ -2,6 +2,7 @@ package com.junkfood.seal.download import android.app.PendingIntent import android.content.Context +import android.content.Intent import android.util.Log import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.snapshotFlow @@ -19,6 +20,7 @@ import com.junkfood.seal.download.Task.DownloadState.Running import com.junkfood.seal.download.Task.RestartableAction.Download import com.junkfood.seal.download.Task.RestartableAction.FetchInfo import com.junkfood.seal.download.Task.TypeInfo +import androidx.core.app.NotificationCompat import com.junkfood.seal.util.DownloadUtil import com.junkfood.seal.util.FileUtil import com.junkfood.seal.util.NotificationUtil @@ -316,22 +318,51 @@ class DownloaderV2Impl(private val appContext: Context) : DownloaderV2, KoinComp if (pathList.isEmpty()) R.string.status_completed else R.string.download_finish_notification ) - FileUtil.createIntentForOpeningFile(pathList.firstOrNull()).run { - NotificationUtil.finishNotification( - notificationId, - title = viewState.title, - text = text, - intent = - if (this != null) + val openIntent = + FileUtil.createIntentForOpeningFile(pathList.firstOrNull())?.let { + PendingIntent.getActivity( + appContext, + notificationId, + it, + PendingIntent.FLAG_IMMUTABLE, + ) + } + + val shareAction = + pathList + .firstOrNull() + ?.takeIf { pathList.size == 1 } + ?.let { filePath -> + FileUtil.createIntentForSharingFile(filePath)?.let { + val chooser = + Intent.createChooser( + it, + appContext.getString(R.string.share), + ) PendingIntent.getActivity( appContext, - 0, - this, - PendingIntent.FLAG_IMMUTABLE, + notificationId + 1, + chooser, + PendingIntent.FLAG_IMMUTABLE or + PendingIntent.FLAG_UPDATE_CURRENT, ) - else null, - ) - } + } + } + ?.let { + NotificationCompat.Action( + android.R.drawable.ic_menu_share, + appContext.getString(R.string.share), + it, + ) + } + + NotificationUtil.finishNotification( + notificationId, + title = viewState.title, + text = text, + intent = openIntent, + actions = listOfNotNull(shareAction), + ) } .onFailure { throwable -> if (throwable is YoutubeDL.CanceledException) { diff --git a/app/src/main/java/com/junkfood/seal/util/NotificationUtil.kt b/app/src/main/java/com/junkfood/seal/util/NotificationUtil.kt index af37af23a6..ba16343b06 100644 --- a/app/src/main/java/com/junkfood/seal/util/NotificationUtil.kt +++ b/app/src/main/java/com/junkfood/seal/util/NotificationUtil.kt @@ -12,6 +12,7 @@ import android.os.Build import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationCompat.Action import androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE import com.junkfood.seal.App.Companion.context import com.junkfood.seal.NotificationActionReceiver @@ -110,6 +111,7 @@ object NotificationUtil { title: String? = null, text: String? = null, intent: PendingIntent? = null, + actions: List = emptyList(), ) { Log.d(TAG, "finishNotification: ") notificationManager.cancel(notificationId) @@ -123,6 +125,7 @@ object NotificationUtil { .setAutoCancel(true) title?.let { builder.setContentTitle(title) } intent?.let { builder.setContentIntent(intent) } + actions.forEach { builder.addAction(it) } notificationManager.notify(notificationId, builder.build()) } diff --git a/app/src/main/java/com/junkfood/seal/util/PreferenceUtil.kt b/app/src/main/java/com/junkfood/seal/util/PreferenceUtil.kt index ed4880820a..e96ed5d525 100644 --- a/app/src/main/java/com/junkfood/seal/util/PreferenceUtil.kt +++ b/app/src/main/java/com/junkfood/seal/util/PreferenceUtil.kt @@ -62,6 +62,7 @@ const val SUBDIRECTORY_PLAYLIST_TITLE = "subdirectory_playlist_title" const val PLAYLIST = "playlist" private const val LANGUAGE = "language" const val NOTIFICATION = "notification" +const val NOTIFICATION_PERMISSION_REQUESTED = "notification_permission_requested" private const val THEME_COLOR = "theme_color" const val PALETTE_STYLE = "palette_style" const val SUBTITLE = "subtitle" @@ -216,6 +217,7 @@ private val BooleanPreferenceDefaults = CELLULAR_DOWNLOAD to false, YT_DLP_AUTO_UPDATE to true, NOTIFICATION to true, + NOTIFICATION_PERMISSION_REQUESTED to false, EMBED_METADATA to true, USE_CUSTOM_AUDIO_PRESET to false, ) diff --git a/color/build.gradle.kts b/color/build.gradle.kts index 661fbc0ad6..d06d12f38a 100644 --- a/color/build.gradle.kts +++ b/color/build.gradle.kts @@ -12,9 +12,9 @@ kotlin { jvmToolchain(21) } android { - compileSdk = 34 + compileSdk = 35 defaultConfig { - minSdk = 21 + minSdk = 24 } namespace = "com.junkfood.seal.color" compileOptions {