From 445c18fab5e37498a8694572db7b7d96ccadd366 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:04:35 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../6.json | 206 ++++++++++++++++ .../7.json | 228 ++++++++++++++++++ .../data/dto/NotificationAppDto.kt | 6 +- .../data/dto/NotificationTitleDto.kt | 8 +- .../notimanager/data/model/AppIconModel.kt | 9 +- .../data/model/NotificationIconModel.kt | 7 +- .../data/source/local/dao/AppIconDao.kt | 8 + .../data/source/local/dao/NotificationDao.kt | 46 +++- .../source/local/dao/NotificationIconDao.kt | 7 + .../source/local/database/MigrationObject.kt | 6 + .../local/database/NotiManagerDatabase.kt | 11 +- .../domain/model/NotificationApp.kt | 4 +- .../domain/model/NotificationTitle.kt | 4 +- 13 files changed, 534 insertions(+), 16 deletions(-) create mode 100644 app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/6.json create mode 100644 app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/7.json diff --git a/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/6.json b/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/6.json new file mode 100644 index 0000000..5bd7b4b --- /dev/null +++ b/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/6.json @@ -0,0 +1,206 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "276c8233ca0bab70a43013da7b6e75ca", + "entities": [ + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `appName` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `timestamp` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "appName", + "columnName": "appName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_notification_appName_title_timestamp", + "unique": false, + "columnNames": [ + "appName", + "title", + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notification_appName_title_timestamp` ON `${TABLE_NAME}` (`appName`, `title`, `timestamp`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notification_meta", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notificationId` INTEGER NOT NULL, `intentActive` INTEGER NOT NULL, `intentArray` BLOB NOT NULL, PRIMARY KEY(`notificationId`), FOREIGN KEY(`notificationId`) REFERENCES `notification`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "intentActive", + "columnName": "intentActive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "intentArray", + "columnName": "intentArray", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "notificationId" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "notification", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "notificationId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "notification_icon", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notificationId` INTEGER NOT NULL, `iconBytes` BLOB NOT NULL, `priorityActive` INTEGER NOT NULL DEFAULT 0, `priority` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`notificationId`), FOREIGN KEY(`notificationId`) REFERENCES `notification`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "iconBytes", + "columnName": "iconBytes", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "priorityActive", + "columnName": "priorityActive", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "notificationId" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "notification", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "notificationId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "app_icon", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notiAppName` TEXT NOT NULL, `iconBytes` BLOB NOT NULL, `priorityActive` INTEGER NOT NULL DEFAULT 0, `priority` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`notiAppName`))", + "fields": [ + { + "fieldPath": "notiAppName", + "columnName": "notiAppName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconBytes", + "columnName": "iconBytes", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "priorityActive", + "columnName": "priorityActive", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "notiAppName" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '276c8233ca0bab70a43013da7b6e75ca')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/7.json b/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/7.json new file mode 100644 index 0000000..9c6f87f --- /dev/null +++ b/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/7.json @@ -0,0 +1,228 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "identityHash": "ddf91ad81bbb46713142177c854d85ad", + "entities": [ + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `appName` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `timestamp` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "appName", + "columnName": "appName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_notification_appName_title_timestamp", + "unique": false, + "columnNames": [ + "appName", + "title", + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notification_appName_title_timestamp` ON `${TABLE_NAME}` (`appName`, `title`, `timestamp`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "notification_meta", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notificationId` INTEGER NOT NULL, `intentActive` INTEGER NOT NULL, `intentArray` BLOB NOT NULL, PRIMARY KEY(`notificationId`), FOREIGN KEY(`notificationId`) REFERENCES `notification`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "intentActive", + "columnName": "intentActive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "intentArray", + "columnName": "intentArray", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "notificationId" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "notification", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "notificationId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "notification_icon", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notificationId` INTEGER NOT NULL, `iconBytes` BLOB NOT NULL, `priorityActive` INTEGER NOT NULL DEFAULT 0, `priority` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`notificationId`), FOREIGN KEY(`notificationId`) REFERENCES `notification`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "iconBytes", + "columnName": "iconBytes", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "priorityActive", + "columnName": "priorityActive", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "notificationId" + ] + }, + "indices": [ + { + "name": "index_notification_icon_priorityActive_priority", + "unique": false, + "columnNames": [ + "priorityActive", + "priority" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notification_icon_priorityActive_priority` ON `${TABLE_NAME}` (`priorityActive`, `priority`)" + } + ], + "foreignKeys": [ + { + "table": "notification", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "notificationId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "app_icon", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notiAppName` TEXT NOT NULL, `iconBytes` BLOB NOT NULL, `priorityActive` INTEGER NOT NULL DEFAULT 0, `priority` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`notiAppName`))", + "fields": [ + { + "fieldPath": "notiAppName", + "columnName": "notiAppName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconBytes", + "columnName": "iconBytes", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "priorityActive", + "columnName": "priorityActive", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "notiAppName" + ] + }, + "indices": [ + { + "name": "index_app_icon_priorityActive_priority", + "unique": false, + "columnNames": [ + "priorityActive", + "priority" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_app_icon_priorityActive_priority` ON `${TABLE_NAME}` (`priorityActive`, `priority`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ddf91ad81bbb46713142177c854d85ad')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/dto/NotificationAppDto.kt b/app/src/main/java/com/example/notimanager/data/dto/NotificationAppDto.kt index 53cad80..49c238d 100644 --- a/app/src/main/java/com/example/notimanager/data/dto/NotificationAppDto.kt +++ b/app/src/main/java/com/example/notimanager/data/dto/NotificationAppDto.kt @@ -8,7 +8,9 @@ data class NotificationAppDto( val title: String, val content: String, val timestamp: Long, - val iconBytes: ByteArray + val iconBytes: ByteArray, + val priorityActive: Boolean, + val priority: Int ){ fun toDomain(): NotificationApp { return NotificationApp( @@ -17,6 +19,8 @@ data class NotificationAppDto( content = this.content, timestamp = this.timestamp, appIcon = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.size), + priorityActive = this.priorityActive, + priority = this.priority ) } } diff --git a/app/src/main/java/com/example/notimanager/data/dto/NotificationTitleDto.kt b/app/src/main/java/com/example/notimanager/data/dto/NotificationTitleDto.kt index 52073cd..b0d6f15 100644 --- a/app/src/main/java/com/example/notimanager/data/dto/NotificationTitleDto.kt +++ b/app/src/main/java/com/example/notimanager/data/dto/NotificationTitleDto.kt @@ -7,14 +7,18 @@ data class NotificationTitleDto( val title: String, val content: String, val timestamp: Long, - val iconBytes: ByteArray + val iconBytes: ByteArray, + val priorityActive: Boolean, + val priority: Int ) { fun toDomain(): NotificationTitle { return NotificationTitle( title = this.title, content = this.content, timestamp = this.timestamp, - notificationIcon = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.size) + notificationIcon = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.size), + priorityActive = this.priorityActive, + priority = this.priority ) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/model/AppIconModel.kt b/app/src/main/java/com/example/notimanager/data/model/AppIconModel.kt index 3c4b765..d8368e3 100644 --- a/app/src/main/java/com/example/notimanager/data/model/AppIconModel.kt +++ b/app/src/main/java/com/example/notimanager/data/model/AppIconModel.kt @@ -1,13 +1,18 @@ package com.example.notimanager.data.model +import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Index import androidx.room.PrimaryKey @Entity( - tableName = "app_icon" + tableName = "app_icon" , + indices = [Index(value = ["priorityActive", "priority"], unique = false)] ) data class AppIconModel( @PrimaryKey val notiAppName: String, - val iconBytes: ByteArray + val iconBytes: ByteArray, + @ColumnInfo(defaultValue = "0") val priorityActive: Boolean = false, + @ColumnInfo(defaultValue = "0") val priority: Int = 0 ) diff --git a/app/src/main/java/com/example/notimanager/data/model/NotificationIconModel.kt b/app/src/main/java/com/example/notimanager/data/model/NotificationIconModel.kt index cf2be4c..7afc3d9 100644 --- a/app/src/main/java/com/example/notimanager/data/model/NotificationIconModel.kt +++ b/app/src/main/java/com/example/notimanager/data/model/NotificationIconModel.kt @@ -1,11 +1,14 @@ package com.example.notimanager.data.model +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey +import androidx.room.Index import androidx.room.PrimaryKey @Entity( tableName = "notification_icon", + indices = [Index(value = ["priorityActive", "priority"], unique = false)], foreignKeys = [ ForeignKey( entity = NotificationModel::class, @@ -18,5 +21,7 @@ import androidx.room.PrimaryKey data class NotificationIconModel( @PrimaryKey val notificationId: Long, - val iconBytes: ByteArray + val iconBytes: ByteArray, + @ColumnInfo(defaultValue = "0") val priorityActive: Boolean = false, + @ColumnInfo(defaultValue = "0") val priority: Int = 0 ) diff --git a/app/src/main/java/com/example/notimanager/data/source/local/dao/AppIconDao.kt b/app/src/main/java/com/example/notimanager/data/source/local/dao/AppIconDao.kt index bd9dd11..aa0d566 100644 --- a/app/src/main/java/com/example/notimanager/data/source/local/dao/AppIconDao.kt +++ b/app/src/main/java/com/example/notimanager/data/source/local/dao/AppIconDao.kt @@ -3,10 +3,18 @@ package com.example.notimanager.data.source.local.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy +import androidx.room.Query import com.example.notimanager.data.model.AppIconModel @Dao interface AppIconDao { @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insert(appIconModel: AppIconModel): Long + + @Query("UPDATE app_icon SET priorityActive = 1, priority = :newPriority WHERE notiAppName = :appName") + suspend fun setPriority(appName: String, newPriority: Int): Long + + @Query("UPDATE app_icon SET priorityActive = 0, priority = 0 WHERE notiAppName = :appName") + suspend fun removePriority(appName: String): Long + } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationDao.kt b/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationDao.kt index 9b48b03..f07494d 100644 --- a/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationDao.kt +++ b/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationDao.kt @@ -16,10 +16,45 @@ interface NotificationDao { @Query( """ - SELECT n1.appName, n1.title, n1.content, n1.timestamp, ai.iconBytes + SELECT n1.appName, n1.title, n1.content, n1.timestamp, ai.iconBytes , ai.priorityActive, ai.priority FROM notification AS n1 INNER JOIN app_icon AS ai ON n1.appName = ai.notiAppName - WHERE timestamp = ( + WHERE ai.priorityActive = 1 + AND timestamp = ( + SELECT MAX(timestamp) + FROM notification AS n2 + WHERE n1.appName = n2.appName + ) + ORDER BY ai.priority ASC + """ + ) + suspend fun getNotificationAppPriorityList(): List + + @Query( + """ + SELECT n1.title, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority + FROM notification AS n1 + INNER JOIN notification_icon AS ni ON n1.id = ni.notificationId + WHERE ni.priorityActive = 1 + AND n1.appName = :appName + AND n1.title = :title + AND timestamp = ( + SELECT MAX(timestamp) + FROM notification AS n2 + WHERE n1.appName = n2.appName AND n1.title = n2.title + ) + ORDER BY ni.priority ASC + """ + ) + suspend fun getNotificationTitlePriorityList(appName: String, title: String): List + + @Query( + """ + SELECT n1.appName, n1.title, n1.content, n1.timestamp, ai.iconBytes, ai.priorityActive, ai.priority + FROM notification AS n1 + INNER JOIN app_icon AS ai ON n1.appName = ai.notiAppName + WHERE ai.priorityActive = 0 + AND timestamp = ( SELECT MAX(timestamp) FROM notification AS n2 WHERE n1.appName = n2.appName @@ -31,10 +66,13 @@ interface NotificationDao { @Query( """ - SELECT n1.title, n1.content, n1.timestamp, ni.iconBytes + SELECT n1.title, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority FROM notification AS n1 INNER JOIN notification_icon AS ni ON n1.id = ni.notificationId - WHERE n1.appName = :appName AND n1.title = :title AND timestamp = ( + WHERE ni.priorityActive = 0 + AND n1.appName = :appName + AND n1.title = :title + AND timestamp = ( SELECT MAX(timestamp) FROM notification AS n2 WHERE n1.appName = n2.appName AND n1.title = n2.title diff --git a/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationIconDao.kt b/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationIconDao.kt index 6a8fa55..bda9a15 100644 --- a/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationIconDao.kt +++ b/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationIconDao.kt @@ -3,10 +3,17 @@ package com.example.notimanager.data.source.local.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy +import androidx.room.Query import com.example.notimanager.data.model.NotificationIconModel @Dao interface NotificationIconDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(notificationIconModel: NotificationIconModel): Long + + @Query("UPDATE notification_icon SET priorityActive = 1, priority = :newPriority WHERE notificationId = :notificationId") + suspend fun setPriority(notificationId: Long, newPriority: Int): Long + + @Query("UPDATE notification_icon SET priorityActive = 0, priority = 0 WHERE notificationId = :notificationId") + suspend fun removePriority(notificationId: Long): Long } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/source/local/database/MigrationObject.kt b/app/src/main/java/com/example/notimanager/data/source/local/database/MigrationObject.kt index 7e5833c..e409fb3 100644 --- a/app/src/main/java/com/example/notimanager/data/source/local/database/MigrationObject.kt +++ b/app/src/main/java/com/example/notimanager/data/source/local/database/MigrationObject.kt @@ -15,4 +15,10 @@ object MigrationObject { db.execSQL("ALTER TABLE app_icon RENAME COLUMN appIconResId iconBytes") } } + val MIGRATION_6_7 = object : Migration(6, 7) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE INDEX index_notification_icon_priorityActive_priority ON notification_icon (priorityActive, priority);") + db.execSQL("CREATE INDEX index_app_icon_priorityActive_priority ON app_icon (priorityActive, priority);") + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/source/local/database/NotiManagerDatabase.kt b/app/src/main/java/com/example/notimanager/data/source/local/database/NotiManagerDatabase.kt index 81e1802..d16c3c3 100644 --- a/app/src/main/java/com/example/notimanager/data/source/local/database/NotiManagerDatabase.kt +++ b/app/src/main/java/com/example/notimanager/data/source/local/database/NotiManagerDatabase.kt @@ -14,9 +14,10 @@ import com.example.notimanager.data.source.local.dao.NotificationDao import com.example.notimanager.data.source.local.dao.NotificationIconDao import com.example.notimanager.data.source.local.dao.NotificationMetaDao import com.example.notimanager.data.source.local.database.MigrationObject.MIGRATION_3_4 +import com.example.notimanager.data.source.local.database.MigrationObject.MIGRATION_6_7 @Database( - version = 5, + version = 7, entities = [ NotificationModel::class, @@ -24,9 +25,9 @@ import com.example.notimanager.data.source.local.database.MigrationObject.MIGRAT NotificationIconModel::class, AppIconModel::class ], - autoMigrations = [ - AutoMigration (from = 4, to = 5) - ] +// autoMigrations = [ +// AutoMigration (from = 6, to = 7) +// ] ) abstract class NotiManagerDatabase : RoomDatabase() { abstract fun notificationDao(): NotificationDao @@ -47,7 +48,9 @@ abstract class NotiManagerDatabase : RoomDatabase() { ) .addMigrations( MIGRATION_3_4, + MIGRATION_6_7 ) + .fallbackToDestructiveMigration() .build() INSTANCE = instance instance diff --git a/app/src/main/java/com/example/notimanager/domain/model/NotificationApp.kt b/app/src/main/java/com/example/notimanager/domain/model/NotificationApp.kt index a9316a3..a3b4a74 100644 --- a/app/src/main/java/com/example/notimanager/domain/model/NotificationApp.kt +++ b/app/src/main/java/com/example/notimanager/domain/model/NotificationApp.kt @@ -7,5 +7,7 @@ data class NotificationApp( val title: String, val content: String, val timestamp: Long, - val appIcon: Bitmap? + val appIcon: Bitmap?, + val priorityActive: Boolean, + val priority: Int ) diff --git a/app/src/main/java/com/example/notimanager/domain/model/NotificationTitle.kt b/app/src/main/java/com/example/notimanager/domain/model/NotificationTitle.kt index e15c530..c872ad4 100644 --- a/app/src/main/java/com/example/notimanager/domain/model/NotificationTitle.kt +++ b/app/src/main/java/com/example/notimanager/domain/model/NotificationTitle.kt @@ -6,5 +6,7 @@ data class NotificationTitle( val title: String, val content: String, val timestamp: Long, - val notificationIcon: Bitmap? + val notificationIcon: Bitmap?, + val priorityActive: Boolean, + val priority: Int ) From b2c2d25fbcb915622d530cad345ddfe0bf2a3f1c Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:11:35 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20rep?= =?UTF-8?q?ository=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20UseCase=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App, title, 알림에 대해 SRP 적용하여, 각 클래스를 담당하도록 분리. 우선순위를 적절히 바꿀 수 있는 useCase 추가 --- .../repository/NotificationAppRepository.kt | 38 ++++++++++++++++ .../NotificationDomainRepository.kt | 19 ++++++++ .../NotificationRepositoryDomain.kt | 39 ---------------- .../repository/NotificationTitleRepository.kt | 44 +++++++++++++++++++ .../NotificationAppRepositoryInterface.kt | 10 +++++ .../NotificationDomainRepositoryInterface.kt | 7 +++ ...tificationPermissionRepositoryInterface.kt | 2 - .../NotificationRepositoryDomainInterface.kt | 11 ----- .../NotificationTitleRepositoryInterface.kt | 10 +++++ .../domain/usecase/NotificationAppUseCase.kt | 21 ++++++++- .../usecase/NotificationTitleUseCase.kt | 29 ++++++++++-- .../domain/usecase/NotificationUseCase.kt | 4 +- 12 files changed, 175 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/com/example/notimanager/data/repository/NotificationAppRepository.kt create mode 100644 app/src/main/java/com/example/notimanager/data/repository/NotificationDomainRepository.kt delete mode 100644 app/src/main/java/com/example/notimanager/data/repository/NotificationRepositoryDomain.kt create mode 100644 app/src/main/java/com/example/notimanager/data/repository/NotificationTitleRepository.kt create mode 100644 app/src/main/java/com/example/notimanager/domain/repository/NotificationAppRepositoryInterface.kt create mode 100644 app/src/main/java/com/example/notimanager/domain/repository/NotificationDomainRepositoryInterface.kt delete mode 100644 app/src/main/java/com/example/notimanager/domain/repository/NotificationRepositoryDomainInterface.kt create mode 100644 app/src/main/java/com/example/notimanager/domain/repository/NotificationTitleRepositoryInterface.kt diff --git a/app/src/main/java/com/example/notimanager/data/repository/NotificationAppRepository.kt b/app/src/main/java/com/example/notimanager/data/repository/NotificationAppRepository.kt new file mode 100644 index 0000000..d1e3ba0 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/data/repository/NotificationAppRepository.kt @@ -0,0 +1,38 @@ +package com.example.notimanager.data.repository + +import com.example.notimanager.data.source.local.dao.AppIconDao +import com.example.notimanager.data.source.local.dao.NotificationDao +import com.example.notimanager.domain.model.NotificationApp +import com.example.notimanager.domain.repository.NotificationAppRepositoryInterface + +class NotificationAppRepository( + private val notificationDao: NotificationDao, + private val appIconDao: AppIconDao +) : NotificationAppRepositoryInterface { + override suspend fun getNotificationAppList(): List{ + return notificationDao.getNotificationAppList() + .asSequence() + .map { it.toDomain() } + .toList() + } + + override suspend fun getNotificationAppPriorityList(): List { + return notificationDao.getNotificationAppPriorityList() + .asSequence() + .map { it.toDomain() } + .toList() + } + + override suspend fun setAppPriority( + appName: String, + newPriority: Int + ): Long { + return appIconDao.setPriority(appName, newPriority) + } + + override suspend fun removeAppPriority( + appName: String, + ): Long { + return appIconDao.removePriority(appName) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/repository/NotificationDomainRepository.kt b/app/src/main/java/com/example/notimanager/data/repository/NotificationDomainRepository.kt new file mode 100644 index 0000000..1b2c63f --- /dev/null +++ b/app/src/main/java/com/example/notimanager/data/repository/NotificationDomainRepository.kt @@ -0,0 +1,19 @@ +package com.example.notimanager.data.repository + +import com.example.notimanager.data.source.local.dao.NotificationDao +import com.example.notimanager.domain.model.Notification +import com.example.notimanager.domain.repository.NotificationDomainRepositoryInterface + +class NotificationDomainRepository( + private val notificationDao: NotificationDao +) : NotificationDomainRepositoryInterface { + override suspend fun getNotificationList( + appName: String, + title: String + ): List{ + return notificationDao.getNotificationList(appName, title) + .asSequence() + .map { it.toDomain() } + .toList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/repository/NotificationRepositoryDomain.kt b/app/src/main/java/com/example/notimanager/data/repository/NotificationRepositoryDomain.kt deleted file mode 100644 index 18e67eb..0000000 --- a/app/src/main/java/com/example/notimanager/data/repository/NotificationRepositoryDomain.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.notimanager.data.repository - -import com.example.notimanager.data.source.local.dao.NotificationDao -import com.example.notimanager.domain.model.Notification -import com.example.notimanager.domain.model.NotificationApp -import com.example.notimanager.domain.model.NotificationTitle -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface - -class NotificationRepositoryDomain( - private val notificationDao: NotificationDao -) : NotificationRepositoryDomainInterface { - override suspend fun getNotificationAppList(): List{ - return notificationDao.getNotificationAppList() - .asSequence() - .map { it.toDomain() } - .toList() - } - - override suspend fun getNotificationTitleList( - appName: String, - title: String - ): List{ - return notificationDao.getNotificationTitleList(appName, title) - .asSequence() - .map { it.toDomain() } - .toList() - } - - override suspend fun getNotificationList( - appName: String, - title: String - ): List{ - return notificationDao.getNotificationList(appName, title) - .asSequence() - .map { it.toDomain() } - .toList() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/repository/NotificationTitleRepository.kt b/app/src/main/java/com/example/notimanager/data/repository/NotificationTitleRepository.kt new file mode 100644 index 0000000..95c8ee3 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/data/repository/NotificationTitleRepository.kt @@ -0,0 +1,44 @@ +package com.example.notimanager.data.repository + +import com.example.notimanager.data.source.local.dao.NotificationDao +import com.example.notimanager.data.source.local.dao.NotificationIconDao +import com.example.notimanager.domain.model.NotificationTitle +import com.example.notimanager.domain.repository.NotificationTitleRepositoryInterface + +class NotificationTitleRepository( + private val notificationDao: NotificationDao, + private val notificationIconDao: NotificationIconDao +) : NotificationTitleRepositoryInterface { + override suspend fun getNotificationTitleList( + appName: String, + title: String + ): List{ + return notificationDao.getNotificationTitleList(appName, title) + .asSequence() + .map { it.toDomain() } + .toList() + } + + override suspend fun getNotificationTitlePriorityList( + appName: String, + title: String + ): List { + return notificationDao.getNotificationTitlePriorityList(appName, title) + .asSequence() + .map { it.toDomain() } + .toList() + } + override suspend fun setTitlePriority( + notificationId: Long, + newPriority: Int + ): Long{ + return notificationIconDao.setPriority(notificationId, newPriority) + } + + override suspend fun removeTitlePriority( + notificationId: Long + ):Long{ + return notificationIconDao.removePriority(notificationId) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/repository/NotificationAppRepositoryInterface.kt b/app/src/main/java/com/example/notimanager/domain/repository/NotificationAppRepositoryInterface.kt new file mode 100644 index 0000000..782181e --- /dev/null +++ b/app/src/main/java/com/example/notimanager/domain/repository/NotificationAppRepositoryInterface.kt @@ -0,0 +1,10 @@ +package com.example.notimanager.domain.repository + +import com.example.notimanager.domain.model.NotificationApp + +interface NotificationAppRepositoryInterface { + suspend fun getNotificationAppList(): List + suspend fun getNotificationAppPriorityList(): List + suspend fun setAppPriority(appName: String, newPriority: Int): Long + suspend fun removeAppPriority(appName: String): Long +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/repository/NotificationDomainRepositoryInterface.kt b/app/src/main/java/com/example/notimanager/domain/repository/NotificationDomainRepositoryInterface.kt new file mode 100644 index 0000000..a77625e --- /dev/null +++ b/app/src/main/java/com/example/notimanager/domain/repository/NotificationDomainRepositoryInterface.kt @@ -0,0 +1,7 @@ +package com.example.notimanager.domain.repository + +import com.example.notimanager.domain.model.Notification + +interface NotificationDomainRepositoryInterface { + suspend fun getNotificationList(appName: String, title: String): List +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/repository/NotificationPermissionRepositoryInterface.kt b/app/src/main/java/com/example/notimanager/domain/repository/NotificationPermissionRepositoryInterface.kt index ec81375..e43f19c 100644 --- a/app/src/main/java/com/example/notimanager/domain/repository/NotificationPermissionRepositoryInterface.kt +++ b/app/src/main/java/com/example/notimanager/domain/repository/NotificationPermissionRepositoryInterface.kt @@ -1,7 +1,5 @@ package com.example.notimanager.domain.repository -import android.content.Context - interface NotificationPermissionRepositoryInterface { fun isNotificationServiceEnabled(): Boolean fun requestPermission() diff --git a/app/src/main/java/com/example/notimanager/domain/repository/NotificationRepositoryDomainInterface.kt b/app/src/main/java/com/example/notimanager/domain/repository/NotificationRepositoryDomainInterface.kt deleted file mode 100644 index dbc68c1..0000000 --- a/app/src/main/java/com/example/notimanager/domain/repository/NotificationRepositoryDomainInterface.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.notimanager.domain.repository - -import com.example.notimanager.domain.model.Notification -import com.example.notimanager.domain.model.NotificationApp -import com.example.notimanager.domain.model.NotificationTitle - -interface NotificationRepositoryDomainInterface { - suspend fun getNotificationAppList(): List - suspend fun getNotificationTitleList(appName: String, title: String): List - suspend fun getNotificationList(appName: String, title: String): List -} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/repository/NotificationTitleRepositoryInterface.kt b/app/src/main/java/com/example/notimanager/domain/repository/NotificationTitleRepositoryInterface.kt new file mode 100644 index 0000000..8ae1807 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/domain/repository/NotificationTitleRepositoryInterface.kt @@ -0,0 +1,10 @@ +package com.example.notimanager.domain.repository + +import com.example.notimanager.domain.model.NotificationTitle + +interface NotificationTitleRepositoryInterface { + suspend fun getNotificationTitleList(appName: String, title: String): List + suspend fun getNotificationTitlePriorityList(appName: String, title: String): List + suspend fun setTitlePriority(notificationId: Long, newPriority: Int): Long + suspend fun removeTitlePriority(notificationId: Long): Long +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationAppUseCase.kt b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationAppUseCase.kt index 12df380..3cef3aa 100644 --- a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationAppUseCase.kt +++ b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationAppUseCase.kt @@ -1,12 +1,29 @@ package com.example.notimanager.domain.usecase import com.example.notimanager.domain.model.NotificationApp -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface +import com.example.notimanager.domain.repository.NotificationAppRepositoryInterface class NotificationAppUseCase( - private val repository: NotificationRepositoryDomainInterface + private val repository: NotificationAppRepositoryInterface ){ suspend fun getNotificationAppList(): List{ return repository.getNotificationAppList() } + + suspend fun getNotificationAppPriorityList(): List{ + return repository.getNotificationAppPriorityList() + } + + suspend fun setAppPriority( + appName: String, + newPriority: Int + ): Long { + return repository.setAppPriority(appName, newPriority) + } + + suspend fun removeTitlePriority( + appName: String, + ): Long { + return repository.removeAppPriority(appName) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationTitleUseCase.kt b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationTitleUseCase.kt index 75657c2..471d34b 100644 --- a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationTitleUseCase.kt +++ b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationTitleUseCase.kt @@ -1,12 +1,35 @@ package com.example.notimanager.domain.usecase import com.example.notimanager.domain.model.NotificationTitle -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface +import com.example.notimanager.domain.repository.NotificationTitleRepositoryInterface class NotificationTitleUseCase( - private val repository: NotificationRepositoryDomainInterface + private val repository: NotificationTitleRepositoryInterface ){ - suspend fun getNotificationTitleList(appName: String, title: String): List{ + suspend fun getNotificationTitleList( + appName: String, + title: String + ): List{ return repository.getNotificationTitleList(appName, title) } + + suspend fun getNotificationTitlePriorityList( + appName: String, + title: String + ): List { + return repository.getNotificationTitlePriorityList(appName, title) + } + + suspend fun setTitlePriority( + notificationId: Long, + newPriority: Int + ): Long { + return repository.setTitlePriority(notificationId, newPriority) + } + + suspend fun removeTitlePriority( + notificationId: Long, + ): Long { + return repository.removeTitlePriority(notificationId) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationUseCase.kt b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationUseCase.kt index c7ec2a0..e96cb5a 100644 --- a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationUseCase.kt +++ b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationUseCase.kt @@ -1,10 +1,10 @@ package com.example.notimanager.domain.usecase import com.example.notimanager.domain.model.Notification -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface +import com.example.notimanager.domain.repository.NotificationDomainRepositoryInterface class NotificationUseCase( - private val repository: NotificationRepositoryDomainInterface + private val repository: NotificationDomainRepositoryInterface ){ suspend fun getNotificationList(appName: String, title: String): List{ return repository.getNotificationList(appName, title) From 22dbfc6c4d7d26937714a0fef62bf8a37cf5b023 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:13:33 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20int?= =?UTF-8?q?erface=EC=97=90=20=EB=A7=9E=EA=B2=8C=20hilt=20=EC=9E=AC?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notimanager/di/RepositoryModule.kt | 32 ++++++++++++++++--- .../example/notimanager/di/UseCaseModule.kt | 14 ++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/example/notimanager/di/RepositoryModule.kt b/app/src/main/java/com/example/notimanager/di/RepositoryModule.kt index b8955bc..bba488e 100644 --- a/app/src/main/java/com/example/notimanager/di/RepositoryModule.kt +++ b/app/src/main/java/com/example/notimanager/di/RepositoryModule.kt @@ -1,16 +1,20 @@ package com.example.notimanager.di import android.content.Context +import com.example.notimanager.data.repository.NotificationAppRepository import com.example.notimanager.data.repository.NotificationPermissionRepository import com.example.notimanager.data.repository.NotificationRepository -import com.example.notimanager.data.repository.NotificationRepositoryDomain +import com.example.notimanager.data.repository.NotificationDomainRepository import com.example.notimanager.data.repository.NotificationRepositoryInterface +import com.example.notimanager.data.repository.NotificationTitleRepository import com.example.notimanager.data.source.local.dao.AppIconDao import com.example.notimanager.data.source.local.dao.NotificationDao import com.example.notimanager.data.source.local.dao.NotificationIconDao import com.example.notimanager.data.source.local.dao.NotificationMetaDao +import com.example.notimanager.domain.repository.NotificationAppRepositoryInterface import com.example.notimanager.domain.repository.NotificationPermissionRepositoryInterface -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface +import com.example.notimanager.domain.repository.NotificationDomainRepositoryInterface +import com.example.notimanager.domain.repository.NotificationTitleRepositoryInterface import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -31,10 +35,10 @@ class RepositoryModule { @Provides @Singleton - fun provideNotificationRepositoryDomain( + fun provideNotificationDomainRepository( notificationDao: NotificationDao - ): NotificationRepositoryDomainInterface { - return NotificationRepositoryDomain(notificationDao) + ): NotificationDomainRepositoryInterface { + return NotificationDomainRepository(notificationDao) } @Provides @@ -47,4 +51,22 @@ class RepositoryModule { ): NotificationRepositoryInterface { return NotificationRepository(notificationDao, notificationMetaDao, notificationIconDao, appIconDao) } + + @Provides + @Singleton + fun provideNotificationAppRepository( + notificationDao: NotificationDao, + appIconDao: AppIconDao + ): NotificationAppRepositoryInterface { + return NotificationAppRepository(notificationDao, appIconDao) + } + + @Provides + @Singleton + fun provideNotificationTitleRepository( + notificationDao: NotificationDao, + notificationIconDao: NotificationIconDao + ): NotificationTitleRepositoryInterface { + return NotificationTitleRepository(notificationDao, notificationIconDao) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/di/UseCaseModule.kt b/app/src/main/java/com/example/notimanager/di/UseCaseModule.kt index 96fee5a..eb505c1 100644 --- a/app/src/main/java/com/example/notimanager/di/UseCaseModule.kt +++ b/app/src/main/java/com/example/notimanager/di/UseCaseModule.kt @@ -1,7 +1,9 @@ package com.example.notimanager.di +import com.example.notimanager.domain.repository.NotificationAppRepositoryInterface import com.example.notimanager.domain.repository.NotificationPermissionRepositoryInterface -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface +import com.example.notimanager.domain.repository.NotificationDomainRepositoryInterface +import com.example.notimanager.domain.repository.NotificationTitleRepositoryInterface import com.example.notimanager.domain.usecase.NotificationAppUseCase import com.example.notimanager.domain.usecase.NotificationPermissionUseCase import com.example.notimanager.domain.usecase.NotificationTitleUseCase @@ -26,7 +28,7 @@ class UseCaseModule { @Provides @Singleton fun provideNotificationUseCase( - notificationRepository: NotificationRepositoryDomainInterface + notificationRepository: NotificationDomainRepositoryInterface ): NotificationUseCase { return NotificationUseCase(notificationRepository) } @@ -34,16 +36,16 @@ class UseCaseModule { @Provides @Singleton fun provideNotificationAppUseCase( - notificationRepository: NotificationRepositoryDomainInterface + notificationAppRepository: NotificationAppRepositoryInterface ): NotificationAppUseCase { - return NotificationAppUseCase(notificationRepository) + return NotificationAppUseCase(notificationAppRepository) } @Provides @Singleton fun provideNotificationTitleUseCase( - notificationRepository: NotificationRepositoryDomainInterface + notificationTitleRepository: NotificationTitleRepositoryInterface ): NotificationTitleUseCase { - return NotificationTitleUseCase(notificationRepository) + return NotificationTitleUseCase(notificationTitleRepository) } } \ No newline at end of file From 08176a2e32010d2ab3d716b6211d7a54b2f274c0 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:36:48 +0900 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20update=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EB=A6=AC=ED=84=B4=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update 쿼리는 업데이트 성공한 행 수를 Int로 반환함. 따라서 Long -> Int로 수정 --- .../data/repository/NotificationAppRepository.kt | 4 ++-- .../data/repository/NotificationTitleRepository.kt | 4 ++-- .../notimanager/data/source/local/dao/AppIconDao.kt | 4 ++-- .../notimanager/data/source/local/dao/NotificationDao.kt | 4 ++-- .../data/source/local/dao/NotificationIconDao.kt | 4 ++-- .../repository/NotificationAppRepositoryInterface.kt | 4 ++-- .../repository/NotificationTitleRepositoryInterface.kt | 4 ++-- .../notimanager/domain/usecase/NotificationAppUseCase.kt | 8 ++++---- .../domain/usecase/NotificationTitleUseCase.kt | 4 ++-- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/example/notimanager/data/repository/NotificationAppRepository.kt b/app/src/main/java/com/example/notimanager/data/repository/NotificationAppRepository.kt index d1e3ba0..b83af97 100644 --- a/app/src/main/java/com/example/notimanager/data/repository/NotificationAppRepository.kt +++ b/app/src/main/java/com/example/notimanager/data/repository/NotificationAppRepository.kt @@ -26,13 +26,13 @@ class NotificationAppRepository( override suspend fun setAppPriority( appName: String, newPriority: Int - ): Long { + ): Int { return appIconDao.setPriority(appName, newPriority) } override suspend fun removeAppPriority( appName: String, - ): Long { + ): Int { return appIconDao.removePriority(appName) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/repository/NotificationTitleRepository.kt b/app/src/main/java/com/example/notimanager/data/repository/NotificationTitleRepository.kt index 95c8ee3..79bdc81 100644 --- a/app/src/main/java/com/example/notimanager/data/repository/NotificationTitleRepository.kt +++ b/app/src/main/java/com/example/notimanager/data/repository/NotificationTitleRepository.kt @@ -31,13 +31,13 @@ class NotificationTitleRepository( override suspend fun setTitlePriority( notificationId: Long, newPriority: Int - ): Long{ + ): Int{ return notificationIconDao.setPriority(notificationId, newPriority) } override suspend fun removeTitlePriority( notificationId: Long - ):Long{ + ):Int{ return notificationIconDao.removePriority(notificationId) } diff --git a/app/src/main/java/com/example/notimanager/data/source/local/dao/AppIconDao.kt b/app/src/main/java/com/example/notimanager/data/source/local/dao/AppIconDao.kt index aa0d566..10b3ff1 100644 --- a/app/src/main/java/com/example/notimanager/data/source/local/dao/AppIconDao.kt +++ b/app/src/main/java/com/example/notimanager/data/source/local/dao/AppIconDao.kt @@ -12,9 +12,9 @@ interface AppIconDao { suspend fun insert(appIconModel: AppIconModel): Long @Query("UPDATE app_icon SET priorityActive = 1, priority = :newPriority WHERE notiAppName = :appName") - suspend fun setPriority(appName: String, newPriority: Int): Long + suspend fun setPriority(appName: String, newPriority: Int): Int @Query("UPDATE app_icon SET priorityActive = 0, priority = 0 WHERE notiAppName = :appName") - suspend fun removePriority(appName: String): Long + suspend fun removePriority(appName: String): Int } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationDao.kt b/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationDao.kt index f07494d..9f973e6 100644 --- a/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationDao.kt +++ b/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationDao.kt @@ -32,7 +32,7 @@ interface NotificationDao { @Query( """ - SELECT n1.title, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority + SELECT n1.id, n1.title, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority FROM notification AS n1 INNER JOIN notification_icon AS ni ON n1.id = ni.notificationId WHERE ni.priorityActive = 1 @@ -66,7 +66,7 @@ interface NotificationDao { @Query( """ - SELECT n1.title, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority + SELECT n1.id, n1.title, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority FROM notification AS n1 INNER JOIN notification_icon AS ni ON n1.id = ni.notificationId WHERE ni.priorityActive = 0 diff --git a/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationIconDao.kt b/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationIconDao.kt index bda9a15..809614f 100644 --- a/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationIconDao.kt +++ b/app/src/main/java/com/example/notimanager/data/source/local/dao/NotificationIconDao.kt @@ -12,8 +12,8 @@ interface NotificationIconDao { suspend fun insert(notificationIconModel: NotificationIconModel): Long @Query("UPDATE notification_icon SET priorityActive = 1, priority = :newPriority WHERE notificationId = :notificationId") - suspend fun setPriority(notificationId: Long, newPriority: Int): Long + suspend fun setPriority(notificationId: Long, newPriority: Int): Int @Query("UPDATE notification_icon SET priorityActive = 0, priority = 0 WHERE notificationId = :notificationId") - suspend fun removePriority(notificationId: Long): Long + suspend fun removePriority(notificationId: Long): Int } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/repository/NotificationAppRepositoryInterface.kt b/app/src/main/java/com/example/notimanager/domain/repository/NotificationAppRepositoryInterface.kt index 782181e..50ca889 100644 --- a/app/src/main/java/com/example/notimanager/domain/repository/NotificationAppRepositoryInterface.kt +++ b/app/src/main/java/com/example/notimanager/domain/repository/NotificationAppRepositoryInterface.kt @@ -5,6 +5,6 @@ import com.example.notimanager.domain.model.NotificationApp interface NotificationAppRepositoryInterface { suspend fun getNotificationAppList(): List suspend fun getNotificationAppPriorityList(): List - suspend fun setAppPriority(appName: String, newPriority: Int): Long - suspend fun removeAppPriority(appName: String): Long + suspend fun setAppPriority(appName: String, newPriority: Int): Int + suspend fun removeAppPriority(appName: String): Int } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/repository/NotificationTitleRepositoryInterface.kt b/app/src/main/java/com/example/notimanager/domain/repository/NotificationTitleRepositoryInterface.kt index 8ae1807..0f82246 100644 --- a/app/src/main/java/com/example/notimanager/domain/repository/NotificationTitleRepositoryInterface.kt +++ b/app/src/main/java/com/example/notimanager/domain/repository/NotificationTitleRepositoryInterface.kt @@ -5,6 +5,6 @@ import com.example.notimanager.domain.model.NotificationTitle interface NotificationTitleRepositoryInterface { suspend fun getNotificationTitleList(appName: String, title: String): List suspend fun getNotificationTitlePriorityList(appName: String, title: String): List - suspend fun setTitlePriority(notificationId: Long, newPriority: Int): Long - suspend fun removeTitlePriority(notificationId: Long): Long + suspend fun setTitlePriority(notificationId: Long, newPriority: Int): Int + suspend fun removeTitlePriority(notificationId: Long): Int } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationAppUseCase.kt b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationAppUseCase.kt index 3cef3aa..d0504d0 100644 --- a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationAppUseCase.kt +++ b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationAppUseCase.kt @@ -13,17 +13,17 @@ class NotificationAppUseCase( suspend fun getNotificationAppPriorityList(): List{ return repository.getNotificationAppPriorityList() } - + suspend fun setAppPriority( appName: String, newPriority: Int - ): Long { + ): Int { return repository.setAppPriority(appName, newPriority) } - suspend fun removeTitlePriority( + suspend fun removeAppPriority( appName: String, - ): Long { + ): Int { return repository.removeAppPriority(appName) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationTitleUseCase.kt b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationTitleUseCase.kt index 471d34b..c725722 100644 --- a/app/src/main/java/com/example/notimanager/domain/usecase/NotificationTitleUseCase.kt +++ b/app/src/main/java/com/example/notimanager/domain/usecase/NotificationTitleUseCase.kt @@ -23,13 +23,13 @@ class NotificationTitleUseCase( suspend fun setTitlePriority( notificationId: Long, newPriority: Int - ): Long { + ): Int { return repository.setTitlePriority(notificationId, newPriority) } suspend fun removeTitlePriority( notificationId: Long, - ): Long { + ): Int { return repository.removeTitlePriority(notificationId) } } \ No newline at end of file From 8f915a1573ec8e2fb42c27b2df1c508ad7da1c2a Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:38:53 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20add=20=EB=8D=94=EB=B3=B4=EA=B8=B0?= =?UTF-8?q?=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EB=AA=A8=EB=8B=AC=EC=B0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 앱, 제목 리스트에서 더보기 클릭 시 하단을 채우는 BottomSheet 적용. 해당 칸을 채우는 BoxView 추가. --- .../presentation/ui/component/BoxView.kt | 26 ++++++++++++++ .../presentation/ui/component/ModalView.kt | 35 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 app/src/main/java/com/example/notimanager/presentation/ui/component/BoxView.kt create mode 100644 app/src/main/java/com/example/notimanager/presentation/ui/component/ModalView.kt diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/component/BoxView.kt b/app/src/main/java/com/example/notimanager/presentation/ui/component/BoxView.kt new file mode 100644 index 0000000..75f13c4 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/ui/component/BoxView.kt @@ -0,0 +1,26 @@ +package com.example.notimanager.presentation.ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun ClickableTextView(text: String, onClick: () -> Unit) { + Box( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(16.dp) + ) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium + ) + } +} diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/component/ModalView.kt b/app/src/main/java/com/example/notimanager/presentation/ui/component/ModalView.kt new file mode 100644 index 0000000..d1ae502 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/ui/component/ModalView.kt @@ -0,0 +1,35 @@ +package com.example.notimanager.presentation.ui.component + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BottomSheet( + isVisible: Boolean, + onDismiss: () -> Unit, + content: @Composable () -> Unit +) { + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() + + LaunchedEffect(isVisible) { + if (isVisible) { + scope.launch { sheetState.show() } + } else { + scope.launch { sheetState.hide() } + } + } + + ModalBottomSheet( + onDismissRequest = onDismiss, + sheetState = sheetState + ) { + content() + } +} \ No newline at end of file From df41958337f5745f211347c6425e4cee8c0c0433 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:40:31 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=EC=95=B1=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../state/NotificationAppPriorityState.kt | 9 ++ .../NotificationAppPriorityViewModel.kt | 51 +++++++++ .../viewmodel/NotificationAppViewModel.kt | 8 ++ .../ui/component/NotificationAppListView.kt | 102 +++++++++++++++--- 4 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/example/notimanager/presentation/stateholder/state/NotificationAppPriorityState.kt create mode 100644 app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationAppPriorityViewModel.kt diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/state/NotificationAppPriorityState.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/state/NotificationAppPriorityState.kt new file mode 100644 index 0000000..e4c2bb7 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/state/NotificationAppPriorityState.kt @@ -0,0 +1,9 @@ +package com.example.notimanager.presentation.stateholder.state + +import com.example.notimanager.domain.model.NotificationApp + +data class NotificationAppPriorityState( + val notificationAppList: List = emptyList(), + val isLoading: Boolean = false, + val error: String? = null +) diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationAppPriorityViewModel.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationAppPriorityViewModel.kt new file mode 100644 index 0000000..35de140 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationAppPriorityViewModel.kt @@ -0,0 +1,51 @@ +package com.example.notimanager.presentation.stateholder.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.notimanager.domain.usecase.NotificationAppUseCase +import com.example.notimanager.presentation.stateholder.state.NotificationAppPriorityState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class NotificationAppPriorityViewModel @Inject constructor( + private val notificationAppUseCase: NotificationAppUseCase +): ViewModel() { + private val _notificationAppPriorityState = MutableLiveData() + val notificationAppPriorityState: LiveData get() = _notificationAppPriorityState + + init { + loadNotificationAppPriority() + } + + fun loadNotificationAppPriority() { + viewModelScope.launch { + _notificationAppPriorityState.value = NotificationAppPriorityState(isLoading = true) + + try { + val notificationApps = notificationAppUseCase.getNotificationAppPriorityList() + _notificationAppPriorityState.value = NotificationAppPriorityState(notificationAppList = notificationApps) + } catch (e: Exception) { + _notificationAppPriorityState.value = NotificationAppPriorityState(error = e.message) + } + } + } + + fun getLength(): Int{ + if (notificationAppPriorityState.value != null){ + return notificationAppPriorityState.value!!.notificationAppList.size + } + return 0 + } + + fun removeAppPriority(appName: String, onComplete: () -> Unit) { + viewModelScope.launch { + notificationAppUseCase.removeAppPriority(appName) + loadNotificationAppPriority() + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationAppViewModel.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationAppViewModel.kt index 10d125a..9414855 100644 --- a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationAppViewModel.kt +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationAppViewModel.kt @@ -33,4 +33,12 @@ class NotificationAppViewModel @Inject constructor( } } } + + fun setAppPriority(appName: String, newPriority: Int = 0, onComplete: () -> Unit) { + viewModelScope.launch { + notificationAppUseCase.setAppPriority(appName, newPriority) + loadNotificationApps() + onComplete() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/component/NotificationAppListView.kt b/app/src/main/java/com/example/notimanager/presentation/ui/component/NotificationAppListView.kt index 4c37f74..0f036ac 100644 --- a/app/src/main/java/com/example/notimanager/presentation/ui/component/NotificationAppListView.kt +++ b/app/src/main/java/com/example/notimanager/presentation/ui/component/NotificationAppListView.kt @@ -10,13 +10,19 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.text.BasicText +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -24,34 +30,49 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.max import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.example.notimanager.common.objects.DateFormatter.formatTimestamp import com.example.notimanager.domain.model.NotificationApp +import com.example.notimanager.presentation.stateholder.state.NotificationAppPriorityState import com.example.notimanager.presentation.stateholder.state.NotificationAppState +import com.example.notimanager.presentation.stateholder.viewmodel.NotificationAppPriorityViewModel import com.example.notimanager.presentation.stateholder.viewmodel.NotificationAppViewModel @Composable -fun NotificationAppListView(navController: NavController, innerPadding: PaddingValues,viewModel: NotificationAppViewModel = hiltViewModel()) { +fun NotificationAppListView( + navController: NavController, + innerPadding: PaddingValues, + viewModel: NotificationAppViewModel = hiltViewModel(), + priorityViewModel: NotificationAppPriorityViewModel = hiltViewModel() +) { val notificationAppState by viewModel.notificationAppState.observeAsState(NotificationAppState()) + val priorityState by priorityViewModel.notificationAppPriorityState.observeAsState((NotificationAppPriorityState())) Column( Modifier.padding(innerPadding) - ) { - if (notificationAppState.isLoading) { + if (notificationAppState.isLoading || priorityState.isLoading) { CircularProgressIndicator(modifier = Modifier.padding(16.dp)) } else if (notificationAppState.error != null) { } else { + val combinedList = mutableListOf() + combinedList.addAll(priorityState.notificationAppList) + combinedList.addAll(notificationAppState.notificationAppList) + // TODO: 드래그로 우선순위 변경 가능하게 수정 LazyColumn { - items(notificationAppState.notificationAppList) { notification -> - NotificationAppItemView(notification = notification, onClick = { - navController.navigate( - "titleScreen/${notification.appName}/${notification.title}" - ) - }) + items(combinedList) { notification -> + NotificationAppItemView( + notification = notification, + onClick = { + navController + .navigate("titleScreen/${notification.appName}/${notification.title}" + ) + }, + viewModel = viewModel, + priorityViewModel = priorityViewModel + ) } } } @@ -59,7 +80,15 @@ fun NotificationAppListView(navController: NavController, innerPadding: PaddingV } @Composable -fun NotificationAppItemView(notification: NotificationApp, onClick: () -> Unit) { +fun NotificationAppItemView( + notification: NotificationApp, + onClick: () -> Unit, + viewModel: NotificationAppViewModel, + priorityViewModel: NotificationAppPriorityViewModel + +) { + var showModal by remember { mutableStateOf(false) } + Row( modifier = Modifier .padding(16.dp) @@ -70,14 +99,15 @@ fun NotificationAppItemView(notification: NotificationApp, onClick: () -> Unit) ) { AppIconView(notification.appIcon) Spacer(modifier = Modifier.width(8.dp)) - Column { + Column( + modifier = Modifier.weight(1f) + ) { Text( text = notification.appName, style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)) Text( text = notification.title, style = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Bold) - ) Text( text = notification.content, @@ -91,6 +121,41 @@ fun NotificationAppItemView(notification: NotificationApp, onClick: () -> Unit) color = Color.LightGray ) } + + IconButton(onClick = { showModal = true }) { + Icon(Icons.Filled.MoreVert, contentDescription = "중요 표시 또는 삭제") + } + } + if (showModal) { + BottomSheet(showModal, onDismiss = { showModal = false }){ + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = notification.appName, + style = MaterialTheme.typography.labelLarge, + color = Color.Gray + ) + if (notification.priorityActive) { + ClickableTextView(text = "중요 알림 취소", onClick = { + priorityViewModel.removeAppPriority(notification.appName){ + viewModel.loadNotificationApps() + } + + }) + } + else{ + ClickableTextView(text = "중요 알림 설정", onClick = { + viewModel.setAppPriority(notification.appName, priorityViewModel.getLength()){ + priorityViewModel.loadNotificationAppPriority() + } + }) + } + ClickableTextView(text = "삭제", onClick = {}) + } + } } } @@ -104,7 +169,12 @@ fun PreviewNotificationAppItemView(){ title = "title", content = "content", timestamp = 1234567890, - appIcon = null - ), onClick = {}) + appIcon = null, + priorityActive = false, + priority = 0 + ), onClick = {}, + viewModel = hiltViewModel(), + priorityViewModel = hiltViewModel() + ) } } \ No newline at end of file From 1d7baf2100c614d1620adfde5110d6cb57f3385d Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:41:25 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EC=A0=9C?= =?UTF-8?q?=EB=AA=A9=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9A=B0=EC=84=A0?= =?UTF-8?q?=EC=88=9C=EC=9C=84=20=EA=B8=B0=EB=8A=A5=20=EC=A0=81=EC=9A=A9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 올바른 적용을 위해, 알림 제목 dto에 id 필드를 추가함. 이 값으로 우선순위 추가, 삭제를 관리. --- .../data/dto/NotificationTitleDto.kt | 2 + .../domain/model/NotificationTitle.kt | 1 + .../state/NotificationTitlePriorityState.kt | 9 ++ .../NotificationTitlePriorityViewModel.kt | 56 ++++++++++++ .../viewmodel/NotificationTitleViewModel.kt | 8 ++ .../ui/component/NotificationTitleListView.kt | 85 +++++++++++++++++-- .../presentation/ui/screen/TitleScreen.kt | 5 +- 7 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/example/notimanager/presentation/stateholder/state/NotificationTitlePriorityState.kt create mode 100644 app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationTitlePriorityViewModel.kt diff --git a/app/src/main/java/com/example/notimanager/data/dto/NotificationTitleDto.kt b/app/src/main/java/com/example/notimanager/data/dto/NotificationTitleDto.kt index b0d6f15..71a0570 100644 --- a/app/src/main/java/com/example/notimanager/data/dto/NotificationTitleDto.kt +++ b/app/src/main/java/com/example/notimanager/data/dto/NotificationTitleDto.kt @@ -4,6 +4,7 @@ import android.graphics.BitmapFactory import com.example.notimanager.domain.model.NotificationTitle data class NotificationTitleDto( + val id: Long, val title: String, val content: String, val timestamp: Long, @@ -13,6 +14,7 @@ data class NotificationTitleDto( ) { fun toDomain(): NotificationTitle { return NotificationTitle( + id = this.id, title = this.title, content = this.content, timestamp = this.timestamp, diff --git a/app/src/main/java/com/example/notimanager/domain/model/NotificationTitle.kt b/app/src/main/java/com/example/notimanager/domain/model/NotificationTitle.kt index c872ad4..266ad56 100644 --- a/app/src/main/java/com/example/notimanager/domain/model/NotificationTitle.kt +++ b/app/src/main/java/com/example/notimanager/domain/model/NotificationTitle.kt @@ -3,6 +3,7 @@ package com.example.notimanager.domain.model import android.graphics.Bitmap data class NotificationTitle( + val id: Long, val title: String, val content: String, val timestamp: Long, diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/state/NotificationTitlePriorityState.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/state/NotificationTitlePriorityState.kt new file mode 100644 index 0000000..88abacf --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/state/NotificationTitlePriorityState.kt @@ -0,0 +1,9 @@ +package com.example.notimanager.presentation.stateholder.state + +import com.example.notimanager.domain.model.NotificationTitle + +data class NotificationTitlePriorityState( + val notificationTitleList: List = emptyList(), + val isLoading: Boolean = false, + val error: String? = null +) diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationTitlePriorityViewModel.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationTitlePriorityViewModel.kt new file mode 100644 index 0000000..aa2a3bb --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationTitlePriorityViewModel.kt @@ -0,0 +1,56 @@ +package com.example.notimanager.presentation.stateholder.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.notimanager.domain.usecase.NotificationTitleUseCase +import com.example.notimanager.presentation.stateholder.state.NotificationTitlePriorityState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class NotificationTitlePriorityViewModel @Inject constructor( + private val notificationTitleUseCase: NotificationTitleUseCase +): ViewModel() { + private val _notificationTitlePriorityState = MutableLiveData() + val notificationTitlePriorityState: LiveData get() = _notificationTitlePriorityState + + private var appName: String = "" + private var title: String = "" + + fun setArgs(appName: String, title: String) { + this.appName = appName + this.title = title + loadNotificationTitles() + } + + fun getLength(): Int{ + if (notificationTitlePriorityState.value != null){ + return notificationTitlePriorityState.value!!.notificationTitleList.size + } + return 0 + } + + fun loadNotificationTitles() { + viewModelScope.launch { + _notificationTitlePriorityState.value = NotificationTitlePriorityState(isLoading = true) + + try { + val notificationTitles = notificationTitleUseCase.getNotificationTitlePriorityList(appName, title) + _notificationTitlePriorityState.value = NotificationTitlePriorityState(notificationTitleList = notificationTitles) + } catch (e: Exception) { + _notificationTitlePriorityState.value = NotificationTitlePriorityState(error = e.message) + } + } + } + + fun removeTitlePriority(notificationId: Long, onComplete: () -> Unit) { + viewModelScope.launch { + notificationTitleUseCase.removeTitlePriority(notificationId) + loadNotificationTitles() + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationTitleViewModel.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationTitleViewModel.kt index ed77051..c8e50e1 100644 --- a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationTitleViewModel.kt +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/NotificationTitleViewModel.kt @@ -42,4 +42,12 @@ class NotificationTitleViewModel @Inject constructor( } } } + + fun setTitlePriority(notificationId: Long, newPriority: Int = 0, onComplete: () -> Unit) { + viewModelScope.launch { + notificationTitleUseCase.setTitlePriority(notificationId, newPriority) + loadNotificationTitles() + onComplete() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/component/NotificationTitleListView.kt b/app/src/main/java/com/example/notimanager/presentation/ui/component/NotificationTitleListView.kt index 0209477..531e5d1 100644 --- a/app/src/main/java/com/example/notimanager/presentation/ui/component/NotificationTitleListView.kt +++ b/app/src/main/java/com/example/notimanager/presentation/ui/component/NotificationTitleListView.kt @@ -10,12 +10,19 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -25,28 +32,45 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.example.notimanager.common.objects.DateFormatter.formatTimestamp import com.example.notimanager.domain.model.NotificationTitle +import com.example.notimanager.presentation.stateholder.state.NotificationTitlePriorityState import com.example.notimanager.presentation.stateholder.state.NotificationTitleState +import com.example.notimanager.presentation.stateholder.viewmodel.NotificationTitlePriorityViewModel import com.example.notimanager.presentation.stateholder.viewmodel.NotificationTitleViewModel @Composable -fun NotificationTitleListView(navController: NavController, innerPadding: PaddingValues,viewModel: NotificationTitleViewModel) { - val notificationTitleState by viewModel.notificationTitleState.observeAsState(NotificationTitleState()) - +fun NotificationTitleListView( + navController: NavController, + innerPadding: PaddingValues, + viewModel: NotificationTitleViewModel, + priorityViewModel: NotificationTitlePriorityViewModel +) { + val notificationTitleState by viewModel.notificationTitleState.observeAsState( + NotificationTitleState() + ) + val priorityState by priorityViewModel.notificationTitlePriorityState.observeAsState( + NotificationTitlePriorityState() + ) Column ( Modifier.padding(innerPadding) ){ - if (notificationTitleState.isLoading) { + if (notificationTitleState.isLoading || priorityState.isLoading) { CircularProgressIndicator(modifier = Modifier.padding(16.dp)) } else if (notificationTitleState.error != null) { } else { + val combinedList = mutableListOf() + combinedList.addAll(priorityState.notificationTitleList) + combinedList.addAll(notificationTitleState.notificationTitleList) + // TODO: 드래그로 우선순위 변경 가능하게 수정 LazyColumn { - items(notificationTitleState.notificationTitleList) { notification -> + items(combinedList) { notification -> NotificationTitleItemView (notification = notification, onClick = { navController.navigate( "notificationScreen/${viewModel.getAppName()}/${notification.title}" - ) - }) + )}, + viewModel = viewModel, + priorityViewModel = priorityViewModel + ) } } } @@ -54,7 +78,13 @@ fun NotificationTitleListView(navController: NavController, innerPadding: Paddin } @Composable -fun NotificationTitleItemView(notification: NotificationTitle, onClick: () -> Unit) { +fun NotificationTitleItemView( + notification: NotificationTitle, + onClick: () -> Unit, + viewModel: NotificationTitleViewModel, + priorityViewModel: NotificationTitlePriorityViewModel +) { + var showModal by remember { mutableStateOf(false) } Row( modifier = Modifier .padding(16.dp) @@ -64,7 +94,9 @@ fun NotificationTitleItemView(notification: NotificationTitle, onClick: () -> Un ) { AppIconView(notification.notificationIcon) Spacer(modifier = Modifier.width(8.dp)) - Column { + Column ( + modifier = Modifier.weight(1f) + ){ Text( text = notification.title, style = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Bold) @@ -82,5 +114,40 @@ fun NotificationTitleItemView(notification: NotificationTitle, onClick: () -> Un color = Color.LightGray ) } + IconButton(onClick = { showModal = true }) { + Icon(Icons.Filled.MoreVert, contentDescription = "중요 표시 또는 삭제") + } + } + + if (showModal) { + BottomSheet(showModal, onDismiss = { showModal = false }){ + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = notification.title, + style = MaterialTheme.typography.labelLarge, + color = Color.Gray + ) + if (notification.priorityActive) { + ClickableTextView(text = "중요 알림 취소", onClick = { + priorityViewModel.removeTitlePriority(notificationId = notification.id){ + viewModel.loadNotificationTitles() + } + + }) + } + else{ + ClickableTextView(text = "중요 알림 설정", onClick = { + viewModel.setTitlePriority(notification.id, priorityViewModel.getLength()){ + priorityViewModel.loadNotificationTitles() + } + }) + } + ClickableTextView(text = "삭제", onClick = {}) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/screen/TitleScreen.kt b/app/src/main/java/com/example/notimanager/presentation/ui/screen/TitleScreen.kt index 7b3a93c..a2a2a15 100644 --- a/app/src/main/java/com/example/notimanager/presentation/ui/screen/TitleScreen.kt +++ b/app/src/main/java/com/example/notimanager/presentation/ui/screen/TitleScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import com.example.notimanager.presentation.stateholder.viewmodel.NotificationTitlePriorityViewModel import com.example.notimanager.presentation.stateholder.viewmodel.NotificationTitleViewModel import com.example.notimanager.presentation.ui.component.NotificationTitleListView import com.example.notimanager.presentation.ui.component.TitleTopAppBar @@ -12,15 +13,17 @@ import com.example.notimanager.presentation.ui.component.TitleTopAppBar @Composable fun TitleScreen(navController: NavController, appName: String = "", title:String = ""){ val viewModel: NotificationTitleViewModel = hiltViewModel() + val priorityViewModel: NotificationTitlePriorityViewModel = hiltViewModel() LaunchedEffect(appName, title) { viewModel.setArgs(appName, title) + priorityViewModel.setArgs(appName, title) } Scaffold( topBar = { TitleTopAppBar(title = title, onBackClick = { navController.popBackStack() }) } ) { innerPadding -> - NotificationTitleListView(navController, innerPadding, viewModel) + NotificationTitleListView(navController, innerPadding, viewModel, priorityViewModel) } } \ No newline at end of file From d512b62b0d977d99e2d3a09937be9c136771cbd3 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Thu, 27 Feb 2025 10:23:04 +0900 Subject: [PATCH 08/10] =?UTF-8?q?Test:=20=EC=9A=B0=EC=84=A0=EC=88=9C?= =?UTF-8?q?=EC=9C=84=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=B6=84=EB=A6=AC=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit json도 수정함. --- .../NotificationAppRepositoryTest.kt | 84 ++++++++++++++++++ .../NotificationRepositoryDomainTest.kt | 40 +-------- .../NotificationTitleRepositoryTest.kt | 85 +++++++++++++++++++ .../usecase/NotificationAppUseCaseTest.kt | 6 +- .../usecase/NotificationTitleUseCaseTest.kt | 7 +- .../domain/usecase/NotificationUseCaseTest.kt | 6 +- .../json/data/dto/NotificationAppDto.json | 20 +++-- .../json/domain/model/NotificationApp.json | 20 +++-- 8 files changed, 210 insertions(+), 58 deletions(-) create mode 100644 app/src/test/java/com/example/notimanager/data/repository/NotificationAppRepositoryTest.kt create mode 100644 app/src/test/java/com/example/notimanager/data/repository/NotificationTitleRepositoryTest.kt diff --git a/app/src/test/java/com/example/notimanager/data/repository/NotificationAppRepositoryTest.kt b/app/src/test/java/com/example/notimanager/data/repository/NotificationAppRepositoryTest.kt new file mode 100644 index 0000000..fea2499 --- /dev/null +++ b/app/src/test/java/com/example/notimanager/data/repository/NotificationAppRepositoryTest.kt @@ -0,0 +1,84 @@ +package com.example.notimanager.data.repository + +import com.example.notimanager.data.dto.NotificationAppDto +import com.example.notimanager.data.source.local.dao.AppIconDao +import com.example.notimanager.data.source.local.dao.NotificationDao +import com.example.notimanager.domain.model.NotificationApp +import com.example.notimanager.utils.TestUtils.readJsonFile +import com.example.notimanager.utils.TestUtils.toDtoList +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.mockk.coEvery +import io.mockk.mockk + +class NotificationAppRepositoryTest: BehaviorSpec({ + val dao = mockk() + val appIconDao = mockk() + val repository = NotificationAppRepository(dao, appIconDao) + + val layer = "data" + val expectedLayer = "domain" + Context("NotificationRepositoryDomain 테스트"){ + + Given("알림 앱 우선순위 리스트가 필요한 경우 "){ + val target = "dto/NotificationAppDto.json" + val expected = "model/NotificationApp.json" + + val notificationAppDtoList = readJsonFile(layer, target) toDtoList NotificationAppDto::class.java + val expectedNotificationAppList = readJsonFile(expectedLayer, expected) toDtoList NotificationApp::class.java + + When("getNotificationAppList를 호출하면"){ + coEvery { dao.getNotificationAppPriorityList() } returns notificationAppDtoList + + Then("notificationAppList를 반환해야 한다."){ + val result = repository.getNotificationAppPriorityList() + result shouldBe expectedNotificationAppList + } + } + } + + Given("알림 앱 비우선순위 리스트가 필요한 경우 "){ + val target = "dto/NotificationAppDto.json" + val expected = "model/NotificationApp.json" + + val notificationAppDtoList = readJsonFile(layer, target) toDtoList NotificationAppDto::class.java + val expectedNotificationAppList = readJsonFile(expectedLayer, expected) toDtoList NotificationApp::class.java + + When("getNotificationAppList를 호출하면"){ + coEvery { dao.getNotificationAppList() } returns notificationAppDtoList + + Then("notificationAppList를 반환해야 한다."){ + val result = repository.getNotificationAppList() + result shouldBe expectedNotificationAppList + } + } + } + + Given("알림 우선순위 적용이 필요할 때 "){ + val appName = "test" + + When("setPriority를 호출하면"){ + coEvery { appIconDao.setPriority(appName, 0) } returns 1 + + Then("우선순위를 적용하고, 1을 반환해야 한다."){ + val result = repository.setAppPriority(appName, 0) + result shouldBe 1 + } + } + } + + Given("알림 우선순위 취소가 필요할 때 "){ + val appName = "test" + + When("remoevPriority를 호출하면"){ + coEvery { appIconDao.removePriority(appName) } returns 1 + + Then("우선순위를 적용하고, 1을 반환해야 한다."){ + val result = repository.removeAppPriority(appName) + result shouldBe 1 + } + } + } + + } +}) \ No newline at end of file diff --git a/app/src/test/java/com/example/notimanager/data/repository/NotificationRepositoryDomainTest.kt b/app/src/test/java/com/example/notimanager/data/repository/NotificationRepositoryDomainTest.kt index 58a1893..d877510 100644 --- a/app/src/test/java/com/example/notimanager/data/repository/NotificationRepositoryDomainTest.kt +++ b/app/src/test/java/com/example/notimanager/data/repository/NotificationRepositoryDomainTest.kt @@ -1,12 +1,8 @@ package com.example.notimanager.data.repository -import com.example.notimanager.data.dto.NotificationAppDto import com.example.notimanager.data.dto.NotificationDto -import com.example.notimanager.data.dto.NotificationTitleDto import com.example.notimanager.data.source.local.dao.NotificationDao import com.example.notimanager.domain.model.Notification -import com.example.notimanager.domain.model.NotificationApp -import com.example.notimanager.domain.model.NotificationTitle import com.example.notimanager.utils.TestUtils.readJsonFile import com.example.notimanager.utils.TestUtils.toDtoList import io.kotest.core.spec.style.BehaviorSpec @@ -16,46 +12,12 @@ import io.mockk.mockk class NotificationRepositoryDomainTest: BehaviorSpec({ val dao = mockk() - val repository = NotificationRepositoryDomain(dao) + val repository = NotificationDomainRepository(dao) val layer = "data" val expectedLayer = "domain" Context("NotificationRepositoryDomain 테스트"){ - Given("알림 앱 리스트가 필요한 경우 "){ - val target = "dto/NotificationAppDto.json" - val expected = "model/NotificationApp.json" - - val notificationAppDtoList = readJsonFile(layer, target) toDtoList NotificationAppDto::class.java - val expectedNotificationAppList = readJsonFile(expectedLayer, expected) toDtoList NotificationApp::class.java - - When("getNotificationAppList를 호출하면"){ - coEvery { dao.getNotificationAppList() } returns notificationAppDtoList - - Then("notificationAppList를 반환해야 한다."){ - val result = repository.getNotificationAppList() - result shouldBe expectedNotificationAppList - } - } - } - - Given("앱의 알림 제목 리스트가 필요한 경우"){ - val target = "dto/NotificationTitleDto.json" - val expected = "model/NotificationTitle.json" - - val notificationTitleDtoList = readJsonFile(layer, target) toDtoList NotificationTitleDto::class.java - val expectedNotificationTitleList = readJsonFile(expectedLayer, expected) toDtoList NotificationTitle::class.java - - When("getNotificationTitleList를 호출하면"){ - coEvery { dao.getNotificationTitleList("", "") } returns notificationTitleDtoList - - Then("notificationTitleList를 반환해야 한다."){ - val result = repository.getNotificationTitleList("", "") - result shouldBe expectedNotificationTitleList - } - } - } - Given("앱의 알림 리스트가 필요한 경우"){ val target = "dto/NotificationDto.json" val expected = "model/Notification.json" diff --git a/app/src/test/java/com/example/notimanager/data/repository/NotificationTitleRepositoryTest.kt b/app/src/test/java/com/example/notimanager/data/repository/NotificationTitleRepositoryTest.kt new file mode 100644 index 0000000..89929ce --- /dev/null +++ b/app/src/test/java/com/example/notimanager/data/repository/NotificationTitleRepositoryTest.kt @@ -0,0 +1,85 @@ +package com.example.notimanager.data.repository + +import com.example.notimanager.data.dto.NotificationTitleDto +import com.example.notimanager.data.source.local.dao.NotificationDao +import com.example.notimanager.data.source.local.dao.NotificationIconDao +import com.example.notimanager.domain.model.NotificationTitle +import com.example.notimanager.utils.TestUtils.readJsonFile +import com.example.notimanager.utils.TestUtils.toDtoList +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.mockk.coEvery +import io.mockk.mockk + +class NotificationTitleRepositoryTest: BehaviorSpec({ + val dao = mockk() + val notificationIconDao = mockk() + val repository = NotificationTitleRepository(dao, notificationIconDao) + + val layer = "data" + val expectedLayer = "domain" + Context("NotificationRepositoryDomain 테스트"){ + + Given("앱의 알림 제목 우선순위 리스트가 필요한 경우"){ + val target = "dto/NotificationTitleDto.json" + val expected = "model/NotificationTitle.json" + + val notificationTitleDtoList = readJsonFile(layer, target) toDtoList NotificationTitleDto::class.java + val expectedNotificationTitleList = readJsonFile(expectedLayer, expected) toDtoList NotificationTitle::class.java + + When("getNotificationTitlePriorityList를 호출하면"){ + coEvery { dao.getNotificationTitlePriorityList("", "") } returns notificationTitleDtoList + + Then("notificationTitleList를 반환해야 한다."){ + val result = repository.getNotificationTitlePriorityList("", "") + result shouldBe expectedNotificationTitleList + } + } + } + + Given("앱의 알림 제목 비우선순위 리스트가 필요한 경우"){ + val target = "dto/NotificationTitleDto.json" + val expected = "model/NotificationTitle.json" + + val notificationTitleDtoList = readJsonFile(layer, target) toDtoList NotificationTitleDto::class.java + val expectedNotificationTitleList = readJsonFile(expectedLayer, expected) toDtoList NotificationTitle::class.java + + When("getNotificationTitleList를 호출하면"){ + coEvery { dao.getNotificationTitleList("", "") } returns notificationTitleDtoList + + Then("notificationTitleList를 반환해야 한다."){ + val result = repository.getNotificationTitleList("", "") + result shouldBe expectedNotificationTitleList + } + } + } + + Given("알림 제목 우선순위 적용이 필요할 때 "){ + val id = 1L + + When("setPriority를 호출하면"){ + coEvery { notificationIconDao.setPriority(id, 0) } returns 1 + + Then("우선순위를 적용하고, 1을 반환해야 한다."){ + val result = repository.setTitlePriority(id, 0) + result shouldBe 1 + } + } + } + + Given("알림 우선순위 취소가 필요할 때 "){ + val id = 1L + + When("remoevPriority를 호출하면"){ + coEvery { notificationIconDao.removePriority(id) } returns 1 + + Then("우선순위를 적용하고, 1을 반환해야 한다."){ + val result = repository.removeTitlePriority(id) + result shouldBe 1 + } + } + } + } + + +}) \ No newline at end of file diff --git a/app/src/test/java/com/example/notimanager/domain/usecase/NotificationAppUseCaseTest.kt b/app/src/test/java/com/example/notimanager/domain/usecase/NotificationAppUseCaseTest.kt index d15baed..bf37bb7 100644 --- a/app/src/test/java/com/example/notimanager/domain/usecase/NotificationAppUseCaseTest.kt +++ b/app/src/test/java/com/example/notimanager/domain/usecase/NotificationAppUseCaseTest.kt @@ -1,7 +1,7 @@ package com.example.notimanager.domain.usecase import com.example.notimanager.domain.model.NotificationApp -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface +import com.example.notimanager.domain.repository.NotificationAppRepositoryInterface import com.example.notimanager.utils.TestUtils.readJsonFile import com.example.notimanager.utils.TestUtils.toDtoList import io.kotest.core.spec.style.BehaviorSpec @@ -10,7 +10,7 @@ import io.mockk.coEvery import io.mockk.mockk class NotificationAppUseCaseTest: BehaviorSpec({ - val repository = mockk() + val repository = mockk() val useCase = NotificationAppUseCase(repository) val layer = "domain" @@ -19,7 +19,7 @@ class NotificationAppUseCaseTest: BehaviorSpec({ Given("알림 앱 리스트가 필요한 경우 "){ val notificationAppList = readJsonFile(layer, target) toDtoList NotificationApp::class.java - When("getNotificationAppList를 호출하며"){ + When("getNotificationAppList를 호출하면"){ coEvery { repository.getNotificationAppList() } returns notificationAppList Then("notificationAppList를 반환해야 한다."){ diff --git a/app/src/test/java/com/example/notimanager/domain/usecase/NotificationTitleUseCaseTest.kt b/app/src/test/java/com/example/notimanager/domain/usecase/NotificationTitleUseCaseTest.kt index 44c8ed5..7c088a4 100644 --- a/app/src/test/java/com/example/notimanager/domain/usecase/NotificationTitleUseCaseTest.kt +++ b/app/src/test/java/com/example/notimanager/domain/usecase/NotificationTitleUseCaseTest.kt @@ -1,7 +1,8 @@ package com.example.notimanager.domain.usecase import com.example.notimanager.domain.model.NotificationTitle -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface +import com.example.notimanager.domain.repository.NotificationDomainRepositoryInterface +import com.example.notimanager.domain.repository.NotificationTitleRepositoryInterface import com.example.notimanager.utils.TestUtils.readJsonFile import com.example.notimanager.utils.TestUtils.toDtoList import io.kotest.core.spec.style.BehaviorSpec @@ -10,7 +11,7 @@ import io.mockk.coEvery import io.mockk.mockk class NotificationTitleUseCaseTest: BehaviorSpec({ - val repository = mockk() + val repository = mockk() val useCase = NotificationTitleUseCase(repository) val layer = "domain" @@ -19,7 +20,7 @@ class NotificationTitleUseCaseTest: BehaviorSpec({ Given("특정 앱의 알림 제목 리스트가 필요한 경우 "){ val notificationTitleList = readJsonFile(layer, target) toDtoList NotificationTitle::class.java - When("getNotificationTitleList를 호출하며"){ + When("getNotificationTitleList를 호출하면"){ coEvery { repository.getNotificationTitleList(any(), any()) } returns notificationTitleList Then("notificationTitleList를 반환해야 한다."){ diff --git a/app/src/test/java/com/example/notimanager/domain/usecase/NotificationUseCaseTest.kt b/app/src/test/java/com/example/notimanager/domain/usecase/NotificationUseCaseTest.kt index 0cc91fc..d1f2c5f 100644 --- a/app/src/test/java/com/example/notimanager/domain/usecase/NotificationUseCaseTest.kt +++ b/app/src/test/java/com/example/notimanager/domain/usecase/NotificationUseCaseTest.kt @@ -1,7 +1,7 @@ package com.example.notimanager.domain.usecase import com.example.notimanager.domain.model.Notification -import com.example.notimanager.domain.repository.NotificationRepositoryDomainInterface +import com.example.notimanager.domain.repository.NotificationDomainRepositoryInterface import com.example.notimanager.utils.TestUtils.readJsonFile import com.example.notimanager.utils.TestUtils.toDtoList import io.kotest.core.spec.style.BehaviorSpec @@ -10,7 +10,7 @@ import io.mockk.coEvery import io.mockk.mockk class NotificationUseCaseTest: BehaviorSpec({ - val repository = mockk() + val repository = mockk() val useCase = NotificationUseCase(repository) val layer = "domain" @@ -19,7 +19,7 @@ class NotificationUseCaseTest: BehaviorSpec({ Given("특정 앱의 알림 리스트가 필요한 경우 "){ val notificationList = readJsonFile(layer, target) toDtoList Notification::class.java - When("getNotificationAppList를 호출하며"){ + When("getNotificationAppList를 호출하면"){ coEvery { repository.getNotificationList(any(), any()) } returns notificationList Then("notificationAppList를 반환해야 한다."){ diff --git a/app/src/test/resources/json/data/dto/NotificationAppDto.json b/app/src/test/resources/json/data/dto/NotificationAppDto.json index af27a82..017cfe1 100644 --- a/app/src/test/resources/json/data/dto/NotificationAppDto.json +++ b/app/src/test/resources/json/data/dto/NotificationAppDto.json @@ -4,34 +4,44 @@ "title": "New Message", "content": "You have received a new message from John.", "timestamp": 1672531199000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "priorityActive": true, + "priority": 1 }, { "appName": "Weather", "title": "Weather Update", "content": "Rain is expected in your area today.", "timestamp": 1672531299000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "priorityActive": true, + "priority": 1 }, { "appName": "Email", "title": "New Email", "content": "You've got a new email from support@example.com.", "timestamp": 1672531399000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "priorityActive": true, + "priority": 1 }, { "appName": "Calendar", "title": "Event Reminder", "content": "Don't forget your meeting at 3 PM today.", "timestamp": 1672531499000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "priorityActive": true, + "priority": 1 }, { "appName": "News", "title": "Breaking News", "content": "New developments in the tech industry.", "timestamp": 1672531599000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "priorityActive": true, + "priority": 1 } ] diff --git a/app/src/test/resources/json/domain/model/NotificationApp.json b/app/src/test/resources/json/domain/model/NotificationApp.json index be1028a..d270dd5 100644 --- a/app/src/test/resources/json/domain/model/NotificationApp.json +++ b/app/src/test/resources/json/domain/model/NotificationApp.json @@ -4,34 +4,44 @@ "title": "New Message", "content": "You have received a new message from John.", "timestamp": 1672531199000, - "appIcon": null + "appIcon": null, + "priorityActive": true, + "priority": 1 }, { "appName": "Weather", "title": "Weather Update", "content": "Rain is expected in your area today.", "timestamp": 1672531299000, - "appIcon": null + "appIcon": null, + "priorityActive": true, + "priority": 1 }, { "appName": "Email", "title": "New Email", "content": "You've got a new email from support@example.com.", "timestamp": 1672531399000, - "appIcon": null + "appIcon": null, + "priorityActive": true, + "priority": 1 }, { "appName": "Calendar", "title": "Event Reminder", "content": "Don't forget your meeting at 3 PM today.", "timestamp": 1672531499000, - "appIcon": null + "appIcon": null, + "priorityActive": true, + "priority": 1 }, { "appName": "News", "title": "Breaking News", "content": "New developments in the tech industry.", "timestamp": 1672531599000, - "appIcon": null + "appIcon": null, + "priorityActive": true, + "priority": 1 } ] From 96c9c7a78721930851a9ecfb7816819f8df0b2fd Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Thu, 27 Feb 2025 10:24:26 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=EC=BB=AC=EB=9F=AC=ED=92=80=20?= =?UTF-8?q?=EC=95=B1=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit smallIcon에 색을 입힘으로써 앱 아이콘 표현. 이후 패키지 앱 아이콘을 가져오는 방식과 비교해볼 예정 일종의 테스트 커밋. --- .../data/service/NotiListenerService.kt | 34 ++++++++++--------- .../notimanager/data/utils/AppIconGetter.kt | 30 ++++++++++++++++ 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/example/notimanager/data/service/NotiListenerService.kt b/app/src/main/java/com/example/notimanager/data/service/NotiListenerService.kt index af4504c..fa3e036 100644 --- a/app/src/main/java/com/example/notimanager/data/service/NotiListenerService.kt +++ b/app/src/main/java/com/example/notimanager/data/service/NotiListenerService.kt @@ -3,24 +3,29 @@ package com.example.notimanager.data.service import android.graphics.drawable.Icon import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification +import android.util.Log import com.example.notimanager.data.model.AppIconModel import com.example.notimanager.data.model.NotificationIconModel import com.example.notimanager.data.model.NotificationMetaModel import com.example.notimanager.data.model.NotificationModel import com.example.notimanager.data.repository.NotificationRepositoryInterface import com.example.notimanager.data.utils.AppIconGetter.convertByteArray +import com.example.notimanager.data.utils.AppIconGetter.convertByteArrayWithColor import com.example.notimanager.data.utils.NameGetter import com.example.notimanager.data.utils.IntentHelper import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import javax.inject.Inject @AndroidEntryPoint class NotiListenerService: NotificationListenerService() { @Inject lateinit var notificationRepository: NotificationRepositoryInterface + private val mutex = Mutex() override fun onNotificationPosted(sbn: StatusBarNotification?) { sbn?.let { @@ -28,23 +33,19 @@ class NotiListenerService: NotificationListenerService() { val postTime = it.postTime val appName = NameGetter.getAppName(this, it) - val title = notification.extras.getString("android.title") ?: "" - val content = notification.extras.getString("android.text") ?: "" + val title = notification.extras.getString("android.title") + val content = notification.extras.getString("android.text") - CoroutineScope(Dispatchers.IO).launch { - val id = insertNotification(appName, title, content, postTime) - - launch { - insertNotificationMeta(id, notification.contentIntent.creatorPackage) - } - - launch { - insertNotificationIcon(id, notification.getLargeIcon()) + if (title != null && content != null){ + CoroutineScope(Dispatchers.IO).launch { + mutex.withLock { + val id = insertNotification(appName, title, content, postTime) + insertNotificationMeta(id, notification.contentIntent.creatorPackage) + insertNotificationIcon(id, notification.getLargeIcon()) + insertAppIcon(appName, notification.smallIcon, notification.color) + } } - launch { - insertAppIcon(appName, notification.smallIcon) - } } } } @@ -90,9 +91,10 @@ class NotiListenerService: NotificationListenerService() { private suspend fun insertAppIcon( appName: String, - icon: Icon? + icon: Icon?, + color: Int ){ - val byteArray = convertByteArray(this@NotiListenerService, icon) + val byteArray = convertByteArrayWithColor(this@NotiListenerService, icon, color) val appIconModel = AppIconModel( notiAppName = appName, iconBytes = byteArray diff --git a/app/src/main/java/com/example/notimanager/data/utils/AppIconGetter.kt b/app/src/main/java/com/example/notimanager/data/utils/AppIconGetter.kt index 0f40f5e..0a1d668 100644 --- a/app/src/main/java/com/example/notimanager/data/utils/AppIconGetter.kt +++ b/app/src/main/java/com/example/notimanager/data/utils/AppIconGetter.kt @@ -2,11 +2,41 @@ package com.example.notimanager.data.utils import android.content.Context import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter import android.graphics.drawable.Icon import com.example.notimanager.common.objects.DateFormatter.toBitmap import java.io.ByteArrayOutputStream object AppIconGetter { + fun convertByteArrayWithColor(context: Context, icon: Icon?, color: Int): ByteArray{ + if (icon == null) return ByteArray(0) + + // Icon을 Drawable로 변환 후 Bitmap으로 변환 + val bitmap = icon.loadDrawable(context)?.toBitmap() ?: return ByteArray(0) + val bitmapConfig = bitmap.config ?: Bitmap.Config.ARGB_8888 + + // 색상 필터 적용 + val coloredBitmap = Bitmap.createBitmap(bitmap.width, bitmap.height, bitmapConfig) + val canvas = Canvas(coloredBitmap) + val paint = Paint().apply { + isAntiAlias = true + colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + } + + // 원래 비트맵을 캔버스에 그리기 + canvas.drawBitmap(bitmap, 0f, 0f, null) + + // 색상 필터를 적용하여 다시 그리기 + canvas.drawBitmap(coloredBitmap, 0f, 0f, paint) + + val stream = ByteArrayOutputStream() + coloredBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) + return stream.toByteArray() + } + fun convertByteArray(context: Context, icon: Icon?): ByteArray{ val bitmap = icon?.loadDrawable(context)?.toBitmap() val stream = ByteArrayOutputStream() From 3699e5f689586dc0e5e4257a0704dd3f815058db Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Thu, 27 Feb 2025 10:24:58 +0900 Subject: [PATCH 10/10] =?UTF-8?q?build:=20kover=EC=97=90=EC=84=9C=20presen?= =?UTF-8?q?tation=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI 로직은 실제 모바일에서 테스트하므로, 굳이 커버리지에 넣지 않음. --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 266ee08..d5852a1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -76,6 +76,7 @@ kover { "dagger", "hilt_aggregated_deps", "*.di", + "*.presentation" )) excludes.classes(listOf( "*_*Factory*",