Skip to content

Commit

Permalink
Merge pull request #82 from Nexters/feature/fcm-push
Browse files Browse the repository at this point in the history
푸시 알림 구현
  • Loading branch information
eshc123 authored Dec 22, 2024
2 parents a294c72 + 368116a commit d7e8db5
Show file tree
Hide file tree
Showing 48 changed files with 637 additions and 27 deletions.
5 changes: 5 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
id("missionmate.android.application")
alias(libs.plugins.google.service)
}

android {
Expand Down Expand Up @@ -47,6 +48,8 @@ dependencies {
ksp(libs.hilt.compiler)
implementation(libs.hilt.android)
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.messaging)
implementation(libs.firebase.analytics)

implementation(project(":feature:main"))
implementation(project(":feature:login"))
Expand All @@ -58,4 +61,6 @@ dependencies {
implementation(project(":core:data:onboarding"))
implementation(project(":core:data:setting"))
implementation(project(":core:data:user"))
implementation(project(":core:push"))
implementation(project(":core:notification"))
}
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:name=".MainApplication"
android:allowBackup="true"
Expand All @@ -30,4 +30,4 @@
</service>

</application>
</manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package com.goalpanzi.mission_mate

import android.app.Application
import com.google.firebase.FirebaseApp
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class MainApplication : Application()
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
FirebaseApp.initializeApp(this)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.goalpanzi.mission_mate.core.data.auth.repository

import com.goalpanzi.mission_mate.core.data.auth.mapper.toModel
import com.goalpanzi.mission_mate.core.data.common.DeviceInfoProvider
import com.goalpanzi.mission_mate.core.data.common.handleResult
import com.goalpanzi.mission_mate.core.datastore.datasource.AuthDataSource
import com.goalpanzi.mission_mate.core.domain.auth.repository.AuthRepository
Expand All @@ -13,11 +14,15 @@ import javax.inject.Inject

class AuthRepositoryImpl @Inject constructor(
private val loginService: LoginService,
private val authDataSource: AuthDataSource
): AuthRepository {
private val authDataSource: AuthDataSource,
private val deviceInfoProvider: DeviceInfoProvider
) : AuthRepository {

override suspend fun requestGoogleLogin(email: String) = handleResult {
val request = GoogleLoginRequest(email = email)
val request = GoogleLoginRequest(
email = email,
deviceIdentifier = deviceInfoProvider.getDeviceSSAID()
)
loginService.requestGoogleLogin(request)
}.convert {
it.toModel()
Expand All @@ -35,7 +40,9 @@ class AuthRepositoryImpl @Inject constructor(

override fun getRefreshToken(): Flow<String?> = authDataSource.getRefreshToken()

override fun setAccessToken(accessToken: String): Flow<Unit> = authDataSource.setAccessToken(accessToken)
override fun setAccessToken(accessToken: String): Flow<Unit> =
authDataSource.setAccessToken(accessToken)

override fun setRefreshToken(refreshToken: String): Flow<Unit> = authDataSource.setRefreshToken(refreshToken)
override fun setRefreshToken(refreshToken: String): Flow<Unit> =
authDataSource.setRefreshToken(refreshToken)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.goalpanzi.mission_mate.core.data.common

import android.annotation.SuppressLint
import android.content.Context
import android.provider.Settings
import javax.inject.Inject

class DeviceInfoProvider @Inject constructor(
private val context: Context
) {
@SuppressLint("HardwareIds")
fun getDeviceSSAID(): String {
return Settings.Secure.getString(context.contentResolver,Settings.Secure.ANDROID_ID)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.goalpanzi.mission_mate.core.data.common.di

import android.content.Context
import com.goalpanzi.mission_mate.core.data.common.DeviceInfoProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
@Module
object DeviceInfoProviderModule {
@Singleton
@Provides
fun provideADeviceInfoProvider(
@ApplicationContext context: Context
): DeviceInfoProvider {
return DeviceInfoProvider(context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.goalpanzi.mission_mate.core.data.user

import kotlinx.coroutines.flow.Flow

interface FcmTokenManager {
fun getFcmToken() : Flow<String>
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.goalpanzi.mission_mate.core.data.user.repository

import com.goalpanzi.mission_mate.core.data.common.DeviceInfoProvider
import com.goalpanzi.mission_mate.core.data.common.handleResult
import com.goalpanzi.mission_mate.core.data.common.mapper.toResponse
import com.goalpanzi.mission_mate.core.data.user.FcmTokenManager
import com.goalpanzi.mission_mate.core.data.user.mapper.toDto
import com.goalpanzi.mission_mate.core.data.user.mapper.toModel
import com.goalpanzi.mission_mate.core.datastore.datasource.DefaultDataSource
Expand All @@ -10,14 +12,17 @@ import com.goalpanzi.mission_mate.core.domain.common.model.user.CharacterType
import com.goalpanzi.mission_mate.core.domain.common.model.user.UserProfile
import com.goalpanzi.mission_mate.core.domain.user.repository.UserRepository
import com.goalpanzi.mission_mate.core.network.model.request.SaveProfileRequest
import com.goalpanzi.mission_mate.core.network.model.request.UpdateDeviceTokenRequest
import com.goalpanzi.mission_mate.core.network.service.ProfileService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class UserRepositoryImpl @Inject constructor(
private val profileService: ProfileService,
private val defaultDataSource: DefaultDataSource
private val defaultDataSource: DefaultDataSource,
private val fcmTokenManager: FcmTokenManager,
private val deviceInfoProvider: DeviceInfoProvider
) : UserRepository {
override suspend fun saveProfile(
nickname: String,
Expand All @@ -31,6 +36,17 @@ class UserRepositoryImpl @Inject constructor(
profileService.saveProfile(request)
}

override suspend fun updateFcmToken(fcmToken: String): DomainResult<Unit> = handleResult {
val request = UpdateDeviceTokenRequest(
deviceToken = fcmToken,
deviceIdentifier = deviceInfoProvider.getDeviceSSAID(),
osType = "AOS"
)
profileService.updateDeviceToken(request)
}

override fun getFcmToken(): Flow<String> = fcmTokenManager.getFcmToken()

override fun clearUserData(): Flow<Unit> = defaultDataSource.clearUserData()

override fun setUserProfile(data: UserProfile): Flow<Unit> = defaultDataSource.setUserProfile(data.toDto())
Expand All @@ -41,4 +57,8 @@ class UserRepositoryImpl @Inject constructor(

override fun getMemberId(): Flow<Long?> = defaultDataSource.getMemberId()

override fun setCachedFcmToken(data: String): Flow<Unit> = defaultDataSource.setFcmToken(data)

override fun getCachedFcmToken(): Flow<String?> = defaultDataSource.getFcmToken()

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ interface DefaultDataSource {
fun setViewedTooltip() : Flow<Unit>
fun setMemberId(data: Long) : Flow<Unit>
fun getMemberId() : Flow<Long?>
}
fun setFcmToken(data: String) : Flow<Unit>
fun getFcmToken() : Flow<String?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class DefaultDataSourceImpl @Inject constructor(
val USER_CHARACTER = stringPreferencesKey("USER_CHARACTER")
val VIEWED_TOOLTIP = booleanPreferencesKey("VIEWED_TOOLTIP")
val MEMBER_ID = longPreferencesKey("MEMBER_ID")
val FCM_TOKEN = stringPreferencesKey("FCM_TOKEN")
}

override fun clearUserData(): Flow<Unit> = flow {
Expand Down Expand Up @@ -73,4 +74,15 @@ class DefaultDataSourceImpl @Inject constructor(
override fun getMemberId(): Flow<Long?> = dataStore.data.map { preferences ->
preferences[PreferencesKey.MEMBER_ID]
}
}

override fun setFcmToken(data: String): Flow<Unit> = flow {
dataStore.edit { preferences ->
preferences[PreferencesKey.FCM_TOKEN] = data
}
emit(Unit)
}

override fun getFcmToken(): Flow<String?> = dataStore.data.map { preferences ->
preferences[PreferencesKey.FCM_TOKEN]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.goalpanzi.mission_mate.core.domain.common.DomainResult
import com.goalpanzi.mission_mate.core.domain.common.model.user.UserProfile
import com.goalpanzi.mission_mate.core.domain.user.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
Expand All @@ -15,6 +16,7 @@ class LoginUseCase @Inject constructor(
private val userRepository: UserRepository
) {
suspend fun requestGoogleLogin(email: String): GoogleLogin? {
userRepository.clearUserData().collect()
return when (val response = authRepository.requestGoogleLogin(email)) {
is DomainResult.Success -> {
response.data.also {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import kotlinx.coroutines.flow.Flow

interface UserRepository {
suspend fun saveProfile(nickname: String, type: CharacterType, isEqualNickname : Boolean): DomainResult<Unit>
suspend fun updateFcmToken(fcmToken : String): DomainResult<Unit>
fun getFcmToken(): Flow<String>
fun clearUserData() : Flow<Unit>
fun setUserProfile(data: UserProfile) : Flow<Unit>
fun getUserProfile() : Flow<UserProfile?>
fun setMemberId(data: Long) : Flow<Unit>
fun getMemberId() : Flow<Long?>
fun setCachedFcmToken(data: String) : Flow<Unit>
fun getCachedFcmToken() : Flow<String?>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.goalpanzi.mission_mate.core.domain.user.usecase

import com.goalpanzi.mission_mate.core.domain.user.repository.UserRepository
import javax.inject.Inject

class GetFcmTokenUseCase @Inject constructor(
private val userRepository: UserRepository
) {
operator fun invoke() = userRepository.getFcmToken()
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.goalpanzi.mission_mate.core.domain.user.usecase

import com.goalpanzi.mission_mate.core.domain.user.repository.UserRepository
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject

class UpdateFcmTokenUseCase @Inject constructor(
private val userRepository: UserRepository
) {
suspend operator fun invoke(fcmToken: String) {
val cachedFcmToken = userRepository.getCachedFcmToken().firstOrNull()
if(fcmToken != cachedFcmToken){
userRepository.setCachedFcmToken(fcmToken).collect()
userRepository.updateFcmToken(fcmToken)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import kotlinx.serialization.Serializable

@Serializable
data class GoogleLoginRequest(
val email: String
val email: String,
val deviceIdentifier: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.goalpanzi.mission_mate.core.network.model.request

import kotlinx.serialization.Serializable

@Serializable
data class UpdateDeviceTokenRequest(
val deviceToken: String,
val deviceIdentifier: String,
val osType: String
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.goalpanzi.mission_mate.core.network.service

import com.goalpanzi.mission_mate.core.network.model.request.SaveProfileRequest
import com.goalpanzi.mission_mate.core.network.model.request.UpdateDeviceTokenRequest
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.PATCH
Expand All @@ -10,4 +11,10 @@ interface ProfileService {
suspend fun saveProfile(
@Body request: SaveProfileRequest
): Response<Unit>
}

@PATCH("/api/device/device-token")
suspend fun updateDeviceToken(
@Body request: UpdateDeviceTokenRequest
): Response<Unit>

}
1 change: 1 addition & 0 deletions core/notification/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
46 changes: 46 additions & 0 deletions core/notification/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.hilt.android)
alias(libs.plugins.kotlin.ksp)
}

android {
namespace = "com.goalpanzi.mission_mate.core.notification"
compileSdk = 34

defaultConfig {
minSdk = 26

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
}

dependencies {
implementation(libs.bundles.test)
implementation(libs.bundles.coroutines)

ksp(libs.hilt.compiler)
implementation(libs.hilt.android)
}

Empty file.
Loading

0 comments on commit d7e8db5

Please sign in to comment.