Skip to content

Commit b845f48

Browse files
committed
알림 api 연결
1 parent f38c544 commit b845f48

File tree

17 files changed

+407
-38
lines changed

17 files changed

+407
-38
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ dependencies {
169169
implementation("com.google.firebase:firebase-crashlytics-ktx")
170170
implementation("com.google.firebase:firebase-analytics-ktx")
171171
implementation("com.google.firebase:firebase-config-ktx")
172+
implementation("com.google.firebase:firebase-messaging-ktx")
172173

173174
// Glide
174175
implementation("com.github.bumptech.glide:glide:4.15.1")

app/src/main/AndroidManifest.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@
123123
android:name=".ui.common.ImageViewerActivity"
124124
android:exported="false"
125125
android:theme="@style/Theme.Siksha.ImageViewer" />
126+
127+
<service
128+
android:name=".fcm.SikshaFirebaseService"
129+
android:exported="false">
130+
<intent-filter>
131+
<action android:name="com.google.firebase.MESSAGING_EVENT" />
132+
</intent-filter>
133+
</service>
126134
</application>
127135

128136
</manifest>

app/src/main/java/com/wafflestudio/siksha2/di/NetworkModule.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import javax.inject.Singleton
2424
object NetworkModule {
2525
@Provides
2626
@Singleton
27-
fun provideHttpClient(sikshaPrefObjects: SikshaPrefObjects): OkHttpClient {
27+
fun provideHttpClient(
28+
@ApplicationContext context: Context,
29+
sikshaPrefObjects: SikshaPrefObjects
30+
): OkHttpClient {
2831
return OkHttpClient.Builder()
2932
/*
3033
.addInterceptor { chain ->
@@ -39,16 +42,18 @@ object NetworkModule {
3942
val request = chain.request()
4043
val builder = request.newBuilder()
4144

42-
val token = sikshaPrefObjects.accessToken.getValue()
43-
val isLoginRequest = request.url.encodedPath.contains("/auth/login")
45+
val path = request.url.encodedPath
46+
val isLoginRequest = path.contains("/auth/login")
4447

45-
if (token.isNotBlank() && !isLoginRequest) {
46-
builder.header("Authorization", token)
48+
if (!isLoginRequest) {
49+
val newRequest = builder
50+
.header(AUTH_TOKEN_HEADER_KEY, sikshaPrefObjects.accessToken.getValue())
51+
.build()
52+
chain.proceed(newRequest)
53+
} else {
54+
chain.proceed(request)
4755
}
48-
49-
chain.proceed(builder.build())
5056
}
51-
5257
.addInterceptor(
5358
HttpLoggingInterceptor().apply {
5459
level =
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.wafflestudio.siksha2.fcm
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.BitmapFactory
5+
import android.graphics.Canvas
6+
import android.graphics.Paint
7+
import android.graphics.RectF
8+
import android.util.Log
9+
import androidx.core.app.NotificationCompat
10+
import com.google.firebase.messaging.FirebaseMessagingService
11+
import com.google.firebase.messaging.RemoteMessage
12+
import com.wafflestudio.siksha2.R
13+
import com.wafflestudio.siksha2.preferences.SikshaPrefObjects
14+
import dagger.hilt.android.AndroidEntryPoint
15+
import javax.inject.Inject
16+
17+
@AndroidEntryPoint
18+
class SikshaFirebaseService : FirebaseMessagingService() {
19+
20+
@Inject lateinit var prefs: SikshaPrefObjects
21+
22+
override fun onNewToken(token: String) {
23+
super.onNewToken(token)
24+
Log.d("FCM", "New FCM token generated: $token")
25+
prefs.fcmToken.setValue(token)
26+
}
27+
28+
override fun onMessageReceived(remoteMessage: RemoteMessage) {
29+
// 수신 로그
30+
Log.d("FCM", "Notification received: ${remoteMessage.notification}")
31+
32+
// FCM notification payload 추출
33+
val title = remoteMessage.notification?.title ?: ""
34+
val body = remoteMessage.notification?.body ?: ""
35+
36+
// 메시지 전체를 로그로도 출력
37+
Log.d("FCM", "Title: $title, Body: $body")
38+
39+
// 알림 표시
40+
val notificationManager =
41+
getSystemService(NOTIFICATION_SERVICE) as android.app.NotificationManager
42+
val channelId = "siksha_channel"
43+
44+
// Android 8.0 이상은 채널 필요
45+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
46+
val channel = android.app.NotificationChannel(
47+
channelId,
48+
"Siksha Notifications",
49+
android.app.NotificationManager.IMPORTANCE_HIGH
50+
)
51+
notificationManager.createNotificationChannel(channel)
52+
}
53+
54+
val largeIcon = createNotificationLargeIcon()
55+
56+
val builder = NotificationCompat.Builder(this, channelId)
57+
.setSmallIcon(R.drawable.siksha_rice_bowl)
58+
.setLargeIcon(largeIcon)
59+
.setContentTitle(title)
60+
.setContentText(body)
61+
.setAutoCancel(true)
62+
63+
notificationManager.notify(System.currentTimeMillis().toInt(), builder.build())
64+
}
65+
66+
private fun createNotificationLargeIcon(): Bitmap {
67+
val size = 39
68+
val radius = 8.5f
69+
70+
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
71+
val canvas = Canvas(bitmap)
72+
73+
val paintBg = Paint().apply {
74+
color = resources.getColor(R.color.orange_500, null)
75+
isAntiAlias = true
76+
}
77+
78+
val rect = RectF(0f, 0f, size.toFloat(), size.toFloat())
79+
canvas.drawRoundRect(rect, radius, radius, paintBg)
80+
81+
val riceBmp = BitmapFactory.decodeResource(resources, R.drawable.siksha_rice_bowl)
82+
83+
val iconSize = (size * 0.55).toInt()
84+
val left = (size - iconSize) / 2
85+
val top = (size - iconSize) / 2
86+
val resized = Bitmap.createScaledBitmap(riceBmp, iconSize, iconSize, true)
87+
88+
canvas.drawBitmap(resized, left.toFloat(), top.toFloat(), null)
89+
90+
return bitmap
91+
}
92+
93+
}

app/src/main/java/com/wafflestudio/siksha2/network/SikshaApi.kt

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ interface SikshaApi {
2525
@GET("/menus/festival")
2626
suspend fun isFestivalDate(@Path(value = "input_date") inputDate: String): NetworkResult<FestivalDateCheckResponse>
2727

28+
@GET("/menus/me")
29+
suspend fun getFavoriteMenus(
30+
@Header("Authorization") token: String
31+
): NetworkResult<GetFavoriteMenusResponse>
32+
33+
@POST("/menus/{menu_id}/alarm/on")
34+
suspend fun postAlarmOn(
35+
@Path("menu_id") menuId: Long,
36+
@Header("Authorization") token: String
37+
): NetworkResult<AlarmResponse>
38+
39+
@POST("/menus/{menu_id}/alarm/off")
40+
suspend fun postAlarmOff(
41+
@Path("menu_id") menuId: Long,
42+
@Header("Authorization") token: String
43+
): NetworkResult<AlarmResponse>
44+
45+
@POST("/menus/alarm/off")
46+
suspend fun postAlarmOffAll(
47+
@Header("Authorization") token: String
48+
): NetworkResult<AlarmResponse>
49+
2850
@GET("/reviews")
2951
suspend fun fetchReviews(
3052
@Query("menu_id") menuId: Long,
@@ -43,23 +65,6 @@ interface SikshaApi {
4365
@GET("/restaurants")
4466
suspend fun fetchRestaurants(): NetworkResult<FetchRestaurantsResult>
4567

46-
@GET("/menus/me")
47-
suspend fun getFavoriteMenus(
48-
@Header("authorization-token") token: String
49-
): NetworkResult<GetFavoriteMenusResponse>
50-
51-
@POST("/menus/{menu_id}/alarm/on")
52-
suspend fun postAlarmOn(
53-
@Path("menu_id") menuId: Long,
54-
@Header("authorization-token") token: String
55-
): NetworkResult<AlarmResponse>
56-
57-
@POST("/menus/{menu_id}/alarm/off")
58-
suspend fun postAlarmOff(
59-
@Path("menu_id") menuId: Long,
60-
@Header("authorization-token") token: String
61-
): NetworkResult<AlarmResponse>
62-
6368
@POST("/reviews/")
6469
suspend fun leaveMenuReview(@Body req: LeaveReviewParam): NetworkResult<LeaveReviewResult>
6570

@@ -84,6 +89,23 @@ interface SikshaApi {
8489
@POST("/auth/refresh")
8590
suspend fun refreshToken(@Header("Authorization") token: String): NetworkResult<LoginOAuthResult>
8691

92+
@POST("/auth/userDevice")
93+
suspend fun registerUserDevice(
94+
@Body body: Map<String, String>,
95+
@Header("Authorization") authorization: String
96+
): Response<Unit>
97+
98+
@POST("/auth/alarm")
99+
suspend fun postAlarmType(
100+
@Header("Authorization") token: String,
101+
@Body body: Map<String, String>
102+
): NetworkResult<Unit>
103+
104+
@GET("/auth/alarm")
105+
suspend fun getAlarmType(
106+
@Header("Authorization") token: String
107+
): NetworkResult<GetAlarmTypeResponse>
108+
87109
@GET("/reviews/comments/recommendation")
88110
suspend fun fetchRecommendationReviewComments(@Query("score") score: Long):
89111
NetworkResult<FetchRecommendationReviewCommentsResult>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.wafflestudio.siksha2.network.dto
2+
3+
data class GetAlarmTypeResponse(
4+
val alarm_type: String // "DAILY" or "EACH_MEAL"
5+
)
6+

app/src/main/java/com/wafflestudio/siksha2/preferences/SikshaPrefObjects.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,22 @@ class SikshaPrefObjects @Inject constructor(
9696
serializer,
9797
Boolean::class.java
9898
)
99+
100+
val fcmToken: Preference<String> =
101+
Preference(
102+
"fcmToken",
103+
"",
104+
sharedPreferences,
105+
serializer,
106+
String::class.java
107+
)
108+
109+
val alarmEnabled: Preference<Boolean> =
110+
Preference(
111+
"alarmEnabled",
112+
false,
113+
sharedPreferences,
114+
serializer,
115+
Boolean::class.java
116+
)
99117
}

app/src/main/java/com/wafflestudio/siksha2/repositories/FavoriteMenuRepository.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.wafflestudio.siksha2.repositories
22

3+
import android.util.Log
34
import com.wafflestudio.siksha2.network.SikshaApi
5+
import com.wafflestudio.siksha2.network.dto.GetAlarmTypeResponse
46
import com.wafflestudio.siksha2.network.dto.GetFavoriteMenusResponse
57
import com.wafflestudio.siksha2.network.result.NetworkResult
68
import javax.inject.Inject
@@ -17,4 +19,16 @@ class FavoriteMenuRepository @Inject constructor(
1719

1820
suspend fun disableAlarm(token: String, menuId: Long) =
1921
api.postAlarmOff(menuId, token)
22+
23+
suspend fun disableAllMenuAlarms(token: String) =
24+
api.postAlarmOffAll(token)
25+
26+
suspend fun setAlarmType(type: String, token: String): NetworkResult<Unit> {
27+
val body = mapOf("type" to type)
28+
return api.postAlarmType(token, body)
29+
}
30+
31+
suspend fun fetchAlarmType(token: String): NetworkResult<GetAlarmTypeResponse> {
32+
return api.getAlarmType(token)
33+
}
2034
}

app/src/main/java/com/wafflestudio/siksha2/repositories/UserStatusManager.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import com.wafflestudio.siksha2.network.dto.core.UserDto
2121
import com.wafflestudio.siksha2.network.result.NetworkResult
2222
import com.wafflestudio.siksha2.preferences.SikshaPrefObjects
2323
import com.wafflestudio.siksha2.utils.showToast
24+
import kotlinx.coroutines.CoroutineScope
25+
import kotlinx.coroutines.Dispatchers
26+
import kotlinx.coroutines.launch
2427
import okhttp3.MultipartBody
2528
import timber.log.Timber
2629
import javax.inject.Inject
@@ -43,6 +46,8 @@ class UserStatusManager @Inject constructor(
4346
val accessToken = response.body.accessToken
4447
sikshaPrefObjects.oAuthProvider.setValue(provider)
4548
sikshaPrefObjects.accessToken.setValue(attachBearerPrefix(accessToken))
49+
50+
registerFcmTokenAfterLogin(attachBearerPrefix(accessToken))
4651
}
4752
else -> { }
4853
}
@@ -162,4 +167,23 @@ class UserStatusManager @Inject constructor(
162167
} else {
163168
"Bearer $token"
164169
}
170+
171+
private fun registerFcmTokenAfterLogin(accessToken: String) {
172+
val fcmToken = sikshaPrefObjects.fcmToken.getValue()
173+
if (fcmToken.isBlank()) {
174+
Log.w("UserStatusManager", "FCM token is blank, skipping registration")
175+
return
176+
}
177+
178+
CoroutineScope(Dispatchers.IO).launch {
179+
try {
180+
val response = sikshaApi.registerUserDevice(
181+
mapOf("fcm_token" to fcmToken),
182+
accessToken
183+
)
184+
} catch (e: Exception) {
185+
Log.e("UserStatusManager", "FCM registration exception", e)
186+
}
187+
}
188+
}
165189
}

app/src/main/java/com/wafflestudio/siksha2/ui/RootActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.wafflestudio.siksha2.ui
22

33
import android.os.Bundle
4+
import android.util.Log
45
import android.widget.Toast
56
import androidx.appcompat.app.AppCompatActivity
67
import androidx.core.view.ViewCompat
78
import androidx.core.view.WindowInsetsCompat
89
import androidx.core.view.updatePadding
910
import androidx.fragment.app.FragmentContainerView
1011
import androidx.lifecycle.lifecycleScope
12+
import com.google.firebase.messaging.FirebaseMessaging
1113
import com.wafflestudio.siksha2.R
1214
import com.wafflestudio.siksha2.repositories.MenuRepository
1315
import com.wafflestudio.siksha2.repositories.RestaurantRepository

0 commit comments

Comments
 (0)