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)) + } +}