Skip to content

Commit

Permalink
Merge pull request #1 from urFate/notifications
Browse files Browse the repository at this point in the history
Merge notifications branch
  • Loading branch information
urFate authored Apr 10, 2024
2 parents 326a5d4 + 401f574 commit 1674173
Show file tree
Hide file tree
Showing 23 changed files with 798 additions and 108 deletions.
37 changes: 21 additions & 16 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools" >

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Expand All @@ -15,7 +15,8 @@
android:label="@string/app_name"
android:supportsRtl="false"
android:theme="@style/Theme.ShiraBox"
tools:targetApi="34">
tools:targetApi="34" >

<activity
android:name=".ui.activity.auth.AuthActivity"
android:exported="false"
Expand All @@ -27,19 +28,6 @@
android:label="@string/title_activity_settings"
android:theme="@style/Theme.ShiraBox" />

<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_shirabox_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/primary" />
<!-- <service -->
<!-- android:name=".NotificationService" -->
<!-- android:exported="false"> -->
<!-- <intent-filter> -->
<!-- <action android:name="com.google.firebase.MESSAGING_EVENT" /> -->
<!-- </intent-filter> -->
<!-- </service> -->
<activity
android:name=".ui.activity.search.SearchActivity"
android:exported="false"
Expand All @@ -60,12 +48,29 @@
android:name=".ui.activity.MainActivity"
android:exported="true"
android:hardwareAccelerated="true"
android:theme="@style/Theme.ShiraBox">
android:theme="@style/Theme.ShiraBox" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name=".NotificationService"
android:enabled="true"
android:exported="false" >
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_shirabox_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/primary" />
</application>

</manifest>
143 changes: 143 additions & 0 deletions app/src/main/java/live/shirabox/shirabox/NotificationService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package live.shirabox.shirabox

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import live.shirabox.core.entity.NotificationEntity
import live.shirabox.core.model.ContentType
import live.shirabox.core.util.Util
import live.shirabox.shirabox.db.AppDatabase
import live.shirabox.shirabox.ui.activity.resource.ResourceActivity
import java.net.URL

class NotificationService : FirebaseMessagingService() {
companion object {
private const val TAG = "ShiraBoxService"
private const val MAIN_CHANNEL_ID = "SB_NOTIFICATIONS"
lateinit var db: AppDatabase
}

private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)

private data class MessageData(
val title: String,
val body: String,
val thumbnailUrl: String?,
val shikimoriId: Int
)

override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d(TAG, "New token observed: $token")
}

override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
remoteMessage.data.ifEmpty { return }

Log.d(TAG, "Message data payload: ${remoteMessage.data}")

try {
db = AppDatabase.getAppDataBase(this)!!

val data = remoteMessage.data

val messageData = MessageData(
title = data["title"] ?: "Undefined title",
body = data["body"] ?: "Undefined notification body",
shikimoriId = data["shikimori_id"]!!.toInt(),
thumbnailUrl = data["thumbnail"]
)

scope.launch {
launch { sendNotification(messageData) }
launch { saveNotification(messageData) }
}

} catch (ex: Exception) {
ex.printStackTrace()
}
}

private suspend fun sendNotification(messageData: MessageData) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.createNotificationChannel(notificationChannel(this))
}

val thumbnailBitmap = messageData.thumbnailUrl?.let {
withContext(Dispatchers.IO) {
async { Util.getBitmapFromURL(URL(it)) }
}.await()
}

val activityIntent = Intent(this, ResourceActivity::class.java).apply {
putExtra("id", messageData.shikimoriId)
putExtra("type", ContentType.ANIME.toString())
}
val activityPendingIntent: PendingIntent? = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(activityIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}

val notification: Notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(this@NotificationService, MAIN_CHANNEL_ID).apply {
setContentTitle(messageData.title)
setContentText(messageData.body)
setLargeIcon(thumbnailBitmap)
setSmallIcon(R.drawable.ic_stat_shirabox_notification)
setContentIntent(activityPendingIntent)
setAutoCancel(true)
}.build()
} else {
Notification.Builder(this@NotificationService).apply {
setContentTitle(messageData.title)
setContentText(messageData.body)
setLargeIcon(thumbnailBitmap)
setSmallIcon(R.drawable.ic_stat_shirabox_notification)
setContentIntent(activityPendingIntent)
setAutoCancel(true)
}.build()
}

manager.notify(System.nanoTime().toInt(), notification)
}

private suspend fun saveNotification(messageData: MessageData) {
withContext(Dispatchers.IO) {
db.notificationDao().insertNotification(
NotificationEntity(
contentShikimoriId = messageData.shikimoriId,
receiveTimestamp = System.currentTimeMillis(),
title = messageData.title,
body = messageData.body,
thumbnailUrl = messageData.thumbnailUrl ?: ""
)
)
}
}

@RequiresApi(Build.VERSION_CODES.O)
private fun notificationChannel(context: Context) = NotificationChannel(
MAIN_CHANNEL_ID,
context.getString(R.string.notificaion_channel),
NotificationManager.IMPORTANCE_DEFAULT
).apply { description = context.getString(R.string.notificaion_channel_description) }
}
8 changes: 6 additions & 2 deletions app/src/main/java/live/shirabox/shirabox/db/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import live.shirabox.core.entity.ContentEntity
import live.shirabox.core.entity.EpisodeEntity
import live.shirabox.core.entity.NotificationEntity
import live.shirabox.core.util.Values
import live.shirabox.shirabox.db.dao.ContentDao
import live.shirabox.shirabox.db.dao.EpisodeDao
import live.shirabox.shirabox.db.dao.NotificationDao

@Database(
entities = [ContentEntity::class, EpisodeEntity::class],
entities = [ContentEntity::class, EpisodeEntity::class, NotificationEntity::class],
version = 1
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun contentDao(): ContentDao
abstract fun episodeDao(): EpisodeDao
abstract fun notificationDao(): NotificationDao

companion object {
private var INSTANCE: AppDatabase? = null
Expand All @@ -28,7 +32,7 @@ abstract class AppDatabase : RoomDatabase() {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"shirabox_db"
Values.DATABASE_NAME
).build()
}
}
Expand Down
38 changes: 38 additions & 0 deletions app/src/main/java/live/shirabox/shirabox/db/dao/NotificationDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package live.shirabox.shirabox.db.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import live.shirabox.core.entity.NotificationEntity
import live.shirabox.core.entity.relation.NotificationAndContent

@Dao
interface NotificationDao {
@Query("SELECT * FROM notification")
fun all(): Flow<List<NotificationEntity>>

@Transaction
@Query("SELECT * FROM notification")
fun allNotificationsWithContent(): Flow<List<NotificationAndContent>>

@Transaction
@Query("SELECT * FROM notification WHERE content_shikimori_id IS :shikimoriId")
fun notificationsFromParent(shikimoriId: Int): Flow<List<NotificationEntity>>

@Insert(onConflict = OnConflictStrategy.ABORT)
fun insertNotification(vararg notificationEntity: NotificationEntity)

@Update
fun updateNotification(vararg notificationEntity: NotificationEntity)

@Delete
fun deleteNotification(vararg notificationEntity: NotificationEntity)

@Query("DELETE FROM notification")
fun deleteAll()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import live.shirabox.shirabox.ui.component.navigation.BottomNavigationView
import live.shirabox.shirabox.ui.screen.explore.notifications.NotificationsDialog
import live.shirabox.shirabox.ui.theme.ShiraBoxTheme

class MainActivity : ComponentActivity() {
@OptIn(ExperimentalFoundationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
ShiraBoxTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) { BottomNavigationView() }
) {
NotificationsDialog()
BottomNavigationView()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ fun Resource(
LaunchedEffect(Unit) {
model.fetchContent(id)
model.fetchRelated(id)
model.clearNotifications(id)
}

AnimatedVisibility(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ fun SourcesSheetScreen(
coverImage = source.icon,
trailingIcon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin,
onTrailingIconClick = {
model.switchSourcePinStatus(content.shikimoriID, source)
model.switchSourcePinStatus(context, content.shikimoriID, source)
}
) {
currentSheetScreenState.value =
Expand Down
Loading

0 comments on commit 1674173

Please sign in to comment.