Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions androidApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

@ssalggnikool ssalggnikool Jun 29, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can somehow send download notifications without permission because i’ve seen apps do that, not sure if you did that here im too lazy to look, but I would definitely prefer that

For example, chrome or discord can download a file in the background, with progress being shown in a silent notification, and it will notify when its finished (or at least thats how it works for me on grapheneos, idk how it behaves on other roms)

Edit: you need to use android.app.DownloadManager to download the file, then do setShowRunningNotification(true)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried not putting it and putting it and don't asking for permission but it doesn't work, idk honestly

<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import android.os.Build
import android.util.Log
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.dsl.module
import paige.navic.androidApp.shared.AndroidResourceProvider
import paige.navic.di.initKoin
import paige.navic.util.android.ActivityProvider
import paige.navic.util.core.ResourceProvider
import kotlin.system.exitProcess

class Application : android.app.Application() {
class Application : android.app.Application(), KoinComponent {
override fun onCreate() {
super.onCreate()

Expand Down Expand Up @@ -43,6 +46,8 @@ class Application : android.app.Application() {
androidContext(this@Application)
androidLogger()
}

registerActivityLifecycleCallbacks(get<ActivityProvider>())
}

private fun isCrashProcess(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import paige.navic.data.database.CacheDatabase
import paige.navic.data.database.DownloadDatabase
import paige.navic.domain.manager.ConnectivityManager
import paige.navic.domain.manager.LogManager
import paige.navic.domain.manager.NotificationManager
import paige.navic.domain.manager.ShareManager
import paige.navic.domain.manager.StorageManager
import paige.navic.domain.repositories.PlayerStateRepository
import paige.navic.shared.AndroidMediaPlayerViewModel
import paige.navic.shared.MediaPlayerViewModel
import paige.navic.util.android.ActivityProvider

actual val platformModule = module {
singleOf(::ActivityProvider)
single<CacheDatabase> {
val dbPath = androidApplication()
.getDatabasePath("cache.db")
Expand Down Expand Up @@ -61,6 +64,7 @@ actual val platformModule = module {
}

singleOf(::ShareManager)
singleOf(::NotificationManager)
singleOf(::StorageManager)
singleOf(::ConnectivityManager)
singleOf(::LogManager)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package paige.navic.domain.manager

import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager as AndroidNotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import paige.navic.util.android.ActivityProvider
import paige.navic.util.core.ResourceProvider

actual class NotificationManager(
private val context: Context,
private val resourceProvider: ResourceProvider,
private val activityProvider: ActivityProvider
) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as AndroidNotificationManager

init {
createNotificationChannel()
}

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Library Updates",
AndroidNotificationManager.IMPORTANCE_LOW
).apply {
description = "Notifications for library sync and downloads"
}
notificationManager.createNotificationChannel(channel)
}
}

actual fun showProgressNotification(
id: Int,
title: String,
message: String,
progress: Float,
indeterminate: Boolean
) {
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(resourceProvider.icNavic)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setProgress(100, (progress * 100).toInt(), indeterminate)

notificationManager.notify(id, builder.build())
}

actual fun cancelNotification(id: Int) {
notificationManager.cancel(id)
}

actual fun requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
activityProvider.currentActivity?.let { activity ->
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
101
)
}
}
}
}

companion object {
private const val CHANNEL_ID = "library_updates"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package paige.navic.util.android

import android.app.Activity
import android.app.Application
import android.os.Bundle

class ActivityProvider : Application.ActivityLifecycleCallbacks {
var currentActivity: Activity? = null
private set

override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
currentActivity = activity
}

override fun onActivityStarted(activity: Activity) {
currentActivity = activity
}

override fun onActivityResumed(activity: Activity) {
currentActivity = activity
}

override fun onActivityPaused(activity: Activity) {
if (currentActivity == activity) {
currentActivity = null
}
}

override fun onActivityStopped(activity: Activity) {
if (currentActivity == activity) {
currentActivity = null
}
}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}

override fun onActivityDestroyed(activity: Activity) {
if (currentActivity == activity) {
currentActivity = null
}
}
}
7 changes: 7 additions & 0 deletions composeApp/src/commonMain/kotlin/paige/navic/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import org.jetbrains.compose.resources.getString
import org.koin.compose.koinInject
import paige.navic.di.initializeSingletonImageLoader
import paige.navic.domain.manager.BottomBarScrollManager
import paige.navic.domain.manager.NotificationManager
import paige.navic.domain.manager.PreferenceManager
import paige.navic.domain.manager.SessionManager
import paige.navic.domain.manager.SnackBarManager
Expand Down Expand Up @@ -141,6 +142,12 @@ fun App() {
val platformContext = rememberPlatformContext()
val sessionManager = koinInject<SessionManager>()
val preferenceManager = koinInject<PreferenceManager>()
val notificationManager = koinInject<NotificationManager>()

LaunchedEffect(Unit) {
notificationManager.requestPermissions()
}

val isLoggedIn by sessionManager.isLoggedIn.collectAsStateWithLifecycle()
val backStack = rememberNavBackStack(
config, if (isLoggedIn) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.sync.withPermit
import org.jetbrains.compose.resources.getString
import paige.navic.data.database.dao.AlbumDao
import paige.navic.data.database.dao.DownloadDao
import paige.navic.data.database.dao.LyricDao
Expand All @@ -40,6 +41,9 @@ import paige.navic.domain.models.DomainSong
import paige.navic.domain.models.DomainSongCollection
import paige.navic.domain.repositories.LyricsRepository
import paige.navic.util.core.Logger
import navic.composeapp.generated.resources.Res
import navic.composeapp.generated.resources.info_progress
import navic.composeapp.generated.resources.title_library_download
import coil3.PlatformContext as CoilPlatformContext

class DownloadManager(
Expand All @@ -50,7 +54,8 @@ class DownloadManager(
private val lyricsRepository: LyricsRepository,
private val lyricDao: LyricDao,
private val sessionManager: SessionManager,
private val preferenceManager: PreferenceManager
private val preferenceManager: PreferenceManager,
private val notificationManager: NotificationManager
) {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val client = HttpClient {
Expand Down Expand Up @@ -132,6 +137,14 @@ class DownloadManager(
isDownloadingLibrary.value = true
libraryDownloadProgress.value = 0f

notificationManager.showProgressNotification(
id = NotificationIds.DOWNLOAD_LIBRARY,
title = getString(Res.string.title_library_download),
message = getString(Res.string.info_progress),
progress = 0f,
indeterminate = true
)

val songsToDownload = songs.filter { !isDownloaded(it.id) }
val totalToDownload = songsToDownload.size

Expand All @@ -155,8 +168,16 @@ class DownloadManager(

progressMutex.withLock {
processedCount++
libraryDownloadProgress.value =
processedCount.toFloat() / totalToDownload.toFloat()
val progress = processedCount.toFloat() / totalToDownload.toFloat()
libraryDownloadProgress.value = progress

notificationManager.showProgressNotification(
id = NotificationIds.DOWNLOAD_LIBRARY,
title = getString(Res.string.title_library_download),
message = getString(Res.string.info_progress),
progress = progress,
indeterminate = false
)
}
}
}
Expand All @@ -168,6 +189,8 @@ class DownloadManager(
} catch (_: CancellationException) {
isDownloadingLibrary.value = false
libraryDownloadProgress.value = 0f
} finally {
notificationManager.cancelNotification(NotificationIds.DOWNLOAD_LIBRARY)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package paige.navic.domain.manager

expect class NotificationManager {
fun showProgressNotification(
id: Int,
title: String,
message: String,
progress: Float,
indeterminate: Boolean = false
)

fun cancelNotification(id: Int)

fun requestPermissions()
}

object NotificationIds {
const val DOWNLOAD_LIBRARY = 1001
const val SYNC_LIBRARY = 1002
}
Loading
Loading