diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index b19d93997..c5ffaa725 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,7 +9,8 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/java/com/happy/friendogly/application/FriendoglyApplication.kt b/android/app/src/main/java/com/happy/friendogly/application/FriendoglyApplication.kt
index 978200434..f82cbd035 100644
--- a/android/app/src/main/java/com/happy/friendogly/application/FriendoglyApplication.kt
+++ b/android/app/src/main/java/com/happy/friendogly/application/FriendoglyApplication.kt
@@ -1,6 +1,7 @@
package com.happy.friendogly.application
import android.app.Application
+import com.google.firebase.FirebaseApp
import com.happy.friendogly.BuildConfig
import com.happy.friendogly.application.di.AppModule
import com.kakao.sdk.common.KakaoSdk
@@ -13,5 +14,6 @@ class FriendoglyApplication : Application() {
NaverMapSdk.NaverCloudPlatformClient(BuildConfig.NAVER_CLIEND_ID)
KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY)
AppModule.setInstance(applicationContext)
+ FirebaseApp.initializeApp(this)
}
}
diff --git a/android/app/src/main/java/com/happy/friendogly/application/di/AppModule.kt b/android/app/src/main/java/com/happy/friendogly/application/di/AppModule.kt
index 5ceed498d..64fc853d1 100644
--- a/android/app/src/main/java/com/happy/friendogly/application/di/AppModule.kt
+++ b/android/app/src/main/java/com/happy/friendogly/application/di/AppModule.kt
@@ -5,6 +5,8 @@ import com.happy.friendogly.BuildConfig
import com.happy.friendogly.analytics.AnalyticsHelper
import com.happy.friendogly.crashlytics.CrashlyticsHelper
import com.happy.friendogly.data.repository.AddressRepositoryImpl
+import com.happy.friendogly.data.repository.AlarmSettingRepositoryImpl
+import com.happy.friendogly.data.repository.AlarmTokenRepositoryImpl
import com.happy.friendogly.data.repository.AuthRepositoryImpl
import com.happy.friendogly.data.repository.ChatRepositoryImpl
import com.happy.friendogly.data.repository.ClubRepositoryImpl
@@ -16,6 +18,8 @@ import com.happy.friendogly.data.repository.TokenRepositoryImpl
import com.happy.friendogly.data.repository.WebSocketRepositoryImpl
import com.happy.friendogly.data.repository.WoofRepositoryImpl
import com.happy.friendogly.data.source.AddressDataSource
+import com.happy.friendogly.data.source.AlarmSettingDataSource
+import com.happy.friendogly.data.source.AlarmTokenDataSource
import com.happy.friendogly.data.source.AuthDataSource
import com.happy.friendogly.data.source.ChatDataSource
import com.happy.friendogly.data.source.ClubDataSource
@@ -26,6 +30,8 @@ import com.happy.friendogly.data.source.TokenDataSource
import com.happy.friendogly.data.source.WebSocketDataSource
import com.happy.friendogly.data.source.WoofDataSource
import com.happy.friendogly.domain.repository.AddressRepository
+import com.happy.friendogly.domain.repository.AlarmSettingRepository
+import com.happy.friendogly.domain.repository.AlarmTokenRepository
import com.happy.friendogly.domain.repository.AuthRepository
import com.happy.friendogly.domain.repository.ChatRepository
import com.happy.friendogly.domain.repository.ClubRepository
@@ -37,9 +43,11 @@ import com.happy.friendogly.domain.repository.TokenRepository
import com.happy.friendogly.domain.repository.WebSocketRepository
import com.happy.friendogly.domain.repository.WoofRepository
import com.happy.friendogly.domain.usecase.DeleteAddressUseCase
+import com.happy.friendogly.domain.usecase.DeleteAlarmSettingUseCase
import com.happy.friendogly.domain.usecase.DeleteClubMemberUseCase
import com.happy.friendogly.domain.usecase.DeleteTokenUseCase
import com.happy.friendogly.domain.usecase.GetAddressUseCase
+import com.happy.friendogly.domain.usecase.GetAlarmSettingUseCase
import com.happy.friendogly.domain.usecase.GetChatListUseCase
import com.happy.friendogly.domain.usecase.GetChatMemberUseCase
import com.happy.friendogly.domain.usecase.GetClubUseCase
@@ -66,17 +74,23 @@ import com.happy.friendogly.domain.usecase.PublishEnterUseCase
import com.happy.friendogly.domain.usecase.PublishLeaveUseCase
import com.happy.friendogly.domain.usecase.PublishSendMessageUseCase
import com.happy.friendogly.domain.usecase.SaveAddressUseCase
+import com.happy.friendogly.domain.usecase.SaveAlamTokenUseCase
+import com.happy.friendogly.domain.usecase.SaveAlarmSettingUseCase
import com.happy.friendogly.domain.usecase.SaveJwtTokenUseCase
import com.happy.friendogly.domain.usecase.SubScribeMessageUseCase
import com.happy.friendogly.kakao.source.KakaoLoginDataSourceImpl
import com.happy.friendogly.local.di.AddressModule
+import com.happy.friendogly.local.di.AlarmModule
+import com.happy.friendogly.local.di.AlarmTokenModule
import com.happy.friendogly.local.di.TokenManager
import com.happy.friendogly.local.source.AddressDataSourceImpl
+import com.happy.friendogly.local.source.AlarmSettingDataSourceImpl
import com.happy.friendogly.local.source.TokenDataSourceImpl
import com.happy.friendogly.remote.api.AuthenticationListener
import com.happy.friendogly.remote.api.BaseUrl
import com.happy.friendogly.remote.api.WebSocketService
import com.happy.friendogly.remote.di.RemoteModule
+import com.happy.friendogly.remote.source.AlamTokenDataSourceImpl
import com.happy.friendogly.remote.source.AuthDataSourceImpl
import com.happy.friendogly.remote.source.ChatDataSourceImpl
import com.happy.friendogly.remote.source.ClubDataSourceImpl
@@ -97,6 +111,8 @@ class AppModule(context: Context) {
private val authenticationListener: AuthenticationListener =
AuthenticationListenerImpl(context, tokenManager)
private val addressModule = AddressModule(context)
+ private val alarmModule = AlarmModule(context)
+ private val alarmTokenModule = AlarmTokenModule(context)
// service
private val authService =
@@ -152,6 +168,13 @@ class AppModule(context: Context) {
authenticationListener = authenticationListener,
)
+ private val alarmTokenService =
+ RemoteModule.createAlarmTokenService(
+ baseUrl = baseUrl,
+ tokenManager = tokenManager,
+ authenticationListener = authenticationListener,
+ )
+
// data source
private val authDataSource: AuthDataSource = AuthDataSourceImpl(service = authService)
private val clubDataSource: ClubDataSource = ClubDataSourceImpl(service = clubService)
@@ -165,6 +188,10 @@ class AppModule(context: Context) {
private val webSocketDataSource: WebSocketDataSource =
WebSocketDataSourceImpl(service = webSocketService)
private val chatDataSource: ChatDataSource = ChatDataSourceImpl(service = chatService)
+ private val alarmSettingDataSource: AlarmSettingDataSource =
+ AlarmSettingDataSourceImpl(alarmModule = alarmModule)
+ private val alarmTokenDataSource: AlarmTokenDataSource =
+ AlamTokenDataSourceImpl(service = alarmTokenService)
// repository
private val authRepository: AuthRepository = AuthRepositoryImpl(source = authDataSource)
@@ -181,6 +208,10 @@ class AppModule(context: Context) {
val webSocketRepository: WebSocketRepository =
WebSocketRepositoryImpl(source = webSocketDataSource)
private val chatRepository: ChatRepository = ChatRepositoryImpl(source = chatDataSource)
+ private val alarmSettingRepository: AlarmSettingRepository =
+ AlarmSettingRepositoryImpl(source = alarmSettingDataSource)
+ private val alarmTokenRepository: AlarmTokenRepository =
+ AlarmTokenRepositoryImpl(source = alarmTokenDataSource)
// use case
val postKakaoLoginUseCase: PostKakaoLoginUseCase =
@@ -234,6 +265,14 @@ class AppModule(context: Context) {
PublishLeaveUseCase(repository = webSocketRepository)
val subScribeMessageUseCase: SubScribeMessageUseCase =
SubScribeMessageUseCase(repository = webSocketRepository)
+ val deleteAlarmSettingUseCase: DeleteAlarmSettingUseCase =
+ DeleteAlarmSettingUseCase(repository = alarmSettingRepository)
+ val saveAlarmSettingUseCase: SaveAlarmSettingUseCase =
+ SaveAlarmSettingUseCase(repository = alarmSettingRepository)
+ val getAlarmSettingUseCase: GetAlarmSettingUseCase =
+ GetAlarmSettingUseCase(repository = alarmSettingRepository)
+ val saveAlarmTokenUseCase: SaveAlamTokenUseCase =
+ SaveAlamTokenUseCase(repository = alarmTokenRepository)
companion object {
private var instance: AppModule? = null
diff --git a/android/app/src/main/java/com/happy/friendogly/data/mapper/AlarmTokenMapper.kt b/android/app/src/main/java/com/happy/friendogly/data/mapper/AlarmTokenMapper.kt
new file mode 100644
index 000000000..d2e6d1058
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/data/mapper/AlarmTokenMapper.kt
@@ -0,0 +1,9 @@
+package com.happy.friendogly.data.mapper
+
+import com.happy.friendogly.data.model.AlarmTokenDto
+import com.happy.friendogly.remote.model.request.DeviceTokenRequest
+
+fun AlarmTokenDto.toRemote(): DeviceTokenRequest =
+ DeviceTokenRequest(
+ deviceToken = token,
+ )
diff --git a/android/app/src/main/java/com/happy/friendogly/data/model/AlarmTokenDto.kt b/android/app/src/main/java/com/happy/friendogly/data/model/AlarmTokenDto.kt
new file mode 100644
index 000000000..ca03cb069
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/data/model/AlarmTokenDto.kt
@@ -0,0 +1,5 @@
+package com.happy.friendogly.data.model
+
+data class AlarmTokenDto(
+ val token: String,
+)
diff --git a/android/app/src/main/java/com/happy/friendogly/data/repository/AlarmSettingRepositoryImpl.kt b/android/app/src/main/java/com/happy/friendogly/data/repository/AlarmSettingRepositoryImpl.kt
new file mode 100644
index 000000000..304971384
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/data/repository/AlarmSettingRepositoryImpl.kt
@@ -0,0 +1,12 @@
+package com.happy.friendogly.data.repository
+
+import com.happy.friendogly.data.source.AlarmSettingDataSource
+import com.happy.friendogly.domain.repository.AlarmSettingRepository
+
+class AlarmSettingRepositoryImpl(private val source: AlarmSettingDataSource) : AlarmSettingRepository {
+ override suspend fun saveAlarm(isSet: Boolean): Result = source.saveAlarm(isSet)
+
+ override suspend fun getAlarm(): Result = source.getAlarm()
+
+ override suspend fun deleteAlarm(): Result = source.deleteAlarm()
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/data/repository/AlarmTokenRepositoryImpl.kt b/android/app/src/main/java/com/happy/friendogly/data/repository/AlarmTokenRepositoryImpl.kt
new file mode 100644
index 000000000..c4998b40f
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/data/repository/AlarmTokenRepositoryImpl.kt
@@ -0,0 +1,8 @@
+package com.happy.friendogly.data.repository
+
+import com.happy.friendogly.data.source.AlarmTokenDataSource
+import com.happy.friendogly.domain.repository.AlarmTokenRepository
+
+class AlarmTokenRepositoryImpl(private val source: AlarmTokenDataSource) : AlarmTokenRepository {
+ override suspend fun saveToken(token: String): Result = source.saveToken(token)
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/data/source/AlarmSettingDataSource.kt b/android/app/src/main/java/com/happy/friendogly/data/source/AlarmSettingDataSource.kt
new file mode 100644
index 000000000..88747e3c1
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/data/source/AlarmSettingDataSource.kt
@@ -0,0 +1,9 @@
+package com.happy.friendogly.data.source
+
+interface AlarmSettingDataSource {
+ suspend fun saveAlarm(isSet: Boolean): Result
+
+ suspend fun getAlarm(): Result
+
+ suspend fun deleteAlarm(): Result
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/data/source/AlarmTokenDataSource.kt b/android/app/src/main/java/com/happy/friendogly/data/source/AlarmTokenDataSource.kt
new file mode 100644
index 000000000..1defd7247
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/data/source/AlarmTokenDataSource.kt
@@ -0,0 +1,5 @@
+package com.happy.friendogly.data.source
+
+interface AlarmTokenDataSource {
+ suspend fun saveToken(token: String): Result
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmSettingRepository.kt b/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmSettingRepository.kt
new file mode 100644
index 000000000..81e6dc1ec
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmSettingRepository.kt
@@ -0,0 +1,9 @@
+package com.happy.friendogly.domain.repository
+
+interface AlarmSettingRepository {
+ suspend fun saveAlarm(isSet: Boolean): Result
+
+ suspend fun getAlarm(): Result
+
+ suspend fun deleteAlarm(): Result
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmTokenRepository.kt b/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmTokenRepository.kt
new file mode 100644
index 000000000..329cc9538
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmTokenRepository.kt
@@ -0,0 +1,5 @@
+package com.happy.friendogly.domain.repository
+
+interface AlarmTokenRepository {
+ suspend fun saveToken(token: String): Result
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/DeleteAlarmSettingUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/DeleteAlarmSettingUseCase.kt
new file mode 100644
index 000000000..b0eea3c9c
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/DeleteAlarmSettingUseCase.kt
@@ -0,0 +1,9 @@
+package com.happy.friendogly.domain.usecase
+
+import com.happy.friendogly.domain.repository.AlarmSettingRepository
+
+class DeleteAlarmSettingUseCase(
+ private val repository: AlarmSettingRepository,
+) {
+ suspend operator fun invoke(): Result = repository.deleteAlarm()
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetAlarmSettingUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetAlarmSettingUseCase.kt
new file mode 100644
index 000000000..4c577cbb5
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetAlarmSettingUseCase.kt
@@ -0,0 +1,9 @@
+package com.happy.friendogly.domain.usecase
+
+import com.happy.friendogly.domain.repository.AlarmSettingRepository
+
+class GetAlarmSettingUseCase(
+ private val repository: AlarmSettingRepository,
+) {
+ suspend operator fun invoke(): Result = repository.getAlarm()
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveAlamTokenUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveAlamTokenUseCase.kt
new file mode 100644
index 000000000..1db14a512
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveAlamTokenUseCase.kt
@@ -0,0 +1,9 @@
+package com.happy.friendogly.domain.usecase
+
+import com.happy.friendogly.domain.repository.AlarmTokenRepository
+
+class SaveAlamTokenUseCase(
+ private val repository: AlarmTokenRepository,
+) {
+ suspend operator fun invoke(token: String): Result = repository.saveToken(token)
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveAlarmSettingUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveAlarmSettingUseCase.kt
new file mode 100644
index 000000000..0213c6d68
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveAlarmSettingUseCase.kt
@@ -0,0 +1,9 @@
+package com.happy.friendogly.domain.usecase
+
+import com.happy.friendogly.domain.repository.AlarmSettingRepository
+
+class SaveAlarmSettingUseCase(
+ private val repository: AlarmSettingRepository,
+) {
+ suspend operator fun invoke(isSet: Boolean): Result = repository.saveAlarm(isSet)
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/local/di/AlarmModule.kt b/android/app/src/main/java/com/happy/friendogly/local/di/AlarmModule.kt
new file mode 100644
index 000000000..5faaf608f
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/local/di/AlarmModule.kt
@@ -0,0 +1,47 @@
+package com.happy.friendogly.local.di
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.emptyPreferences
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import java.io.IOException
+
+class AlarmModule(val context: Context) {
+ private val Context.dataStore: DataStore by preferencesDataStore(name = DATA_STORE_NAME)
+
+ private val key = booleanPreferencesKey(ALARM_SETTING)
+
+ var isSet: Flow =
+ context.dataStore.data.catch { exception ->
+ if (exception is IOException) {
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }.map { preferences ->
+ preferences[key] ?: true
+ }
+
+ suspend fun saveSetting(value: Boolean) {
+ context.dataStore.edit { preferences ->
+ preferences[key] = value
+ }
+ }
+
+ suspend fun deleteSetting() {
+ context.dataStore.edit { prefs ->
+ prefs.remove(key)
+ }
+ }
+
+ companion object {
+ private const val ALARM_SETTING = "ALARM_SETTING"
+ private const val DATA_STORE_NAME = "alarmDataStore"
+ }
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/local/di/AlarmTokenModule.kt b/android/app/src/main/java/com/happy/friendogly/local/di/AlarmTokenModule.kt
new file mode 100644
index 000000000..4694fa0b9
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/local/di/AlarmTokenModule.kt
@@ -0,0 +1,47 @@
+package com.happy.friendogly.local.di
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.emptyPreferences
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import java.io.IOException
+
+class AlarmTokenModule(val context: Context) {
+ private val Context.dataStore: DataStore by preferencesDataStore(name = DATA_STORE_NAME)
+
+ private val key = stringPreferencesKey(FCM_TOKEN)
+
+ var token: Flow =
+ context.dataStore.data.catch { exception ->
+ if (exception is IOException) {
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }.map { preferences ->
+ preferences[key] ?: ""
+ }
+
+ suspend fun saveToken(value: String) {
+ context.dataStore.edit { preferences ->
+ preferences[key] = value
+ }
+ }
+
+ suspend fun deleteToken() {
+ context.dataStore.edit { prefs ->
+ prefs.remove(key)
+ }
+ }
+
+ companion object {
+ private const val FCM_TOKEN = "FCM_TOKEN"
+ private const val DATA_STORE_NAME = "fcmDataStore"
+ }
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/local/source/AlarmSettingDataSourceImpl.kt b/android/app/src/main/java/com/happy/friendogly/local/source/AlarmSettingDataSourceImpl.kt
new file mode 100644
index 000000000..c2ff57a49
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/local/source/AlarmSettingDataSourceImpl.kt
@@ -0,0 +1,24 @@
+package com.happy.friendogly.local.source
+
+import com.happy.friendogly.data.source.AlarmSettingDataSource
+import com.happy.friendogly.local.di.AlarmModule
+import kotlinx.coroutines.flow.first
+
+class AlarmSettingDataSourceImpl(
+ private val alarmModule: AlarmModule,
+) : AlarmSettingDataSource {
+ override suspend fun saveAlarm(isSet: Boolean): Result =
+ runCatching {
+ alarmModule.saveSetting(isSet)
+ }
+
+ override suspend fun getAlarm(): Result =
+ runCatching {
+ alarmModule.isSet.first()
+ }
+
+ override suspend fun deleteAlarm(): Result =
+ runCatching {
+ alarmModule.deleteSetting()
+ }
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/alarm/AlarmReceiver.kt b/android/app/src/main/java/com/happy/friendogly/presentation/alarm/AlarmReceiver.kt
new file mode 100644
index 000000000..3117b2236
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/presentation/alarm/AlarmReceiver.kt
@@ -0,0 +1,101 @@
+package com.happy.friendogly.presentation.alarm
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import androidx.core.app.NotificationCompat
+import com.google.firebase.messaging.FirebaseMessagingService
+import com.google.firebase.messaging.RemoteMessage
+import com.happy.friendogly.R
+import com.happy.friendogly.application.di.AppModule
+import com.happy.friendogly.presentation.ui.MainActivity
+import com.happy.friendogly.presentation.ui.permission.AlarmPermission
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class AlarmReceiver : FirebaseMessagingService() {
+ private lateinit var notificationManager: NotificationManager
+
+ override fun onNewToken(token: String) {
+ CoroutineScope(Dispatchers.IO).launch {
+ if (token.isNotBlank()) {
+ AppModule.getInstance().saveAlarmTokenUseCase.invoke(token)
+ }
+ }
+ super.onNewToken(token)
+ }
+
+ override fun onMessageReceived(message: RemoteMessage) {
+ super.onMessageReceived(message)
+ if (message.notification != null) {
+ onReceive(message.notification?.title, message.notification?.body)
+ } else if (message.data.isNotEmpty()) {
+ onReceive(message.data[ALARM_TITLE], message.data[ALARM_BODY])
+ }
+ }
+
+ private fun onReceive(
+ title: String?,
+ body: String?,
+ ) = CoroutineScope(Dispatchers.IO).launch {
+ notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+ if (AppModule.getInstance().getAlarmSettingUseCase.invoke().getOrDefault(false)) {
+ createNotificationChannel()
+ deliverNotification(title, body)
+ }
+ }
+
+ private fun createNotificationChannel() {
+ if (AlarmPermission.isValidPermissionSDK()) {
+ val notificationChannel =
+ NotificationChannel(
+ CHANNEL_ID,
+ CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_HIGH,
+ )
+ notificationChannel.enableVibration(true)
+ notificationManager.createNotificationChannel(
+ notificationChannel,
+ )
+ }
+ }
+
+ private fun deliverNotification(
+ title: String?,
+ body: String?,
+ ) {
+ val contentIntent = MainActivity.getIntent(this)
+ val contentPendingIntent =
+ PendingIntent.getActivity(
+ this,
+ DEFAULT_INTENT_ID,
+ contentIntent,
+ PendingIntent.FLAG_IMMUTABLE,
+ )
+
+ val builder =
+ NotificationCompat.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle(title)
+ .setContentText(body)
+ .setContentIntent(contentPendingIntent)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setAutoCancel(true)
+ .setDefaults(NotificationCompat.DEFAULT_ALL)
+
+ notificationManager.notify(NOTIFICATION_ID, builder.build())
+ }
+
+ companion object {
+ private const val CHANNEL_ID = "alarm_id"
+ private const val CHANNEL_NAME = "alam"
+ private const val ALARM_TITLE = "title"
+ private const val ALARM_BODY = "body"
+
+ const val NOTIFICATION_ID = 0
+ const val DEFAULT_INTENT_ID = 1
+ }
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/register/RegisterViewModel.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/register/RegisterViewModel.kt
index 2d722aada..f428beed8 100644
--- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/register/RegisterViewModel.kt
+++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/register/RegisterViewModel.kt
@@ -5,7 +5,10 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
+import com.google.android.gms.tasks.Task
+import com.google.firebase.messaging.FirebaseMessaging
import com.happy.friendogly.analytics.AnalyticsHelper
+import com.happy.friendogly.application.di.AppModule
import com.happy.friendogly.domain.error.DataError
import com.happy.friendogly.domain.fold
import com.happy.friendogly.domain.model.JwtToken
@@ -74,6 +77,7 @@ class RegisterViewModel(
onSuccess = { login ->
if (login.isRegistered) {
val tokens = login.tokens ?: return
+ saveAlarmToken()
saveJwtToken(tokens)
} else {
_navigateAction.emit(RegisterNavigationAction.NavigateToProfileSetting(idToken = kakaAccessToken.accessToken))
@@ -88,6 +92,19 @@ class RegisterViewModel(
)
}
+ private fun saveAlarmToken() {
+ FirebaseMessaging.getInstance().token
+ .addOnCompleteListener { task: Task ->
+ if (!task.isSuccessful) {
+ return@addOnCompleteListener
+ }
+ val token = task.result
+ launch { // TODO 에러 핸들링
+ AppModule.getInstance().saveAlarmTokenUseCase.invoke(token)
+ }
+ }
+ }
+
fun executeGoogleLogin() {
analyticsHelper.logGoogleLoginClicked()
_navigateAction.emit(RegisterNavigationAction.NavigateToGoogleLogin)
diff --git a/android/app/src/main/java/com/happy/friendogly/remote/api/AlarmTokenService.kt b/android/app/src/main/java/com/happy/friendogly/remote/api/AlarmTokenService.kt
new file mode 100644
index 000000000..ef416303d
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/remote/api/AlarmTokenService.kt
@@ -0,0 +1,13 @@
+package com.happy.friendogly.remote.api
+
+import com.happy.friendogly.remote.model.request.DeviceTokenRequest
+import com.happy.friendogly.remote.model.response.BaseResponse
+import retrofit2.http.Body
+import retrofit2.http.PATCH
+
+interface AlarmTokenService {
+ @PATCH(ApiClient.AlarmToken.DEVICE_TOKEN)
+ suspend fun patchDeviceTokens(
+ @Body body: DeviceTokenRequest,
+ ): BaseResponse
+}
diff --git a/android/app/src/main/java/com/happy/friendogly/remote/api/ApiClient.kt b/android/app/src/main/java/com/happy/friendogly/remote/api/ApiClient.kt
index f35a86205..0e8fc7ec7 100644
--- a/android/app/src/main/java/com/happy/friendogly/remote/api/ApiClient.kt
+++ b/android/app/src/main/java/com/happy/friendogly/remote/api/ApiClient.kt
@@ -55,4 +55,8 @@ class ApiClient {
fun subscribeChat(chatRoomId: Long) = "/topic/chat/$chatRoomId"
}
+
+ object AlarmToken {
+ const val DEVICE_TOKEN = "/device-tokens"
+ }
}
diff --git a/android/app/src/main/java/com/happy/friendogly/remote/di/RemoteModule.kt b/android/app/src/main/java/com/happy/friendogly/remote/di/RemoteModule.kt
index da6037b17..09cdbd138 100644
--- a/android/app/src/main/java/com/happy/friendogly/remote/di/RemoteModule.kt
+++ b/android/app/src/main/java/com/happy/friendogly/remote/di/RemoteModule.kt
@@ -1,6 +1,7 @@
package com.happy.friendogly.remote.di
import com.happy.friendogly.local.di.TokenManager
+import com.happy.friendogly.remote.api.AlarmTokenService
import com.happy.friendogly.remote.api.AuthService
import com.happy.friendogly.remote.api.AuthenticationListener
import com.happy.friendogly.remote.api.Authenticator
@@ -104,6 +105,16 @@ object RemoteModule {
).create(ChatService::class.java)
}
+ fun createAlarmTokenService(
+ baseUrl: BaseUrl,
+ tokenManager: TokenManager,
+ authenticationListener: AuthenticationListener,
+ ): AlarmTokenService {
+ return createRetrofit(baseUrl, tokenManager, authenticationListener).create(
+ AlarmTokenService::class.java,
+ )
+ }
+
private val json =
Json {
ignoreUnknownKeys = true
diff --git a/android/app/src/main/java/com/happy/friendogly/remote/model/request/DeviceTokenRequest.kt b/android/app/src/main/java/com/happy/friendogly/remote/model/request/DeviceTokenRequest.kt
new file mode 100644
index 000000000..ed526acd3
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/remote/model/request/DeviceTokenRequest.kt
@@ -0,0 +1,8 @@
+package com.happy.friendogly.remote.model.request
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class DeviceTokenRequest(
+ val deviceToken: String,
+)
diff --git a/android/app/src/main/java/com/happy/friendogly/remote/source/AlamTokenDataSourceImpl.kt b/android/app/src/main/java/com/happy/friendogly/remote/source/AlamTokenDataSourceImpl.kt
new file mode 100644
index 000000000..ef6d735f4
--- /dev/null
+++ b/android/app/src/main/java/com/happy/friendogly/remote/source/AlamTokenDataSourceImpl.kt
@@ -0,0 +1,12 @@
+package com.happy.friendogly.remote.source
+
+import com.happy.friendogly.data.source.AlarmTokenDataSource
+import com.happy.friendogly.remote.api.AlarmTokenService
+import com.happy.friendogly.remote.model.request.DeviceTokenRequest
+
+class AlamTokenDataSourceImpl(private val service: AlarmTokenService) : AlarmTokenDataSource {
+ override suspend fun saveToken(token: String): Result =
+ runCatching {
+ service.patchDeviceTokens(DeviceTokenRequest(token))
+ }
+}