From 2962fef9d896ad55df513ca2248232a2f91e8d0c Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:41:01 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=20data=20=EA=B3=84=EC=B8=B5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FilteredNotification 모델 추가 해당 모델을 추가, 조회, 삭제하는 dao 로직 추가 데이터베이스 버전 업그레이드. --- .../10.json | 294 ++++++++++++++++++ .../data/dto/FilteredNotificationDto.kt | 19 ++ .../data/model/FilteredNotificationModel.kt | 18 ++ .../FilteredNotificationRepository.kt | 41 +++ .../local/dao/FilteredNotificationDao.kt | 30 ++ .../local/database/NotiManagerDatabase.kt | 14 +- .../domain/model/FilteredNotification.kt | 9 + ...FilteredNotificationRepositoryInterface.kt | 10 + 8 files changed, 430 insertions(+), 5 deletions(-) create mode 100644 app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/10.json create mode 100644 app/src/main/java/com/example/notimanager/data/dto/FilteredNotificationDto.kt create mode 100644 app/src/main/java/com/example/notimanager/data/model/FilteredNotificationModel.kt create mode 100644 app/src/main/java/com/example/notimanager/data/repository/FilteredNotificationRepository.kt create mode 100644 app/src/main/java/com/example/notimanager/data/source/local/dao/FilteredNotificationDao.kt create mode 100644 app/src/main/java/com/example/notimanager/domain/model/FilteredNotification.kt create mode 100644 app/src/main/java/com/example/notimanager/domain/repository/FilteredNotificationRepositoryInterface.kt diff --git a/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/10.json b/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/10.json new file mode 100644 index 0000000..1c90ca3 --- /dev/null +++ b/app/schemas/com.example.notimanager.data.source.local.database.NotiManagerDatabase/10.json @@ -0,0 +1,294 @@ +{ + "formatVersion": 1, + "database": { + "version": 10, + "identityHash": "24dcb52dc8f665a8365553089fdd6155", + "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, `subText` TEXT 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 + }, + { + "fieldPath": "subText", + "columnName": "subText", + "affinity": "TEXT", + "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`)" + }, + { + "name": "index_notification_appName_subText_timestamp", + "unique": false, + "columnNames": [ + "appName", + "subText", + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notification_appName_subText_timestamp` ON `${TABLE_NAME}` (`appName`, `subText`, `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, `isRead` INTEGER 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 + }, + { + "fieldPath": "isRead", + "columnName": "isRead", + "affinity": "INTEGER", + "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": [] + }, + { + "tableName": "filtered_notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `appName` TEXT NOT NULL, `title` TEXT 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 + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_filtered_notification_appName_title", + "unique": false, + "columnNames": [ + "appName", + "title" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_filtered_notification_appName_title` ON `${TABLE_NAME}` (`appName`, `title`)" + } + ], + "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, '24dcb52dc8f665a8365553089fdd6155')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/dto/FilteredNotificationDto.kt b/app/src/main/java/com/example/notimanager/data/dto/FilteredNotificationDto.kt new file mode 100644 index 0000000..dc6bb45 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/data/dto/FilteredNotificationDto.kt @@ -0,0 +1,19 @@ +package com.example.notimanager.data.dto + +import com.example.notimanager.domain.model.FilteredNotification + +data class FilteredNotificationDto( + val id: Long, + val appName: String, + val title: String, +){ + fun toDomain(): FilteredNotification { + return FilteredNotification( + id = this.id, + appName = this.appName, + title = this.title, + ) + } +} + + diff --git a/app/src/main/java/com/example/notimanager/data/model/FilteredNotificationModel.kt b/app/src/main/java/com/example/notimanager/data/model/FilteredNotificationModel.kt new file mode 100644 index 0000000..b291eee --- /dev/null +++ b/app/src/main/java/com/example/notimanager/data/model/FilteredNotificationModel.kt @@ -0,0 +1,18 @@ +package com.example.notimanager.data.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "filtered_notification" , + indices = [Index(value = ["appName", "title"], unique = false)] +) +data class FilteredNotificationModel( + @PrimaryKey(autoGenerate = true) + val id: Long = 0, + val appName: String, + val title: String, +) + + diff --git a/app/src/main/java/com/example/notimanager/data/repository/FilteredNotificationRepository.kt b/app/src/main/java/com/example/notimanager/data/repository/FilteredNotificationRepository.kt new file mode 100644 index 0000000..b67997c --- /dev/null +++ b/app/src/main/java/com/example/notimanager/data/repository/FilteredNotificationRepository.kt @@ -0,0 +1,41 @@ +package com.example.notimanager.data.repository + +import com.example.notimanager.data.model.FilteredNotificationModel +import com.example.notimanager.data.source.local.dao.FilteredNotificationDao +import com.example.notimanager.domain.model.FilteredNotification +import com.example.notimanager.domain.repository.FilteredNotificationRepositoryInterface + + +class FilteredNotificationRepository( + private val dao: FilteredNotificationDao +) : FilteredNotificationRepositoryInterface { + override suspend fun insertFiltered(appName: String, title: String): Long { + val filteredNotification = FilteredNotificationModel( + appName = appName, + title = title + ) + return dao.insert(filteredNotification) + } + + override suspend fun getSpecificFilteredList( + appName: String, + title: String, + subText: String + ): List { + return dao.getSpecificFilteredList(appName, title, subText) + .asSequence() + .map { it.toDomain() } + .toList() + } + + override suspend fun getFilteredList(): List { + return dao.getFilteredList() + .asSequence() + .map { it.toDomain() } + .toList() + } + + override suspend fun deleteById(id: Long): Int { + return dao.deleteById(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/data/source/local/dao/FilteredNotificationDao.kt b/app/src/main/java/com/example/notimanager/data/source/local/dao/FilteredNotificationDao.kt new file mode 100644 index 0000000..511ddb7 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/data/source/local/dao/FilteredNotificationDao.kt @@ -0,0 +1,30 @@ +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.dto.FilteredNotificationDto +import com.example.notimanager.data.model.FilteredNotificationModel + +@Dao +interface FilteredNotificationDao { + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(filteredNotificationModel: FilteredNotificationModel): Long + + @Query(""" + SELECT id, appName, title FROM filtered_notification + WHERE appName = :appName AND (title = "" OR title = :title OR title = :subText) + """) + suspend fun getSpecificFilteredList(appName: String, title: String, subText: String): List + + @Query(""" + SELECT id, appName, title FROM filtered_notification + ORDER BY appName ASC + """) + suspend fun getFilteredList(): List + + @Query("DELETE FROM filtered_notification WHERE id = :id") + suspend fun deleteById(id: Long): Int + +} \ 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 03e12f8..f6db9bd 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 @@ -6,10 +6,12 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import com.example.notimanager.data.model.AppIconModel +import com.example.notimanager.data.model.FilteredNotificationModel 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.source.local.dao.AppIconDao +import com.example.notimanager.data.source.local.dao.FilteredNotificationDao 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 @@ -19,23 +21,25 @@ import com.example.notimanager.data.source.local.database.MigrationObject.MIGRAT import com.example.notimanager.data.source.local.database.MigrationObject.MIGRATION_8_9 @Database( - version = 9, + version = 10, entities = [ NotificationModel::class, NotificationMetaModel::class, NotificationIconModel::class, - AppIconModel::class + AppIconModel::class, + FilteredNotificationModel::class ], -// autoMigrations = [ -// AutoMigration (from = 7, to = 8) -// ] + autoMigrations = [ + AutoMigration (from = 9, to = 10) + ] ) abstract class NotiManagerDatabase : RoomDatabase() { abstract fun notificationDao(): NotificationDao abstract fun notificationIntentDao(): NotificationMetaDao abstract fun notificationIconDao(): NotificationIconDao abstract fun appIconDao(): AppIconDao + abstract fun filteredNotificationDao(): FilteredNotificationDao companion object { @Volatile diff --git a/app/src/main/java/com/example/notimanager/domain/model/FilteredNotification.kt b/app/src/main/java/com/example/notimanager/domain/model/FilteredNotification.kt new file mode 100644 index 0000000..a5e486c --- /dev/null +++ b/app/src/main/java/com/example/notimanager/domain/model/FilteredNotification.kt @@ -0,0 +1,9 @@ +package com.example.notimanager.domain.model + +data class FilteredNotification( + val id: Long, + val appName: String, + val title: String, +) + + diff --git a/app/src/main/java/com/example/notimanager/domain/repository/FilteredNotificationRepositoryInterface.kt b/app/src/main/java/com/example/notimanager/domain/repository/FilteredNotificationRepositoryInterface.kt new file mode 100644 index 0000000..2ed6a76 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/domain/repository/FilteredNotificationRepositoryInterface.kt @@ -0,0 +1,10 @@ +package com.example.notimanager.domain.repository + +import com.example.notimanager.domain.model.FilteredNotification + +interface FilteredNotificationRepositoryInterface { + suspend fun insertFiltered(appName: String, title: String): Long + suspend fun getSpecificFilteredList(appName: String, title: String, subText: String): List + suspend fun getFilteredList(): List + suspend fun deleteById(id: Long): Int +} \ No newline at end of file From a3e4cea8ff1e91f0454b5194eb3cd8393c916742 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Mon, 3 Mar 2025 13:33:39 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=20domain=20=EA=B3=84=EC=B8=B5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 도메인 계층 활용을 위해 DI에 알림 제외 관련 의존성 주입 추가. NotiListenerService에서 알림 제외 리스트일 경우 무시하도록 함. Guard Condition 적용으로 가독성 개선 UseCase 추가. --- .../example/notimanager/di/DatabaseModule.kt | 6 +++ .../notimanager/di/RepositoryModule.kt | 11 +++++ .../example/notimanager/di/UseCaseModule.kt | 11 +++++ .../domain/service/NotiListenerService.kt | 44 ++++++++++--------- .../usecase/FilteredNotificationUseCase.kt | 20 +++++++++ 5 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/example/notimanager/domain/usecase/FilteredNotificationUseCase.kt diff --git a/app/src/main/java/com/example/notimanager/di/DatabaseModule.kt b/app/src/main/java/com/example/notimanager/di/DatabaseModule.kt index 112aed1..f19384f 100644 --- a/app/src/main/java/com/example/notimanager/di/DatabaseModule.kt +++ b/app/src/main/java/com/example/notimanager/di/DatabaseModule.kt @@ -2,6 +2,7 @@ package com.example.notimanager.di import android.content.Context import com.example.notimanager.data.source.local.dao.AppIconDao +import com.example.notimanager.data.source.local.dao.FilteredNotificationDao 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 @@ -41,4 +42,9 @@ class DatabaseModule { fun provideAppIconDao(database: NotiManagerDatabase): AppIconDao { return database.appIconDao() } + + @Provides + fun provideFilteredNotificationDao(database: NotiManagerDatabase): FilteredNotificationDao { + return database.filteredNotificationDao() + } } \ No newline at end of file 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 c2855aa..efebf26 100644 --- a/app/src/main/java/com/example/notimanager/di/RepositoryModule.kt +++ b/app/src/main/java/com/example/notimanager/di/RepositoryModule.kt @@ -1,6 +1,7 @@ package com.example.notimanager.di import android.content.Context +import com.example.notimanager.data.repository.FilteredNotificationRepository import com.example.notimanager.data.repository.NotificationAppRepository import com.example.notimanager.data.repository.NotificationPermissionRepository import com.example.notimanager.data.repository.NotificationRepository @@ -8,9 +9,11 @@ import com.example.notimanager.data.repository.NotificationDomainRepository import com.example.notimanager.domain.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.FilteredNotificationDao 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.FilteredNotificationRepositoryInterface import com.example.notimanager.domain.repository.NotificationAppRepositoryInterface import com.example.notimanager.domain.repository.NotificationPermissionRepositoryInterface import com.example.notimanager.domain.repository.NotificationDomainRepositoryInterface @@ -69,4 +72,12 @@ class RepositoryModule { ): NotificationTitleRepositoryInterface { return NotificationTitleRepository(notificationDao, notificationIconDao) } + + @Provides + @Singleton + fun provideFilteredNotificationRepository( + filteredNotificationDao: FilteredNotificationDao + ): FilteredNotificationRepositoryInterface { + return FilteredNotificationRepository(filteredNotificationDao) + } } \ 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 eb505c1..e4bba0d 100644 --- a/app/src/main/java/com/example/notimanager/di/UseCaseModule.kt +++ b/app/src/main/java/com/example/notimanager/di/UseCaseModule.kt @@ -1,9 +1,12 @@ package com.example.notimanager.di +import com.example.notimanager.data.repository.FilteredNotificationRepository +import com.example.notimanager.domain.repository.FilteredNotificationRepositoryInterface import com.example.notimanager.domain.repository.NotificationAppRepositoryInterface import com.example.notimanager.domain.repository.NotificationPermissionRepositoryInterface import com.example.notimanager.domain.repository.NotificationDomainRepositoryInterface import com.example.notimanager.domain.repository.NotificationTitleRepositoryInterface +import com.example.notimanager.domain.usecase.FilteredNotificationUseCase import com.example.notimanager.domain.usecase.NotificationAppUseCase import com.example.notimanager.domain.usecase.NotificationPermissionUseCase import com.example.notimanager.domain.usecase.NotificationTitleUseCase @@ -48,4 +51,12 @@ class UseCaseModule { ): NotificationTitleUseCase { return NotificationTitleUseCase(notificationTitleRepository) } + + @Provides + @Singleton + fun provideFilteredNotificationUseCase( + filteredNotificationRepository: FilteredNotificationRepositoryInterface + ): FilteredNotificationUseCase { + return FilteredNotificationUseCase(filteredNotificationRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/domain/service/NotiListenerService.kt b/app/src/main/java/com/example/notimanager/domain/service/NotiListenerService.kt index 46e3701..7696ab8 100644 --- a/app/src/main/java/com/example/notimanager/domain/service/NotiListenerService.kt +++ b/app/src/main/java/com/example/notimanager/domain/service/NotiListenerService.kt @@ -7,6 +7,7 @@ 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.domain.repository.FilteredNotificationRepositoryInterface import com.example.notimanager.domain.repository.NotificationRepositoryInterface import com.example.notimanager.domain.utils.AppIconGetter.convertByteArray import com.example.notimanager.domain.utils.AppIconGetter.convertByteArrayWithColor @@ -23,33 +24,36 @@ import javax.inject.Inject @AndroidEntryPoint class NotiListenerService: NotificationListenerService() { - @Inject - lateinit var notificationRepository: NotificationRepositoryInterface + @Inject lateinit var notificationRepository: NotificationRepositoryInterface + @Inject lateinit var filterRepository: FilteredNotificationRepositoryInterface private val mutex = Mutex() override fun onNotificationPosted(sbn: StatusBarNotification?) { - sbn?.let { - val notification = it.notification - 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 subText = notification.extras.getString("android.subText") ?: "" - - if (title != null && content != null){ - CoroutineScope(Dispatchers.IO).launch { - mutex.withLock { - val id = insertNotification(appName, title, content, postTime, subText) - insertNotificationMeta(id, it.packageName) - insertNotificationIcon(id, notification.getLargeIcon(), notification.smallIcon, notification.color) - insertAppIcon(appName, it.packageName) - } - } + if (sbn == null) return + val notification = sbn.notification + + val appName = NameGetter.getAppName(this, sbn) + val title = notification.extras.getString("android.title") ?: "" + val subText = notification.extras.getString("android.subText") ?: "" + val content = notification.extras.getString("android.text") ?: "" + val postTime = sbn.postTime + + CoroutineScope(Dispatchers.IO).launch { + if (title == "" && content == "" && subText == "") return@launch + + val filteredList = filterRepository.getSpecificFilteredList(appName, title, subText) + if (filteredList.isEmpty()) return@launch + + mutex.withLock { + val id = insertNotification(appName, title, content, postTime, subText) + insertNotificationMeta(id, sbn.packageName) + insertNotificationIcon(id, notification.getLargeIcon(), notification.smallIcon, notification.color) + insertAppIcon(appName, sbn.packageName) } } } + private suspend fun insertNotification( appName: String, title: String, diff --git a/app/src/main/java/com/example/notimanager/domain/usecase/FilteredNotificationUseCase.kt b/app/src/main/java/com/example/notimanager/domain/usecase/FilteredNotificationUseCase.kt new file mode 100644 index 0000000..ad9b826 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/domain/usecase/FilteredNotificationUseCase.kt @@ -0,0 +1,20 @@ +package com.example.notimanager.domain.usecase + +import com.example.notimanager.domain.model.FilteredNotification +import com.example.notimanager.domain.repository.FilteredNotificationRepositoryInterface + +class FilteredNotificationUseCase( + private val repository: FilteredNotificationRepositoryInterface +){ + suspend fun insertFiltered(appName: String, title: String): Long{ + return repository.insertFiltered(appName, title) + } + + suspend fun getFilteredList(): List{ + return repository.getFilteredList() + } + + suspend fun deleteById(id: Long): Int{ + return repository.deleteById(id) + } +} \ No newline at end of file From 78be7fbc079999379b2e74fd79609468812be77c Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Mon, 3 Mar 2025 13:40:55 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=20viewmodel=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../state/FilteredNotificationState.kt | 9 ++++ .../FilteredNotificationViewModel.kt | 48 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 app/src/main/java/com/example/notimanager/presentation/stateholder/state/FilteredNotificationState.kt create mode 100644 app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/FilteredNotificationViewModel.kt diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/state/FilteredNotificationState.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/state/FilteredNotificationState.kt new file mode 100644 index 0000000..033db01 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/state/FilteredNotificationState.kt @@ -0,0 +1,9 @@ +package com.example.notimanager.presentation.stateholder.state + +import com.example.notimanager.domain.model.FilteredNotification + +data class FilteredNotificationState( + val filteredList: List = emptyList(), + val isLoading: Boolean = false, + val error: String? = null +) diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/FilteredNotificationViewModel.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/FilteredNotificationViewModel.kt new file mode 100644 index 0000000..2cb0f98 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/FilteredNotificationViewModel.kt @@ -0,0 +1,48 @@ +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.FilteredNotificationUseCase +import com.example.notimanager.presentation.stateholder.state.FilteredNotificationState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FilteredNotificationViewModel @Inject constructor( + private val filteredNotificationUseCase: FilteredNotificationUseCase +): ViewModel() { + private val _filteredNotiState = MutableLiveData() + val filteredNotiState: LiveData get() = _filteredNotiState + + init { + loadFilteredNoti() + } + + fun loadFilteredNoti() { + viewModelScope.launch { + _filteredNotiState.value = FilteredNotificationState(isLoading = true) + + try { + val filteredList = filteredNotificationUseCase.getFilteredList() + _filteredNotiState.value = FilteredNotificationState(filteredList = filteredList) + } catch (e: Exception) { + _filteredNotiState.value = FilteredNotificationState(error = e.message) + } + } + } + + fun insertFilteredNoti(appNane: String, title: String) { + viewModelScope.launch { + filteredNotificationUseCase.insertFiltered(appNane, title) + } + } + + fun deleteFilteredNoti(id: Long) { + viewModelScope.launch { + filteredNotificationUseCase.deleteById(id) + } + } +} \ No newline at end of file From 318f133d90a73a2e83c353b311ea1a8926120f9c Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:22:27 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=EC=9D=98=EB=8F=84=ED=95=98=EC=A7=80?= =?UTF-8?q?=20=EC=95=8A=EC=9D=80=20=EC=95=8C=EB=A6=BC=EC=9D=84=20=EB=AC=B4?= =?UTF-8?q?=EC=8B=9C=ED=95=98=EB=8A=94=20=ED=98=84=EC=83=81=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 제외 리스트가 비어 있지 않아야 무시. --- .../example/notimanager/domain/service/NotiListenerService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/notimanager/domain/service/NotiListenerService.kt b/app/src/main/java/com/example/notimanager/domain/service/NotiListenerService.kt index 7696ab8..3444cf4 100644 --- a/app/src/main/java/com/example/notimanager/domain/service/NotiListenerService.kt +++ b/app/src/main/java/com/example/notimanager/domain/service/NotiListenerService.kt @@ -3,6 +3,7 @@ package com.example.notimanager.domain.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 @@ -43,7 +44,7 @@ class NotiListenerService: NotificationListenerService() { if (title == "" && content == "" && subText == "") return@launch val filteredList = filterRepository.getSpecificFilteredList(appName, title, subText) - if (filteredList.isEmpty()) return@launch + if (filteredList.isNotEmpty()) return@launch mutex.withLock { val id = insertNotification(appName, title, content, postTime, subText) From bd696247d9e28f66ab51b46ed56e192b7be5b054 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:24:07 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20UI=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 알림 제외 리스트 목록을 보기 위한 페이지 추가. 해당 페이지로 들어가기 위해 설정 페이지 추가. --- .../FilteredNotificationViewModel.kt | 6 +- .../ui/component/FilteredListView.kt | 107 ++++++++++++++++++ .../presentation/ui/component/SettingView.kt | 23 ++++ .../presentation/ui/component/TopAppBar.kt | 40 ++++++- .../presentation/ui/navigation/AppNavHost.kt | 8 ++ .../ui/screen/FilteredListScreen.kt | 24 ++++ .../presentation/ui/screen/MainScreen.kt | 4 +- .../presentation/ui/screen/SettingScreen.kt | 20 ++++ 8 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/example/notimanager/presentation/ui/component/FilteredListView.kt create mode 100644 app/src/main/java/com/example/notimanager/presentation/ui/component/SettingView.kt create mode 100644 app/src/main/java/com/example/notimanager/presentation/ui/screen/FilteredListScreen.kt create mode 100644 app/src/main/java/com/example/notimanager/presentation/ui/screen/SettingScreen.kt diff --git a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/FilteredNotificationViewModel.kt b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/FilteredNotificationViewModel.kt index 2cb0f98..7ea66f4 100644 --- a/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/FilteredNotificationViewModel.kt +++ b/app/src/main/java/com/example/notimanager/presentation/stateholder/viewmodel/FilteredNotificationViewModel.kt @@ -34,15 +34,17 @@ class FilteredNotificationViewModel @Inject constructor( } } - fun insertFilteredNoti(appNane: String, title: String) { + fun insertFilteredNoti(appNane: String, title: String, onComplete: () -> Unit) { viewModelScope.launch { filteredNotificationUseCase.insertFiltered(appNane, title) + onComplete() } } - fun deleteFilteredNoti(id: Long) { + fun deleteFilteredNoti(id: Long, onComplete: () -> Unit) { viewModelScope.launch { filteredNotificationUseCase.deleteById(id) + onComplete() } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/component/FilteredListView.kt b/app/src/main/java/com/example/notimanager/presentation/ui/component/FilteredListView.kt new file mode 100644 index 0000000..84abb73 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/ui/component/FilteredListView.kt @@ -0,0 +1,107 @@ +package com.example.notimanager.presentation.ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +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.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 +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.example.notimanager.domain.model.FilteredNotification +import com.example.notimanager.presentation.stateholder.state.FilteredNotificationState +import com.example.notimanager.presentation.stateholder.viewmodel.FilteredNotificationViewModel + +@Composable +fun FilteredListView( + innerPadding: PaddingValues, + viewModel: FilteredNotificationViewModel +) { + val filteredNotificationState by viewModel.filteredNotiState.observeAsState(FilteredNotificationState()) + if (filteredNotificationState.filteredList.isEmpty()){ + Text("No notifications") + } + else{ + LazyColumn( + Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + + items(filteredNotificationState.filteredList) { item -> + FilteredItemView(item, viewModel) + } + } + } + +} + +@Composable +fun FilteredItemView( + filteredItem: FilteredNotification, + viewModel: FilteredNotificationViewModel, +) { + var showModal by remember { mutableStateOf(false) } + + Row( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + .clickable(onClick = {showModal = true}) + , + verticalAlignment = Alignment.CenterVertically + ) { +// AppIconView(filteredItem.appIcon) + Spacer(modifier = Modifier.width(8.dp)) + Column( + modifier = Modifier + .weight(1f) + ) { + Text( + text = filteredItem.appName, + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)) + Text( + text = filteredItem.title, + style = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Bold) + ) + } + } + if (showModal) { + BottomSheet(showModal, onDismiss = { showModal = false }){ + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = filteredItem.appName, + style = MaterialTheme.typography.labelLarge, + color = Color.Gray + ) + + ClickableTextView(text = "제외 리스트에서 삭제", onClick = { + viewModel.deleteFilteredNoti(filteredItem.id){ + viewModel.loadFilteredNoti() + } + showModal = false + }) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/component/SettingView.kt b/app/src/main/java/com/example/notimanager/presentation/ui/component/SettingView.kt new file mode 100644 index 0000000..ff888d0 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/ui/component/SettingView.kt @@ -0,0 +1,23 @@ +package com.example.notimanager.presentation.ui.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavController + +@Composable +fun SettingView( + innerPadding: PaddingValues, + navController: NavController +){ + Column( + modifier = Modifier + .padding(innerPadding) + ) { + ClickableTextView("알림 제외 리스트") { + navController.navigate("FilteredListScreen") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/component/TopAppBar.kt b/app/src/main/java/com/example/notimanager/presentation/ui/component/TopAppBar.kt index 7aba4bc..08132cc 100644 --- a/app/src/main/java/com/example/notimanager/presentation/ui/component/TopAppBar.kt +++ b/app/src/main/java/com/example/notimanager/presentation/ui/component/TopAppBar.kt @@ -2,6 +2,7 @@ package com.example.notimanager.presentation.ui.component import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -15,11 +16,16 @@ import androidx.compose.ui.tooling.preview.Preview @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MainTopAppBar(){ +fun MainTopAppBar(settingOnClick: () -> Unit){ TopAppBar( title = { Text(text = "Notification Manager") }, + navigationIcon = { + IconButton(onClick = settingOnClick) { + Icon(Icons.Filled.Menu, contentDescription = "설정") + } + }, actions = { IconButton(onClick = { /* TODO: 클릭 이벤트 처리 */ }) { Icon(Icons.Filled.MoreVert, contentDescription = "더 보기") @@ -73,10 +79,40 @@ fun NotificationTopAppBar(title: String, onBackClick: () -> Unit){ }) } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingTopAppBar(onBackClick: () -> Unit){ + TopAppBar( + title = { + Text(text = "설정") + }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "뒤로 가기") + } + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FilteredTopAppBar(onBackClick: () -> Unit){ + TopAppBar( + title = { + Text(text = "알림 제외 리스트") + }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "뒤로 가기") + } + } + ) +} + @Preview(backgroundColor = 1) @Composable fun PreviewMainTopAppBar(){ MaterialTheme{ - MainTopAppBar() + MainTopAppBar({}) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/navigation/AppNavHost.kt b/app/src/main/java/com/example/notimanager/presentation/ui/navigation/AppNavHost.kt index 141022a..354adf5 100644 --- a/app/src/main/java/com/example/notimanager/presentation/ui/navigation/AppNavHost.kt +++ b/app/src/main/java/com/example/notimanager/presentation/ui/navigation/AppNavHost.kt @@ -5,9 +5,11 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.example.notimanager.common.objects.Encoder.getDecodeString +import com.example.notimanager.presentation.ui.screen.FilteredListScreen import com.example.notimanager.presentation.ui.screen.MainScreen import com.example.notimanager.presentation.ui.screen.NotificationScreen import com.example.notimanager.presentation.ui.screen.NotificationSubScreen +import com.example.notimanager.presentation.ui.screen.SettingScreen import com.example.notimanager.presentation.ui.screen.TitleScreen @Composable @@ -19,6 +21,9 @@ fun AppNavHost(navController: NavHostController) { composable("mainScreen") { MainScreen(navController) } + composable("SettingScreen") { + SettingScreen(navController) + } composable("titleScreen/{appName}") { backStackEntry -> val appName = backStackEntry.arguments?.getString("appName") TitleScreen(navController, appName!!) @@ -30,5 +35,8 @@ fun AppNavHost(navController: NavHostController) { if (isSubText == "False") NotificationScreen(navController, appName!!, getDecodeString(notiName!!)) else NotificationSubScreen(navController, appName!!, getDecodeString(notiName!!)) } + composable("FilteredListScreen") { + FilteredListScreen(navController) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/screen/FilteredListScreen.kt b/app/src/main/java/com/example/notimanager/presentation/ui/screen/FilteredListScreen.kt new file mode 100644 index 0000000..589ae87 --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/ui/screen/FilteredListScreen.kt @@ -0,0 +1,24 @@ +package com.example.notimanager.presentation.ui.screen + +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.example.notimanager.presentation.stateholder.viewmodel.FilteredNotificationViewModel +import com.example.notimanager.presentation.ui.component.FilteredListView +import com.example.notimanager.presentation.ui.component.FilteredTopAppBar + + +@Composable +fun FilteredListScreen( + navController: NavController, + viewModel: FilteredNotificationViewModel = hiltViewModel() +) { + Scaffold( + topBar = { + FilteredTopAppBar{ navController.popBackStack() } + } + ) { innerPadding -> + FilteredListView(innerPadding, viewModel) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/screen/MainScreen.kt b/app/src/main/java/com/example/notimanager/presentation/ui/screen/MainScreen.kt index 70ba6cc..28cde25 100644 --- a/app/src/main/java/com/example/notimanager/presentation/ui/screen/MainScreen.kt +++ b/app/src/main/java/com/example/notimanager/presentation/ui/screen/MainScreen.kt @@ -54,7 +54,9 @@ fun MainScreen( Scaffold( topBar = { - MainTopAppBar() + MainTopAppBar{ + navController.navigate("SettingScreen") + } } ) { innerPadding -> HorizontalDivider( diff --git a/app/src/main/java/com/example/notimanager/presentation/ui/screen/SettingScreen.kt b/app/src/main/java/com/example/notimanager/presentation/ui/screen/SettingScreen.kt new file mode 100644 index 0000000..fa8552f --- /dev/null +++ b/app/src/main/java/com/example/notimanager/presentation/ui/screen/SettingScreen.kt @@ -0,0 +1,20 @@ +package com.example.notimanager.presentation.ui.screen + +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.navigation.NavController +import com.example.notimanager.presentation.ui.component.SettingTopAppBar +import com.example.notimanager.presentation.ui.component.SettingView + +@Composable +fun SettingScreen( + navController: NavController, +) { + Scaffold( + topBar = { + SettingTopAppBar{ navController.popBackStack() } + } + ) { innerPadding -> + SettingView(innerPadding, navController) + } +} \ No newline at end of file From db4332a49230bd7f62dfafc84a986cf41e63cad9 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:25:26 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8,=20=EC=A0=9C=EB=AA=A9=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=95=8C=EB=A6=BC=20=EC=A0=9C=EC=99=B8=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 메인, 제목 페이지에서 원하는 알림을 제외, 제외 취소할 수 있도록 data, domain 계층 수정 Bottom Sheet에서 원하는 기능을 수행할 수 있음. --- .../data/dto/NotificationAppDto.kt | 6 ++- .../data/dto/NotificationTitleDto.kt | 6 ++- .../data/source/local/dao/NotificationDao.kt | 9 ++-- .../domain/model/NotificationApp.kt | 3 +- .../domain/model/NotificationTitle.kt | 3 +- .../ui/component/NotificationAppListView.kt | 20 +++++++-- .../ui/component/NotificationTitleListView.kt | 45 ++++++++++++++++++- 7 files changed, 79 insertions(+), 13 deletions(-) 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 49c238d..8760e66 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 @@ -10,7 +10,8 @@ data class NotificationAppDto( val timestamp: Long, val iconBytes: ByteArray, val priorityActive: Boolean, - val priority: Int + val priority: Int, + val filteredId: Long = 0L ){ fun toDomain(): NotificationApp { return NotificationApp( @@ -20,7 +21,8 @@ data class NotificationAppDto( timestamp = this.timestamp, appIcon = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.size), priorityActive = this.priorityActive, - priority = this.priority + priority = this.priority, + filteredId = this.filteredId ) } } 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 8c207d0..a0a8b59 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 @@ -11,7 +11,8 @@ data class NotificationTitleDto( val timestamp: Long, val iconBytes: ByteArray, val priorityActive: Boolean, - val priority: Int + val priority: Int, + val filteredId: Long = 0L ) { fun toDomain(): NotificationTitle { return NotificationTitle( @@ -22,7 +23,8 @@ data class NotificationTitleDto( timestamp = this.timestamp, notificationIcon = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.size), priorityActive = this.priorityActive, - priority = this.priority + priority = this.priority, + filteredId = this.filteredId ) } } \ 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 427f3f9..209c531 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 @@ -17,9 +17,10 @@ interface NotificationDao { @Query( """ - SELECT n1.appName, n1.title, n1.content, n1.timestamp, ai.iconBytes, ai.priorityActive, ai.priority + SELECT n1.appName, n1.title, n1.content, n1.timestamp, ai.iconBytes, ai.priorityActive, ai.priority, fn.id AS filteredId FROM notification AS n1 INNER JOIN app_icon AS ai ON n1.appName = ai.notiAppName + LEFT OUTER JOIN filtered_notification AS fn ON n1.appName = fn.appName AND fn.title = "" WHERE ai.priorityActive = :priorityActive AND timestamp = ( SELECT MAX(timestamp) @@ -33,9 +34,10 @@ interface NotificationDao { @Query( """ - SELECT n1.id, n1.title, n1.subText, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority + SELECT n1.id, n1.title, n1.subText, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority, fn.id AS filteredId FROM notification AS n1 INNER JOIN notification_icon AS ni ON n1.id = ni.notificationId + LEFT OUTER JOIN filtered_notification AS fn ON n1.appName = fn.appName AND fn.title = n1.title WHERE ni.priorityActive = :priorityActive AND n1.appName = :appName AND n1.subText = "" @@ -53,9 +55,10 @@ interface NotificationDao { UNION ALL - SELECT n1.id, n1.title, n1.subText, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority + SELECT n1.id, n1.title, n1.subText, n1.content, n1.timestamp, ni.iconBytes, ni.priorityActive, ni.priority, fn.id AS filteredId FROM notification AS n1 INNER JOIN notification_icon AS ni ON n1.id = ni.notificationId + LEFT OUTER JOIN filtered_notification AS fn ON n1.appName = fn.appName AND fn.title = n1.subText WHERE ni.priorityActive = :priorityActive AND n1.appName = :appName AND n1.subText != "" 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 a3b4a74..5d67481 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 @@ -9,5 +9,6 @@ data class NotificationApp( val timestamp: Long, val appIcon: Bitmap?, val priorityActive: Boolean, - val priority: Int + val priority: Int, + val filteredId: Long ) 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 f99af72..6fc9171 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 @@ -10,5 +10,6 @@ data class NotificationTitle( val timestamp: Long, val notificationIcon: Bitmap?, val priorityActive: Boolean, - val priority: Int + val priority: Int, + val filteredId: Long ) 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 2a7c4e0..0a856b1 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 @@ -37,6 +37,7 @@ 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.FilteredNotificationViewModel import com.example.notimanager.presentation.stateholder.viewmodel.NotificationAppPriorityViewModel import com.example.notimanager.presentation.stateholder.viewmodel.NotificationAppViewModel @@ -105,8 +106,8 @@ fun NotificationAppItemView( notification: NotificationApp, onClick: () -> Unit, viewModel: NotificationAppViewModel, - priorityViewModel: NotificationAppPriorityViewModel - + priorityViewModel: NotificationAppPriorityViewModel, + filteredNotificationViewModel: FilteredNotificationViewModel = hiltViewModel() ) { var showModal by remember { mutableStateOf(false) } @@ -179,6 +180,18 @@ fun NotificationAppItemView( viewModel.deleteNotificationApp(notification.appName) showModal = false }) + + ClickableTextView(text = if (notification.filteredId == 0L) "알림 제외 리스트에 추가" else "알림 제외 리스트에서 제거", onClick = { + if (notification.filteredId == 0L) filteredNotificationViewModel.insertFilteredNoti(notification.appName, ""){ + viewModel.loadNotificationApps() + priorityViewModel.loadNotificationAppPriority() + } + else filteredNotificationViewModel.deleteFilteredNoti(notification.filteredId){ + viewModel.loadNotificationApps() + priorityViewModel.loadNotificationAppPriority() + } + showModal = false + }) } } } @@ -196,7 +209,8 @@ fun PreviewNotificationAppItemView(){ timestamp = 1234567890, appIcon = null, priorityActive = false, - priority = 0 + priority = 0, + filteredId = 0L ), onClick = {}, viewModel = hiltViewModel(), priorityViewModel = hiltViewModel() 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 e8cecbc..f7bd96d 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 @@ -30,12 +30,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.example.notimanager.common.objects.DateFormatter.formatTimestamp import com.example.notimanager.common.objects.Encoder.getEncodedString 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.FilteredNotificationViewModel import com.example.notimanager.presentation.stateholder.viewmodel.NotificationTitlePriorityViewModel import com.example.notimanager.presentation.stateholder.viewmodel.NotificationTitleViewModel @@ -96,7 +98,8 @@ fun NotificationTitleItemView( notification: NotificationTitle, onClick: () -> Unit, viewModel: NotificationTitleViewModel, - priorityViewModel: NotificationTitlePriorityViewModel + priorityViewModel: NotificationTitlePriorityViewModel, + filteredNotificationViewModel: FilteredNotificationViewModel = hiltViewModel() ) { var showModal by remember { mutableStateOf(false) } Row( @@ -168,6 +171,46 @@ fun NotificationTitleItemView( viewModel.deleteBySubText(notification.subText) { priorityViewModel.loadNotificationTitles() } showModal = false }) + + val onComplete: () -> Unit = { + viewModel.loadNotificationTitles() + priorityViewModel.loadNotificationTitles() + } + + if(notification.filteredId == 0L) { + ClickableTextView( + text = "이 방 알림 무시하기", + onClick = { + if (notification.subText == "") { + filteredNotificationViewModel.insertFilteredNoti( + viewModel.getAppName(), + notification.title, + onComplete + ) + } + else{ + filteredNotificationViewModel.insertFilteredNoti( + viewModel.getAppName(), + notification.subText, + onComplete + ) + } + showModal = false + } + ) + }else{ + ClickableTextView( + text = "이 방 알림 계속 받기", + onClick = { + if (notification.subText == "") + filteredNotificationViewModel.deleteFilteredNoti(notification.filteredId, onComplete) + + else + filteredNotificationViewModel.deleteFilteredNoti(notification.filteredId, onComplete) + showModal = false + } + ) + } } } } From d58840a8ed9532039215e354bf5844053d5b7646 Mon Sep 17 00:00:00 2001 From: Richter3766 <97567615+Richter3766@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:30:54 +0900 Subject: [PATCH 7/7] =?UTF-8?q?test:=20domain=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20json=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../json/data/dto/NotificationAppDto.json | 15 ++++++++++----- .../json/data/dto/NotificationTitleDto.json | 15 ++++++++++----- .../json/domain/model/NotificationApp.json | 15 ++++++++++----- .../json/domain/model/NotificationTitle.json | 15 ++++++++++----- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/app/src/test/resources/json/data/dto/NotificationAppDto.json b/app/src/test/resources/json/data/dto/NotificationAppDto.json index 017cfe1..9175885 100644 --- a/app/src/test/resources/json/data/dto/NotificationAppDto.json +++ b/app/src/test/resources/json/data/dto/NotificationAppDto.json @@ -6,7 +6,8 @@ "timestamp": 1672531199000, "iconBytes": [6, 7, 8, 9, 10], "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 }, { "appName": "Weather", @@ -15,7 +16,8 @@ "timestamp": 1672531299000, "iconBytes": [6, 7, 8, 9, 10], "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 }, { "appName": "Email", @@ -24,7 +26,8 @@ "timestamp": 1672531399000, "iconBytes": [6, 7, 8, 9, 10], "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 }, { "appName": "Calendar", @@ -33,7 +36,8 @@ "timestamp": 1672531499000, "iconBytes": [6, 7, 8, 9, 10], "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 }, { "appName": "News", @@ -42,6 +46,7 @@ "timestamp": 1672531599000, "iconBytes": [6, 7, 8, 9, 10], "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 } ] diff --git a/app/src/test/resources/json/data/dto/NotificationTitleDto.json b/app/src/test/resources/json/data/dto/NotificationTitleDto.json index e4dbd99..e239653 100644 --- a/app/src/test/resources/json/data/dto/NotificationTitleDto.json +++ b/app/src/test/resources/json/data/dto/NotificationTitleDto.json @@ -4,34 +4,39 @@ "content": "You have received a new message from John.", "subText": "", "timestamp": 1672531199000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "filteredId": 0 }, { "title": "Weather Update", "content": "Rain is expected in your area today.", "subText": "", "timestamp": 1672531299000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "filteredId": 0 }, { "title": "New Email", "content": "You've got a new email from support@example.com.", "subText": "", "timestamp": 1672531399000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "filteredId": 0 }, { "title": "Event Reminder", "content": "Don't forget your meeting at 3 PM today.", "subText": "", "timestamp": 1672531499000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "filteredId": 0 }, { "title": "Breaking News", "content": "New developments in the tech industry.", "subText": "", "timestamp": 1672531599000, - "iconBytes": [6, 7, 8, 9, 10] + "iconBytes": [6, 7, 8, 9, 10], + "filteredId": 0 } ] diff --git a/app/src/test/resources/json/domain/model/NotificationApp.json b/app/src/test/resources/json/domain/model/NotificationApp.json index d270dd5..57d586e 100644 --- a/app/src/test/resources/json/domain/model/NotificationApp.json +++ b/app/src/test/resources/json/domain/model/NotificationApp.json @@ -6,7 +6,8 @@ "timestamp": 1672531199000, "appIcon": null, "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 }, { "appName": "Weather", @@ -15,7 +16,8 @@ "timestamp": 1672531299000, "appIcon": null, "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 }, { "appName": "Email", @@ -24,7 +26,8 @@ "timestamp": 1672531399000, "appIcon": null, "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 }, { "appName": "Calendar", @@ -33,7 +36,8 @@ "timestamp": 1672531499000, "appIcon": null, "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 }, { "appName": "News", @@ -42,6 +46,7 @@ "timestamp": 1672531599000, "appIcon": null, "priorityActive": true, - "priority": 1 + "priority": 1, + "filteredId": 0 } ] diff --git a/app/src/test/resources/json/domain/model/NotificationTitle.json b/app/src/test/resources/json/domain/model/NotificationTitle.json index 0892112..1676801 100644 --- a/app/src/test/resources/json/domain/model/NotificationTitle.json +++ b/app/src/test/resources/json/domain/model/NotificationTitle.json @@ -4,34 +4,39 @@ "content": "You have received a new message from John.", "subText": "", "timestamp": 1672531199000, - "notificationIcon": null + "notificationIcon": null, + "filteredId": 0 }, { "title": "Weather Update", "content": "Rain is expected in your area today.", "subText": "", "timestamp": 1672531299000, - "notificationIcon": null + "notificationIcon": null, + "filteredId": 0 }, { "title": "New Email", "content": "You've got a new email from support@example.com.", "subText": "", "timestamp": 1672531399000, - "notificationIcon": null + "notificationIcon": null, + "filteredId": 0 }, { "title": "Event Reminder", "content": "Don't forget your meeting at 3 PM today.", "subText": "", "timestamp": 1672531499000, - "notificationIcon": null + "notificationIcon": null, + "filteredId": 0 }, { "title": "Breaking News", "content": "New developments in the tech industry.", "subText": "", "timestamp": 1672531599000, - "notificationIcon": null + "notificationIcon": null, + "filteredId": 0 } ]