Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

푸시 알림 구현 #82

Merged
merged 11 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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