From 6e455da45d4996443301a8e80abab43234c31ab3 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 16 Sep 2023 18:15:49 +0900 Subject: [PATCH 01/38] =?UTF-8?q?[Feat/#341]=20Notification=20API=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationSubscriptionRequest.kt | 11 +++ .../NotificationHistoryItemResponse.kt | 22 ++++++ .../NotificationReadingStateResponse.kt | 11 +++ .../NotificationSubscriptionResponse.kt | 10 +++ .../UnreadNotificationExistenceResponse.kt | 8 +++ .../notfication/NotificationRepositoryImpl.kt | 70 +++++++++++++++++++ .../notification/NotificationService.kt | 54 ++++++++++++++ .../sopt/official/di/NotificationModule.kt | 33 +++++++++ .../notification/NotificationRepository.kt | 27 +++++++ .../notification/DeletePushTokenUseCase.kt | 12 ++++ .../GetNotificationHistoryUseCase.kt | 13 ++++ .../GetNotificationSubscriptionUseCase.kt | 13 ++++ .../GetUnreadNotificationExistenceUseCase.kt | 13 ++++ .../notification/RegisterPushTokenUseCase.kt | 12 ++++ ...teEntireNotificationReadingStateUseCase.kt | 13 ++++ .../UpdateNotificationReadingStateUseCase.kt | 13 ++++ .../UpdateNotificationSubscriptionUseCase.kt | 18 +++++ 17 files changed, 353 insertions(+) create mode 100644 app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt create mode 100644 app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt create mode 100644 app/src/main/java/org/sopt/official/data/model/notification/response/NotificationReadingStateResponse.kt create mode 100644 app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt create mode 100644 app/src/main/java/org/sopt/official/data/model/notification/response/UnreadNotificationExistenceResponse.kt create mode 100644 app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt create mode 100644 app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt create mode 100644 app/src/main/java/org/sopt/official/di/NotificationModule.kt create mode 100644 app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationSubscriptionUseCase.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/GetUnreadNotificationExistenceUseCase.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/RegisterPushTokenUseCase.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateEntireNotificationReadingStateUseCase.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationReadingStateUseCase.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt diff --git a/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt b/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt new file mode 100644 index 000000000..9b87d7644 --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt @@ -0,0 +1,11 @@ +package org.sopt.official.data.model.notification.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class NotificationSubscriptionRequest( + @SerialName("allOptIn") val allOptIn: Boolean? = null, + @SerialName("partOptIn") val partOptIn: Boolean? = null, + @SerialName("newsOptIn") val newsOptIn: Boolean? = null +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt new file mode 100644 index 000000000..c8baa51c9 --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt @@ -0,0 +1,22 @@ +package org.sopt.official.data.model.notification.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class NotificationHistoryItemResponse( + @SerialName("") + val list: ArrayList +) + +@Serializable +data class NotificationHistoryItem( + val id: Int, + val userId: Int, + val title: String, + val content: String, + val type: String?, + val isRead: Boolean, + val createdAt: String, + val updatedAt: String, +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationReadingStateResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationReadingStateResponse.kt new file mode 100644 index 000000000..271dd578d --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationReadingStateResponse.kt @@ -0,0 +1,11 @@ +package org.sopt.official.data.model.notification.response + +import kotlinx.serialization.Serializable + +@Serializable +data class NotificationReadingStateResponse( + val id: Int, + val isRead: Boolean, + val createdAt: String, + val updatedAt: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt new file mode 100644 index 000000000..a4f0f02ec --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt @@ -0,0 +1,10 @@ +package org.sopt.official.data.model.notification.response + +import kotlinx.serialization.Serializable + +@Serializable +data class NotificationSubscriptionResponse( + val allOptIn: Boolean, + val partOptIn: Boolean, + val newsOptIn: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/UnreadNotificationExistenceResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/UnreadNotificationExistenceResponse.kt new file mode 100644 index 000000000..2b596873b --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/UnreadNotificationExistenceResponse.kt @@ -0,0 +1,8 @@ +package org.sopt.official.data.model.notification.response + +import kotlinx.serialization.Serializable + +@Serializable +data class UnreadNotificationExistenceResponse( + val exists: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt new file mode 100644 index 000000000..5982d1018 --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -0,0 +1,70 @@ +package org.sopt.official.data.repository.notfication + +import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse +import org.sopt.official.data.service.notification.NotificationService +import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse +import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse +import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class NotificationRepositoryImpl @Inject constructor( + private val service: NotificationService +) : NotificationRepository { + + override suspend fun registerToken(pushToken: String): Result { + return runCatching { + service.registerToken(pushToken) + } + } + + override suspend fun deleteToken(): Result { + return runCatching { + service.deleteToken() + } + } + + override suspend fun getNotificationHistory( + page: Int + ): Result { + return runCatching { + service.getNotificationHistory(page) + } + } + + override suspend fun getUnreadNotificationExistence(): Result { + return runCatching { + service.getUnreadNotificationExistence() + } + } + + override suspend fun updateNotificationReadingState( + notificationId: Int + ): Result { + return runCatching { + service.updateNotificationReadingState(notificationId) + } + } + + override suspend fun updateEntireNotificationReadingState(): Result { + return runCatching { + service.updateEntireNotificationReadingState() + } + } + + override suspend fun getNotificationSubscription(): Result { + return runCatching { + service.getNotificationSubscription() + } + } + + override suspend fun updateNotificationSubscription( + notificationSubscriptionRequest: NotificationSubscriptionRequest + ): Result { + return runCatching { + service.updateNotificationSubscription(notificationSubscriptionRequest) + } + } + +} diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt new file mode 100644 index 000000000..8132cba3f --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -0,0 +1,54 @@ +package org.sopt.official.data.service.notification + +import org.sopt.official.data.model.attendance.* +import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse +import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse +import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse +import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.PATCH +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query + +interface NotificationService { + @POST("user/push-token") + suspend fun registerToken( + @Path("token") token: String + ) + + @DELETE("user/push-token") + suspend fun deleteToken() + + + @GET("notification") + suspend fun getNotificationHistory( + @Query("page") page: Int + ): NotificationHistoryItemResponse + + @GET("notification/main") + suspend fun getUnreadNotificationExistence( + ): UnreadNotificationExistenceResponse + + @PATCH("notification/{notificationId}") + suspend fun updateNotificationReadingState( + @Path("notificationId") notificationId: Int + ): NotificationReadingStateResponse + + @PATCH("notification/0") + suspend fun updateEntireNotificationReadingState( + ): NotificationReadingStateResponse + + + @GET("user/opt-in") + suspend fun getNotificationSubscription( + ): NotificationSubscriptionResponse + + @PATCH("user/opt-in") + suspend fun updateNotificationSubscription( + @Body body: NotificationSubscriptionRequest + ): NotificationSubscriptionResponse +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/di/NotificationModule.kt b/app/src/main/java/org/sopt/official/di/NotificationModule.kt new file mode 100644 index 000000000..2f391179c --- /dev/null +++ b/app/src/main/java/org/sopt/official/di/NotificationModule.kt @@ -0,0 +1,33 @@ +package org.sopt.official.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.sopt.official.core.di.AppRetrofit +import org.sopt.official.data.repository.notfication.NotificationRepositoryImpl +import org.sopt.official.data.service.notification.NotificationService +import org.sopt.official.domain.repository.notification.NotificationRepository +import retrofit2.Retrofit +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object NotificationModule { + + @Provides + @Singleton + fun provideFcmService( + @AppRetrofit retrofit: Retrofit + ): NotificationService { + return retrofit.create(NotificationService::class.java) + } + + @Provides + @Singleton + fun provideNotificationRepository( + repository: NotificationRepositoryImpl + ): NotificationRepository { + return repository + } +} diff --git a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt new file mode 100644 index 000000000..838e68a91 --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt @@ -0,0 +1,27 @@ +package org.sopt.official.domain.repository.notification + +import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse +import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse +import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse +import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse + +interface NotificationRepository { + + suspend fun registerToken(pushToken: String): Result + suspend fun deleteToken(): Result + + suspend fun getNotificationHistory( + page: Int + ): Result + suspend fun getUnreadNotificationExistence(): Result + suspend fun updateNotificationReadingState( + notificationId: Int + ): Result + suspend fun updateEntireNotificationReadingState(): Result + + suspend fun getNotificationSubscription(): Result + suspend fun updateNotificationSubscription( + notificationSubscriptionRequest: NotificationSubscriptionRequest + ): Result +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt new file mode 100644 index 000000000..00db95adb --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt @@ -0,0 +1,12 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class DeletePushTokenUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke(): Result { + return notificationRepository.deleteToken() + } +} diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt new file mode 100644 index 000000000..75514e81c --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class GetNotificationHistoryUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke(page: Int): Result> { + return notificationRepository.getNotificationHistory(page) + } +} diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationSubscriptionUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationSubscriptionUseCase.kt new file mode 100644 index 000000000..7b57f6cac --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationSubscriptionUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class GetNotificationSubscriptionUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke(): Result { + return notificationRepository.getNotificationSubscription() + } +} diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetUnreadNotificationExistenceUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetUnreadNotificationExistenceUseCase.kt new file mode 100644 index 000000000..87fbdfdfd --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetUnreadNotificationExistenceUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class GetUnreadNotificationExistenceUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke(): Result { + return notificationRepository.getUnreadNotificationExistence() + } +} diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/RegisterPushTokenUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/RegisterPushTokenUseCase.kt new file mode 100644 index 000000000..93e7f2fa6 --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/RegisterPushTokenUseCase.kt @@ -0,0 +1,12 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class RegisterPushTokenUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke(pushToken: String): Result { + return notificationRepository.registerToken(pushToken) + } +} diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateEntireNotificationReadingStateUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateEntireNotificationReadingStateUseCase.kt new file mode 100644 index 000000000..bd157f5db --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateEntireNotificationReadingStateUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class UpdateEntireNotificationReadingStateUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke(): Result { + return notificationRepository.updateEntireNotificationReadingState() + } +} diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationReadingStateUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationReadingStateUseCase.kt new file mode 100644 index 000000000..d0f590b02 --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationReadingStateUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class UpdateNotificationReadingStateUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke(notificationId: Int): Result { + return notificationRepository.updateNotificationReadingState(notificationId) + } +} diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt new file mode 100644 index 000000000..91fa3908b --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt @@ -0,0 +1,18 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest +import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class UpdateNotificationSubscriptionUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke( + notificationSubscriptionRequest: NotificationSubscriptionRequest + ): Result { + return notificationRepository.updateNotificationSubscription( + notificationSubscriptionRequest + ) + } +} From a46173e2cf2461d1e630505cf742279abb0f793d Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 16 Sep 2023 02:16:54 +0900 Subject: [PATCH 02/38] =?UTF-8?q?[Feat/#341]=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=B7=B0=EC=97=90=20=EB=85=B8=ED=8B=B0=ED=94=BC=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B1=83=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/icon_notification.xml | 13 +++++++++++ .../main/res/layout/activity_sopt_main.xml | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 app/src/main/res/drawable/icon_notification.xml diff --git a/app/src/main/res/drawable/icon_notification.xml b/app/src/main/res/drawable/icon_notification.xml new file mode 100644 index 000000000..908bfd243 --- /dev/null +++ b/app/src/main/res/drawable/icon_notification.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/layout/activity_sopt_main.xml b/app/src/main/res/layout/activity_sopt_main.xml index 4d4c061b8..8dcd14b55 100644 --- a/app/src/main/res/layout/activity_sopt_main.xml +++ b/app/src/main/res/layout/activity_sopt_main.xml @@ -28,6 +28,29 @@ app:layout_constraintStart_toStartOf="@id/mypage" app:layout_constraintTop_toTopOf="@id/mypage" /> + + + + Date: Sat, 16 Sep 2023 14:37:06 +0900 Subject: [PATCH 03/38] =?UTF-8?q?[Feat/#341]=20=EA=B3=B5=EC=9A=A9=20includ?= =?UTF-8?q?e=20layout=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20AppBar=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/feature/mypage/MyPageActivity.kt | 14 +++++- app/src/main/res/layout/activity_my_page.xml | 44 ++++++++----------- .../res/layout/include_app_bar_back_arrow.xml | 27 ++++++++++++ 3 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 app/src/main/res/layout/include_app_bar_back_arrow.xml diff --git a/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt b/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt index 81cb842f4..e2b93c01a 100644 --- a/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt @@ -56,8 +56,18 @@ class MyPageActivity : AppCompatActivity() { } private fun initToolbar() { - binding.icBack.setOnSingleClickListener { - onBackPressedDispatcher.onBackPressed() + binding.includeAppBarBackArrow.apply { + textViewTitle.text = getString(R.string.toolbar_mypage) + toolbar.clicks() + .throttleUi() + .observeOnMain() + .onBackpressureLatest() + .subscribeBy( + createDisposable, + onNext = { + onBackPressedDispatcher.onBackPressed() + } + ) } } diff --git a/app/src/main/res/layout/activity_my_page.xml b/app/src/main/res/layout/activity_my_page.xml index 25fd9ba13..0357a896c 100644 --- a/app/src/main/res/layout/activity_my_page.xml +++ b/app/src/main/res/layout/activity_my_page.xml @@ -6,34 +6,28 @@ android:layout_height="match_parent" android:orientation="vertical"> - + android:layout_height="wrap_content" /> - - - - + app:navigationIcon="@drawable/btn_arrow_left"> + + + + + + + + + + + + + From 1f6615b0e8f693142b8bb2f2add4aa7c0cf1d670 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 16 Sep 2023 14:38:50 +0900 Subject: [PATCH 04/38] =?UTF-8?q?[Feat/#341]=20NotificationHistoryActivity?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 4 +- .../NotificationHistoryActivity.kt | 50 ++++++++++ .../NotificationHistoryItemClickListener.kt | 7 ++ .../NotificationHistoryRecyclerViewAdapter.kt | 54 +++++++++++ .../NotificationHistoryViewModel.kt | 43 +++++++++ ...or_notification_filter_chip_background.xml | 5 + .../selector_notification_filter_text.xml | 5 + .../icon_notification_history_empty_view.xml | 32 +++++++ .../layout/activity_notification_history.xml | 96 +++++++++++++++++++ ...nclude_notification_history_empty_view.xml | 30 ++++++ .../res/layout/item_notification_history.xml | 71 ++++++++++++++ app/src/main/res/values/strings.xml | 9 ++ app/src/main/res/values/styles.xml | 27 ++++++ 13 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt create mode 100644 app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt create mode 100644 app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt create mode 100644 app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt create mode 100644 app/src/main/res/color/selector_notification_filter_chip_background.xml create mode 100644 app/src/main/res/color/selector_notification_filter_text.xml create mode 100644 app/src/main/res/drawable/icon_notification_history_empty_view.xml create mode 100644 app/src/main/res/layout/activity_notification_history.xml create mode 100644 app/src/main/res/layout/include_notification_history_empty_view.xml create mode 100644 app/src/main/res/layout/item_notification_history.xml create mode 100644 app/src/main/res/values/styles.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 802723cd6..2d3eeaabc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -103,7 +103,9 @@ - + diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt new file mode 100644 index 000000000..047cd94f2 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -0,0 +1,50 @@ +package org.sopt.official.feature.notification + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import androidx.activity.viewModels +import com.jakewharton.rxbinding4.view.clicks +import org.sopt.official.R +import org.sopt.official.databinding.ActivityNotificationHistoryBinding +import org.sopt.official.databinding.ActivitySoptMainBinding +import org.sopt.official.domain.entity.auth.UserStatus +import org.sopt.official.feature.main.MainActivity +import org.sopt.official.feature.main.MainViewModel +import org.sopt.official.util.rx.observeOnMain +import org.sopt.official.util.serializableExtra +import org.sopt.official.util.ui.throttleUi +import org.sopt.official.util.viewBinding + +class NotificationHistoryActivity : AppCompatActivity() { + + private val binding by viewBinding(ActivityNotificationHistoryBinding::inflate) +// private val viewModel by viewModels() +// private val args by serializableExtra(MainActivity.StartArgs(UserStatus.UNAUTHENTICATED)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + + initToolbar() + initClickListeners() + } + + private fun initToolbar() { + binding.includeAppBarBackArrow.textViewTitle.text = getString(R.string.toolbar_notification) + } + + private fun initClickListeners() { + binding.apply { + includeAppBarBackArrow.toolbar.setOnClickListener(clickListener) + } + } + + private val clickListener = View.OnClickListener { + binding.apply { + when (it) { + includeAppBarBackArrow.toolbar -> onBackPressedDispatcher.onBackPressed() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt new file mode 100644 index 000000000..79f4e4dab --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt @@ -0,0 +1,7 @@ +package org.sopt.official.feature.notification + +import org.sopt.official.data.model.notification.response.NotificationHistoryItem + +interface NotificationHistoryItemClickListener { + fun onClickNotificationHistoryItem(item: NotificationHistoryItem) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt new file mode 100644 index 000000000..eb3bf8960 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt @@ -0,0 +1,54 @@ +package org.sopt.official.feature.notification + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.sopt.official.data.model.notification.response.NotificationHistoryItem +import org.sopt.official.databinding.ItemNotificationHistoryBinding + +class NotificationHistoryRecyclerViewAdapter( + private var notificationList: List, + private val clickListener: NotificationHistoryItemClickListener +) : RecyclerView.Adapter() { + + private var _viewBinding: ItemNotificationHistoryBinding? = null + private val viewBinding get() = _viewBinding!! + + override fun getItemCount(): Int = notificationList.size + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationHistoryViewHolder { + _viewBinding = ItemNotificationHistoryBinding.inflate(LayoutInflater.from(parent.context)) + return NotificationHistoryViewHolder(viewBinding) + } + + override fun onBindViewHolder(holder: NotificationHistoryViewHolder, position: Int) { + notificationList[position].let { item -> + holder.bind(item) + holder.itemView.setOnClickListener { + clickListener.onClickNotificationHistoryItem(item) + } + } + } + + override fun onViewDetachedFromWindow(holder: NotificationHistoryViewHolder) { + super.onViewDetachedFromWindow(holder) + _viewBinding = null + } + + + inner class NotificationHistoryViewHolder( + private val viewBinding: ItemNotificationHistoryBinding + ): RecyclerView.ViewHolder(viewBinding.root) { + + fun bind(item: NotificationHistoryItem) { + viewBinding.apply { + textViewTitle.text = item.title + textViewContent.text = item.content + textViewReceivedTime.text = item.createdAt + } + + // TODO: Set background and divider color. + when (item.type) { + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt new file mode 100644 index 000000000..faa4632b9 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt @@ -0,0 +1,43 @@ +package org.sopt.official.feature.notification + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse +import org.sopt.official.domain.usecase.notification.GetNotificationHistoryUseCase +import org.sopt.official.domain.usecase.notification.UpdateEntireNotificationReadingStateUseCase +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class NotificationHistoryViewModel @Inject constructor( + private val getNotificationHistoryUseCase: GetNotificationHistoryUseCase, + private val updateEntireNotificationReadingStateUseCase: UpdateEntireNotificationReadingStateUseCase +) : ViewModel() { + + private val _notificationHistoryList = MutableStateFlow(NotificationHistoryItemResponse(arrayListOf())) + val notificationHistoryList: StateFlow get() = _notificationHistoryList + + private val _updateEntireNotificationReadingState = MutableStateFlow(false) + val updateEntireNotificationReadingState = _updateEntireNotificationReadingState.asStateFlow() + + fun getNotificationHistory(page: Int) { + viewModelScope.launch { + getNotificationHistoryUseCase.invoke(page) + .onSuccess { _notificationHistoryList.value = it } + .onFailure { Timber.e(it) } + } + } + + fun updateEntireNotificationReadingState() { + viewModelScope.launch { + updateEntireNotificationReadingStateUseCase.invoke() + .onSuccess { Timber.d("updateEntireNotificationReadingStateUseCase: ", it) } + .onFailure { Timber.e("updateEntireNotificationReadingStateUseCase: ", it) } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/color/selector_notification_filter_chip_background.xml b/app/src/main/res/color/selector_notification_filter_chip_background.xml new file mode 100644 index 000000000..aff031d6b --- /dev/null +++ b/app/src/main/res/color/selector_notification_filter_chip_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_notification_filter_text.xml b/app/src/main/res/color/selector_notification_filter_text.xml new file mode 100644 index 000000000..2fb7afc26 --- /dev/null +++ b/app/src/main/res/color/selector_notification_filter_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_notification_history_empty_view.xml b/app/src/main/res/drawable/icon_notification_history_empty_view.xml new file mode 100644 index 000000000..a9bb37e6d --- /dev/null +++ b/app/src/main/res/drawable/icon_notification_history_empty_view.xml @@ -0,0 +1,32 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_notification_history.xml b/app/src/main/res/layout/activity_notification_history.xml new file mode 100644 index 000000000..55e6454fc --- /dev/null +++ b/app/src/main/res/layout/activity_notification_history.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_notification_history_empty_view.xml b/app/src/main/res/layout/include_notification_history_empty_view.xml new file mode 100644 index 000000000..03690389a --- /dev/null +++ b/app/src/main/res/layout/include_notification_history_empty_view.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_notification_history.xml b/app/src/main/res/layout/item_notification_history.xml new file mode 100644 index 000000000..2f46791bf --- /dev/null +++ b/app/src/main/res/layout/item_notification_history.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7419f40c..e17e093e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -108,4 +108,13 @@ 한 마디 편집 저장 + + + 알림 + 모두 읽음 + 전체 알림 + 공지 + 소식 + 아직 도착한 알림이 없어요. + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..9dfbfc46d --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file From fd62b97a9780312028a640eb3f5351b929c21b6b Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 30 Sep 2023 00:56:13 +0900 Subject: [PATCH 05/38] =?UTF-8?q?[Feat/#341]=20MyPageActivity=EC=97=90=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EA=B4=80=EB=A0=A8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/feature/mypage/MyPageActivity.kt | 38 +++++++--- .../feature/mypage/MyPageViewModel.kt | 26 +++++++ app/src/main/res/layout/activity_my_page.xml | 70 ++++++++++++++----- app/src/main/res/values/strings.xml | 2 + 4 files changed, 110 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt b/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt index e2b93c01a..7c874e301 100644 --- a/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt @@ -6,11 +6,15 @@ import android.net.Uri import android.os.Bundle import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope import com.jakewharton.processphoenix.ProcessPhoenix import com.jakewharton.rxbinding4.view.clicks import dagger.hilt.android.AndroidEntryPoint import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.disposables.CompositeDisposable +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.sopt.official.R import org.sopt.official.databinding.ActivityMyPageBinding import org.sopt.official.designsystem.AlertDialogPositiveNegative @@ -47,6 +51,11 @@ class MyPageActivity : AppCompatActivity() { initView() initClick() initRestart() + + // TODO: 로그인 상태 아닐때 제외해줄 기능 처리 필요 + initStateFlowValues() + initNotificationClickListener() + viewModel.getNotificationSubscription() } private fun initStartArgs() { @@ -81,14 +90,15 @@ class MyPageActivity : AppCompatActivity() { .onBackpressureLatest() .subscribeBy( createDisposable, - onNext = { - binding.containerSoptampInfo.setVisible(it) - binding.textLogIn.setVisible(!it) - binding.iconLogIn.setVisible(!it) - binding.textLogOut.setVisible(it) - binding.iconLogOut.setVisible(it) - binding.textSignOut.setVisible(it) - binding.iconSignOut.setVisible(it) + onNext = { isAuthenticated -> + binding.containerNotificationSetting.setVisible(isAuthenticated) + binding.containerSoptampInfo.setVisible(isAuthenticated) + binding.textLogIn.setVisible(!isAuthenticated) + binding.iconLogIn.setVisible(!isAuthenticated) + binding.textLogOut.setVisible(isAuthenticated) + binding.iconLogOut.setVisible(isAuthenticated) + binding.textSignOut.setVisible(isAuthenticated) + binding.iconSignOut.setVisible(isAuthenticated) } ) } @@ -226,6 +236,18 @@ class MyPageActivity : AppCompatActivity() { ) } + private fun initStateFlowValues() { + viewModel.notificationSubscriptionState.flowWithLifecycle(lifecycle) + .onEach { binding.switchNotification.isChecked = it } + .launchIn(lifecycleScope) + } + + private fun initNotificationClickListener() { + binding.switchNotification.setOnCheckedChangeListener { _, isChecked -> + viewModel.updateNotificationSubscription(isChecked) + } + } + override fun onDestroy() { super.onDestroy() diff --git a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt index 505a7e3d8..c688529c2 100644 --- a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt @@ -5,8 +5,12 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.subjects.PublishSubject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import org.sopt.official.domain.repository.AuthRepository +import org.sopt.official.domain.usecase.notification.GetNotificationSubscriptionUseCase +import org.sopt.official.domain.usecase.notification.UpdateNotificationSubscriptionUseCase import org.sopt.official.feature.mypage.model.MyPageUiState import org.sopt.official.stamp.domain.repository.StampRepository import timber.log.Timber @@ -16,10 +20,16 @@ import javax.inject.Inject class MyPageViewModel @Inject constructor( private val authRepository: AuthRepository, private val stampRepository: StampRepository, + private val getNotificationSubscriptionUseCase: GetNotificationSubscriptionUseCase, + private val updateNotificationSubscriptionUseCase: UpdateNotificationSubscriptionUseCase, ) : ViewModel() { + val userActiveState = BehaviorProcessor.createDefault(MyPageUiState.UnInitialized) val restartSignal = PublishSubject.create() + private val _notificationSubscriptionState = MutableStateFlow(false) + val notificationSubscriptionState: StateFlow get() = _notificationSubscriptionState + fun logOut() { viewModelScope.launch { authRepository.logout() @@ -40,4 +50,20 @@ class MyPageViewModel @Inject constructor( } } } + + fun getNotificationSubscription() { + viewModelScope.launch { + getNotificationSubscriptionUseCase.invoke() + .onSuccess { _notificationSubscriptionState.value = it.isOptIn } + .onFailure { Timber.e("getNotificationSubscription: ", it) } + } + } + + fun updateNotificationSubscription(isSubscribed: Boolean) { + viewModelScope.launch { + updateNotificationSubscriptionUseCase.invoke(isSubscribed) + .onSuccess { _notificationSubscriptionState.value = it.isOptIn } + .onFailure { Timber.e("updateNotificationSubscription: ", it) } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_my_page.xml b/app/src/main/res/layout/activity_my_page.xml index 0357a896c..c08c77eff 100644 --- a/app/src/main/res/layout/activity_my_page.xml +++ b/app/src/main/res/layout/activity_my_page.xml @@ -12,23 +12,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> - - - - - - + + + + + + + + + + + + - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e17e093e9..d50c644fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,6 +76,8 @@ 개인정보 처리 방침 서비스 이용약관 의견 보내기 + 알림 설정 + 알림 솝탬프 설정 한 마디 편집 닉네임 변경 From 54bbe4ac1ef6b2aa276f9259c68f7b095cd59a9d Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 30 Sep 2023 00:58:12 +0900 Subject: [PATCH 06/38] =?UTF-8?q?[Feat/#341]=20API=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/UpdatePushTokenRequest.kt | 10 ++++ .../NotificationSubscriptionResponse.kt | 4 +- .../notfication/NotificationRepositoryImpl.kt | 26 ++++++--- .../notification/NotificationService.kt | 10 ++-- .../notification/NotificationRepository.kt | 5 +- .../notification/DeletePushTokenUseCase.kt | 4 +- .../GetNotificationHistoryUseCase.kt | 2 +- .../UpdateNotificationSubscriptionUseCase.kt | 5 +- .../NotificationHistoryActivity.kt | 54 +++++++++++++++---- 9 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt diff --git a/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt b/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt new file mode 100644 index 000000000..5e64de919 --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt @@ -0,0 +1,10 @@ +package org.sopt.official.data.model.notification.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UpdatePushTokenRequest( + @SerialName("platform") val platform: String = "Android", + @SerialName("pushToken") val pushToken: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt index a4f0f02ec..aa97b03d4 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt @@ -4,7 +4,5 @@ import kotlinx.serialization.Serializable @Serializable data class NotificationSubscriptionResponse( - val allOptIn: Boolean, - val partOptIn: Boolean, - val newsOptIn: Boolean + val isOptIn: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt index 5982d1018..692f7059b 100644 --- a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -1,6 +1,7 @@ package org.sopt.official.data.repository.notfication import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest +import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.data.service.notification.NotificationService import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse @@ -15,13 +16,21 @@ class NotificationRepositoryImpl @Inject constructor( override suspend fun registerToken(pushToken: String): Result { return runCatching { - service.registerToken(pushToken) + service.registerToken( + UpdatePushTokenRequest( + pushToken = pushToken + ) + ) } } - override suspend fun deleteToken(): Result { + override suspend fun deleteToken(pushToken: String): Result { return runCatching { - service.deleteToken() + service.deleteToken( + UpdatePushTokenRequest( + pushToken = pushToken + ) + ) } } @@ -60,11 +69,16 @@ class NotificationRepositoryImpl @Inject constructor( } override suspend fun updateNotificationSubscription( - notificationSubscriptionRequest: NotificationSubscriptionRequest + isSubscribed: Boolean ): Result { return runCatching { - service.updateNotificationSubscription(notificationSubscriptionRequest) + service.updateNotificationSubscription( + NotificationSubscriptionRequest( + allOptIn = isSubscribed, + partOptIn = isSubscribed, + newsOptIn = isSubscribed + ) + ) } } - } diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index 8132cba3f..4ad20336f 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -2,6 +2,7 @@ package org.sopt.official.data.service.notification import org.sopt.official.data.model.attendance.* import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest +import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse @@ -17,11 +18,13 @@ import retrofit2.http.Query interface NotificationService { @POST("user/push-token") suspend fun registerToken( - @Path("token") token: String - ) + @Body body: UpdatePushTokenRequest + ): Result @DELETE("user/push-token") - suspend fun deleteToken() + suspend fun deleteToken( + @Body body: UpdatePushTokenRequest + ): Result @GET("notification") @@ -33,6 +36,7 @@ interface NotificationService { suspend fun getUnreadNotificationExistence( ): UnreadNotificationExistenceResponse + @PATCH("notification/{notificationId}") suspend fun updateNotificationReadingState( @Path("notificationId") notificationId: Int diff --git a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt index 838e68a91..b8ea245d7 100644 --- a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt +++ b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt @@ -1,6 +1,5 @@ package org.sopt.official.domain.repository.notification -import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse @@ -9,7 +8,7 @@ import org.sopt.official.data.model.notification.response.UnreadNotificationExis interface NotificationRepository { suspend fun registerToken(pushToken: String): Result - suspend fun deleteToken(): Result + suspend fun deleteToken(pushToken: String): Result suspend fun getNotificationHistory( page: Int @@ -22,6 +21,6 @@ interface NotificationRepository { suspend fun getNotificationSubscription(): Result suspend fun updateNotificationSubscription( - notificationSubscriptionRequest: NotificationSubscriptionRequest + isSubscribed: Boolean ): Result } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt index 00db95adb..be4efe855 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt @@ -6,7 +6,7 @@ import javax.inject.Inject class DeletePushTokenUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { - suspend operator fun invoke(): Result { - return notificationRepository.deleteToken() + suspend operator fun invoke(pushToken: String): Result { + return notificationRepository.deleteToken(pushToken) } } diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt index 75514e81c..412229b7c 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt @@ -7,7 +7,7 @@ import javax.inject.Inject class GetNotificationHistoryUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { - suspend operator fun invoke(page: Int): Result> { + suspend operator fun invoke(page: Int): Result { return notificationRepository.getNotificationHistory(page) } } diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt index 91fa3908b..46ec839de 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt @@ -1,6 +1,5 @@ package org.sopt.official.domain.usecase.notification -import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.domain.repository.notification.NotificationRepository import javax.inject.Inject @@ -9,10 +8,10 @@ class UpdateNotificationSubscriptionUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { suspend operator fun invoke( - notificationSubscriptionRequest: NotificationSubscriptionRequest + isSubscribed: Boolean ): Result { return notificationRepository.updateNotificationSubscription( - notificationSubscriptionRequest + isSubscribed = isSubscribed ) } } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index 047cd94f2..6c98c05d0 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -4,39 +4,46 @@ import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import androidx.activity.viewModels -import com.jakewharton.rxbinding4.view.clicks +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.sopt.official.R +import org.sopt.official.data.model.notification.response.NotificationHistoryItem import org.sopt.official.databinding.ActivityNotificationHistoryBinding -import org.sopt.official.databinding.ActivitySoptMainBinding -import org.sopt.official.domain.entity.auth.UserStatus -import org.sopt.official.feature.main.MainActivity -import org.sopt.official.feature.main.MainViewModel -import org.sopt.official.util.rx.observeOnMain -import org.sopt.official.util.serializableExtra -import org.sopt.official.util.ui.throttleUi import org.sopt.official.util.viewBinding -class NotificationHistoryActivity : AppCompatActivity() { +@AndroidEntryPoint +class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItemClickListener { private val binding by viewBinding(ActivityNotificationHistoryBinding::inflate) -// private val viewModel by viewModels() -// private val args by serializableExtra(MainActivity.StartArgs(UserStatus.UNAUTHENTICATED)) + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) initToolbar() + initRecyclerView() initClickListeners() + initStateFlowValues() + + viewModel.getNotificationHistory(0) } private fun initToolbar() { binding.includeAppBarBackArrow.textViewTitle.text = getString(R.string.toolbar_notification) } + private fun initRecyclerView() { + binding.recyclerViewNotificationHistory.adapter = NotificationHistoryRecyclerViewAdapter(viewModel.notificationHistoryList.value.list, this) + } + private fun initClickListeners() { binding.apply { includeAppBarBackArrow.toolbar.setOnClickListener(clickListener) + textViewReadAll.setOnClickListener(clickListener) } } @@ -44,7 +51,32 @@ class NotificationHistoryActivity : AppCompatActivity() { binding.apply { when (it) { includeAppBarBackArrow.toolbar -> onBackPressedDispatcher.onBackPressed() + textViewReadAll -> viewModel.updateEntireNotificationReadingState() } } } + + private fun initStateFlowValues() { + viewModel.notificationHistoryList.flowWithLifecycle(lifecycle) + .onEach { setTextViewReadAllVisibility(it.list.isNotEmpty()) } + .launchIn(lifecycleScope) + + viewModel.updateEntireNotificationReadingState.flowWithLifecycle(lifecycle) + .onEach { + + }.launchIn(lifecycleScope) + } + + private fun setTextViewReadAllVisibility(isVisible: Boolean) { + binding.textViewReadAll.visibility = when (isVisible) { + true -> View.VISIBLE + false -> View.GONE + } + } + + override fun onClickNotificationHistoryItem(item: NotificationHistoryItem) { + when (item.type) { + + } + } } \ No newline at end of file From 0c7f9293e21409de9328f55032390eecbd2c31ff Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 30 Sep 2023 01:00:03 +0900 Subject: [PATCH 07/38] =?UTF-8?q?[Feat/#341]=20NotificationDetailActivity?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 3 + .../NotificationDetailActivity.kt | 20 ++++ .../layout/activity_notification_detail.xml | 94 +++++++++++++++++++ app/src/main/res/values/strings.xml | 2 +- 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt create mode 100644 app/src/main/res/layout/activity_notification_detail.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2d3eeaabc..f784ee2b7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -106,6 +106,9 @@ + diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt new file mode 100644 index 000000000..13f274c41 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt @@ -0,0 +1,20 @@ +package org.sopt.official.feature.notification + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import org.sopt.official.R +import org.sopt.official.databinding.ActivityNotificationDetailBinding +import org.sopt.official.databinding.ActivityNotificationHistoryBinding +import org.sopt.official.util.viewBinding + +class NotificationDetailActivity : AppCompatActivity() { + + private val binding by viewBinding(ActivityNotificationDetailBinding::inflate) +// private val viewModel by viewModels() +// private val args by serializableExtra(MainActivity.StartArgs(UserStatus.UNAUTHENTICATED)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification_detail.xml b/app/src/main/res/layout/activity_notification_detail.xml new file mode 100644 index 000000000..fabcd5ee8 --- /dev/null +++ b/app/src/main/res/layout/activity_notification_detail.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d50c644fc..46de3bd12 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,5 +118,5 @@ 공지 소식 아직 도착한 알림이 없어요. - + 바로가기 \ No newline at end of file From ef6f91175f89cbf53ce0b566bd0f1666aee0cde5 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 30 Sep 2023 02:29:28 +0900 Subject: [PATCH 08/38] =?UTF-8?q?[Feat/#341]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20/=20onNewIntent()=20/=20=EC=95=8C=EB=A6=BC=20=EC=8A=A4?= =?UTF-8?q?=EC=9C=84=EC=B9=98=EC=97=90=20=ED=91=B8=EC=8B=9C=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EB=93=B1=EB=A1=9D=20=EB=A1=9C=EC=A7=81=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 --- .../messaging/SoptFirebaseMessagingService.kt | 27 +++++++++++++++---- .../request/UpdatePushTokenRequest.kt | 2 +- .../response/UpdatePushTokenResponse.kt | 10 +++++++ .../notfication/NotificationRepositoryImpl.kt | 7 +++-- .../notification/NotificationService.kt | 5 ++-- .../notification/NotificationRepository.kt | 5 ++-- .../notification/DeletePushTokenUseCase.kt | 3 ++- .../notification/RegisterPushTokenUseCase.kt | 3 ++- .../official/feature/home/HomeActivity.kt | 1 + .../official/feature/home/HomeViewModel.kt | 18 ++++++++++++- .../feature/mypage/MyPageViewModel.kt | 19 ++++++++++++- 11 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/data/model/notification/response/UpdatePushTokenResponse.kt diff --git a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt index 88663f400..742be6ecf 100644 --- a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt +++ b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt @@ -2,7 +2,13 @@ package org.sopt.official.config.messaging import com.google.firebase.messaging.FirebaseMessagingService import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import org.sopt.official.data.persistence.SoptDataStore +import org.sopt.official.domain.entity.auth.UserStatus +import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase import javax.inject.Inject @AndroidEntryPoint @@ -11,11 +17,22 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { @Inject lateinit var dataStore: SoptDataStore + @Inject + lateinit var registerPushTokenUseCase: RegisterPushTokenUseCase + + private val job = SupervisorJob() + private val scope = CoroutineScope(Dispatchers.IO + job) + override fun onNewToken(token: String) { - dataStore.pushToken = token - // If you want to send messages to this application instance or - // manage this apps subscriptions on the server side, send the - // FCM registration token to your app server. - // sendRegistrationToServer(token) + if (dataStore.userStatus == UserStatus.UNAUTHENTICATED.name) return + scope.launch { + dataStore.pushToken = token + registerPushTokenUseCase.invoke(token) + } + } + + override fun onDestroy() { + super.onDestroy() + job.cancel() } } diff --git a/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt b/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt index 5e64de919..f03b15344 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt @@ -5,6 +5,6 @@ import kotlinx.serialization.Serializable @Serializable data class UpdatePushTokenRequest( - @SerialName("platform") val platform: String = "Android", + @SerialName("platform") val platform: String, @SerialName("pushToken") val pushToken: String ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/UpdatePushTokenResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/UpdatePushTokenResponse.kt new file mode 100644 index 000000000..55700deea --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/UpdatePushTokenResponse.kt @@ -0,0 +1,10 @@ +package org.sopt.official.data.model.notification.response + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdatePushTokenResponse( + val status: Int, + val success: Boolean, + val message: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt index 692f7059b..42704f0a5 100644 --- a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -7,6 +7,7 @@ import org.sopt.official.data.service.notification.NotificationService import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse +import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import org.sopt.official.domain.repository.notification.NotificationRepository import javax.inject.Inject @@ -14,20 +15,22 @@ class NotificationRepositoryImpl @Inject constructor( private val service: NotificationService ) : NotificationRepository { - override suspend fun registerToken(pushToken: String): Result { + override suspend fun registerToken(pushToken: String): Result { return runCatching { service.registerToken( UpdatePushTokenRequest( + platform = "Android", pushToken = pushToken ) ) } } - override suspend fun deleteToken(pushToken: String): Result { + override suspend fun deleteToken(pushToken: String): Result { return runCatching { service.deleteToken( UpdatePushTokenRequest( + platform = "Android", pushToken = pushToken ) ) diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index 4ad20336f..e1513f257 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -7,6 +7,7 @@ import org.sopt.official.data.model.notification.response.NotificationHistoryIte import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse +import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -19,12 +20,12 @@ interface NotificationService { @POST("user/push-token") suspend fun registerToken( @Body body: UpdatePushTokenRequest - ): Result + ): UpdatePushTokenResponse @DELETE("user/push-token") suspend fun deleteToken( @Body body: UpdatePushTokenRequest - ): Result + ): UpdatePushTokenResponse @GET("notification") diff --git a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt index b8ea245d7..09dc9e771 100644 --- a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt +++ b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt @@ -4,11 +4,12 @@ import org.sopt.official.data.model.notification.response.NotificationHistoryIte import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse +import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse interface NotificationRepository { - suspend fun registerToken(pushToken: String): Result - suspend fun deleteToken(pushToken: String): Result + suspend fun registerToken(pushToken: String): Result + suspend fun deleteToken(pushToken: String): Result suspend fun getNotificationHistory( page: Int diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt index be4efe855..fcae2dd4b 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/DeletePushTokenUseCase.kt @@ -1,12 +1,13 @@ package org.sopt.official.domain.usecase.notification +import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import org.sopt.official.domain.repository.notification.NotificationRepository import javax.inject.Inject class DeletePushTokenUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { - suspend operator fun invoke(pushToken: String): Result { + suspend operator fun invoke(pushToken: String): Result { return notificationRepository.deleteToken(pushToken) } } diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/RegisterPushTokenUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/RegisterPushTokenUseCase.kt index 93e7f2fa6..9014ee312 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/RegisterPushTokenUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/RegisterPushTokenUseCase.kt @@ -1,12 +1,13 @@ package org.sopt.official.domain.usecase.notification +import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import org.sopt.official.domain.repository.notification.NotificationRepository import javax.inject.Inject class RegisterPushTokenUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { - suspend operator fun invoke(pushToken: String): Result { + suspend operator fun invoke(pushToken: String): Result { return notificationRepository.registerToken(pushToken) } } diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt index 6e3ebcdf9..d98a2ab32 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt @@ -102,6 +102,7 @@ class HomeActivity : AppCompatActivity() { private fun initUserStatus() { viewModel.initHomeUi(args ?: UserStatus.UNAUTHENTICATED) viewModel.initMainDescription(args ?: UserStatus.UNAUTHENTICATED) + viewModel.registerPushToken(args ?: UserStatus.UNAUTHENTICATED) } private fun initToolbar() { diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt b/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt index 111a3ebb4..da0dee82d 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt @@ -2,6 +2,7 @@ package org.sopt.official.feature.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.firebase.messaging.FirebaseMessaging import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -21,6 +22,7 @@ import org.sopt.official.domain.entity.home.SoptActiveRecord import org.sopt.official.domain.entity.home.SoptUser import org.sopt.official.domain.entity.home.User import org.sopt.official.domain.repository.home.HomeRepository +import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase import org.sopt.official.feature.home.model.HomeCTAType import org.sopt.official.feature.home.model.HomeMenuType import org.sopt.official.feature.home.model.SoptMainContentUrl @@ -36,8 +38,10 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( - private val mainViewRepository: HomeRepository + private val mainViewRepository: HomeRepository, + private val registerPushTokenUseCase: RegisterPushTokenUseCase ) : ViewModel() { + private val homeUiState = MutableStateFlow(SoptUser()) private val user = homeUiState.map { it.user } private val _description = MutableStateFlow(HomeSection()) @@ -174,4 +178,16 @@ class HomeViewModel @Inject constructor( } } } + + fun registerPushToken(userStatus: UserStatus) { + if (userStatus == UserStatus.UNAUTHENTICATED) return + FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> + if (task.isComplete) { + viewModelScope.launch { + registerPushTokenUseCase.invoke(task.result) + .onFailure { Timber.e(it) } + } + } + } + } } diff --git a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt index c688529c2..f0d1b14b6 100644 --- a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt @@ -2,6 +2,7 @@ package org.sopt.official.feature.mypage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.firebase.messaging.FirebaseMessaging import dagger.hilt.android.lifecycle.HiltViewModel import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.subjects.PublishSubject @@ -10,6 +11,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import org.sopt.official.domain.repository.AuthRepository import org.sopt.official.domain.usecase.notification.GetNotificationSubscriptionUseCase +import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase import org.sopt.official.domain.usecase.notification.UpdateNotificationSubscriptionUseCase import org.sopt.official.feature.mypage.model.MyPageUiState import org.sopt.official.stamp.domain.repository.StampRepository @@ -20,6 +22,7 @@ import javax.inject.Inject class MyPageViewModel @Inject constructor( private val authRepository: AuthRepository, private val stampRepository: StampRepository, + private val registerPushTokenUseCase: RegisterPushTokenUseCase, private val getNotificationSubscriptionUseCase: GetNotificationSubscriptionUseCase, private val updateNotificationSubscriptionUseCase: UpdateNotificationSubscriptionUseCase, ) : ViewModel() { @@ -62,8 +65,22 @@ class MyPageViewModel @Inject constructor( fun updateNotificationSubscription(isSubscribed: Boolean) { viewModelScope.launch { updateNotificationSubscriptionUseCase.invoke(isSubscribed) - .onSuccess { _notificationSubscriptionState.value = it.isOptIn } + .onSuccess { + if (it.isOptIn) registerPushToken() + _notificationSubscriptionState.value = it.isOptIn + } .onFailure { Timber.e("updateNotificationSubscription: ", it) } } } + + private fun registerPushToken() { + FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> + if (task.isComplete) { + viewModelScope.launch { + registerPushTokenUseCase.invoke(task.result) + .onFailure { Timber.e(it) } + } + } + } + } } \ No newline at end of file From b795adc7e4b5aabec044c0204b08080f71944286 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 30 Sep 2023 03:04:02 +0900 Subject: [PATCH 09/38] =?UTF-8?q?[Feat/#341]=20HomeActivity=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=97=90=20=EC=9D=BD=EC=A7=80=20=EC=95=8A=EC=9D=80=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=A1=B4=EC=9E=AC=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/feature/home/HomeActivity.kt | 22 ++++++++++++++++ .../official/feature/home/HomeViewModel.kt | 26 ++++++++++++++++++- .../main/res/layout/activity_sopt_main.xml | 3 ++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt index d98a2ab32..2d49e79a5 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt @@ -37,6 +37,7 @@ import org.sopt.official.feature.home.model.HomeCTAType import org.sopt.official.feature.home.model.HomeMenuType import org.sopt.official.feature.home.model.UserUiState import org.sopt.official.feature.mypage.MyPageActivity +import org.sopt.official.feature.notification.NotificationHistoryActivity import org.sopt.official.stamp.SoptampActivity import org.sopt.official.util.dp import org.sopt.official.util.drawableOf @@ -97,6 +98,13 @@ class HomeActivity : AppCompatActivity() { initUserStatus() initUserInfo() initBlock() + + initNotificationBadgeVisibility() + } + + override fun onResume() { + super.onResume() + viewModel.getUnreadNotificationExistence() } private fun initUserStatus() { @@ -152,6 +160,13 @@ class HomeActivity : AppCompatActivity() { this@HomeActivity.startActivity(intent) } } + + val isAuthenticated = userActiveState != UserActiveState.UNAUTHENTICATED + binding.imageViewNotificationHistory.visibility = if (isAuthenticated) View.VISIBLE else View.GONE + binding.imageViewNotificationHistory.setOnClickListener { + val intent = Intent(this, NotificationHistoryActivity::class.java) + startActivity(intent) + } }.launchIn(lifecycleScope) viewModel.generatedTagText .flowWithLifecycle(lifecycle) @@ -257,6 +272,13 @@ class HomeActivity : AppCompatActivity() { } } + private fun initNotificationBadgeVisibility() { + if (viewModel.userActiveState.value == UserActiveState.UNAUTHENTICATED) return + viewModel.isUnreadNotificationExist.flowWithLifecycle(lifecycle) + .onEach { binding.imageViewNotificationBadge.visibility = if (it) View.VISIBLE else View.GONE } + .launchIn(lifecycleScope) + } + companion object { @JvmStatic fun getIntent(context: Context, args: UserStatus) = diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt b/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt index da0dee82d..f323fd015 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -22,6 +23,7 @@ import org.sopt.official.domain.entity.home.SoptActiveRecord import org.sopt.official.domain.entity.home.SoptUser import org.sopt.official.domain.entity.home.User import org.sopt.official.domain.repository.home.HomeRepository +import org.sopt.official.domain.usecase.notification.GetUnreadNotificationExistenceUseCase import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase import org.sopt.official.feature.home.model.HomeCTAType import org.sopt.official.feature.home.model.HomeMenuType @@ -39,11 +41,16 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( private val mainViewRepository: HomeRepository, - private val registerPushTokenUseCase: RegisterPushTokenUseCase + private val registerPushTokenUseCase: RegisterPushTokenUseCase, + private val getUnreadNotificationExistenceUseCase: GetUnreadNotificationExistenceUseCase ) : ViewModel() { private val homeUiState = MutableStateFlow(SoptUser()) private val user = homeUiState.map { it.user } + + private val _isUnreadNotificationExist = MutableStateFlow(false) + val isUnreadNotificationExist = _isUnreadNotificationExist.asStateFlow() + private val _description = MutableStateFlow(HomeSection()) val description = _description.asStateFlow() val userActiveState = homeUiState @@ -190,4 +197,21 @@ class HomeViewModel @Inject constructor( } } } + + + fun getUnreadNotificationExistence() { + viewModelScope.launch { + val userState = user.map { it.activeState }.first() + if (userState == UserActiveState.UNAUTHENTICATED) return@launch + + getUnreadNotificationExistenceUseCase.invoke() + .onSuccess { + _isUnreadNotificationExist.value = it.exists + } + .onFailure { + _isUnreadNotificationExist.value = false + Timber.e(it) + } + } + } } diff --git a/app/src/main/res/layout/activity_sopt_main.xml b/app/src/main/res/layout/activity_sopt_main.xml index 8dcd14b55..eec11ab5c 100644 --- a/app/src/main/res/layout/activity_sopt_main.xml +++ b/app/src/main/res/layout/activity_sopt_main.xml @@ -35,6 +35,7 @@ android:layout_gravity="center_vertical|end" android:layout_marginEnd="4dp" android:src="@drawable/icon_notification" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="@id/mypage" app:layout_constraintEnd_toStartOf="@id/mypage" app:layout_constraintTop_toTopOf="@id/mypage" /> @@ -47,7 +48,7 @@ android:layout_marginEnd="10dp" android:background="@drawable/oval" android:backgroundTint="@color/orange_100" - android:visibility="invisible" + android:visibility="gone" app:layout_constraintEnd_toEndOf="@id/imageView_notificationHistory" app:layout_constraintTop_toTopOf="@id/imageView_notificationHistory" /> From 5516c79968268e7fc2c35e02b44fc3f0f563e12e Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sat, 30 Sep 2023 03:18:56 +0900 Subject: [PATCH 10/38] =?UTF-8?q?[Feat/#341]=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=ED=9E=88=EC=8A=A4=ED=86=A0=EB=A6=AC=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=ED=85=9C=20=EC=A1=B0=ED=9A=8C=20API=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8F=B0=EC=8A=A4=20=EC=88=98=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/NotificationHistoryItemMapper.kt | 19 +++++++++++++++++++ .../NotificationHistoryItemResponse.kt | 6 ------ .../notfication/NotificationRepositoryImpl.kt | 8 ++++++-- .../notification/NotificationService.kt | 2 +- .../notification/NotificationHistoryItem.kt | 12 ++++++++++++ .../notification/NotificationRepository.kt | 4 ++-- .../GetNotificationHistoryUseCase.kt | 2 +- .../NotificationHistoryActivity.kt | 8 ++++---- .../NotificationHistoryItemClickListener.kt | 4 ++-- .../NotificationHistoryRecyclerViewAdapter.kt | 6 +++--- .../NotificationHistoryViewModel.kt | 5 ++--- 11 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/data/mapper/NotificationHistoryItemMapper.kt create mode 100644 app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt diff --git a/app/src/main/java/org/sopt/official/data/mapper/NotificationHistoryItemMapper.kt b/app/src/main/java/org/sopt/official/data/mapper/NotificationHistoryItemMapper.kt new file mode 100644 index 000000000..59696dee0 --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/mapper/NotificationHistoryItemMapper.kt @@ -0,0 +1,19 @@ +package org.sopt.official.data.mapper + +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse +import org.sopt.official.domain.entity.notification.NotificationHistoryItem + +class NotificationHistoryItemMapper { + fun toNotificationHistoryItem(responseItem: NotificationHistoryItemResponse): NotificationHistoryItem { + return NotificationHistoryItem( + id = responseItem.id, + userId = responseItem.userId, + title = responseItem.title, + content = responseItem.content, + type = responseItem.type, + isRead = responseItem.isRead, + createdAt = responseItem.createdAt, + updatedAt = responseItem.updatedAt + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt index c8baa51c9..075f92332 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt @@ -5,12 +5,6 @@ import kotlinx.serialization.Serializable @Serializable data class NotificationHistoryItemResponse( - @SerialName("") - val list: ArrayList -) - -@Serializable -data class NotificationHistoryItem( val id: Int, val userId: Int, val title: String, diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt index 42704f0a5..2c5af4d98 100644 --- a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -1,5 +1,6 @@ package org.sopt.official.data.repository.notfication +import org.sopt.official.data.mapper.NotificationHistoryItemMapper import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse @@ -8,6 +9,7 @@ import org.sopt.official.data.model.notification.response.NotificationReadingSta import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse +import org.sopt.official.domain.entity.notification.NotificationHistoryItem import org.sopt.official.domain.repository.notification.NotificationRepository import javax.inject.Inject @@ -15,6 +17,8 @@ class NotificationRepositoryImpl @Inject constructor( private val service: NotificationService ) : NotificationRepository { + private val notificationHistoryMapper = NotificationHistoryItemMapper() + override suspend fun registerToken(pushToken: String): Result { return runCatching { service.registerToken( @@ -39,9 +43,9 @@ class NotificationRepositoryImpl @Inject constructor( override suspend fun getNotificationHistory( page: Int - ): Result { + ): Result> { return runCatching { - service.getNotificationHistory(page) + service.getNotificationHistory(page).map(notificationHistoryMapper::toNotificationHistoryItem) } } diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index e1513f257..c70226a05 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -31,7 +31,7 @@ interface NotificationService { @GET("notification") suspend fun getNotificationHistory( @Query("page") page: Int - ): NotificationHistoryItemResponse + ): ArrayList @GET("notification/main") suspend fun getUnreadNotificationExistence( diff --git a/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt b/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt new file mode 100644 index 000000000..2d8e7d2c0 --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt @@ -0,0 +1,12 @@ +package org.sopt.official.domain.entity.notification + +data class NotificationHistoryItem( + val id: Int, + val userId: Int, + val title: String, + val content: String, + val type: String?, + var isRead: Boolean, + val createdAt: String, + val updatedAt: String, +) diff --git a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt index 09dc9e771..1d9fe2bf2 100644 --- a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt +++ b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt @@ -1,10 +1,10 @@ package org.sopt.official.domain.repository.notification -import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse +import org.sopt.official.domain.entity.notification.NotificationHistoryItem interface NotificationRepository { @@ -13,7 +13,7 @@ interface NotificationRepository { suspend fun getNotificationHistory( page: Int - ): Result + ): Result> suspend fun getUnreadNotificationExistence(): Result suspend fun updateNotificationReadingState( notificationId: Int diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt index 412229b7c..cb638aa11 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt @@ -7,7 +7,7 @@ import javax.inject.Inject class GetNotificationHistoryUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { - suspend operator fun invoke(page: Int): Result { + suspend operator fun invoke(page: Int): Result> { return notificationRepository.getNotificationHistory(page) } } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index 6c98c05d0..1880ddd77 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -10,7 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.sopt.official.R -import org.sopt.official.data.model.notification.response.NotificationHistoryItem +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.databinding.ActivityNotificationHistoryBinding import org.sopt.official.util.viewBinding @@ -37,7 +37,7 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem } private fun initRecyclerView() { - binding.recyclerViewNotificationHistory.adapter = NotificationHistoryRecyclerViewAdapter(viewModel.notificationHistoryList.value.list, this) + binding.recyclerViewNotificationHistory.adapter = NotificationHistoryRecyclerViewAdapter(viewModel.notificationHistoryList.value, this) } private fun initClickListeners() { @@ -58,7 +58,7 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem private fun initStateFlowValues() { viewModel.notificationHistoryList.flowWithLifecycle(lifecycle) - .onEach { setTextViewReadAllVisibility(it.list.isNotEmpty()) } + .onEach { setTextViewReadAllVisibility(it.isNotEmpty()) } .launchIn(lifecycleScope) viewModel.updateEntireNotificationReadingState.flowWithLifecycle(lifecycle) @@ -74,7 +74,7 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem } } - override fun onClickNotificationHistoryItem(item: NotificationHistoryItem) { + override fun onClickNotificationHistoryItem(item: NotificationHistoryItemResponse) { when (item.type) { } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt index 79f4e4dab..f58b5ae27 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt @@ -1,7 +1,7 @@ package org.sopt.official.feature.notification -import org.sopt.official.data.model.notification.response.NotificationHistoryItem +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse interface NotificationHistoryItemClickListener { - fun onClickNotificationHistoryItem(item: NotificationHistoryItem) + fun onClickNotificationHistoryItem(item: NotificationHistoryItemResponse) } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt index eb3bf8960..46759bc28 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt @@ -3,11 +3,11 @@ package org.sopt.official.feature.notification import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import org.sopt.official.data.model.notification.response.NotificationHistoryItem +import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.databinding.ItemNotificationHistoryBinding class NotificationHistoryRecyclerViewAdapter( - private var notificationList: List, + private var notificationList: List, private val clickListener: NotificationHistoryItemClickListener ) : RecyclerView.Adapter() { @@ -39,7 +39,7 @@ class NotificationHistoryRecyclerViewAdapter( private val viewBinding: ItemNotificationHistoryBinding ): RecyclerView.ViewHolder(viewBinding.root) { - fun bind(item: NotificationHistoryItem) { + fun bind(item: NotificationHistoryItemResponse) { viewBinding.apply { textViewTitle.text = item.title textViewContent.text = item.content diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt index faa4632b9..3b4840b6c 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse @@ -19,8 +18,8 @@ class NotificationHistoryViewModel @Inject constructor( private val updateEntireNotificationReadingStateUseCase: UpdateEntireNotificationReadingStateUseCase ) : ViewModel() { - private val _notificationHistoryList = MutableStateFlow(NotificationHistoryItemResponse(arrayListOf())) - val notificationHistoryList: StateFlow get() = _notificationHistoryList + private val _notificationHistoryList = MutableStateFlow>(arrayListOf()) + val notificationHistoryList = _notificationHistoryList.asStateFlow() private val _updateEntireNotificationReadingState = MutableStateFlow(false) val updateEntireNotificationReadingState = _updateEntireNotificationReadingState.asStateFlow() From 3cb4f228f2c99d7954c8ff74a0ca1608d680a6a4 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 1 Oct 2023 00:07:07 +0900 Subject: [PATCH 11/38] =?UTF-8?q?[Feat/#341]=20AuthActivity=EC=97=90=20not?= =?UTF-8?q?ification=20channel=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/feature/auth/AuthActivity.kt | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt b/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt index 1ae0b0687..17b1ed572 100644 --- a/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt @@ -3,11 +3,14 @@ package org.sopt.official.feature.auth import android.animation.ObjectAnimator import android.content.Context import android.content.Intent +import android.app.NotificationChannel +import android.app.NotificationManager import android.graphics.Paint import android.os.Bundle import android.view.animation.AnimationUtils import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NotificationCompat import androidx.core.view.isVisible import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -49,14 +52,32 @@ class AuthActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (dataStore.accessToken.isNotEmpty()) { - startActivity(HomeActivity.getIntent(this, UserStatus.valueOf(dataStore.userStatus))) + startActivity(HomeActivity.getIntent(this, HomeActivity.StartArgs( + UserStatus.valueOf(dataStore.userStatus) + ))) } setContentView(binding.root) + initNotificationChannel() + initUi() initAnimation() collectUiEvent() } + private fun initNotificationChannel() { + NotificationChannel( + getString(R.string.toolbar_notification_filter_all), + getString(R.string.toolbar_notification_filter_all), + NotificationManager.IMPORTANCE_HIGH + ).apply { + setSound(null, null) + enableLights(false) + enableVibration(false) + lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC + (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(this) + } + } + private fun collectUiEvent() { viewModel.uiEvent .flowWithLifecycle(lifecycle) @@ -123,7 +144,9 @@ class AuthActivity : AppCompatActivity() { } } binding.btnSoptNotMember.setOnSingleClickListener { - startActivity(HomeActivity.getIntent(this, UserStatus.UNAUTHENTICATED)) + startActivity(HomeActivity.getIntent(this, HomeActivity.StartArgs( + UserStatus.UNAUTHENTICATED + ))) } } From 8aa3d9b18833e1dcabdb1ebafc4ffbdf28c21bdd Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 1 Oct 2023 00:19:06 +0900 Subject: [PATCH 12/38] =?UTF-8?q?[Feat/#341]=20SoptFirebaseMessagingServic?= =?UTF-8?q?e=20=ED=8C=8C=EC=9D=BC=EC=97=90=20Notification=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/messaging/RemoteMessageLinkType.kt | 11 +++ .../messaging/SoptFirebaseMessagingService.kt | 87 +++++++++++++++++++ app/src/main/res/values/colors.xml | 2 + 3 files changed, 100 insertions(+) create mode 100644 app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt diff --git a/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt new file mode 100644 index 000000000..4cd0d63f9 --- /dev/null +++ b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt @@ -0,0 +1,11 @@ +package org.sopt.official.config.messaging + +enum class RemoteMessageLinkType { + WEB_LINK, DEEP_LINK; + + companion object { + fun of(name: String) = entries.find { + it.name == name + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt index 742be6ecf..9978af410 100644 --- a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt +++ b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt @@ -1,14 +1,24 @@ package org.sopt.official.config.messaging +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.NotificationCompat import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import org.sopt.official.R import org.sopt.official.data.persistence.SoptDataStore import org.sopt.official.domain.entity.auth.UserStatus import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase +import org.sopt.official.feature.auth.AuthActivity +import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -22,6 +32,8 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) + private val channelId = "SOPT" + override fun onNewToken(token: String) { if (dataStore.userStatus == UserStatus.UNAUTHENTICATED.name) return @@ -31,6 +43,81 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { } } + override fun onMessageReceived(remoteMessage: RemoteMessage) { + super.onMessageReceived(remoteMessage) + if (remoteMessage.data.isEmpty()) return + + val receivedData = remoteMessage.data + val title = receivedData["title"] ?: "" + val body = receivedData["content"] ?: "" + val webLink = receivedData["webLink"] ?: "" + val deepLink = receivedData["deepLink"] ?: "" + Timber.tag("SOPT").e("onMessageReceived - title: %s", title) + + val notificationId = System.currentTimeMillis().toInt() + val notificationBuilder = NotificationCompat.Builder(this, channelId) + .setContentTitle(title) + .setContentText(body) + .setStyle(NotificationCompat.BigTextStyle().bigText(body)) + .setSmallIcon(R.drawable.img_logo_small) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setChannelId(getString(R.string.toolbar_notification)) + .setAutoCancel(true) + + notificationBuilder.setNotificationContentIntent( + if (webLink.isNotBlank()) { + RemoteMessageLinkType.WEB_LINK + } else if (deepLink.isNotBlank()) { + RemoteMessageLinkType.DEEP_LINK + } else { + RemoteMessageLinkType.DEFAULT + }, + webLink.ifBlank { deepLink.ifBlank { "" } }, + notificationId + ) + + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.notify(notificationId, notificationBuilder.build()) + + Timber.e("onMessageReceived: %s", remoteMessage) + Timber.e("onMessageReceived - notification: " + remoteMessage.notification) + Timber.e("onMessageReceived - notification title: " + remoteMessage.notification?.title) + Timber.e("onMessageReceived - notification body: " + remoteMessage.notification?.body) + Timber.e("onMessageReceived - data: " + remoteMessage.data) + Timber.e("onMessageReceived - data entries: " + remoteMessage.data.entries) + Timber.d("SOPT", "----------------------------") + Timber.d("SOPT", "received message: $remoteMessage") + } + + private fun NotificationCompat.Builder.setNotificationContentIntent( + remoteMessageLinkType: RemoteMessageLinkType, + link: String, + notificationId: Int + ): NotificationCompat.Builder { + + val intent = Intent(this@SoptFirebaseMessagingService, AuthActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(REMOTE_MESSAGE_EVENT_TYPE, remoteMessageLinkType.name) + putExtra(REMOTE_MESSAGE_EVENT_LINK, link) + } + + return this.setContentIntent( + PendingIntent.getActivity( + this@SoptFirebaseMessagingService, + notificationId, + intent, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + } else PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + } + + companion object { + const val REMOTE_MESSAGE_EVENT_TYPE = "REMOTE_MESSAGE_EVENT_TYPE" + const val REMOTE_MESSAGE_EVENT_LINK = "REMOTE_MESSAGE_EVENT_LINK" + } + override fun onDestroy() { super.onDestroy() job.cancel() diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3949159dc..ae76bc808 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -28,6 +28,8 @@ #2C2D2E #3C3D40 + #1C1D1ECC + #6A3232 #FFD3D3 From 80ab43decb5237bbc7bfbd073c141bb169c884f5 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 1 Oct 2023 01:58:07 +0900 Subject: [PATCH 13/38] =?UTF-8?q?[Feat/#341]=20NotificationHistoryActivity?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=EC=97=90=20=ED=81=B4=EB=A6=AD=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/messaging/RemoteMessageType.kt | 11 +++ .../GetNotificationHistoryUseCase.kt | 4 +- .../NotificationHistoryActivity.kt | 44 ++++++++---- .../NotificationHistoryItemClickListener.kt | 2 +- .../NotificationHistoryListViewAdapter.kt | 68 +++++++++++++++++++ .../NotificationHistoryRecyclerViewAdapter.kt | 54 --------------- .../NotificationHistoryViewModel.kt | 25 +++++-- .../res/layout/item_notification_history.xml | 22 +++--- 8 files changed, 143 insertions(+), 87 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/config/messaging/RemoteMessageType.kt create mode 100644 app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt diff --git a/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageType.kt b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageType.kt new file mode 100644 index 000000000..3c5f02cd6 --- /dev/null +++ b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageType.kt @@ -0,0 +1,11 @@ +package org.sopt.official.config.messaging + +enum class RemoteMessageType { + NOTICE, NEWS; + + companion object { + fun of(name: String) = entries.find { + it.name == name + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt index cb638aa11..a0573ac1d 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationHistoryUseCase.kt @@ -1,13 +1,13 @@ package org.sopt.official.domain.usecase.notification -import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse +import org.sopt.official.domain.entity.notification.NotificationHistoryItem import org.sopt.official.domain.repository.notification.NotificationRepository import javax.inject.Inject class GetNotificationHistoryUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { - suspend operator fun invoke(page: Int): Result> { + suspend operator fun invoke(page: Int): Result> { return notificationRepository.getNotificationHistory(page) } } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index 1880ddd77..600c32b69 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -1,5 +1,6 @@ package org.sopt.official.feature.notification +import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View @@ -10,7 +11,6 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.sopt.official.R -import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.databinding.ActivityNotificationHistoryBinding import org.sopt.official.util.viewBinding @@ -20,6 +20,8 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem private val binding by viewBinding(ActivityNotificationHistoryBinding::inflate) private val viewModel by viewModels() + private lateinit var notificationHistoryAdapter: NotificationHistoryListViewAdapter + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) @@ -28,8 +30,6 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem initRecyclerView() initClickListeners() initStateFlowValues() - - viewModel.getNotificationHistory(0) } private fun initToolbar() { @@ -37,7 +37,8 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem } private fun initRecyclerView() { - binding.recyclerViewNotificationHistory.adapter = NotificationHistoryRecyclerViewAdapter(viewModel.notificationHistoryList.value, this) + notificationHistoryAdapter = NotificationHistoryListViewAdapter(this@NotificationHistoryActivity) + binding.recyclerViewNotificationHistory.adapter = notificationHistoryAdapter } private fun initClickListeners() { @@ -58,13 +59,19 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem private fun initStateFlowValues() { viewModel.notificationHistoryList.flowWithLifecycle(lifecycle) - .onEach { setTextViewReadAllVisibility(it.isNotEmpty()) } - .launchIn(lifecycleScope) - - viewModel.updateEntireNotificationReadingState.flowWithLifecycle(lifecycle) .onEach { + setEmptyViewVisibility(it.isEmpty()) + setTextViewReadAllVisibility(it.isNotEmpty()) + notificationHistoryAdapter.submitList(it) + } + .launchIn(lifecycleScope) + } - }.launchIn(lifecycleScope) + private fun setEmptyViewVisibility(isVisible: Boolean) { + binding.includeNotificationHistoryEmptyView.root.visibility = when (isVisible) { + true -> View.VISIBLE + false -> View.GONE + } } private fun setTextViewReadAllVisibility(isVisible: Boolean) { @@ -74,9 +81,22 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem } } - override fun onClickNotificationHistoryItem(item: NotificationHistoryItemResponse) { - when (item.type) { - + override fun onClickNotificationHistoryItem(position: Int) { + viewModel.updateNotificationReadingState(position) + val clickedNotification = viewModel.notificationHistoryList.value[position] + Intent(this, NotificationDetailActivity::class.java).run { + putExtra(ID, clickedNotification.id) + putExtra(TITLE, clickedNotification.title) + putExtra(CONTENT, clickedNotification.content) + putExtra(TYPE, clickedNotification.type) + startActivity(this) } } + + companion object { + const val ID = "id" + const val TITLE = "title" + const val CONTENT = "content" + const val TYPE = "type" + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt index f58b5ae27..caaa24e17 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt @@ -3,5 +3,5 @@ package org.sopt.official.feature.notification import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse interface NotificationHistoryItemClickListener { - fun onClickNotificationHistoryItem(item: NotificationHistoryItemResponse) + fun onClickNotificationHistoryItem(position: Int) } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt new file mode 100644 index 000000000..ded775a3f --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt @@ -0,0 +1,68 @@ +package org.sopt.official.feature.notification + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.sopt.official.R +import org.sopt.official.core.view.ItemDiffCallback +import org.sopt.official.databinding.ItemNotificationHistoryBinding +import org.sopt.official.domain.entity.notification.NotificationHistoryItem +import org.sopt.official.util.drawableOf + +class NotificationHistoryListViewAdapter( + private val clickListener: NotificationHistoryItemClickListener +) : ListAdapter( + + ItemDiffCallback( + onContentsTheSame = { old, new -> old.title == new.title }, + onItemsTheSame = { old, new -> old == new } + ) +) { + private var _viewBinding: ItemNotificationHistoryBinding? = null + private val viewBinding get() = _viewBinding!! + + override fun getItemCount(): Int = currentList.size + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + _viewBinding = ItemNotificationHistoryBinding.inflate(LayoutInflater.from(parent.context)) + return ViewHolder(viewBinding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + currentList[position].let { item -> + holder.bind(item) + holder.itemView.setOnClickListener { + clickListener.onClickNotificationHistoryItem(position) + } + } + } + + override fun onViewDetachedFromWindow(holder: ViewHolder) { + super.onViewDetachedFromWindow(holder) + _viewBinding = null + } + + fun updateNotificationReadingState(position: Int) { + val newList = currentList.toMutableList() + newList[position].isRead = true + submitList(newList) + } + + + inner class ViewHolder( + private val viewBinding: ItemNotificationHistoryBinding + ) : RecyclerView.ViewHolder(viewBinding.root) { + + fun bind(item: NotificationHistoryItem) { + viewBinding.apply { + textViewTitle.text = item.title + textViewContent.text = item.content + textViewReceivedTime.text = item.createdAt + constraintLayoutBackground.background = when (item.isRead) { + true -> root.context.drawableOf(R.color.black_100) + false -> root.context.drawableOf(R.color.black_80) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt deleted file mode 100644 index 46759bc28..000000000 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryRecyclerViewAdapter.kt +++ /dev/null @@ -1,54 +0,0 @@ -package org.sopt.official.feature.notification - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse -import org.sopt.official.databinding.ItemNotificationHistoryBinding - -class NotificationHistoryRecyclerViewAdapter( - private var notificationList: List, - private val clickListener: NotificationHistoryItemClickListener -) : RecyclerView.Adapter() { - - private var _viewBinding: ItemNotificationHistoryBinding? = null - private val viewBinding get() = _viewBinding!! - - override fun getItemCount(): Int = notificationList.size - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationHistoryViewHolder { - _viewBinding = ItemNotificationHistoryBinding.inflate(LayoutInflater.from(parent.context)) - return NotificationHistoryViewHolder(viewBinding) - } - - override fun onBindViewHolder(holder: NotificationHistoryViewHolder, position: Int) { - notificationList[position].let { item -> - holder.bind(item) - holder.itemView.setOnClickListener { - clickListener.onClickNotificationHistoryItem(item) - } - } - } - - override fun onViewDetachedFromWindow(holder: NotificationHistoryViewHolder) { - super.onViewDetachedFromWindow(holder) - _viewBinding = null - } - - - inner class NotificationHistoryViewHolder( - private val viewBinding: ItemNotificationHistoryBinding - ): RecyclerView.ViewHolder(viewBinding.root) { - - fun bind(item: NotificationHistoryItemResponse) { - viewBinding.apply { - textViewTitle.text = item.title - textViewContent.text = item.content - textViewReceivedTime.text = item.createdAt - } - - // TODO: Set background and divider color. - when (item.type) { - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt index 3b4840b6c..b4b250430 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt @@ -6,7 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse +import org.sopt.official.domain.entity.notification.NotificationHistoryItem import org.sopt.official.domain.usecase.notification.GetNotificationHistoryUseCase import org.sopt.official.domain.usecase.notification.UpdateEntireNotificationReadingStateUseCase import timber.log.Timber @@ -18,11 +18,12 @@ class NotificationHistoryViewModel @Inject constructor( private val updateEntireNotificationReadingStateUseCase: UpdateEntireNotificationReadingStateUseCase ) : ViewModel() { - private val _notificationHistoryList = MutableStateFlow>(arrayListOf()) + private val _notificationHistoryList = MutableStateFlow>(arrayListOf()) val notificationHistoryList = _notificationHistoryList.asStateFlow() - private val _updateEntireNotificationReadingState = MutableStateFlow(false) - val updateEntireNotificationReadingState = _updateEntireNotificationReadingState.asStateFlow() + init { + getNotificationHistory(0) + } fun getNotificationHistory(page: Int) { viewModelScope.launch { @@ -32,10 +33,24 @@ class NotificationHistoryViewModel @Inject constructor( } } + fun updateNotificationReadingState(position: Int) { + val newNotificationList = _notificationHistoryList.value + newNotificationList[position].isRead = true + _notificationHistoryList.value = newNotificationList + } + fun updateEntireNotificationReadingState() { viewModelScope.launch { updateEntireNotificationReadingStateUseCase.invoke() - .onSuccess { Timber.d("updateEntireNotificationReadingStateUseCase: ", it) } + .onSuccess { + val newNotificationList = _notificationHistoryList.value + for (notification in newNotificationList) { + notification.isRead = true + } + _notificationHistoryList.value = newNotificationList +// _updateEntireNotificationReadingState.value = true + Timber.d("updateEntireNotificationReadingStateUseCase: ", it) + } .onFailure { Timber.e("updateEntireNotificationReadingStateUseCase: ", it) } } } diff --git a/app/src/main/res/layout/item_notification_history.xml b/app/src/main/res/layout/item_notification_history.xml index 2f46791bf..1ecf4f6a3 100644 --- a/app/src/main/res/layout/item_notification_history.xml +++ b/app/src/main/res/layout/item_notification_history.xml @@ -1,14 +1,10 @@ + android:background="@color/black_100"> @@ -37,7 +33,7 @@ android:maxLines="1" android:text="@string/soptamp" android:textColor="@color/gray_100" - android:textSize="12sp" + android:textSize="12dp" app:layout_constraintBottom_toBottomOf="@id/textView_title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/textView_title" /> @@ -48,13 +44,13 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="4dp" + android:layout_marginBottom="16dp" android:ellipsize="end" - android:lines="2" android:gravity="center_vertical" + android:lines="2" android:text="@string/soptamp" android:textColor="@color/gray_100" - android:textSize="16sp" - android:layout_marginBottom="16dp" + android:textSize="16dp" app:layout_constraintBottom_toTopOf="@id/view_divider" app:layout_constraintEnd_toEndOf="@id/textView_receivedTime" app:layout_constraintStart_toStartOf="@id/textView_title" @@ -64,8 +60,8 @@ android:id="@+id/view_divider" android:layout_width="match_parent" android:layout_height="1dp" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" + android:background="@color/black_60" app:layout_constraintBottom_toBottomOf="parent" - android:backgroundTint="@color/black_60" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> \ No newline at end of file From a005c79304c6698e73add8ff0e120a0c0d4edac5 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 1 Oct 2023 02:16:23 +0900 Subject: [PATCH 14/38] =?UTF-8?q?[Feat/#341]=20=ED=91=B8=EC=8B=9C=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B5=AC=EB=8F=85=20=EC=83=81=ED=83=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=88=98=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/request/NotificationSubscriptionRequest.kt | 4 +--- .../data/repository/notfication/NotificationRepositoryImpl.kt | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt b/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt index 9b87d7644..df2979e92 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt @@ -5,7 +5,5 @@ import kotlinx.serialization.Serializable @Serializable data class NotificationSubscriptionRequest( - @SerialName("allOptIn") val allOptIn: Boolean? = null, - @SerialName("partOptIn") val partOptIn: Boolean? = null, - @SerialName("newsOptIn") val newsOptIn: Boolean? = null + @SerialName("isOptIn") val isOptIn: Boolean? = null, ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt index 2c5af4d98..5570cbd93 100644 --- a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -81,9 +81,7 @@ class NotificationRepositoryImpl @Inject constructor( return runCatching { service.updateNotificationSubscription( NotificationSubscriptionRequest( - allOptIn = isSubscribed, - partOptIn = isSubscribed, - newsOptIn = isSubscribed + isOptIn = isSubscribed ) ) } From 84dcf899cdc9c8aa9e71a9c09679d0acdc0d9cd2 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Mon, 2 Oct 2023 15:10:32 +0900 Subject: [PATCH 15/38] =?UTF-8?q?[Feat/#341]=20NotificationDetailActivity?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationDetailActivity.kt | 58 ++++++++++++++++++- .../NotificationDetailViewModel.kt | 28 +++++++++ .../layout/activity_notification_detail.xml | 6 +- 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt index 13f274c41..8e9cd7fc6 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt @@ -2,19 +2,71 @@ package org.sopt.official.feature.notification import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.view.View +import androidx.activity.viewModels +import dagger.hilt.android.AndroidEntryPoint import org.sopt.official.R +import org.sopt.official.config.messaging.RemoteMessageType import org.sopt.official.databinding.ActivityNotificationDetailBinding -import org.sopt.official.databinding.ActivityNotificationHistoryBinding +import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.CONTENT +import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.ID +import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.TITLE +import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.TYPE import org.sopt.official.util.viewBinding +@AndroidEntryPoint class NotificationDetailActivity : AppCompatActivity() { private val binding by viewBinding(ActivityNotificationDetailBinding::inflate) -// private val viewModel by viewModels() -// private val args by serializableExtra(MainActivity.StartArgs(UserStatus.UNAUTHENTICATED)) + private val viewModel by viewModels() + + private val id: Int by lazy { + intent.getIntExtra(ID, 0) + } + private val title: String? by lazy { + intent.getStringExtra(TITLE) + } + private val content: String? by lazy { + intent.getStringExtra(CONTENT) + } + private val type: String? by lazy { + intent.getStringExtra(TYPE) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) + + if (id != 0) viewModel.updateNotificationReadingState(id) + initNotificationDetail() + initClickListeners() + } + + private fun initNotificationDetail() { + binding.apply { + includeAppBarBackArrow.textViewTitle.text = getString(R.string.toolbar_notification) + textViewNotificationTitle.text = title + textViewNotificationContent.text = content + linearLayoutNewsDetailButton.visibility = when (type) { + RemoteMessageType.NOTICE.name -> View.GONE + else -> View.VISIBLE + } + } + } + + private fun initClickListeners() { + binding.apply { + includeAppBarBackArrow.toolbar.setOnClickListener(clickListener) + linearLayoutNewsDetailButton.setOnClickListener(clickListener) + } + } + + private val clickListener = View.OnClickListener { + binding.apply { + when (it) { + includeAppBarBackArrow.toolbar -> onBackPressed() + linearLayoutNewsDetailButton -> {} // TODO: Set intent to link. + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt new file mode 100644 index 000000000..bb01dbc06 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt @@ -0,0 +1,28 @@ +package org.sopt.official.feature.notification + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import org.sopt.official.config.messaging.RemoteMessageType +import org.sopt.official.domain.entity.notification.NotificationHistoryItem +import org.sopt.official.domain.usecase.notification.GetNotificationHistoryUseCase +import org.sopt.official.domain.usecase.notification.UpdateEntireNotificationReadingStateUseCase +import org.sopt.official.domain.usecase.notification.UpdateNotificationReadingStateUseCase +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class NotificationDetailViewModel @Inject constructor( + private val updateNotificationReadingStateUseCase: UpdateNotificationReadingStateUseCase, +) : ViewModel() { + + fun updateNotificationReadingState(id: Int) { + viewModelScope.launch { + updateNotificationReadingStateUseCase.invoke(id) + .onFailure { Timber.e(it) } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification_detail.xml b/app/src/main/res/layout/activity_notification_detail.xml index fabcd5ee8..3de41659d 100644 --- a/app/src/main/res/layout/activity_notification_detail.xml +++ b/app/src/main/res/layout/activity_notification_detail.xml @@ -18,6 +18,9 @@ Date: Wed, 4 Oct 2023 02:43:36 +0900 Subject: [PATCH 16/38] =?UTF-8?q?[Feat/#341]=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B4=EC=9E=AC=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EC=A1=B0=ED=9A=8C=20API=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/data/model/home/HomeResponse.kt | 3 +++ .../UnreadNotificationExistenceResponse.kt | 8 ------ .../notfication/NotificationRepositoryImpl.kt | 8 ------ .../notification/NotificationService.kt | 5 ---- .../sopt/official/domain/entity/home/Home.kt | 1 + .../notification/NotificationRepository.kt | 2 -- .../GetUnreadNotificationExistenceUseCase.kt | 13 ---------- .../official/feature/home/HomeActivity.kt | 17 +++--------- .../official/feature/home/HomeViewModel.kt | 26 +++---------------- 9 files changed, 10 insertions(+), 73 deletions(-) delete mode 100644 app/src/main/java/org/sopt/official/data/model/notification/response/UnreadNotificationExistenceResponse.kt delete mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/GetUnreadNotificationExistenceUseCase.kt diff --git a/app/src/main/java/org/sopt/official/data/model/home/HomeResponse.kt b/app/src/main/java/org/sopt/official/data/model/home/HomeResponse.kt index 998400af2..6e0c838b7 100644 --- a/app/src/main/java/org/sopt/official/data/model/home/HomeResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/home/HomeResponse.kt @@ -14,11 +14,14 @@ data class HomeResponse( val user: HomeUserResponse, @SerialName("operation") val operation: HomeOperationResponse, + @SerialName("isAllConfirm") + val isAllConfirm: Boolean ) { fun toEntity(): SoptUser = SoptUser( user = this.user.toEntity(), operation = this.operation.toEntity(), + isAllConfirm = this.isAllConfirm ) @Serializable diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/UnreadNotificationExistenceResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/UnreadNotificationExistenceResponse.kt deleted file mode 100644 index 2b596873b..000000000 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/UnreadNotificationExistenceResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.official.data.model.notification.response - -import kotlinx.serialization.Serializable - -@Serializable -data class UnreadNotificationExistenceResponse( - val exists: Boolean -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt index 5570cbd93..7a285d355 100644 --- a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -3,11 +3,9 @@ package org.sopt.official.data.repository.notfication import org.sopt.official.data.mapper.NotificationHistoryItemMapper import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest -import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.data.service.notification.NotificationService import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse -import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import org.sopt.official.domain.entity.notification.NotificationHistoryItem import org.sopt.official.domain.repository.notification.NotificationRepository @@ -49,12 +47,6 @@ class NotificationRepositoryImpl @Inject constructor( } } - override suspend fun getUnreadNotificationExistence(): Result { - return runCatching { - service.getUnreadNotificationExistence() - } - } - override suspend fun updateNotificationReadingState( notificationId: Int ): Result { diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index c70226a05..57c7537f9 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -6,7 +6,6 @@ import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse -import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import retrofit2.http.Body import retrofit2.http.DELETE @@ -33,10 +32,6 @@ interface NotificationService { @Query("page") page: Int ): ArrayList - @GET("notification/main") - suspend fun getUnreadNotificationExistence( - ): UnreadNotificationExistenceResponse - @PATCH("notification/{notificationId}") suspend fun updateNotificationReadingState( diff --git a/app/src/main/java/org/sopt/official/domain/entity/home/Home.kt b/app/src/main/java/org/sopt/official/domain/entity/home/Home.kt index ca8a288ae..8d9e0d77a 100644 --- a/app/src/main/java/org/sopt/official/domain/entity/home/Home.kt +++ b/app/src/main/java/org/sopt/official/domain/entity/home/Home.kt @@ -5,6 +5,7 @@ import org.sopt.official.domain.entity.UserActiveState data class SoptUser( val user: User = User(), val operation: SoptActiveRecord = SoptActiveRecord(), + val isAllConfirm: Boolean = false ) data class User( diff --git a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt index 1d9fe2bf2..89f393e9c 100644 --- a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt +++ b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt @@ -2,7 +2,6 @@ package org.sopt.official.domain.repository.notification import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse -import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import org.sopt.official.domain.entity.notification.NotificationHistoryItem @@ -14,7 +13,6 @@ interface NotificationRepository { suspend fun getNotificationHistory( page: Int ): Result> - suspend fun getUnreadNotificationExistence(): Result suspend fun updateNotificationReadingState( notificationId: Int ): Result diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetUnreadNotificationExistenceUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetUnreadNotificationExistenceUseCase.kt deleted file mode 100644 index 87fbdfdfd..000000000 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetUnreadNotificationExistenceUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.sopt.official.domain.usecase.notification - -import org.sopt.official.data.model.notification.response.UnreadNotificationExistenceResponse -import org.sopt.official.domain.repository.notification.NotificationRepository -import javax.inject.Inject - -class GetUnreadNotificationExistenceUseCase @Inject constructor( - private val notificationRepository: NotificationRepository -) { - suspend operator fun invoke(): Result { - return notificationRepository.getUnreadNotificationExistence() - } -} diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt index 2d49e79a5..1e92d7fd4 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt @@ -98,13 +98,6 @@ class HomeActivity : AppCompatActivity() { initUserStatus() initUserInfo() initBlock() - - initNotificationBadgeVisibility() - } - - override fun onResume() { - super.onResume() - viewModel.getUnreadNotificationExistence() } private fun initUserStatus() { @@ -168,6 +161,9 @@ class HomeActivity : AppCompatActivity() { startActivity(intent) } }.launchIn(lifecycleScope) + viewModel.isAllNotificationsConfirm.flowWithLifecycle(lifecycle) + .onEach { binding.imageViewNotificationBadge.visibility = if (it) View.GONE else View.VISIBLE } + .launchIn(lifecycleScope) viewModel.generatedTagText .flowWithLifecycle(lifecycle) .onEach { (id, text) -> @@ -272,13 +268,6 @@ class HomeActivity : AppCompatActivity() { } } - private fun initNotificationBadgeVisibility() { - if (viewModel.userActiveState.value == UserActiveState.UNAUTHENTICATED) return - viewModel.isUnreadNotificationExist.flowWithLifecycle(lifecycle) - .onEach { binding.imageViewNotificationBadge.visibility = if (it) View.VISIBLE else View.GONE } - .launchIn(lifecycleScope) - } - companion object { @JvmStatic fun getIntent(context: Context, args: UserStatus) = diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt b/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt index f323fd015..40a200dd8 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeViewModel.kt @@ -23,7 +23,6 @@ import org.sopt.official.domain.entity.home.SoptActiveRecord import org.sopt.official.domain.entity.home.SoptUser import org.sopt.official.domain.entity.home.User import org.sopt.official.domain.repository.home.HomeRepository -import org.sopt.official.domain.usecase.notification.GetUnreadNotificationExistenceUseCase import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase import org.sopt.official.feature.home.model.HomeCTAType import org.sopt.official.feature.home.model.HomeMenuType @@ -42,20 +41,17 @@ import javax.inject.Inject class HomeViewModel @Inject constructor( private val mainViewRepository: HomeRepository, private val registerPushTokenUseCase: RegisterPushTokenUseCase, - private val getUnreadNotificationExistenceUseCase: GetUnreadNotificationExistenceUseCase ) : ViewModel() { private val homeUiState = MutableStateFlow(SoptUser()) private val user = homeUiState.map { it.user } - private val _isUnreadNotificationExist = MutableStateFlow(false) - val isUnreadNotificationExist = _isUnreadNotificationExist.asStateFlow() - private val _description = MutableStateFlow(HomeSection()) val description = _description.asStateFlow() val userActiveState = homeUiState .map { it.user.activeState } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), UserActiveState.UNAUTHENTICATED) + val isAllNotificationsConfirm: Flow = homeUiState.map { it.isAllConfirm } val generationList: Flow = homeUiState .filterNotNull() .map { it.user.generationList } @@ -161,7 +157,8 @@ class HomeViewModel @Inject constructor( if (error is HttpException && error.code() == 400) { homeUiState.value = SoptUser( User(activeState = UserActiveState.INACTIVE), - SoptActiveRecord() + SoptActiveRecord(), + false ) } Timber.e(error) @@ -197,21 +194,4 @@ class HomeViewModel @Inject constructor( } } } - - - fun getUnreadNotificationExistence() { - viewModelScope.launch { - val userState = user.map { it.activeState }.first() - if (userState == UserActiveState.UNAUTHENTICATED) return@launch - - getUnreadNotificationExistenceUseCase.invoke() - .onSuccess { - _isUnreadNotificationExist.value = it.exists - } - .onFailure { - _isUnreadNotificationExist.value = false - Timber.e(it) - } - } - } } From ec3a25b3a72c5206abe2a57d4c4efc7d40221010 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Wed, 4 Oct 2023 02:52:59 +0900 Subject: [PATCH 17/38] =?UTF-8?q?[Feat/#341]=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/data/mapper/NotificationHistoryItemMapper.kt | 7 +++---- .../response/NotificationHistoryItemResponse.kt | 8 +++----- .../domain/entity/notification/NotificationHistoryItem.kt | 7 +++---- .../feature/notification/NotificationDetailActivity.kt | 4 ++-- .../feature/notification/NotificationHistoryActivity.kt | 6 +++--- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/sopt/official/data/mapper/NotificationHistoryItemMapper.kt b/app/src/main/java/org/sopt/official/data/mapper/NotificationHistoryItemMapper.kt index 59696dee0..90a2fe3df 100644 --- a/app/src/main/java/org/sopt/official/data/mapper/NotificationHistoryItemMapper.kt +++ b/app/src/main/java/org/sopt/official/data/mapper/NotificationHistoryItemMapper.kt @@ -6,14 +6,13 @@ import org.sopt.official.domain.entity.notification.NotificationHistoryItem class NotificationHistoryItemMapper { fun toNotificationHistoryItem(responseItem: NotificationHistoryItemResponse): NotificationHistoryItem { return NotificationHistoryItem( - id = responseItem.id, + notificationId = responseItem.notificationId, userId = responseItem.userId, title = responseItem.title, content = responseItem.content, - type = responseItem.type, + category = responseItem.category, isRead = responseItem.isRead, - createdAt = responseItem.createdAt, - updatedAt = responseItem.updatedAt + createdAt = responseItem.createdAt ) } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt index 075f92332..42fb70b40 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt @@ -1,16 +1,14 @@ package org.sopt.official.data.model.notification.response -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class NotificationHistoryItemResponse( - val id: Int, + val notificationId: Int, val userId: Int, val title: String, - val content: String, - val type: String?, + val content: String?, + val category: String, val isRead: Boolean, val createdAt: String, - val updatedAt: String, ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt b/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt index 2d8e7d2c0..819b89c57 100644 --- a/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt +++ b/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt @@ -1,12 +1,11 @@ package org.sopt.official.domain.entity.notification data class NotificationHistoryItem( - val id: Int, + val notificationId: Int, val userId: Int, val title: String, - val content: String, - val type: String?, + val content: String?, + val category: String, var isRead: Boolean, val createdAt: String, - val updatedAt: String, ) diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt index 8e9cd7fc6..d4e641ab7 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt @@ -11,7 +11,7 @@ import org.sopt.official.databinding.ActivityNotificationDetailBinding import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.CONTENT import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.ID import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.TITLE -import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.TYPE +import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.CATEGORY import org.sopt.official.util.viewBinding @AndroidEntryPoint @@ -30,7 +30,7 @@ class NotificationDetailActivity : AppCompatActivity() { intent.getStringExtra(CONTENT) } private val type: String? by lazy { - intent.getStringExtra(TYPE) + intent.getStringExtra(CATEGORY) } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index 600c32b69..29bb231b3 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -85,10 +85,10 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem viewModel.updateNotificationReadingState(position) val clickedNotification = viewModel.notificationHistoryList.value[position] Intent(this, NotificationDetailActivity::class.java).run { - putExtra(ID, clickedNotification.id) + putExtra(ID, clickedNotification.notificationId) putExtra(TITLE, clickedNotification.title) putExtra(CONTENT, clickedNotification.content) - putExtra(TYPE, clickedNotification.type) + putExtra(CATEGORY, clickedNotification.category) startActivity(this) } } @@ -97,6 +97,6 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem const val ID = "id" const val TITLE = "title" const val CONTENT = "content" - const val TYPE = "type" + const val CATEGORY = "category" } } \ No newline at end of file From 3316d3a97bc2b2c40fb2aa4ab1fc7c6155f3d696 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Wed, 4 Oct 2023 02:54:18 +0900 Subject: [PATCH 18/38] =?UTF-8?q?[Feat/#341]=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=9D=BD=EC=9D=8C=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?API=20=EC=88=98=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/data/service/notification/NotificationService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index 57c7537f9..d23fd6b45 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -38,7 +38,7 @@ interface NotificationService { @Path("notificationId") notificationId: Int ): NotificationReadingStateResponse - @PATCH("notification/0") + @PATCH("notification") suspend fun updateEntireNotificationReadingState( ): NotificationReadingStateResponse From de9dceceebc75b04312a135384cf30d02b3bc1f8 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Wed, 4 Oct 2023 03:26:01 +0900 Subject: [PATCH 19/38] =?UTF-8?q?[Feat/#341]=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/NotificationDetailResponse.kt | 15 +++++ .../notfication/NotificationRepositoryImpl.kt | 9 +++ .../notification/NotificationService.kt | 6 ++ .../notification/NotificationRepository.kt | 5 ++ .../GetNotificationDetailUseCase.kt | 13 ++++ .../NotificationDetailActivity.kt | 60 +++++++++++-------- .../NotificationDetailViewModel.kt | 25 ++++++-- .../NotificationHistoryActivity.kt | 11 +--- 8 files changed, 104 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt create mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationDetailUseCase.kt diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt new file mode 100644 index 000000000..05b426edb --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt @@ -0,0 +1,15 @@ +package org.sopt.official.data.model.notification.response + +import kotlinx.serialization.Serializable + +@Serializable +data class NotificationDetailResponse( + val notificationId: Int, + val userId: Int, + val title: String, + val content: String?, + val deepLink: String?, + val webLink: String?, + val createdAt: String, + val updatedAt: String, +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt index 7a285d355..e75ecc6bf 100644 --- a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -3,6 +3,7 @@ package org.sopt.official.data.repository.notfication import org.sopt.official.data.mapper.NotificationHistoryItemMapper import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest +import org.sopt.official.data.model.notification.response.NotificationDetailResponse import org.sopt.official.data.service.notification.NotificationService import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse @@ -47,6 +48,14 @@ class NotificationRepositoryImpl @Inject constructor( } } + override suspend fun getNotificationDetail( + notificationId: Int + ): Result { + return runCatching { + service.getNotificationDetail(notificationId) + } + } + override suspend fun updateNotificationReadingState( notificationId: Int ): Result { diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index d23fd6b45..74cf1f204 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -3,6 +3,7 @@ package org.sopt.official.data.service.notification import org.sopt.official.data.model.attendance.* import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest +import org.sopt.official.data.model.notification.response.NotificationDetailResponse import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse @@ -32,6 +33,11 @@ interface NotificationService { @Query("page") page: Int ): ArrayList + @GET("notification/{notificationId}") + suspend fun getNotificationDetail( + @Path("notificationId") notificationId: Int + ): NotificationDetailResponse + @PATCH("notification/{notificationId}") suspend fun updateNotificationReadingState( diff --git a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt index 89f393e9c..a03f64cb6 100644 --- a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt +++ b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt @@ -1,5 +1,6 @@ package org.sopt.official.domain.repository.notification +import org.sopt.official.data.model.notification.response.NotificationDetailResponse import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse @@ -13,6 +14,10 @@ interface NotificationRepository { suspend fun getNotificationHistory( page: Int ): Result> + suspend fun getNotificationDetail( + notificationId: Int + ): Result + suspend fun updateNotificationReadingState( notificationId: Int ): Result diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationDetailUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationDetailUseCase.kt new file mode 100644 index 000000000..1401516dc --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationDetailUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.official.domain.usecase.notification + +import org.sopt.official.data.model.notification.response.NotificationDetailResponse +import org.sopt.official.domain.repository.notification.NotificationRepository +import javax.inject.Inject + +class GetNotificationDetailUseCase @Inject constructor( + private val notificationRepository: NotificationRepository +) { + suspend operator fun invoke(notificationId: Int): Result { + return notificationRepository.getNotificationDetail(notificationId) + } +} diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt index d4e641ab7..7d4aa67a6 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt @@ -1,17 +1,20 @@ package org.sopt.official.feature.notification +import android.content.Intent +import android.net.Uri import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import androidx.activity.viewModels +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.sopt.official.R -import org.sopt.official.config.messaging.RemoteMessageType +import org.sopt.official.data.model.notification.response.NotificationDetailResponse import org.sopt.official.databinding.ActivityNotificationDetailBinding -import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.CONTENT -import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.ID -import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.TITLE -import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.CATEGORY +import org.sopt.official.feature.notification.NotificationHistoryActivity.Companion.NOTIFICATION_ID import org.sopt.official.util.viewBinding @AndroidEntryPoint @@ -20,36 +23,39 @@ class NotificationDetailActivity : AppCompatActivity() { private val binding by viewBinding(ActivityNotificationDetailBinding::inflate) private val viewModel by viewModels() - private val id: Int by lazy { - intent.getIntExtra(ID, 0) - } - private val title: String? by lazy { - intent.getStringExtra(TITLE) - } - private val content: String? by lazy { - intent.getStringExtra(CONTENT) - } - private val type: String? by lazy { - intent.getStringExtra(CATEGORY) + private val notificationId: Int by lazy { + intent.getIntExtra(NOTIFICATION_ID, 0) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - if (id != 0) viewModel.updateNotificationReadingState(id) - initNotificationDetail() + binding.includeAppBarBackArrow.textViewTitle.text = getString(R.string.toolbar_notification) + viewModel.getNotificationDetail(notificationId) + + initStateFlowValues() initClickListeners() } - private fun initNotificationDetail() { + private fun initStateFlowValues() { + viewModel.apply { + notificationDetail.flowWithLifecycle(lifecycle) + .onEach { it?.let { notification -> initNotificationDetail(notification) } } + .launchIn(lifecycleScope) + } + } + + private fun initNotificationDetail(notification: NotificationDetailResponse) { binding.apply { - includeAppBarBackArrow.textViewTitle.text = getString(R.string.toolbar_notification) - textViewNotificationTitle.text = title - textViewNotificationContent.text = content - linearLayoutNewsDetailButton.visibility = when (type) { - RemoteMessageType.NOTICE.name -> View.GONE - else -> View.VISIBLE + textViewNotificationTitle.text = notification.title + textViewNotificationContent.text = notification.content + linearLayoutNewsDetailButton.visibility = when ( + notification.deepLink.isNullOrBlank() + && notification.webLink.isNullOrBlank() + ) { + true -> View.GONE + false -> View.VISIBLE } } } @@ -65,7 +71,9 @@ class NotificationDetailActivity : AppCompatActivity() { binding.apply { when (it) { includeAppBarBackArrow.toolbar -> onBackPressed() - linearLayoutNewsDetailButton -> {} // TODO: Set intent to link. + linearLayoutNewsDetailButton -> viewModel.notificationDetail.value?.let { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.webLink))) + } } } } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt index bb01dbc06..3f7927917 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt @@ -4,22 +4,35 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import org.sopt.official.config.messaging.RemoteMessageType -import org.sopt.official.domain.entity.notification.NotificationHistoryItem -import org.sopt.official.domain.usecase.notification.GetNotificationHistoryUseCase -import org.sopt.official.domain.usecase.notification.UpdateEntireNotificationReadingStateUseCase +import org.sopt.official.data.model.notification.response.NotificationDetailResponse +import org.sopt.official.domain.usecase.notification.GetNotificationDetailUseCase import org.sopt.official.domain.usecase.notification.UpdateNotificationReadingStateUseCase import timber.log.Timber import javax.inject.Inject @HiltViewModel class NotificationDetailViewModel @Inject constructor( + private val getNotificationDetailUseCase: GetNotificationDetailUseCase, private val updateNotificationReadingStateUseCase: UpdateNotificationReadingStateUseCase, ) : ViewModel() { - fun updateNotificationReadingState(id: Int) { + private val _notificationDetail = MutableStateFlow(null) + val notificationDetail: StateFlow get() = _notificationDetail + + fun getNotificationDetail(id: Int) { + viewModelScope.launch { + getNotificationDetailUseCase.invoke(id) + .onSuccess { + _notificationDetail.value = it + updateNotificationReadingState(it.notificationId) + } + .onFailure { Timber.e(it) } + } + } + + private fun updateNotificationReadingState(id: Int) { viewModelScope.launch { updateNotificationReadingStateUseCase.invoke(id) .onFailure { Timber.e(it) } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index 29bb231b3..a7bbd15a6 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -84,19 +84,14 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem override fun onClickNotificationHistoryItem(position: Int) { viewModel.updateNotificationReadingState(position) val clickedNotification = viewModel.notificationHistoryList.value[position] + Intent(this, NotificationDetailActivity::class.java).run { - putExtra(ID, clickedNotification.notificationId) - putExtra(TITLE, clickedNotification.title) - putExtra(CONTENT, clickedNotification.content) - putExtra(CATEGORY, clickedNotification.category) + putExtra(NOTIFICATION_ID, clickedNotification.notificationId) startActivity(this) } } companion object { - const val ID = "id" - const val TITLE = "title" - const val CONTENT = "content" - const val CATEGORY = "category" + const val NOTIFICATION_ID = "notificationId" } } \ No newline at end of file From 224b41e72eaadc7ac75706a9b6c0bfbfd9b2deed Mon Sep 17 00:00:00 2001 From: yxnsx Date: Wed, 4 Oct 2023 04:48:32 +0900 Subject: [PATCH 20/38] =?UTF-8?q?[Feat/#341]=20NotificationHistoryActivity?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=EC=97=90=20=EC=A0=84=EC=B2=B4=20/=20?= =?UTF-8?q?=EA=B0=9C=EB=B3=84=20=EC=95=8C=EB=A6=BC=20=EC=9D=BD=EC=9D=8C=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationHistoryActivity.kt | 28 ++++++++++--------- .../NotificationHistoryListViewAdapter.kt | 13 +++++++-- .../NotificationHistoryViewModel.kt | 11 ++------ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index a7bbd15a6..02b626dcb 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -1,15 +1,14 @@ package org.sopt.official.feature.notification import android.content.Intent -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import androidx.activity.viewModels -import androidx.lifecycle.flowWithLifecycle +import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import org.sopt.official.R import org.sopt.official.databinding.ActivityNotificationHistoryBinding import org.sopt.official.util.viewBinding @@ -20,7 +19,8 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem private val binding by viewBinding(ActivityNotificationHistoryBinding::inflate) private val viewModel by viewModels() - private lateinit var notificationHistoryAdapter: NotificationHistoryListViewAdapter + private val notificationHistoryAdapter + get() = binding.recyclerViewNotificationHistory.adapter as NotificationHistoryListViewAdapter? override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -37,8 +37,7 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem } private fun initRecyclerView() { - notificationHistoryAdapter = NotificationHistoryListViewAdapter(this@NotificationHistoryActivity) - binding.recyclerViewNotificationHistory.adapter = notificationHistoryAdapter + binding.recyclerViewNotificationHistory.adapter = NotificationHistoryListViewAdapter(this@NotificationHistoryActivity) } private fun initClickListeners() { @@ -52,19 +51,22 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem binding.apply { when (it) { includeAppBarBackArrow.toolbar -> onBackPressedDispatcher.onBackPressed() - textViewReadAll -> viewModel.updateEntireNotificationReadingState() + textViewReadAll -> { + viewModel.updateEntireNotificationReadingState() + notificationHistoryAdapter?.updateEntireNotificationReadingState() + } } } } private fun initStateFlowValues() { - viewModel.notificationHistoryList.flowWithLifecycle(lifecycle) - .onEach { + lifecycleScope.launch { + viewModel.notificationHistoryList.collectLatest { setEmptyViewVisibility(it.isEmpty()) setTextViewReadAllVisibility(it.isNotEmpty()) - notificationHistoryAdapter.submitList(it) + notificationHistoryAdapter?.submitList(it) } - .launchIn(lifecycleScope) + } } private fun setEmptyViewVisibility(isVisible: Boolean) { @@ -82,7 +84,7 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem } override fun onClickNotificationHistoryItem(position: Int) { - viewModel.updateNotificationReadingState(position) + notificationHistoryAdapter?.updateNotificationReadingState(position) val clickedNotification = viewModel.notificationHistoryList.value[position] Intent(this, NotificationDetailActivity::class.java).run { diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt index ded775a3f..78985112b 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt @@ -13,9 +13,8 @@ import org.sopt.official.util.drawableOf class NotificationHistoryListViewAdapter( private val clickListener: NotificationHistoryItemClickListener ) : ListAdapter( - ItemDiffCallback( - onContentsTheSame = { old, new -> old.title == new.title }, + onContentsTheSame = { old, new -> old.notificationId == new.notificationId }, onItemsTheSame = { old, new -> old == new } ) ) { @@ -46,6 +45,16 @@ class NotificationHistoryListViewAdapter( val newList = currentList.toMutableList() newList[position].isRead = true submitList(newList) + notifyItemChanged(position) + } + + fun updateEntireNotificationReadingState() { + val newList = currentList.toMutableList() + for (notification in newList) { + notification.isRead = true + } + submitList(newList) + notifyItemRangeChanged(0, newList.size) } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt index b4b250430..65dbe1b48 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.sopt.official.domain.entity.notification.NotificationHistoryItem @@ -19,7 +20,7 @@ class NotificationHistoryViewModel @Inject constructor( ) : ViewModel() { private val _notificationHistoryList = MutableStateFlow>(arrayListOf()) - val notificationHistoryList = _notificationHistoryList.asStateFlow() + val notificationHistoryList: StateFlow> get() = _notificationHistoryList.asStateFlow() init { getNotificationHistory(0) @@ -33,12 +34,6 @@ class NotificationHistoryViewModel @Inject constructor( } } - fun updateNotificationReadingState(position: Int) { - val newNotificationList = _notificationHistoryList.value - newNotificationList[position].isRead = true - _notificationHistoryList.value = newNotificationList - } - fun updateEntireNotificationReadingState() { viewModelScope.launch { updateEntireNotificationReadingStateUseCase.invoke() @@ -48,8 +43,6 @@ class NotificationHistoryViewModel @Inject constructor( notification.isRead = true } _notificationHistoryList.value = newNotificationList -// _updateEntireNotificationReadingState.value = true - Timber.d("updateEntireNotificationReadingStateUseCase: ", it) } .onFailure { Timber.e("updateEntireNotificationReadingStateUseCase: ", it) } } From 25918f11c41c5a1d3659e6c7ff1b07637c2bf258 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Wed, 4 Oct 2023 06:45:13 +0900 Subject: [PATCH 21/38] =?UTF-8?q?[Feat/#341]=20Notification=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=ED=83=80=EC=9E=85=EB=B3=84=20intent=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/messaging/RemoteMessageLinkType.kt | 2 +- .../official/feature/auth/AuthActivity.kt | 42 ++++++++++++++++--- .../official/feature/home/HomeActivity.kt | 37 +++++++++++++--- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt index 4cd0d63f9..276278ac5 100644 --- a/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt +++ b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt @@ -1,7 +1,7 @@ package org.sopt.official.config.messaging enum class RemoteMessageLinkType { - WEB_LINK, DEEP_LINK; + WEB_LINK, DEEP_LINK, DEFAULT; companion object { fun of(name: String) = entries.find { diff --git a/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt b/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt index 17b1ed572..75a365bb8 100644 --- a/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.Intent import android.app.NotificationChannel import android.app.NotificationManager +import android.content.Intent import android.graphics.Paint import android.os.Bundle import android.view.animation.AnimationUtils @@ -23,6 +24,8 @@ import org.sopt.official.R import org.sopt.official.auth.PlaygroundAuth import org.sopt.official.auth.data.PlaygroundAuthDatasource import org.sopt.official.auth.data.remote.model.response.OAuthToken +import org.sopt.official.config.messaging.SoptFirebaseMessagingService.Companion.REMOTE_MESSAGE_EVENT_LINK +import org.sopt.official.config.messaging.SoptFirebaseMessagingService.Companion.REMOTE_MESSAGE_EVENT_TYPE import org.sopt.official.common.di.Auth import org.sopt.official.data.model.request.AuthRequest import org.sopt.official.data.persistence.SoptDataStore @@ -52,9 +55,7 @@ class AuthActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (dataStore.accessToken.isNotEmpty()) { - startActivity(HomeActivity.getIntent(this, HomeActivity.StartArgs( - UserStatus.valueOf(dataStore.userStatus) - ))) + onNewIntent(intent) } setContentView(binding.root) initNotificationChannel() @@ -64,6 +65,29 @@ class AuthActivity : AppCompatActivity() { collectUiEvent() } + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + + intent?.let { + val remoteMessageEventType = it.getStringExtra(REMOTE_MESSAGE_EVENT_TYPE) ?: "" + val remoteMessageEventLink = it.getStringExtra(REMOTE_MESSAGE_EVENT_LINK) ?: "" + + if ( + dataStore.userStatus.isNotBlank() + && dataStore.userStatus != UserStatus.UNAUTHENTICATED.name + && remoteMessageEventType.isNotBlank() + ) { + startActivity(HomeActivity.getIntent(this, + HomeActivity.StartArgs( + UserStatus.of(dataStore.userStatus), + remoteMessageEventType, + remoteMessageEventLink + ) + )) + } + } + } + private fun initNotificationChannel() { NotificationChannel( getString(R.string.toolbar_notification_filter_all), @@ -82,13 +106,21 @@ class AuthActivity : AppCompatActivity() { viewModel.uiEvent .flowWithLifecycle(lifecycle) .onEach { event -> + val remoteMessageEventType = intent.getStringExtra(REMOTE_MESSAGE_EVENT_TYPE) ?: "" + val remoteMessageEventLink = intent.getStringExtra(REMOTE_MESSAGE_EVENT_LINK) ?: "" when (event) { is AuthUiEvent.Success -> { - startActivity(HomeActivity.getIntent(this, event.userStatus)) + startActivity(HomeActivity.getIntent(this, + HomeActivity.StartArgs( + event.userStatus, + remoteMessageEventType, + remoteMessageEventLink + ) + )) } is AuthUiEvent.Failure -> { - startActivity(HomeActivity.getIntent(this, UserStatus.UNAUTHENTICATED)) + startActivity(HomeActivity.getIntent(this, HomeActivity.StartArgs(UserStatus.UNAUTHENTICATED))) } } }.launchIn(lifecycleScope) diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt index 1e92d7fd4..700f3566e 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.launch import org.sopt.official.R import org.sopt.official.analytics.AmplitudeTracker import org.sopt.official.analytics.EventType +import org.sopt.official.config.messaging.RemoteMessageLinkType import org.sopt.official.databinding.ActivitySoptMainBinding import org.sopt.official.databinding.ItemMainSmallBinding import org.sopt.official.domain.entity.UserActiveState @@ -47,12 +48,13 @@ import org.sopt.official.util.stringOf import org.sopt.official.util.ui.setVisible import org.sopt.official.util.viewBinding import javax.inject.Inject +import java.io.Serializable @AndroidEntryPoint class HomeActivity : AppCompatActivity() { private val binding by viewBinding(ActivitySoptMainBinding::inflate) private val viewModel by viewModels() - private val args by serializableExtra(UserStatus.UNAUTHENTICATED) + private val args by serializableExtra(StartArgs(UserStatus.UNAUTHENTICATED)) @Inject lateinit var tracker: AmplitudeTracker @@ -93,6 +95,7 @@ class HomeActivity : AppCompatActivity() { setContentView(binding.root) tracker.track(type = EventType.VIEW, name = "apphome", properties = mapOf("view_type" to args?.value)) + initIntentData() requestNotificationPermission() initToolbar() initUserStatus() @@ -100,10 +103,28 @@ class HomeActivity : AppCompatActivity() { initBlock() } + private fun initIntentData() { + + args?.remoteMessageEventType?.let { + if (it.isBlank()) return + when (RemoteMessageLinkType.of(it)) { + RemoteMessageLinkType.WEB_LINK -> { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(args?.remoteMessageEventLink)) + startActivity(intent) + } +// RemoteMessageLinkType.DEEP_LINK -> {} TODO: 딥링크 정의된 후 구현 예정 + else -> { + val intent = Intent(this, NotificationHistoryActivity::class.java) + startActivity(intent) + } + } + } + } + private fun initUserStatus() { - viewModel.initHomeUi(args ?: UserStatus.UNAUTHENTICATED) - viewModel.initMainDescription(args ?: UserStatus.UNAUTHENTICATED) - viewModel.registerPushToken(args ?: UserStatus.UNAUTHENTICATED) + viewModel.initHomeUi(args?.userStatus ?: UserStatus.UNAUTHENTICATED) + viewModel.initMainDescription(args?.userStatus ?: UserStatus.UNAUTHENTICATED) + viewModel.registerPushToken(args?.userStatus ?: UserStatus.UNAUTHENTICATED) } private fun initToolbar() { @@ -268,9 +289,15 @@ class HomeActivity : AppCompatActivity() { } } + data class StartArgs( + val userStatus: UserStatus, + val remoteMessageEventType: String = "", + val remoteMessageEventLink: String = "", + ) : Serializable + companion object { @JvmStatic - fun getIntent(context: Context, args: UserStatus) = + fun getIntent(context: Context, args: StartArgs) = Intent(context, HomeActivity::class.java).apply { putExtra("args", args) } From 126e55e91038dce8d72f6414358a2f6eda43f634 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Wed, 4 Oct 2023 23:05:18 +0900 Subject: [PATCH 22/38] =?UTF-8?q?[Feat/#341]=20NotificationHistoryActivity?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=EC=97=90=20pagination=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationHistoryActivity.kt | 25 +++++++++++++++++-- .../NotificationHistoryListViewAdapter.kt | 5 ++++ .../NotificationHistoryViewModel.kt | 23 ++++++++++++----- .../layout/activity_notification_history.xml | 1 + 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index 02b626dcb..a7174477a 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -6,6 +6,8 @@ import android.view.View import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -13,6 +15,7 @@ import org.sopt.official.R import org.sopt.official.databinding.ActivityNotificationHistoryBinding import org.sopt.official.util.viewBinding + @AndroidEntryPoint class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItemClickListener { @@ -22,6 +25,9 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem private val notificationHistoryAdapter get() = binding.recyclerViewNotificationHistory.adapter as NotificationHistoryListViewAdapter? + private val notificationHistoryLayoutManager + get() = binding.recyclerViewNotificationHistory.layoutManager as LinearLayoutManager + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) @@ -37,7 +43,22 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem } private fun initRecyclerView() { - binding.recyclerViewNotificationHistory.adapter = NotificationHistoryListViewAdapter(this@NotificationHistoryActivity) + binding.recyclerViewNotificationHistory.apply { + adapter = NotificationHistoryListViewAdapter(this@NotificationHistoryActivity) + addOnScrollListener(scrollListener) + } + } + + private val scrollListener = object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + val lastVisibleItemPosition = notificationHistoryLayoutManager.findLastVisibleItemPosition() + val totalItemCount = notificationHistoryLayoutManager.itemCount + if (lastVisibleItemPosition == totalItemCount - 1 && totalItemCount % 10 == 0) { + viewModel.getNotificationHistory() + } + } } private fun initClickListeners() { @@ -64,7 +85,7 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem viewModel.notificationHistoryList.collectLatest { setEmptyViewVisibility(it.isEmpty()) setTextViewReadAllVisibility(it.isNotEmpty()) - notificationHistoryAdapter?.submitList(it) + notificationHistoryAdapter?.updateNotificationHistoryList(it) } } } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt index 78985112b..620f46716 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt @@ -57,6 +57,11 @@ class NotificationHistoryListViewAdapter( notifyItemRangeChanged(0, newList.size) } + fun updateNotificationHistoryList(newList: List) { + submitList(newList) + notifyDataSetChanged() + } + inner class ViewHolder( private val viewBinding: ItemNotificationHistoryBinding diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt index 65dbe1b48..e25763458 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryViewModel.kt @@ -3,6 +3,7 @@ package org.sopt.official.feature.notification import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -22,14 +23,24 @@ class NotificationHistoryViewModel @Inject constructor( private val _notificationHistoryList = MutableStateFlow>(arrayListOf()) val notificationHistoryList: StateFlow> get() = _notificationHistoryList.asStateFlow() + private var currentPaginationIndex = 0 + private var notificationHistoryJob: Job? = null + init { - getNotificationHistory(0) + getNotificationHistory() } - fun getNotificationHistory(page: Int) { - viewModelScope.launch { - getNotificationHistoryUseCase.invoke(page) - .onSuccess { _notificationHistoryList.value = it } + fun getNotificationHistory() { + notificationHistoryJob?.let { + if (it.isActive || !it.isCompleted) return + } + + notificationHistoryJob = viewModelScope.launch { + getNotificationHistoryUseCase.invoke(currentPaginationIndex) + .onSuccess { + _notificationHistoryList.value = _notificationHistoryList.value.plus(it) + currentPaginationIndex++ + } .onFailure { Timber.e(it) } } } @@ -44,7 +55,7 @@ class NotificationHistoryViewModel @Inject constructor( } _notificationHistoryList.value = newNotificationList } - .onFailure { Timber.e("updateEntireNotificationReadingStateUseCase: ", it) } + .onFailure { Timber.e(it) } } } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification_history.xml b/app/src/main/res/layout/activity_notification_history.xml index 55e6454fc..06a60e064 100644 --- a/app/src/main/res/layout/activity_notification_history.xml +++ b/app/src/main/res/layout/activity_notification_history.xml @@ -86,6 +86,7 @@ android:id="@+id/recyclerView_notificationHistory" android:layout_width="match_parent" android:layout_height="0dp" + android:scrollbars="vertical" android:orientation="vertical" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" From a2b8626cc8582306b6addc8d24321576134a2e52 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Thu, 5 Oct 2023 01:34:47 +0900 Subject: [PATCH 23/38] =?UTF-8?q?[Feat/#341]=20MyPageActivity=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=98=20notification=20switch=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9D=BC=EB=B0=98=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationSubscriptionResponse.kt | 8 ---- .../notfication/NotificationRepositoryImpl.kt | 20 --------- .../notification/NotificationService.kt | 12 ------ .../notification/NotificationRepository.kt | 6 --- .../GetNotificationSubscriptionUseCase.kt | 13 ------ .../UpdateNotificationSubscriptionUseCase.kt | 17 -------- .../official/feature/mypage/MyPageActivity.kt | 27 ++++-------- .../feature/mypage/MyPageViewModel.kt | 42 ------------------- app/src/main/res/layout/activity_my_page.xml | 11 +++-- app/src/main/res/values/strings.xml | 4 +- 10 files changed, 19 insertions(+), 141 deletions(-) delete mode 100644 app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt delete mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationSubscriptionUseCase.kt delete mode 100644 app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt deleted file mode 100644 index aa97b03d4..000000000 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationSubscriptionResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.official.data.model.notification.response - -import kotlinx.serialization.Serializable - -@Serializable -data class NotificationSubscriptionResponse( - val isOptIn: Boolean -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt index e75ecc6bf..c8274d042 100644 --- a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -1,12 +1,10 @@ package org.sopt.official.data.repository.notfication import org.sopt.official.data.mapper.NotificationHistoryItemMapper -import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest import org.sopt.official.data.model.notification.response.NotificationDetailResponse import org.sopt.official.data.service.notification.NotificationService import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse -import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import org.sopt.official.domain.entity.notification.NotificationHistoryItem import org.sopt.official.domain.repository.notification.NotificationRepository @@ -69,22 +67,4 @@ class NotificationRepositoryImpl @Inject constructor( service.updateEntireNotificationReadingState() } } - - override suspend fun getNotificationSubscription(): Result { - return runCatching { - service.getNotificationSubscription() - } - } - - override suspend fun updateNotificationSubscription( - isSubscribed: Boolean - ): Result { - return runCatching { - service.updateNotificationSubscription( - NotificationSubscriptionRequest( - isOptIn = isSubscribed - ) - ) - } - } } diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index 74cf1f204..6a3969bea 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -1,12 +1,10 @@ package org.sopt.official.data.service.notification import org.sopt.official.data.model.attendance.* -import org.sopt.official.data.model.notification.request.NotificationSubscriptionRequest import org.sopt.official.data.model.notification.request.UpdatePushTokenRequest import org.sopt.official.data.model.notification.response.NotificationDetailResponse import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse -import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import retrofit2.http.Body import retrofit2.http.DELETE @@ -47,14 +45,4 @@ interface NotificationService { @PATCH("notification") suspend fun updateEntireNotificationReadingState( ): NotificationReadingStateResponse - - - @GET("user/opt-in") - suspend fun getNotificationSubscription( - ): NotificationSubscriptionResponse - - @PATCH("user/opt-in") - suspend fun updateNotificationSubscription( - @Body body: NotificationSubscriptionRequest - ): NotificationSubscriptionResponse } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt index a03f64cb6..dd4e3dd7f 100644 --- a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt +++ b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt @@ -2,7 +2,6 @@ package org.sopt.official.domain.repository.notification import org.sopt.official.data.model.notification.response.NotificationDetailResponse import org.sopt.official.data.model.notification.response.NotificationReadingStateResponse -import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse import org.sopt.official.data.model.notification.response.UpdatePushTokenResponse import org.sopt.official.domain.entity.notification.NotificationHistoryItem @@ -22,9 +21,4 @@ interface NotificationRepository { notificationId: Int ): Result suspend fun updateEntireNotificationReadingState(): Result - - suspend fun getNotificationSubscription(): Result - suspend fun updateNotificationSubscription( - isSubscribed: Boolean - ): Result } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationSubscriptionUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationSubscriptionUseCase.kt deleted file mode 100644 index 7b57f6cac..000000000 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationSubscriptionUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.sopt.official.domain.usecase.notification - -import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse -import org.sopt.official.domain.repository.notification.NotificationRepository -import javax.inject.Inject - -class GetNotificationSubscriptionUseCase @Inject constructor( - private val notificationRepository: NotificationRepository -) { - suspend operator fun invoke(): Result { - return notificationRepository.getNotificationSubscription() - } -} diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt deleted file mode 100644 index 46ec839de..000000000 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationSubscriptionUseCase.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.sopt.official.domain.usecase.notification - -import org.sopt.official.data.model.notification.response.NotificationSubscriptionResponse -import org.sopt.official.domain.repository.notification.NotificationRepository -import javax.inject.Inject - -class UpdateNotificationSubscriptionUseCase @Inject constructor( - private val notificationRepository: NotificationRepository -) { - suspend operator fun invoke( - isSubscribed: Boolean - ): Result { - return notificationRepository.updateNotificationSubscription( - isSubscribed = isSubscribed - ) - } -} diff --git a/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt b/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt index 7c874e301..91b1fd368 100644 --- a/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/mypage/MyPageActivity.kt @@ -4,17 +4,14 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle +import android.provider.Settings import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.flowWithLifecycle -import androidx.lifecycle.lifecycleScope import com.jakewharton.processphoenix.ProcessPhoenix import com.jakewharton.rxbinding4.view.clicks import dagger.hilt.android.AndroidEntryPoint import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.disposables.CompositeDisposable -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import org.sopt.official.R import org.sopt.official.databinding.ActivityMyPageBinding import org.sopt.official.designsystem.AlertDialogPositiveNegative @@ -28,7 +25,6 @@ import org.sopt.official.util.rx.observeOnMain import org.sopt.official.util.rx.subscribeBy import org.sopt.official.util.rx.subscribeOnIo import org.sopt.official.util.serializableExtra -import org.sopt.official.util.setOnSingleClickListener import org.sopt.official.util.ui.setVisible import org.sopt.official.util.ui.throttleUi import org.sopt.official.util.viewBinding @@ -52,10 +48,7 @@ class MyPageActivity : AppCompatActivity() { initClick() initRestart() - // TODO: 로그인 상태 아닐때 제외해줄 기능 처리 필요 - initStateFlowValues() - initNotificationClickListener() - viewModel.getNotificationSubscription() + initNotificationSettingClickListener() } private fun initStartArgs() { @@ -236,15 +229,13 @@ class MyPageActivity : AppCompatActivity() { ) } - private fun initStateFlowValues() { - viewModel.notificationSubscriptionState.flowWithLifecycle(lifecycle) - .onEach { binding.switchNotification.isChecked = it } - .launchIn(lifecycleScope) - } - - private fun initNotificationClickListener() { - binding.switchNotification.setOnCheckedChangeListener { _, isChecked -> - viewModel.updateNotificationSubscription(isChecked) + private fun initNotificationSettingClickListener() { + binding.linearLayoutNotificationSettingContainer.setOnClickListener { + Intent().apply { + action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + putExtra(Settings.EXTRA_APP_PACKAGE, packageName) + startActivity(this) + } } } diff --git a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt index f0d1b14b6..2e9827145 100644 --- a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt @@ -2,17 +2,11 @@ package org.sopt.official.feature.mypage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.google.firebase.messaging.FirebaseMessaging import dagger.hilt.android.lifecycle.HiltViewModel import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.subjects.PublishSubject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import org.sopt.official.domain.repository.AuthRepository -import org.sopt.official.domain.usecase.notification.GetNotificationSubscriptionUseCase -import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase -import org.sopt.official.domain.usecase.notification.UpdateNotificationSubscriptionUseCase import org.sopt.official.feature.mypage.model.MyPageUiState import org.sopt.official.stamp.domain.repository.StampRepository import timber.log.Timber @@ -22,17 +16,11 @@ import javax.inject.Inject class MyPageViewModel @Inject constructor( private val authRepository: AuthRepository, private val stampRepository: StampRepository, - private val registerPushTokenUseCase: RegisterPushTokenUseCase, - private val getNotificationSubscriptionUseCase: GetNotificationSubscriptionUseCase, - private val updateNotificationSubscriptionUseCase: UpdateNotificationSubscriptionUseCase, ) : ViewModel() { val userActiveState = BehaviorProcessor.createDefault(MyPageUiState.UnInitialized) val restartSignal = PublishSubject.create() - private val _notificationSubscriptionState = MutableStateFlow(false) - val notificationSubscriptionState: StateFlow get() = _notificationSubscriptionState - fun logOut() { viewModelScope.launch { authRepository.logout() @@ -53,34 +41,4 @@ class MyPageViewModel @Inject constructor( } } } - - fun getNotificationSubscription() { - viewModelScope.launch { - getNotificationSubscriptionUseCase.invoke() - .onSuccess { _notificationSubscriptionState.value = it.isOptIn } - .onFailure { Timber.e("getNotificationSubscription: ", it) } - } - } - - fun updateNotificationSubscription(isSubscribed: Boolean) { - viewModelScope.launch { - updateNotificationSubscriptionUseCase.invoke(isSubscribed) - .onSuccess { - if (it.isOptIn) registerPushToken() - _notificationSubscriptionState.value = it.isOptIn - } - .onFailure { Timber.e("updateNotificationSubscription: ", it) } - } - } - - private fun registerPushToken() { - FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> - if (task.isComplete) { - viewModelScope.launch { - registerPushTokenUseCase.invoke(task.result) - .onFailure { Timber.e(it) } - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_my_page.xml b/app/src/main/res/layout/activity_my_page.xml index c08c77eff..a31f34738 100644 --- a/app/src/main/res/layout/activity_my_page.xml +++ b/app/src/main/res/layout/activity_my_page.xml @@ -148,10 +148,15 @@ android:textAppearance="?textAppearanceBodyLarge" android:textColor="?colorOnBackground" /> - + android:layout_height="wrap_content" + android:background="@drawable/btn_arrow_right" + android:backgroundTint="@color/gray_80" + app:layout_constraintBottom_toBottomOf="@id/text_send_opinion" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/text_send_opinion" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 46de3bd12..c46abf7f7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,7 +77,7 @@ 서비스 이용약관 의견 보내기 알림 설정 - 알림 + 알림 설정하기 솝탬프 설정 한 마디 편집 닉네임 변경 @@ -111,7 +111,7 @@ 한 마디 편집 저장 - + 알림 모두 읽음 전체 알림 From b6ff8cb1252b201b2cdd07b0901293b69be61a87 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Thu, 5 Oct 2023 01:37:10 +0900 Subject: [PATCH 24/38] =?UTF-8?q?[Feat/#341]=20HomeActivity=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=82=B4=20initHomeUi()=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=EC=8B=9C=EC=A0=90=20onResume()=EC=9C=BC=EB=A1=9C=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 --- .../java/org/sopt/official/feature/home/HomeActivity.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt index 700f3566e..a9952e8ad 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt @@ -103,8 +103,12 @@ class HomeActivity : AppCompatActivity() { initBlock() } - private fun initIntentData() { + override fun onResume() { + super.onResume() + viewModel.initHomeUi(args?.userStatus ?: UserStatus.UNAUTHENTICATED) + } + private fun initIntentData() { args?.remoteMessageEventType?.let { if (it.isBlank()) return when (RemoteMessageLinkType.of(it)) { @@ -122,7 +126,6 @@ class HomeActivity : AppCompatActivity() { } private fun initUserStatus() { - viewModel.initHomeUi(args?.userStatus ?: UserStatus.UNAUTHENTICATED) viewModel.initMainDescription(args?.userStatus ?: UserStatus.UNAUTHENTICATED) viewModel.registerPushToken(args?.userStatus ?: UserStatus.UNAUTHENTICATED) } From f792046d9e5dd64a2d4a9edb5a2a00560a08ebb0 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Thu, 5 Oct 2023 19:59:12 +0900 Subject: [PATCH 25/38] =?UTF-8?q?[Feat/#341]=20NotificationHistoryListView?= =?UTF-8?q?Adapter=20=ED=8C=8C=EC=9D=BC=EC=97=90=20=EB=85=B8=ED=8B=B0?= =?UTF-8?q?=ED=94=BC=EC=BC=80=EC=9D=B4=EC=85=98=20=EC=88=98=EC=8B=A0=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=B3=80=ED=99=98=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationHistoryListViewAdapter.kt | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt index 620f46716..f1014fe5f 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt @@ -1,5 +1,8 @@ package org.sopt.official.feature.notification +import android.icu.text.DateFormat +import android.icu.text.SimpleDateFormat +import android.icu.util.TimeZone import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter @@ -9,6 +12,8 @@ import org.sopt.official.core.view.ItemDiffCallback import org.sopt.official.databinding.ItemNotificationHistoryBinding import org.sopt.official.domain.entity.notification.NotificationHistoryItem import org.sopt.official.util.drawableOf +import java.util.Date +import java.util.Locale class NotificationHistoryListViewAdapter( private val clickListener: NotificationHistoryItemClickListener @@ -71,12 +76,38 @@ class NotificationHistoryListViewAdapter( viewBinding.apply { textViewTitle.text = item.title textViewContent.text = item.content - textViewReceivedTime.text = item.createdAt + textViewReceivedTime.text = item.createdAt.convertToTimesAgo() constraintLayoutBackground.background = when (item.isRead) { true -> root.context.drawableOf(R.color.black_100) false -> root.context.drawableOf(R.color.black_80) } } } + + private fun String.convertToTimesAgo(): String { + val dateFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.KOREA) + dateFormat.timeZone = TimeZone.getTimeZone("Asia/Seoul") + + val currentDate = Date() + val receivedDate = dateFormat.parse(this) + val diffInMillis = currentDate.time - receivedDate.time + + val diffInDays = diffInMillis / ONE_DAY_IN_MILLISECONDS + val diffInHours = diffInMillis / ONE_HOUR_IN_MILLISECONDS + val diffInMinutes = diffInMillis / ONE_MINUTE_IN_MILLISECONDS + + return when { + diffInDays >= 1 -> "${diffInDays}일 전" + diffInHours >= 1 ->"${diffInHours}시간 전" + diffInMinutes >= 1 ->"${diffInMinutes}분 전" + else -> "방금" + } + } + } + + companion object { + const val ONE_DAY_IN_MILLISECONDS = 86400000L + const val ONE_HOUR_IN_MILLISECONDS = 3600000L + const val ONE_MINUTE_IN_MILLISECONDS = 60000L } } \ No newline at end of file From dc117ea8f6d38d1af9765e8f1b9673ff07463023 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Fri, 6 Oct 2023 17:57:37 +0900 Subject: [PATCH 26/38] =?UTF-8?q?[Feat/#341]=20MyPageActivity=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=97=90=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/model/request/LogOutRequest.kt | 10 +++++++ .../data/model/response/LogOutResponse.kt | 10 +++++++ .../data/repository/AuthRepositoryImpl.kt | 13 +++++++-- .../sopt/official/data/service/AuthService.kt | 8 ++++++ .../domain/repository/AuthRepository.kt | 3 ++- .../feature/mypage/MyPageViewModel.kt | 27 ++++++++++++------- 6 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/data/model/request/LogOutRequest.kt create mode 100644 app/src/main/java/org/sopt/official/data/model/response/LogOutResponse.kt diff --git a/app/src/main/java/org/sopt/official/data/model/request/LogOutRequest.kt b/app/src/main/java/org/sopt/official/data/model/request/LogOutRequest.kt new file mode 100644 index 000000000..b6187a4da --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/request/LogOutRequest.kt @@ -0,0 +1,10 @@ +package org.sopt.official.data.model.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LogOutRequest( + @SerialName("platform") val platform: String, + @SerialName("pushToken") val pushToken: String +) diff --git a/app/src/main/java/org/sopt/official/data/model/response/LogOutResponse.kt b/app/src/main/java/org/sopt/official/data/model/response/LogOutResponse.kt new file mode 100644 index 000000000..d81620944 --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/model/response/LogOutResponse.kt @@ -0,0 +1,10 @@ +package org.sopt.official.data.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class LogOutResponse( + val status: Int, + val success: Boolean, + val message: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/AuthRepositoryImpl.kt index e69c20a99..4c841a9c8 100644 --- a/app/src/main/java/org/sopt/official/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/AuthRepositoryImpl.kt @@ -1,7 +1,9 @@ package org.sopt.official.data.repository import org.sopt.official.common.di.Auth +import org.sopt.official.data.model.request.LogOutRequest import org.sopt.official.data.model.request.RefreshRequest +import org.sopt.official.data.model.response.LogOutResponse import org.sopt.official.data.persistence.SoptDataStore import org.sopt.official.data.service.AuthService import org.sopt.official.domain.entity.auth.Token @@ -36,7 +38,14 @@ class AuthRepositoryImpl @Inject constructor( service.withdraw() } - override suspend fun logout() = runCatching { - dataStore.clear() + override suspend fun logout( + pushToken: String + ): Result = runCatching { + service.logOut( + LogOutRequest( + platform = "Android", + pushToken = pushToken + ) + ) } } diff --git a/app/src/main/java/org/sopt/official/data/service/AuthService.kt b/app/src/main/java/org/sopt/official/data/service/AuthService.kt index e12902c1c..0030d7869 100644 --- a/app/src/main/java/org/sopt/official/data/service/AuthService.kt +++ b/app/src/main/java/org/sopt/official/data/service/AuthService.kt @@ -1,10 +1,13 @@ package org.sopt.official.data.service import org.sopt.official.data.model.request.AuthRequest +import org.sopt.official.data.model.request.LogOutRequest import org.sopt.official.data.model.request.RefreshRequest import org.sopt.official.data.model.response.AuthResponse +import org.sopt.official.data.model.response.LogOutResponse import retrofit2.http.Body import retrofit2.http.DELETE +import retrofit2.http.HTTP import retrofit2.http.PATCH import retrofit2.http.POST @@ -21,4 +24,9 @@ interface AuthService { @DELETE("user") suspend fun withdraw() + + @HTTP(method = "DELETE", path="user/logout", hasBody = true) + suspend fun logOut( + @Body body: LogOutRequest + ): LogOutResponse } diff --git a/app/src/main/java/org/sopt/official/domain/repository/AuthRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/AuthRepository.kt index 3b665a85b..c4365aa22 100644 --- a/app/src/main/java/org/sopt/official/domain/repository/AuthRepository.kt +++ b/app/src/main/java/org/sopt/official/domain/repository/AuthRepository.kt @@ -1,5 +1,6 @@ package org.sopt.official.domain.repository +import org.sopt.official.data.model.response.LogOutResponse import org.sopt.official.domain.entity.auth.Auth import org.sopt.official.domain.entity.auth.Token import org.sopt.official.domain.entity.auth.UserStatus @@ -9,5 +10,5 @@ interface AuthRepository { fun save(token: Token) fun save(status: UserStatus) suspend fun withdraw(): Result - suspend fun logout(): Result + suspend fun logout(pushToken: String): Result } diff --git a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt index 2e9827145..fe2b62d4e 100644 --- a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt @@ -2,10 +2,12 @@ package org.sopt.official.feature.mypage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.firebase.messaging.FirebaseMessaging import dagger.hilt.android.lifecycle.HiltViewModel import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.subjects.PublishSubject import kotlinx.coroutines.launch +import org.sopt.official.data.persistence.SoptDataStore import org.sopt.official.domain.repository.AuthRepository import org.sopt.official.feature.mypage.model.MyPageUiState import org.sopt.official.stamp.domain.repository.StampRepository @@ -18,27 +20,34 @@ class MyPageViewModel @Inject constructor( private val stampRepository: StampRepository, ) : ViewModel() { + @Inject + lateinit var dataStore: SoptDataStore + val userActiveState = BehaviorProcessor.createDefault(MyPageUiState.UnInitialized) val restartSignal = PublishSubject.create() fun logOut() { viewModelScope.launch { - authRepository.logout() - .onSuccess { - restartSignal.onNext(true) - }.onFailure { - Timber.e(it) + FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> + if (task.isComplete) { + viewModelScope.launch { + authRepository.logout(task.result) + .onSuccess { + dataStore.clear() + restartSignal.onNext(true) + } + .onFailure { Timber.e(it) } + } } + } } } fun resetSoptamp() { viewModelScope.launch { stampRepository.deleteAllStamps() - .onSuccess { - }.onFailure { - Timber.e(it) - } + .onSuccess {} + .onFailure { Timber.e(it) } } } } \ No newline at end of file From 82a932da068ed80ec2990e9f89a935d207643030 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Fri, 6 Oct 2023 17:58:40 +0900 Subject: [PATCH 27/38] =?UTF-8?q?[Feat/#341]=20Notification=20permission?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f784ee2b7..3f6459dd0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> + From 6f7cf5b7dcb049d01c70efd25bc2cc973eec252e Mon Sep 17 00:00:00 2001 From: yxnsx Date: Fri, 6 Oct 2023 18:26:38 +0900 Subject: [PATCH 28/38] =?UTF-8?q?[Feat/#341]=20SoptFirebaseMessagingServic?= =?UTF-8?q?e=20=ED=8C=8C=EC=9D=BC=20=EB=82=B4=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=A1=9C=EA=B7=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/messaging/SoptFirebaseMessagingService.kt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt index 9978af410..e5d2106ce 100644 --- a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt +++ b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt @@ -52,7 +52,6 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { val body = receivedData["content"] ?: "" val webLink = receivedData["webLink"] ?: "" val deepLink = receivedData["deepLink"] ?: "" - Timber.tag("SOPT").e("onMessageReceived - title: %s", title) val notificationId = System.currentTimeMillis().toInt() val notificationBuilder = NotificationCompat.Builder(this, channelId) @@ -78,15 +77,6 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(notificationId, notificationBuilder.build()) - - Timber.e("onMessageReceived: %s", remoteMessage) - Timber.e("onMessageReceived - notification: " + remoteMessage.notification) - Timber.e("onMessageReceived - notification title: " + remoteMessage.notification?.title) - Timber.e("onMessageReceived - notification body: " + remoteMessage.notification?.body) - Timber.e("onMessageReceived - data: " + remoteMessage.data) - Timber.e("onMessageReceived - data entries: " + remoteMessage.data.entries) - Timber.d("SOPT", "----------------------------") - Timber.d("SOPT", "received message: $remoteMessage") } private fun NotificationCompat.Builder.setNotificationContentIntent( From 6d8705ede2a8fe47a9dc27a969db2a0643bd9a3a Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 15:56:14 +0900 Subject: [PATCH 29/38] =?UTF-8?q?[Chore/#341]=20Data=20class=EC=97=90=20@S?= =?UTF-8?q?erialName=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/NotificationSubscriptionRequest.kt | 9 --------- .../model/notification/request/UpdatePushTokenRequest.kt | 6 ++++-- .../notification/response/NotificationDetailResponse.kt | 9 +++++++++ .../response/NotificationHistoryItemResponse.kt | 8 ++++++++ .../response/NotificationReadingStateResponse.kt | 5 +++++ .../notification/response/UpdatePushTokenResponse.kt | 4 ++++ .../org/sopt/official/data/model/request/AuthRequest.kt | 6 ++++-- .../sopt/official/data/model/response/LogOutResponse.kt | 4 +++- 8 files changed, 37 insertions(+), 14 deletions(-) delete mode 100644 app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt diff --git a/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt b/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt deleted file mode 100644 index df2979e92..000000000 --- a/app/src/main/java/org/sopt/official/data/model/notification/request/NotificationSubscriptionRequest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.sopt.official.data.model.notification.request - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class NotificationSubscriptionRequest( - @SerialName("isOptIn") val isOptIn: Boolean? = null, -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt b/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt index f03b15344..8e9bda805 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/request/UpdatePushTokenRequest.kt @@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable @Serializable data class UpdatePushTokenRequest( - @SerialName("platform") val platform: String, - @SerialName("pushToken") val pushToken: String + @SerialName("platform") + val platform: String, + @SerialName("pushToken") + val pushToken: String ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt index 05b426edb..eb5d9c048 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt @@ -1,15 +1,24 @@ package org.sopt.official.data.model.notification.response +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class NotificationDetailResponse( + @SerialName("notificationId") val notificationId: Int, + @SerialName("userId") val userId: Int, + @SerialName("title") val title: String, + @SerialName("content") val content: String?, + @SerialName("deepLink") val deepLink: String?, + @SerialName("webLink") val webLink: String?, + @SerialName("createdAt") val createdAt: String, + @SerialName("updatedAt") val updatedAt: String, ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt index 42fb70b40..0ebfd51bb 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt @@ -1,14 +1,22 @@ package org.sopt.official.data.model.notification.response +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class NotificationHistoryItemResponse( + @SerialName("notificationId") val notificationId: Int, + @SerialName("userId") val userId: Int, + @SerialName("title") val title: String, + @SerialName("content") val content: String?, + @SerialName("category") val category: String, + @SerialName("isRead") val isRead: Boolean, + @SerialName("createdAt") val createdAt: String, ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationReadingStateResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationReadingStateResponse.kt index 271dd578d..7dd2c76a5 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationReadingStateResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationReadingStateResponse.kt @@ -1,11 +1,16 @@ package org.sopt.official.data.model.notification.response +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class NotificationReadingStateResponse( + @SerialName("id") val id: Int, + @SerialName("isRead") val isRead: Boolean, + @SerialName("createdAt") val createdAt: String, + @SerialName("updatedAt") val updatedAt: String ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/UpdatePushTokenResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/UpdatePushTokenResponse.kt index 55700deea..e1e99217b 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/UpdatePushTokenResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/UpdatePushTokenResponse.kt @@ -1,10 +1,14 @@ package org.sopt.official.data.model.notification.response +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class UpdatePushTokenResponse( + @SerialName("status") val status: Int, + @SerialName("success") val success: Boolean, + @SerialName("message") val message: String ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/model/request/AuthRequest.kt b/app/src/main/java/org/sopt/official/data/model/request/AuthRequest.kt index 9e0cd9e7c..3806d9b40 100644 --- a/app/src/main/java/org/sopt/official/data/model/request/AuthRequest.kt +++ b/app/src/main/java/org/sopt/official/data/model/request/AuthRequest.kt @@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable @Serializable data class AuthRequest( - @SerialName("code") val code: String, - @SerialName("pushToken") val pushToken: String + @SerialName("code") + val code: String, + @SerialName("pushToken") + val pushToken: String ) diff --git a/app/src/main/java/org/sopt/official/data/model/response/LogOutResponse.kt b/app/src/main/java/org/sopt/official/data/model/response/LogOutResponse.kt index d81620944..ad31356c9 100644 --- a/app/src/main/java/org/sopt/official/data/model/response/LogOutResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/response/LogOutResponse.kt @@ -1,10 +1,12 @@ package org.sopt.official.data.model.response +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class LogOutResponse( - val status: Int, + @SerialName("success") val success: Boolean, + @SerialName("message") val message: String ) \ No newline at end of file From d5cd73fa0ec91e57f5923864a6e06d199ac03b62 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 15:57:57 +0900 Subject: [PATCH 30/38] =?UTF-8?q?[Chore/#341]=20enum=20class=20=EB=82=B4?= =?UTF-8?q?=20of()=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20valueOf()=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/config/messaging/RemoteMessageLinkType.kt | 8 +------- .../sopt/official/config/messaging/RemoteMessageType.kt | 8 +------- .../java/org/sopt/official/feature/home/HomeActivity.kt | 2 +- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt index 276278ac5..6ef9ce2af 100644 --- a/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt +++ b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageLinkType.kt @@ -1,11 +1,5 @@ package org.sopt.official.config.messaging enum class RemoteMessageLinkType { - WEB_LINK, DEEP_LINK, DEFAULT; - - companion object { - fun of(name: String) = entries.find { - it.name == name - } - } + WEB_LINK, DEEP_LINK, DEFAULT } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageType.kt b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageType.kt index 3c5f02cd6..3061c6301 100644 --- a/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageType.kt +++ b/app/src/main/java/org/sopt/official/config/messaging/RemoteMessageType.kt @@ -1,11 +1,5 @@ package org.sopt.official.config.messaging enum class RemoteMessageType { - NOTICE, NEWS; - - companion object { - fun of(name: String) = entries.find { - it.name == name - } - } + NOTICE, NEWS } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt index a9952e8ad..ff514d2ed 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt @@ -111,7 +111,7 @@ class HomeActivity : AppCompatActivity() { private fun initIntentData() { args?.remoteMessageEventType?.let { if (it.isBlank()) return - when (RemoteMessageLinkType.of(it)) { + when (RemoteMessageLinkType.valueOf(it)) { RemoteMessageLinkType.WEB_LINK -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(args?.remoteMessageEventLink)) startActivity(intent) From da9e284c6efbc7db245479171821fa50ba8bbd9d Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 16:00:44 +0900 Subject: [PATCH 31/38] =?UTF-8?q?[Chore/#341]=20SoptFirebaseMessagingServi?= =?UTF-8?q?ce=20=ED=8C=8C=EC=9D=BC=20=EB=82=B4=20string=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=20companion=20object=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/messaging/SoptFirebaseMessagingService.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt index e5d2106ce..066506175 100644 --- a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt +++ b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt @@ -18,7 +18,6 @@ import org.sopt.official.data.persistence.SoptDataStore import org.sopt.official.domain.entity.auth.UserStatus import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase import org.sopt.official.feature.auth.AuthActivity -import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -32,7 +31,6 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) - private val channelId = "SOPT" override fun onNewToken(token: String) { @@ -54,7 +52,7 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { val deepLink = receivedData["deepLink"] ?: "" val notificationId = System.currentTimeMillis().toInt() - val notificationBuilder = NotificationCompat.Builder(this, channelId) + val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) .setContentTitle(title) .setContentText(body) .setStyle(NotificationCompat.BigTextStyle().bigText(body)) @@ -104,6 +102,7 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { } companion object { + const val NOTIFICATION_CHANNEL_ID = "SOPT" const val REMOTE_MESSAGE_EVENT_TYPE = "REMOTE_MESSAGE_EVENT_TYPE" const val REMOTE_MESSAGE_EVENT_LINK = "REMOTE_MESSAGE_EVENT_LINK" } From ef768ed60c6b42edb9694c564d69d2b75494cbdf Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 16:20:08 +0900 Subject: [PATCH 32/38] =?UTF-8?q?[Chore/#341]=20NotificationId=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20Int=EC=97=90=EC=84=9C=20Long=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/notification/response/NotificationDetailResponse.kt | 2 +- .../notification/response/NotificationHistoryItemResponse.kt | 2 +- .../data/repository/notfication/NotificationRepositoryImpl.kt | 4 ++-- .../official/data/service/notification/NotificationService.kt | 4 ++-- .../domain/entity/notification/NotificationHistoryItem.kt | 2 +- .../domain/repository/notification/NotificationRepository.kt | 4 ++-- .../usecase/notification/GetNotificationDetailUseCase.kt | 2 +- .../notification/UpdateNotificationReadingStateUseCase.kt | 2 +- .../feature/notification/NotificationDetailActivity.kt | 4 ++-- .../feature/notification/NotificationDetailViewModel.kt | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt index eb5d9c048..688936b27 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationDetailResponse.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable data class NotificationDetailResponse( @SerialName("notificationId") - val notificationId: Int, + val notificationId: Long, @SerialName("userId") val userId: Int, @SerialName("title") diff --git a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt index 0ebfd51bb..6d98cf9b2 100644 --- a/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt +++ b/app/src/main/java/org/sopt/official/data/model/notification/response/NotificationHistoryItemResponse.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable data class NotificationHistoryItemResponse( @SerialName("notificationId") - val notificationId: Int, + val notificationId: Long, @SerialName("userId") val userId: Int, @SerialName("title") diff --git a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt index c8274d042..a46bb3091 100644 --- a/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/official/data/repository/notfication/NotificationRepositoryImpl.kt @@ -47,7 +47,7 @@ class NotificationRepositoryImpl @Inject constructor( } override suspend fun getNotificationDetail( - notificationId: Int + notificationId: Long ): Result { return runCatching { service.getNotificationDetail(notificationId) @@ -55,7 +55,7 @@ class NotificationRepositoryImpl @Inject constructor( } override suspend fun updateNotificationReadingState( - notificationId: Int + notificationId: Long ): Result { return runCatching { service.updateNotificationReadingState(notificationId) diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index 6a3969bea..747cf8ebd 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -33,13 +33,13 @@ interface NotificationService { @GET("notification/{notificationId}") suspend fun getNotificationDetail( - @Path("notificationId") notificationId: Int + @Path("notificationId") notificationId: Long ): NotificationDetailResponse @PATCH("notification/{notificationId}") suspend fun updateNotificationReadingState( - @Path("notificationId") notificationId: Int + @Path("notificationId") notificationId: Long ): NotificationReadingStateResponse @PATCH("notification") diff --git a/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt b/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt index 819b89c57..f70f3a8c5 100644 --- a/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt +++ b/app/src/main/java/org/sopt/official/domain/entity/notification/NotificationHistoryItem.kt @@ -1,7 +1,7 @@ package org.sopt.official.domain.entity.notification data class NotificationHistoryItem( - val notificationId: Int, + val notificationId: Long, val userId: Int, val title: String, val content: String?, diff --git a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt index dd4e3dd7f..c8c37b4dd 100644 --- a/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt +++ b/app/src/main/java/org/sopt/official/domain/repository/notification/NotificationRepository.kt @@ -14,11 +14,11 @@ interface NotificationRepository { page: Int ): Result> suspend fun getNotificationDetail( - notificationId: Int + notificationId: Long ): Result suspend fun updateNotificationReadingState( - notificationId: Int + notificationId: Long ): Result suspend fun updateEntireNotificationReadingState(): Result } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationDetailUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationDetailUseCase.kt index 1401516dc..8b4210aba 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationDetailUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/GetNotificationDetailUseCase.kt @@ -7,7 +7,7 @@ import javax.inject.Inject class GetNotificationDetailUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { - suspend operator fun invoke(notificationId: Int): Result { + suspend operator fun invoke(notificationId: Long): Result { return notificationRepository.getNotificationDetail(notificationId) } } diff --git a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationReadingStateUseCase.kt b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationReadingStateUseCase.kt index d0f590b02..53ddb82a6 100644 --- a/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationReadingStateUseCase.kt +++ b/app/src/main/java/org/sopt/official/domain/usecase/notification/UpdateNotificationReadingStateUseCase.kt @@ -7,7 +7,7 @@ import javax.inject.Inject class UpdateNotificationReadingStateUseCase @Inject constructor( private val notificationRepository: NotificationRepository ) { - suspend operator fun invoke(notificationId: Int): Result { + suspend operator fun invoke(notificationId: Long): Result { return notificationRepository.updateNotificationReadingState(notificationId) } } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt index 7d4aa67a6..80485ce64 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt @@ -23,8 +23,8 @@ class NotificationDetailActivity : AppCompatActivity() { private val binding by viewBinding(ActivityNotificationDetailBinding::inflate) private val viewModel by viewModels() - private val notificationId: Int by lazy { - intent.getIntExtra(NOTIFICATION_ID, 0) + private val notificationId: Long by lazy { + intent.getLongExtra(NOTIFICATION_ID, 0L) } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt index 3f7927917..4bd92727f 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailViewModel.kt @@ -21,7 +21,7 @@ class NotificationDetailViewModel @Inject constructor( private val _notificationDetail = MutableStateFlow(null) val notificationDetail: StateFlow get() = _notificationDetail - fun getNotificationDetail(id: Int) { + fun getNotificationDetail(id: Long) { viewModelScope.launch { getNotificationDetailUseCase.invoke(id) .onSuccess { @@ -32,7 +32,7 @@ class NotificationDetailViewModel @Inject constructor( } } - private fun updateNotificationReadingState(id: Int) { + private fun updateNotificationReadingState(id: Long) { viewModelScope.launch { updateNotificationReadingStateUseCase.invoke(id) .onFailure { Timber.e(it) } From 1dfd90c2391018eeff1d5f91c4da5eb438cc77df Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 16:44:16 +0900 Subject: [PATCH 33/38] =?UTF-8?q?[Chore/#341]=20MyPageViewModel=EC=9D=98?= =?UTF-8?q?=20dataStore=20=EA=B0=9D=EC=B2=B4=20constructor=20injection?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/sopt/official/feature/mypage/MyPageViewModel.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt index fe2b62d4e..d86f9f2b8 100644 --- a/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/mypage/MyPageViewModel.kt @@ -16,13 +16,11 @@ import javax.inject.Inject @HiltViewModel class MyPageViewModel @Inject constructor( + private val dataStore: SoptDataStore, private val authRepository: AuthRepository, private val stampRepository: StampRepository, ) : ViewModel() { - @Inject - lateinit var dataStore: SoptDataStore - val userActiveState = BehaviorProcessor.createDefault(MyPageUiState.UnInitialized) val restartSignal = PublishSubject.create() From aac607b12f3b15849ea1564fb8ad8d6079ac7ee9 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 16:51:52 +0900 Subject: [PATCH 34/38] =?UTF-8?q?[Chore/#341]=20rebase=20conflicts=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/sopt/official/di/NotificationModule.kt | 2 +- .../org/sopt/official/feature/auth/AuthActivity.kt | 1 - .../org/sopt/official/feature/home/HomeActivity.kt | 10 +++++----- .../notification/NotificationHistoryListViewAdapter.kt | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/sopt/official/di/NotificationModule.kt b/app/src/main/java/org/sopt/official/di/NotificationModule.kt index 2f391179c..16854b72a 100644 --- a/app/src/main/java/org/sopt/official/di/NotificationModule.kt +++ b/app/src/main/java/org/sopt/official/di/NotificationModule.kt @@ -4,7 +4,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import org.sopt.official.core.di.AppRetrofit +import org.sopt.official.common.di.AppRetrofit import org.sopt.official.data.repository.notfication.NotificationRepositoryImpl import org.sopt.official.data.service.notification.NotificationService import org.sopt.official.domain.repository.notification.NotificationRepository diff --git a/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt b/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt index 75a365bb8..0b4b6a482 100644 --- a/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt @@ -5,7 +5,6 @@ import android.content.Context import android.content.Intent import android.app.NotificationChannel import android.app.NotificationManager -import android.content.Intent import android.graphics.Paint import android.os.Bundle import android.view.animation.AnimationUtils diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt index ff514d2ed..a077a1925 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt @@ -93,7 +93,7 @@ class HomeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - tracker.track(type = EventType.VIEW, name = "apphome", properties = mapOf("view_type" to args?.value)) + tracker.track(type = EventType.VIEW, name = "apphome", properties = mapOf("view_type" to args?.userStatus?.value)) initIntentData() requestNotificationPermission() @@ -132,7 +132,7 @@ class HomeActivity : AppCompatActivity() { private fun initToolbar() { binding.mypage.setOnClickListener { - tracker.track(type = EventType.CLICK, name = "mypage", properties = mapOf("view_type" to args?.value)) + tracker.track(type = EventType.CLICK, name = "mypage", properties = mapOf("view_type" to args?.userStatus?.value)) lifecycleScope.launch { startActivity( MyPageActivity.getIntent(this@HomeActivity, MyPageActivity.StartArgs(viewModel.userActiveState.value)) @@ -173,7 +173,7 @@ class HomeActivity : AppCompatActivity() { if (isClickable) { val intent = Intent(this@HomeActivity, SoptampActivity::class.java) binding.contentSoptamp.root.setOnSingleClickListener { - tracker.track(type = EventType.CLICK, name = "soptamp", properties = mapOf("view_type" to args?.value)) + tracker.track(type = EventType.CLICK, name = "soptamp", properties = mapOf("view_type" to args?.userStatus?.value)) this@HomeActivity.startActivity(intent) } } @@ -268,7 +268,7 @@ class HomeActivity : AppCompatActivity() { Intent(Intent.ACTION_VIEW, Uri.parse(item.url)) } largeBlock.root.setOnSingleClickListener { - tracker.track(type = EventType.CLICK, name = "attendance", properties = mapOf("view_type" to args?.value)) + tracker.track(type = EventType.CLICK, name = "attendance", properties = mapOf("view_type" to args?.userStatus?.value)) startActivity(intent) } } @@ -284,7 +284,7 @@ class HomeActivity : AppCompatActivity() { tracker.track( type = EventType.CLICK, name = item.clickEventName, - properties = mapOf("view_type" to args?.value) + properties = mapOf("view_type" to args?.userStatus?.value) ) val intent = Intent(Intent.ACTION_VIEW, Uri.parse(item.url)) startActivity(intent) diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt index f1014fe5f..10df3f673 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt @@ -8,7 +8,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.sopt.official.R -import org.sopt.official.core.view.ItemDiffCallback +import org.sopt.official.common.view.ItemDiffCallback import org.sopt.official.databinding.ItemNotificationHistoryBinding import org.sopt.official.domain.entity.notification.NotificationHistoryItem import org.sopt.official.util.drawableOf From 73ff5cee8c4bb589944caacfae9cae542b9fe18a Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 16:55:31 +0900 Subject: [PATCH 35/38] =?UTF-8?q?[Chore/#341]=20NotificationHistoryActivit?= =?UTF-8?q?y=EC=97=90=20isVisible=20=ED=99=95=EC=9E=A5=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationHistoryActivity.kt | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index a7174477a..ef7b7018a 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -83,27 +84,13 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem private fun initStateFlowValues() { lifecycleScope.launch { viewModel.notificationHistoryList.collectLatest { - setEmptyViewVisibility(it.isEmpty()) - setTextViewReadAllVisibility(it.isNotEmpty()) + binding.textViewReadAll.isVisible = it.isNotEmpty() + binding.includeNotificationHistoryEmptyView.root.isVisible = it.isEmpty() notificationHistoryAdapter?.updateNotificationHistoryList(it) } } } - private fun setEmptyViewVisibility(isVisible: Boolean) { - binding.includeNotificationHistoryEmptyView.root.visibility = when (isVisible) { - true -> View.VISIBLE - false -> View.GONE - } - } - - private fun setTextViewReadAllVisibility(isVisible: Boolean) { - binding.textViewReadAll.visibility = when (isVisible) { - true -> View.VISIBLE - false -> View.GONE - } - } - override fun onClickNotificationHistoryItem(position: Int) { notificationHistoryAdapter?.updateNotificationReadingState(position) val clickedNotification = viewModel.notificationHistoryList.value[position] From 293b33c722409c950e59a6c57f26a16d00fe7953 Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 16:59:41 +0900 Subject: [PATCH 36/38] =?UTF-8?q?[Chore/#341]=20NotificationHistoryItemCli?= =?UTF-8?q?ckListener=20=ED=8C=8C=EC=9D=BC=20=EB=82=B4=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20import=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/NotificationHistoryItemClickListener.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt index caaa24e17..a77bedebe 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt @@ -1,7 +1,5 @@ package org.sopt.official.feature.notification -import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse - interface NotificationHistoryItemClickListener { fun onClickNotificationHistoryItem(position: Int) } \ No newline at end of file From fab44420d075d0f9e1eac7f2736913e48ac1c09c Mon Sep 17 00:00:00 2001 From: yxnsx Date: Sun, 8 Oct 2023 17:46:57 +0900 Subject: [PATCH 37/38] =?UTF-8?q?[Chore/#341]=20NotificationHistoryItemCli?= =?UTF-8?q?ckListener=20functional=20interface=EB=A1=9C=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 --- .../NotificationHistoryActivity.kt | 24 +++++++++---------- .../NotificationHistoryItemClickListener.kt | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index ef7b7018a..701bca65a 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -18,7 +18,7 @@ import org.sopt.official.util.viewBinding @AndroidEntryPoint -class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItemClickListener { +class NotificationHistoryActivity : AppCompatActivity() { private val binding by viewBinding(ActivityNotificationHistoryBinding::inflate) private val viewModel by viewModels() @@ -45,11 +45,21 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem private fun initRecyclerView() { binding.recyclerViewNotificationHistory.apply { - adapter = NotificationHistoryListViewAdapter(this@NotificationHistoryActivity) + adapter = NotificationHistoryListViewAdapter(notificationHistoryItemClickListener) addOnScrollListener(scrollListener) } } + private val notificationHistoryItemClickListener = NotificationHistoryItemClickListener { position -> + notificationHistoryAdapter?.updateNotificationReadingState(position) + val clickedNotification = viewModel.notificationHistoryList.value[position] + + Intent(this@NotificationHistoryActivity, NotificationDetailActivity::class.java).run { + putExtra(NOTIFICATION_ID, clickedNotification.notificationId) + startActivity(this) + } + } + private val scrollListener = object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) @@ -91,16 +101,6 @@ class NotificationHistoryActivity : AppCompatActivity(), NotificationHistoryItem } } - override fun onClickNotificationHistoryItem(position: Int) { - notificationHistoryAdapter?.updateNotificationReadingState(position) - val clickedNotification = viewModel.notificationHistoryList.value[position] - - Intent(this, NotificationDetailActivity::class.java).run { - putExtra(NOTIFICATION_ID, clickedNotification.notificationId) - startActivity(this) - } - } - companion object { const val NOTIFICATION_ID = "notificationId" } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt index a77bedebe..29606ddde 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryItemClickListener.kt @@ -1,5 +1,5 @@ package org.sopt.official.feature.notification -interface NotificationHistoryItemClickListener { +fun interface NotificationHistoryItemClickListener { fun onClickNotificationHistoryItem(position: Int) } \ No newline at end of file From 0f1303a09c5f06c29d66772956e774b6793126f0 Mon Sep 17 00:00:00 2001 From: HyunWoo Lee Date: Sun, 8 Oct 2023 18:54:08 +0900 Subject: [PATCH 38/38] apply ktlintFormat & change provides to binds in noti module --- .../messaging/SoptFirebaseMessagingService.kt | 6 +-- .../sopt/official/data/service/AuthService.kt | 2 +- .../notification/NotificationService.kt | 5 +- .../sopt/official/di/NotificationModule.kt | 13 ++--- .../official/feature/auth/AuthActivity.kt | 47 ++++++++++++------- .../NotificationDetailActivity.kt | 4 +- .../NotificationHistoryActivity.kt | 1 - .../NotificationHistoryListViewAdapter.kt | 5 +- 8 files changed, 45 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt index 066506175..60f1c9a7d 100644 --- a/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt +++ b/app/src/main/java/org/sopt/official/config/messaging/SoptFirebaseMessagingService.kt @@ -32,7 +32,6 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.IO + job) - override fun onNewToken(token: String) { if (dataStore.userStatus == UserStatus.UNAUTHENTICATED.name) return scope.launch { @@ -82,7 +81,6 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { link: String, notificationId: Int ): NotificationCompat.Builder { - val intent = Intent(this@SoptFirebaseMessagingService, AuthActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) putExtra(REMOTE_MESSAGE_EVENT_TYPE, remoteMessageLinkType.name) @@ -96,7 +94,9 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() { intent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE - } else PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } ) ) } diff --git a/app/src/main/java/org/sopt/official/data/service/AuthService.kt b/app/src/main/java/org/sopt/official/data/service/AuthService.kt index 0030d7869..f4047826f 100644 --- a/app/src/main/java/org/sopt/official/data/service/AuthService.kt +++ b/app/src/main/java/org/sopt/official/data/service/AuthService.kt @@ -25,7 +25,7 @@ interface AuthService { @DELETE("user") suspend fun withdraw() - @HTTP(method = "DELETE", path="user/logout", hasBody = true) + @HTTP(method = "DELETE", path = "user/logout", hasBody = true) suspend fun logOut( @Body body: LogOutRequest ): LogOutResponse diff --git a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt index 747cf8ebd..8d8e17114 100644 --- a/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt +++ b/app/src/main/java/org/sopt/official/data/service/notification/NotificationService.kt @@ -25,7 +25,6 @@ interface NotificationService { @Body body: UpdatePushTokenRequest ): UpdatePushTokenResponse - @GET("notification") suspend fun getNotificationHistory( @Query("page") page: Int @@ -36,13 +35,11 @@ interface NotificationService { @Path("notificationId") notificationId: Long ): NotificationDetailResponse - @PATCH("notification/{notificationId}") suspend fun updateNotificationReadingState( @Path("notificationId") notificationId: Long ): NotificationReadingStateResponse @PATCH("notification") - suspend fun updateEntireNotificationReadingState( - ): NotificationReadingStateResponse + suspend fun updateEntireNotificationReadingState(): NotificationReadingStateResponse } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/di/NotificationModule.kt b/app/src/main/java/org/sopt/official/di/NotificationModule.kt index 16854b72a..c095d0e7c 100644 --- a/app/src/main/java/org/sopt/official/di/NotificationModule.kt +++ b/app/src/main/java/org/sopt/official/di/NotificationModule.kt @@ -1,5 +1,6 @@ package org.sopt.official.di +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -23,11 +24,11 @@ object NotificationModule { return retrofit.create(NotificationService::class.java) } - @Provides - @Singleton - fun provideNotificationRepository( - repository: NotificationRepositoryImpl - ): NotificationRepository { - return repository + @Module + @InstallIn(SingletonComponent::class) + interface Binder { + @Binds + @Singleton + fun bindNotificationRepository(repository: NotificationRepositoryImpl): NotificationRepository } } diff --git a/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt b/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt index 0b4b6a482..49bb82643 100644 --- a/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/auth/AuthActivity.kt @@ -72,17 +72,20 @@ class AuthActivity : AppCompatActivity() { val remoteMessageEventLink = it.getStringExtra(REMOTE_MESSAGE_EVENT_LINK) ?: "" if ( - dataStore.userStatus.isNotBlank() - && dataStore.userStatus != UserStatus.UNAUTHENTICATED.name - && remoteMessageEventType.isNotBlank() + dataStore.userStatus.isNotBlank() && + dataStore.userStatus != UserStatus.UNAUTHENTICATED.name && + remoteMessageEventType.isNotBlank() ) { - startActivity(HomeActivity.getIntent(this, - HomeActivity.StartArgs( - UserStatus.of(dataStore.userStatus), - remoteMessageEventType, - remoteMessageEventLink + startActivity( + HomeActivity.getIntent( + this, + HomeActivity.StartArgs( + UserStatus.of(dataStore.userStatus), + remoteMessageEventType, + remoteMessageEventLink + ) ) - )) + ) } } } @@ -109,13 +112,16 @@ class AuthActivity : AppCompatActivity() { val remoteMessageEventLink = intent.getStringExtra(REMOTE_MESSAGE_EVENT_LINK) ?: "" when (event) { is AuthUiEvent.Success -> { - startActivity(HomeActivity.getIntent(this, - HomeActivity.StartArgs( - event.userStatus, - remoteMessageEventType, - remoteMessageEventLink + startActivity( + HomeActivity.getIntent( + this, + HomeActivity.StartArgs( + event.userStatus, + remoteMessageEventType, + remoteMessageEventLink + ) ) - )) + ) } is AuthUiEvent.Failure -> { @@ -175,9 +181,14 @@ class AuthActivity : AppCompatActivity() { } } binding.btnSoptNotMember.setOnSingleClickListener { - startActivity(HomeActivity.getIntent(this, HomeActivity.StartArgs( - UserStatus.UNAUTHENTICATED - ))) + startActivity( + HomeActivity.getIntent( + this, + HomeActivity.StartArgs( + UserStatus.UNAUTHENTICATED + ) + ) + ) } } diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt index 80485ce64..e67a55baf 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationDetailActivity.kt @@ -51,8 +51,8 @@ class NotificationDetailActivity : AppCompatActivity() { textViewNotificationTitle.text = notification.title textViewNotificationContent.text = notification.content linearLayoutNewsDetailButton.visibility = when ( - notification.deepLink.isNullOrBlank() - && notification.webLink.isNullOrBlank() + notification.deepLink.isNullOrBlank() && + notification.webLink.isNullOrBlank() ) { true -> View.GONE false -> View.VISIBLE diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt index 701bca65a..b9f30b5be 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryActivity.kt @@ -16,7 +16,6 @@ import org.sopt.official.R import org.sopt.official.databinding.ActivityNotificationHistoryBinding import org.sopt.official.util.viewBinding - @AndroidEntryPoint class NotificationHistoryActivity : AppCompatActivity() { diff --git a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt index 10df3f673..c571f4290 100644 --- a/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt +++ b/app/src/main/java/org/sopt/official/feature/notification/NotificationHistoryListViewAdapter.kt @@ -67,7 +67,6 @@ class NotificationHistoryListViewAdapter( notifyDataSetChanged() } - inner class ViewHolder( private val viewBinding: ItemNotificationHistoryBinding ) : RecyclerView.ViewHolder(viewBinding.root) { @@ -98,8 +97,8 @@ class NotificationHistoryListViewAdapter( return when { diffInDays >= 1 -> "${diffInDays}일 전" - diffInHours >= 1 ->"${diffInHours}시간 전" - diffInMinutes >= 1 ->"${diffInMinutes}분 전" + diffInHours >= 1 -> "${diffInHours}시간 전" + diffInMinutes >= 1 -> "${diffInMinutes}분 전" else -> "방금" } }