feat: better notification

This commit is contained in:
rebelonion 2024-02-28 01:27:48 -06:00
parent 2f7c6e734e
commit e256fb1560
8 changed files with 131 additions and 27 deletions

View file

@ -1,5 +1,6 @@
package ani.dantotsu
import android.Manifest
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.app.Activity
@ -16,7 +17,6 @@ import android.content.res.Configuration
import android.content.res.Resources.getSystem
import android.graphics.Bitmap
import android.graphics.Color
import android.Manifest
import android.media.MediaScannerConnection
import android.net.ConnectivityManager
import android.net.NetworkCapabilities.*
@ -688,7 +688,11 @@ fun downloadsPermission(activity: AppCompatActivity): Boolean {
}.toTypedArray()
return if (requiredPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(activity, requiredPermissions, DOWNLOADS_PERMISSION_REQUEST_CODE)
ActivityCompat.requestPermissions(
activity,
requiredPermissions,
DOWNLOADS_PERMISSION_REQUEST_CODE
)
false
} else {
true
@ -1068,3 +1072,11 @@ suspend fun View.pop() {
}
delay(100)
}
fun logToFile(context: Context, message: String) {
val externalFilesDir = context.getExternalFilesDir(null)
val file = File(externalFilesDir, "notifications.log")
file.appendText(message)
file.appendText("\n")
}

View file

@ -220,6 +220,21 @@ class MainActivity : AppCompatActivity() {
}
}
intent.extras?.let { extras ->
val fragmentToLoad = extras.getString("FRAGMENT_TO_LOAD")
val mediaId = extras.getInt("mediaId", -1)
val commentId = extras.getInt("commentId", -1)
if (fragmentToLoad != null && mediaId != -1 && commentId != -1) {
val detailIntent = Intent(this, MediaDetailsActivity::class.java).apply {
putExtra("FRAGMENT_TO_LOAD", fragmentToLoad)
putExtra("mediaId", mediaId)
putExtra("commentId", commentId)
}
startActivity(detailIntent)
return
}
}
val offlineMode: Boolean = PrefManager.getVal(PrefName.OfflineMode)
if (!isOnline(this)) {
snackString(this@MainActivity.getString(R.string.no_internet_connection))

View file

@ -17,9 +17,11 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okio.IOException
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
object CommentsAPI {
val address: String = "https://1224665.xyz:443"
@ -214,11 +216,13 @@ object CommentsAPI {
return res
}
suspend fun reportComment(commentId: Int, username: String, mediaTitle: String): Boolean {
suspend fun reportComment(commentId: Int, username: String, mediaTitle: String, reportedId: String): Boolean {
val url = "$address/report/$commentId"
val body = FormBody.Builder()
.add("username", username)
.add("mediaName", mediaTitle)
.add("reporter", Anilist.username ?: "unknown")
.add("reportedId", reportedId)
.build()
val request = requestBuilder()
val json = try{
@ -234,9 +238,9 @@ object CommentsAPI {
return res
}
suspend fun getNotifications(): NotificationResponse? {
suspend fun getNotifications(client: OkHttpClient): NotificationResponse? {
val url = "$address/notification/reply"
val request = requestBuilder()
val request = requestBuilder(client)
val json = try {
request.get(url)
} catch (e: IOException) {
@ -255,7 +259,7 @@ object CommentsAPI {
return parsed
}
suspend fun fetchAuthToken() {
suspend fun fetchAuthToken(client: OkHttpClient? = null) {
if (authToken != null) return
val MAX_RETRIES = 5
val tokenLifetime: Long = 1000 * 60 * 60 * 24 * 6 // 6 days
@ -276,7 +280,7 @@ object CommentsAPI {
val token = PrefManager.getVal(PrefName.AnilistToken, null as String?) ?: return
repeat(MAX_RETRIES) {
try {
val json = authRequest(token, url)
val json = authRequest(token, url, client)
if (json.code == 200) {
if (!json.text.startsWith("{")) throw IOException("Invalid response")
val parsed = try {
@ -307,11 +311,11 @@ object CommentsAPI {
snackString("Failed to login after multiple attempts")
}
private suspend fun authRequest(token: String, url: String): NiceResponse {
private suspend fun authRequest(token: String, url: String, client: OkHttpClient? = null): NiceResponse {
val body: FormBody = FormBody.Builder()
.add("token", token)
.build()
val request = requestBuilder()
val request = if (client != null) requestBuilder(client) else requestBuilder()
return request.post(url, requestBody = body)
}
@ -325,9 +329,9 @@ object CommentsAPI {
return map
}
private fun requestBuilder(): Requests {
private fun requestBuilder(client: OkHttpClient = Injekt.get<NetworkHelper>().client): Requests {
return Requests(
Injekt.get<NetworkHelper>().client,
client,
headerBuilder()
)
}

View file

@ -139,7 +139,7 @@ class CommentItem(val comment: Comment,
dialogBuilder("Report Comment", "Only report comments that violate the rules. Are you sure you want to report this comment?") {
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch {
val success = CommentsAPI.reportComment(comment.commentId, comment.username, commentsFragment.mediaName)
val success = CommentsAPI.reportComment(comment.commentId, comment.username, commentsFragment.mediaName, comment.userId)
if (success) {
snackString("Comment Reported")
}

View file

@ -348,7 +348,6 @@ class CommentsFragment : Fragment() {
@SuppressLint("NotifyDataSetChanged")
override fun onStart() {
super.onStart()
adapter.notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
@ -423,6 +422,10 @@ class CommentsFragment : Fragment() {
)
}
}
binding.commentsProgressBar.visibility = View.GONE
binding.commentsList.visibility = View.VISIBLE
adapter.add(section)
}
private fun sortComments(comments: List<Comment>?): List<Comment> {
@ -693,7 +696,7 @@ class CommentsFragment : Fragment() {
.usePlugin(TaskListPlugin.create(activity))
.usePlugin(HtmlPlugin.create { plugin ->
plugin.addHandler(
TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre")
TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a")
)
})
.usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore {

View file

@ -13,6 +13,10 @@ class MediaNameFetch {
mediaIds.forEachIndexed { index, mediaId ->
query += """
media$index: Media(id: $mediaId) {
coverImage {
medium
color
}
id
title {
romaji
@ -24,7 +28,7 @@ class MediaNameFetch {
return query
}
suspend fun fetchMediaTitles(ids: List<Int>): Map<Int, String> {
suspend fun fetchMediaTitles(ids: List<Int>): Map<Int, ReturnedData> {
return try {
val url = "https://graphql.anilist.co/"
val data = mapOf(
@ -40,15 +44,19 @@ class MediaNameFetch {
data = data
)
val mediaResponse = parseMediaResponseWithGson(response.text)
val mediaMap = mutableMapOf<Int, String>()
val mediaMap = mutableMapOf<Int, ReturnedData>()
mediaResponse.data.forEach { (_, mediaItem) ->
mediaMap[mediaItem.id] = mediaItem.title.romaji
mediaMap[mediaItem.id] = ReturnedData(
mediaItem.title.romaji,
mediaItem.coverImage.medium,
mediaItem.coverImage.color
)
}
mediaMap
}
} catch (e: Exception) {
val errorMap = mutableMapOf<Int, String>()
ids.forEach { errorMap[it] = "Unknown" }
val errorMap = mutableMapOf<Int, ReturnedData>()
ids.forEach { errorMap[it] = ReturnedData("Unknown", "", "#222222") }
errorMap
}
}
@ -58,14 +66,17 @@ class MediaNameFetch {
val type = object : TypeToken<MediaResponse>() {}.type
return gson.fromJson(response, type)
}
data class ReturnedData(val title: String, val coverImage: String, val color: String)
data class MediaResponse(val data: Map<String, MediaItem>)
data class MediaItem(
val coverImage: MediaCoverImage,
val id: Int,
val title: MediaTitle
)
data class MediaTitle(val romaji: String)
data class MediaCoverImage(val medium: String, val color: String)
}
}

View file

@ -5,37 +5,55 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import ani.dantotsu.MainActivity
import ani.dantotsu.R
import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.settings.saving.PrefManager
import eu.kanade.tachiyomi.data.notification.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import java.io.File
import java.text.DateFormat
class NotificationWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
val scope = CoroutineScope(Dispatchers.IO)
//time in human readable format
val formattedTime = DateFormat.getDateTimeInstance().format(System.currentTimeMillis())
scope.launch {
val notifications = CommentsAPI.getNotifications()
PrefManager.init(applicationContext) //make sure prefs are initialized
val client = OkHttpClient()
CommentsAPI.fetchAuthToken(client)
val notifications = CommentsAPI.getNotifications(client)
val mediaIds = notifications?.notifications?.map { it.mediaId }
val names = MediaNameFetch.fetchMediaTitles(mediaIds ?: emptyList())
notifications?.notifications?.forEach {
val title = "New Comment Reply"
val mediaName = names[it.mediaId] ?: "Unknown"
val mediaName = names[it.mediaId]?.title ?: "Unknown"
val message = "${it.username} replied to your comment in $mediaName"
val notification = createNotification(
NotificationType.COMMENT_REPLY,
message,
title,
it.mediaId,
it.commentId
it.commentId,
names[it.mediaId]?.color ?: "#222222",
names[it.mediaId]?.coverImage ?: ""
)
if (ActivityCompat.checkSelfPermission(
@ -46,11 +64,10 @@ class NotificationWorker(appContext: Context, workerParams: WorkerParameters) :
NotificationManagerCompat.from(applicationContext)
.notify(
NotificationType.COMMENT_REPLY.id,
Notifications.ID_COMMENT_REPLY,
it.commentId,
notification
)
}
}
}
return Result.success()
@ -61,11 +78,13 @@ class NotificationWorker(appContext: Context, workerParams: WorkerParameters) :
message: String,
title: String,
mediaId: Int,
commentId: Int
commentId: Int,
color: String,
imageUrl: String
): android.app.Notification {
val notification = when (notificationType) {
NotificationType.COMMENT_REPLY -> {
val intent = Intent(applicationContext, MediaDetailsActivity::class.java).apply {
val intent = Intent(applicationContext, MainActivity::class.java).apply {
putExtra("FRAGMENT_TO_LOAD", "COMMENTS")
putExtra("mediaId", mediaId)
putExtra("commentId", commentId)
@ -80,16 +99,46 @@ class NotificationWorker(appContext: Context, workerParams: WorkerParameters) :
val builder = NotificationCompat.Builder(applicationContext, notificationType.id)
.setContentTitle(title)
.setContentText(message)
.setSmallIcon(R.drawable.ic_round_comment_24)
.setSmallIcon(R.drawable.notification_icon)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
if (imageUrl.isNotEmpty()) {
val bitmap = getBitmapFromUrl(imageUrl)
if (bitmap != null) {
builder.setLargeIcon(bitmap)
}
}
if (color.isNotEmpty()) {
builder.color = Color.parseColor(color)
}
builder.build()
}
}
return notification
}
private fun getBitmapFromVectorDrawable(context: Context, drawableId: Int): Bitmap? {
val drawable = ContextCompat.getDrawable(context, drawableId) ?: return null
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
private fun getBitmapFromUrl(url: String): Bitmap? {
return try {
val inputStream = java.net.URL(url).openStream()
BitmapFactory.decodeStream(inputStream)
} catch (e: Exception) {
null
}
}
enum class NotificationType(val id: String) {
COMMENT_REPLY(Notifications.CHANNEL_COMMENTS),
}

View file

@ -0,0 +1,10 @@
<vector android:height="200dp" android:viewportHeight="768"
android:viewportWidth="768" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<group>
<clip-path android:pathData="M-118.57,-113.45l1005.14,0l0,994.92l-1005.14,0z"/>
<path android:fillColor="#FFFFFF"
android:pathData="m-277.06,-109.04c0,87.59 70.57,158.7 158.49,161.06L-118.57,-109.02L886.57,-109.02L886.57,877L1131.17,877L1131.17,-109.04ZM886.57,877L-118.57,877l0,0.04l1005.14,0zM-118.57,877L-118.57,716c-87.89,2.36 -158.45,73.43 -158.49,161zM-118.57,716c1.49,-0.04 2.95,-0.22 4.45,-0.22L384,715.78C569.12,715.78 719.18,567.23 719.18,384.01 719.18,200.77 569.1,52.24 384,52.24L-114.12,52.24c-1.5,0 -2.96,-0.18 -4.45,-0.22l0,143.59L384,195.61c105.11,0 190.34,84.36 190.34,188.4 0,104.06 -85.23,188.4 -190.34,188.4L-118.57,572.41ZM-118.57,572.41L-118.57,195.61L-363.17,195.61l0,376.8z" android:strokeWidth="0"/>
<path android:fillColor="#FFFFFF"
android:pathData="m496.85,350.69 l-147.92,-84.53c-25.92,-14.81 -58.3,3.7 -58.3,33.32l0,169.06c0,29.62 32.4,48.13 58.3,33.32L496.85,417.33c25.92,-14.81 25.92,-51.83 0,-66.64z" android:strokeWidth="0"/>
</group>
</vector>