Skip to content

Commit 48293d5

Browse files
authored
Publish 1.0.4
2 parents 599ff0d + 8b1b748 commit 48293d5

File tree

41 files changed

+1389
-125
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1389
-125
lines changed

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ dependencies {
6868

6969
implementation(Dependency.Hilt.HILT_ANDROID)
7070
kapt(Dependency.Hilt.HILT_ANDROID_COMPILER)
71+
implementation(Dependency.Compose.COMPOSE_HILT_NAV)
7172

73+
implementation(platform(Dependency.FIREBASE.FIREBASE_BOM))
74+
implementation(Dependency.FIREBASE.FIREBASE_MESSAGING)
7275
implementation(Dependency.FIREBASE.FIREBASE_CRASHLYTICS)
7376
implementation(Dependency.FIREBASE.FIREBASE_ANALYTICS)
7477

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@
3333
<category android:name="android.intent.category.LAUNCHER" />
3434
</intent-filter>
3535
</activity>
36-
</application>
3736

38-
</manifest>
37+
<service
38+
android:name=".firebase.FCMManager"
39+
android:enabled="true"
40+
android:exported="true"
41+
android:stopWithTask="false">
42+
<intent-filter>
43+
<action android:name="com.google.firebase.MESSAGING_EVENT" />
44+
</intent-filter>
45+
</service>
46+
</application>
47+
</manifest>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
@file:Suppress(
2+
"MissingFirebaseInstanceTokenRefresh",
3+
"MagicNumber",
4+
)
5+
6+
package com.comit.simtong.firebase
7+
8+
import android.app.Notification
9+
import android.app.NotificationChannel
10+
import android.app.NotificationManager
11+
import android.os.Build
12+
import androidx.core.app.NotificationCompat
13+
import com.comit.simtong.R
14+
import com.google.firebase.messaging.FirebaseMessagingService
15+
import com.google.firebase.messaging.RemoteMessage
16+
17+
class FCMManager : FirebaseMessagingService() {
18+
19+
private companion object Channel {
20+
const val CHANNEL_ID = "simtong_fcm"
21+
const val CHANNEL_NAME = "SimTong"
22+
}
23+
24+
override fun onMessageReceived(remoteMessage: RemoteMessage) {
25+
super.onMessageReceived(remoteMessage)
26+
if (remoteMessage.data.isNotEmpty()) {
27+
sendNotification(remoteMessage)
28+
}
29+
}
30+
31+
private fun sendNotification(remoteMessage: RemoteMessage) {
32+
val title = remoteMessage.data["title"]
33+
val message = remoteMessage.data["message"]
34+
35+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
36+
val notificationChannel = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
37+
val channelMessage = NotificationChannel(
38+
CHANNEL_ID, CHANNEL_NAME,
39+
NotificationManager.IMPORTANCE_DEFAULT
40+
)
41+
42+
channelMessage.apply {
43+
enableLights(true)
44+
enableVibration(true)
45+
setShowBadge(false)
46+
vibrationPattern = longArrayOf(100, 200, 100, 200)
47+
}
48+
notificationChannel.createNotificationChannel(channelMessage)
49+
50+
val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
51+
.setSmallIcon(R.drawable.ic_launcher_background)
52+
.setContentTitle(title)
53+
.setContentText(message)
54+
.setChannelId(CHANNEL_ID)
55+
.setAutoCancel(true)
56+
.setDefaults(Notification.DEFAULT_SOUND or Notification.DEFAULT_VIBRATE)
57+
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
58+
59+
notificationManager.notify(9999, notificationBuilder.build())
60+
} else {
61+
val notificationBuilder = NotificationCompat.Builder(this, "")
62+
.setSmallIcon(R.drawable.ic_launcher_background)
63+
.setContentTitle(title)
64+
.setContentText(message)
65+
.setAutoCancel(true)
66+
.setDefaults(Notification.DEFAULT_SOUND or Notification.DEFAULT_VIBRATE)
67+
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
68+
notificationManager.notify(9999, notificationBuilder.build())
69+
}
70+
}
71+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.comit.simtong.firebase
2+
3+
import com.comit.domain.exception.UnknownException
4+
import com.google.firebase.messaging.FirebaseMessaging
5+
6+
/**
7+
* FCM 에서 사용되는 DeviceToken 을 불러오는 함수
8+
*
9+
* @return 디바이스 토큰
10+
* @throws UnknownException DeviceToken 을 불러오지 못한 경우 발생
11+
*/
12+
internal fun getDeviceToken(): String =
13+
FirebaseMessaging.getInstance().token.result
14+
?: throw UnknownException("디바이스 토큰 발급에 실패했습니다.\n잠시 후 다시 시도해주세요.")

app/src/main/java/com/comit/simtong/root/MainActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.compose.ui.graphics.luminance
1111
import androidx.compose.ui.graphics.toArgb
1212
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
1313
import androidx.core.view.WindowCompat
14+
import androidx.hilt.navigation.compose.hiltViewModel
1415
import androidx.navigation.compose.NavHost
1516
import androidx.navigation.compose.rememberNavController
1617
import com.comit.common.systemBarPadding
@@ -32,6 +33,7 @@ class MainActivity : ComponentActivity() {
3233

3334
setContent {
3435
setWindowInset()
36+
saveDeviceToken(hiltViewModel())
3537

3638
val navController = rememberNavController()
3739

@@ -85,4 +87,10 @@ class MainActivity : ComponentActivity() {
8587
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
8688
}
8789
}
90+
91+
private fun saveDeviceToken(
92+
vm: MainViewModel,
93+
) {
94+
vm.saveDeviceToken()
95+
}
8896
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.comit.simtong.root
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
5+
import com.comit.domain.usecase.users.SaveDeviceTokenUseCase
6+
import com.google.firebase.messaging.FirebaseMessaging
7+
import dagger.hilt.android.lifecycle.HiltViewModel
8+
import kotlinx.coroutines.launch
9+
import javax.inject.Inject
10+
11+
/**
12+
* TODO(limsaehyun)
13+
*
14+
* Device 토큰을 google-service 가 위치한 app 에서 추출하여 저장하기 위해
15+
* MainViewModel 이 존재함
16+
* app 모듈에 viewModel 이 존재해도 될까? app 모듈의 범위에 대해서 고민한 필요가 있음
17+
*/
18+
@HiltViewModel
19+
class MainViewModel @Inject constructor(
20+
private val saveDeviceTokenUseCase: SaveDeviceTokenUseCase,
21+
) : ViewModel() {
22+
23+
fun saveDeviceToken() {
24+
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
25+
val token = task.result
26+
viewModelScope.launch {
27+
saveDeviceTokenUseCase(
28+
token = token
29+
)
30+
}
31+
}
32+
}
33+
}

buildSrc/src/main/java/Dependency.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ object Dependency {
1010
}
1111

1212
object FIREBASE {
13+
const val FIREBASE_BOM = "com.google.firebase:firebase-bom:${Versions.FIREBASE_BOM}"
1314
const val FIREBASE_CRASHLYTICS = "com.google.firebase:firebase-crashlytics-ktx:${Versions.FIREBASE_CRASHLYTICS}"
1415
const val FIREBASE_ANALYTICS = "com.google.firebase:firebase-analytics-ktx:${Versions.FIREBASE_ANALYTICS}"
16+
const val FIREBASE_MESSAGING = "com.google.firebase:firebase-messaging-ktx"
1517
}
1618

1719
object Kotlin {

buildSrc/src/main/java/ProjectProperties.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import org.gradle.api.JavaVersion
22

33
object ProjectProperties{
4-
const val VERSION_CODE = 6
5-
const val VERSION_NAME = "1.0.3"
4+
const val VERSION_CODE = 7
5+
const val VERSION_NAME = "1.0.4"
66

77
const val APPLICATION_ID = "com.comit.simtong"
88

buildSrc/src/main/java/Versions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ object Versions {
55
const val GRADLE_FIREBASE_CRASHLYTICS = "2.9.2"
66
const val FIREBASE_CRASHLYTICS = "18.3.2"
77
const val FIREBASE_ANALYTICS = "21.2.0"
8+
const val FIREBASE_BOM = "31.1.1"
89

910
const val KT_LINT = "10.2.0"
1011

core-design-system/src/main/java/com/comit/core_design_system/icon/SimTongIcon.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,13 @@ class SimTongIcon private constructor(
258258
@Stable
259259
val Logo = SimTongIcon(
260260
drawableId = R.drawable.ic_logo,
261-
contentDescription = "logo icon"
261+
contentDescription = "logo icon",
262262
)
263263

264264
@Stable
265265
val Profile_Big = SimTongIcon(
266266
drawableId = R.drawable.ic_profile_big,
267-
contentDescription = "profile big"
267+
contentDescription = "profile big",
268268
)
269269
}
270270
}
Loading

core-design-system/src/main/res/drawable/ic_logo.xml

Lines changed: 0 additions & 10 deletions
This file was deleted.

data/src/main/java/com/comit/data/datasource/LocalAuthDataSource.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ interface LocalAuthDataSource {
1313
suspend fun saveAccessToken(token: String)
1414
suspend fun saveRefreshToken(token: String)
1515
suspend fun saveExpiredAt(expiredAt: LocalDateTime)
16-
1716
suspend fun saveToken(token: Token)
17+
18+
suspend fun clearToken()
19+
20+
suspend fun fetchDeviceToken(): Flow<String>
21+
suspend fun saveDeviceToken(token: String)
1822
}

data/src/main/java/com/comit/data/datasource/RemoteAuthDataSource.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface RemoteAuthDataSource {
99
suspend fun signIn(
1010
employeeNumber: Int,
1111
password: String,
12+
deviceToken: String,
1213
): Token
1314

1415
suspend fun verificationEmployee(
@@ -23,6 +24,7 @@ interface RemoteAuthDataSource {
2324
password: String,
2425
nickname: String?,
2526
profileImagePath: String?,
27+
deviceToken: String,
2628
): Token
2729

2830
suspend fun checkNicknameDuplication(

data/src/main/java/com/comit/data/repository/AuthRepositoryImpl.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:Suppress("TooManyFunctions")
2+
13
package com.comit.data.repository
24

35
import com.comit.data.datasource.LocalAuthDataSource
@@ -6,6 +8,7 @@ import com.comit.data.datasource.RemoteFileDataSource
68
import com.comit.data.util.FormDataUtil
79
import com.comit.domain.repository.AuthRepository
810
import com.comit.model.User
11+
import kotlinx.coroutines.flow.first
912
import java.io.File
1013
import java.util.UUID
1114
import javax.inject.Inject
@@ -23,6 +26,7 @@ class AuthRepositoryImpl @Inject constructor(
2326
remoteAuthDataSource.signIn(
2427
employeeNumber = employeeNumber,
2528
password = password,
29+
deviceToken = getDeviceToken(),
2630
).also {
2731
localAuthDataSource.saveToken(it)
2832
}
@@ -44,7 +48,7 @@ class AuthRepositoryImpl @Inject constructor(
4448
email: String,
4549
password: String,
4650
nickname: String?,
47-
profileImage: File?
51+
profileImage: File?,
4852
) {
4953
val profileImagePath = profileImage?.let {
5054
getImagePathByFile(
@@ -59,6 +63,7 @@ class AuthRepositoryImpl @Inject constructor(
5963
password = password,
6064
nickname = nickname,
6165
profileImagePath = profileImagePath,
66+
deviceToken = getDeviceToken(),
6267
).also {
6368
localAuthDataSource.saveToken(it)
6469
}
@@ -108,6 +113,20 @@ class AuthRepositoryImpl @Inject constructor(
108113
return remoteAuthDataSource.fetchUserInformation()
109114
}
110115

116+
override suspend fun saveDeviceToken(token: String) {
117+
localAuthDataSource.saveDeviceToken(
118+
token = token,
119+
)
120+
}
121+
122+
override suspend fun clearToken() {
123+
localAuthDataSource.clearToken()
124+
}
125+
126+
private suspend fun getDeviceToken(): String {
127+
return localAuthDataSource.fetchDeviceToken().first()
128+
}
129+
111130
private suspend fun getImagePathByFile(
112131
file: File,
113132
): String {

detekt-config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ complexity:
66
ignoreAnnotated: [ 'Composable' ]
77
LongParameterList:
88
active: false
9+
TooManyFunctions:
10+
active: false
911

1012
performance:
1113
SpreadOperator:

domain/src/main/java/com/comit/domain/repository/AuthRepository.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,8 @@ interface AuthRepository {
4646
)
4747

4848
suspend fun fetchUserInformation(): User
49+
50+
suspend fun saveDeviceToken(token: String)
51+
52+
suspend fun clearToken()
4953
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.comit.domain.usecase.users
2+
3+
import com.comit.domain.repository.AuthRepository
4+
import javax.inject.Inject
5+
6+
class ClearTokenUseCase @Inject constructor(
7+
private val authRepository: AuthRepository,
8+
) {
9+
10+
suspend operator fun invoke() = kotlin.runCatching {
11+
authRepository.clearToken()
12+
}
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.comit.domain.usecase.users
2+
3+
import com.comit.domain.repository.AuthRepository
4+
import javax.inject.Inject
5+
6+
class SaveDeviceTokenUseCase @Inject constructor(
7+
private val repository: AuthRepository,
8+
) {
9+
10+
suspend operator fun invoke(token: String) = kotlin.runCatching {
11+
repository.saveDeviceToken(
12+
token = token,
13+
)
14+
}
15+
}

feature/feature-home/src/main/java/com/comit/feature_home/screen/HomeScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.compose.runtime.LaunchedEffect
2525
import androidx.compose.runtime.collectAsState
2626
import androidx.compose.ui.Alignment
2727
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.draw.clip
2829
import androidx.compose.ui.graphics.painter.Painter
2930
import androidx.compose.ui.res.painterResource
3031
import androidx.compose.ui.res.stringResource
@@ -248,6 +249,9 @@ private fun HomeBottomIconLayout(
248249
color = SimTongColor.White,
249250
shape = RoundedCornerShape(16.dp),
250251
)
252+
.clip(
253+
shape = RoundedCornerShape(16.dp)
254+
)
251255
.simClickable {
252256
onClick()
253257
}

0 commit comments

Comments
 (0)