Skip to content

Commit

Permalink
Merge pull request #115 from YAPP-Github/feature/TNT-242
Browse files Browse the repository at this point in the history
[TNT-242] ์•Œ๋ฆผ ๋”ฅ๋งํฌ ๊ตฌํ˜„
  • Loading branch information
hoyahozz authored Feb 14, 2025
2 parents 930c49d + 9a4c706 commit 473d49f
Show file tree
Hide file tree
Showing 17 changed files with 179 additions and 46 deletions.
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification_logo" />

<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/ic_notification_background" />
</application>

</manifest>
96 changes: 94 additions & 2 deletions app/src/main/java/co/kr/tnt/service/MessagingService.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package co.kr.tnt.service

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import co.kr.tnt.R
import co.kr.tnt.main.MainActivity
import com.google.firebase.messaging.Constants.MessageNotificationKeys
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import dagger.hilt.android.AndroidEntryPoint
Expand All @@ -9,11 +22,90 @@ class MessagingService : FirebaseMessagingService() {

override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
// TODO

// TODO message.data ํ˜•ํƒœ๋กœ ์˜ค๋„๋ก ์š”์ฒญ
launchNotification(
title = message.data["title"] ?: "ํŠธ๋ ˆ์ด๋„ˆ ์—ฐ๊ฒฐ ์•Œ๋ฆผ",
content = message.data["content"] ?: "ํŠธ๋ ˆ์ด๋‹ˆ๋‹˜๊ณผ ์—ฐ๊ฒฐ๋˜์—ˆ์–ด์š”!",
pendingIntent = createPendingIntent(message.data)
)
}

private fun createPendingIntent(
data: Map<String, String>,
): PendingIntent? {
val trainerId = data["trainerId"] ?: "0"
val traineeId = data["traineeId"] ?: "0"

val intent = Intent(this, MainActivity::class.java).apply {
this.data = "tnt-deeplink://trainee-connect-complete/$trainerId/$traineeId".toUri()
}

return TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(intent)
getPendingIntent(
INTENT_REQUEST_CODE,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
}
}

override fun handleIntent(intent: Intent?) {
// Background ์ผ ๋•Œ FCM notification ํ•„๋“œ๊ฐ€ ์žˆ์œผ๋ฉด
// data ํ•„๋“œ๋ฅผ ์ˆ˜์‹ ํ•  ์ˆ˜ ์—†๋Š” ํ˜„์ƒ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด,
// notification key ๊ฐ’ ์ œ๊ฑฐ
val new = intent?.apply {
val temp = extras?.apply {
remove(MessageNotificationKeys.ENABLE_NOTIFICATION)
remove(keyWithOldPrefix(MessageNotificationKeys.ENABLE_NOTIFICATION))
}
replaceExtras(temp)
}
super.handleIntent(new)
}

private fun launchNotification(
title: String?,
content: String,
pendingIntent: PendingIntent?,
) {
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH)
val notificationManager: NotificationManager =
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
createNotificationChannel(channel)
}

val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_logo)
.setColor(ContextCompat.getColor(this, R.color.ic_notification_background))
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build()

notificationManager.notify(System.currentTimeMillis().toInt(), notification)
}

private fun keyWithOldPrefix(key: String): String {
if (!key.startsWith(MessageNotificationKeys.NOTIFICATION_PREFIX)) {
return key
}

return key.replace(
MessageNotificationKeys.NOTIFICATION_PREFIX,
MessageNotificationKeys.NOTIFICATION_PREFIX_OLD
)
}

override fun onNewToken(token: String) {
super.onNewToken(token)
// TODO
Log.w("Messaging service", "token : $token") // TODO
}

companion object {
private const val CHANNEL_ID = "TNT_NOTIFICATION_ID"
private const val CHANNEL_NAME = "TNT_NOTIFICATION"
private const val INTENT_REQUEST_CODE = 100
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion app/src/main/res/values/ic_launcher_background.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FF472F</color>
</resources>
<color name="ic_notification_background">#FF472F</color>
</resources>
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
package co.kr.data.network.model

import co.kr.tnt.domain.model.ConnectedResult
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ConnectedTraineeResponse(
val trainerName: String,
val trainee: Trainee,
val trainer: Trainer,
)

@Serializable
data class Trainee(
val traineeName: String,
val trainerProfileImageUrl: String?,
val traineeProfileImageUrl: String?,
val traineeAge: Int,
val height: Double,
val weight: Double,
val ptGoal: String,
val traineeProfileImageUrl: String,
val cautionNote: String?,
val height: Double?,
val ptGoal: String,
val traineeAge: Int?,
val weight: Double?,
)

@Serializable
data class Trainer(
@SerialName("trainerName")
val trainerName: String,
@SerialName("trainerProfileImageUrl")
val trainerProfileImageUrl: String,
)

fun ConnectedTraineeResponse.toDomain(): ConnectedResult =
ConnectedResult(
trainerName = trainerName,
traineeName = traineeName,
trainerImage = trainerProfileImageUrl,
traineeImage = traineeProfileImageUrl,
age = traineeAge,
height = height,
weight = weight,
ptGoal = ptGoal,
cautionNote = cautionNote,
trainerName = trainer.trainerName,
traineeName = trainee.traineeName,
trainerImage = trainer.trainerProfileImageUrl,
traineeImage = trainee.traineeProfileImageUrl,
age = trainee.traineeAge,
height = trainee.height,
weight = trainee.weight,
ptGoal = trainee.ptGoal,
cautionNote = trainee.cautionNote,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ data class ConnectedResult(
val traineeName: String,
val trainerImage: String?,
val traineeImage: String?,
val age: Int,
val height: Double,
val weight: Double,
val age: Int?,
val height: Double?,
val weight: Double?,
val ptGoal: String,
val cautionNote: String?,
)
11 changes: 3 additions & 8 deletions domain/src/main/java/co/kr/tnt/domain/model/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,26 @@ sealed class User {
}
}

// TODO ๋„๋ฉ”์ธ ๋ชจ๋ธ ๊ฐœ์„ 
data class Trainee(
override val id: String,
override val name: String,
override val image: String?,
val birthday: LocalDate?,
val age: Int? = 0,
val weight: Double?,
val height: Int?,
val ptPurpose: List<String>?,
val caution: String?,
val isConnected: Boolean,
) : User() {
/** ํ•œ๊ตญ์‹ ๋‚˜์ด */
val age: Int? =
if (birthday == null) {
null
} else {
LocalDate.now().year - birthday.year + 1
}

companion object {
val EMPTY = Trainee(
id = "",
name = "",
image = null,
birthday = null,
age = null,
weight = null,
height = null,
ptPurpose = emptyList(),
Expand Down
11 changes: 11 additions & 0 deletions feature/main/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="trainee-connect-complete"
android:scheme="tnt-deeplink"
android:pathPattern="/.*"/>
</intent-filter>
</activity>
</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ private fun TraineeMyPageScreenPreview() {
user = User.Trainee(
id = "",
name = "๊น€ํ—ฌ์Šค",
age = 0,
image = null,
birthday = LocalDate.of(2001, 1, 1),
weight = 10.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,11 @@ internal fun TraineeProfilePage(
text = trainee.ptPurpose?.joinToString(", ") ?: "",
)
Spacer(Modifier.height(32.dp))
if (!trainee.caution.isNullOrEmpty()) {
TextWithBackground(
label = stringResource(R.string.caution),
text = trainee.caution ?: "",
modifier = Modifier.height(128.dp),
)
}
TextWithBackground(
label = stringResource(R.string.caution),
text = trainee.caution ?: "",
modifier = Modifier.height(128.dp),
)
}
}
TnTBottomButton(
Expand Down Expand Up @@ -207,9 +205,14 @@ private fun TextWithBackground(
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
Text(
text = text,
text = text.ifEmpty { "๋ฏธ์ž…๋ ฅ" },
style = TnTTheme.typography.label1Medium,
color = TnTTheme.colors.neutralColors.Neutral800,
color =
if (text.isEmpty()) {
TnTTheme.colors.neutralColors.Neutral400
} else {
TnTTheme.colors.neutralColors.Neutral800
},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ internal class TrainerConnectViewModel @Inject constructor(
name = result.traineeName,
image = result.traineeImage,
birthday = null,
age = result.age,
weight = result.weight,
height = result.height.toInt(),
height = result.height?.toInt(),
ptPurpose = listOf(result.ptGoal),
caution = result.cautionNote,
isConnected = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptionsBuilder
import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink
import androidx.navigation.toRoute
import co.kr.tnt.navigation.Route
import co.kr.tnt.trainer.connect.TrainerConnectRoute
Expand All @@ -21,7 +22,13 @@ fun NavGraphBuilder.trainerConnectScreen(
navigateToPrevious: () -> Unit,
navigateToHome: (Boolean) -> Unit,
) {
composable<Route.TrainerConnect> { backstackEntry ->
composable<Route.TrainerConnect>(
deepLinks = listOf(
navDeepLink<Route.TrainerConnect>(
basePath = "tnt-deeplink://trainee-connect-complete",
),
),
) { backstackEntry ->
backstackEntry.toRoute<Route.TrainerConnect>().apply {
TrainerConnectRoute(
trainerId = trainerId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiEvent
import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiState
import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiState.DialogState
import co.kr.tnt.ui.base.BaseViewModel
import com.kizitonwose.calendar.core.yearMonth
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
Expand All @@ -21,7 +22,6 @@ import java.time.LocalDate
import java.time.LocalDateTime
import java.time.YearMonth
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import javax.inject.Inject

const val DIALOG_HIDE_DURATION_HOURS = 72
Expand All @@ -32,9 +32,9 @@ internal class TrainerHomeViewModel @Inject constructor(
private val connectRepository: ConnectRepository,
) :
BaseViewModel<TrainerHomeUiState, TrainerHomeUiEvent, TrainerHomeSideEffect>(TrainerHomeUiState()) {
private val cachedMonthlyPtSessionCounts: ConcurrentMap<YearMonth, List<TrainerDailyPtSessionCount>> =
private val cachedMonthlyPtSessionCounts: ConcurrentHashMap<YearMonth, List<TrainerDailyPtSessionCount>> =
ConcurrentHashMap()
private val cachedDailyPtSession: ConcurrentMap<LocalDate, List<PtSession>> = ConcurrentHashMap()
private val cachedDailyPtSession: ConcurrentHashMap<LocalDate, List<PtSession>> = ConcurrentHashMap()

init {
selectDay(LocalDate.now())
Expand All @@ -44,7 +44,7 @@ internal class TrainerHomeViewModel @Inject constructor(
when (event) {
TrainerHomeUiEvent.OnScreen -> refresh()
TrainerHomeUiEvent.OnClickNotification -> sendEffect(TrainerHomeSideEffect.NavigateToNotification)
is TrainerHomeUiEvent.OnChangeVisibleMonth -> handleChangeVisibleMonth(event.yearMonth)
is TrainerHomeUiEvent.OnChangeVisibleMonth -> getMonthlySessionCounts(event.yearMonth)
is TrainerHomeUiEvent.OnClickDay -> selectDay(event.day)
TrainerHomeUiEvent.OnClickAddPtSession -> showConnectDialog(false)

Expand All @@ -55,7 +55,7 @@ internal class TrainerHomeViewModel @Inject constructor(
}
}

private fun handleChangeVisibleMonth(yearMonth: YearMonth) {
private fun getMonthlySessionCounts(yearMonth: YearMonth) {
// ํ˜„์žฌ ๋‹ฌ์„ ๊ธฐ์ค€์œผ๋กœ 2๊ฐœ์›” ์ „๋ถ€ํ„ฐ 2๊ฐœ์›” ํ›„๊นŒ์ง€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
val targetRange = -2L..2L

Expand Down Expand Up @@ -142,6 +142,7 @@ internal class TrainerHomeViewModel @Inject constructor(
cachedMonthlyPtSessionCounts.clear()
cachedDailyPtSession.clear()
selectDay(currentState.selectedDay)
getMonthlySessionCounts(currentState.selectedDay.yearMonth)
showConnectDialog(true)
}

Expand Down

0 comments on commit 473d49f

Please sign in to comment.