Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
710a198
[REFACTOR/#317] 알림 권한 로직 개선
MoonsuKang Aug 11, 2025
7a956e3
[REFACTOR/#319] 홈 화면 데이터 로딩 로직 개선 및 동시성 관리
MoonsuKang Aug 11, 2025
dbfb291
[MOD/#317] 버전정보 수정
SYAAINN Aug 14, 2025
fdd6da5
[CHORE/#317] 불필요 파라미터 삭ㅈ
SYAAINN Aug 14, 2025
2dd1d8f
[REFACTOR/#317] 미사용 변수 삭제 후 홈화면에서 알림 권한 요청 승인 여부에 따라 알림 설정 API를 호출하도록…
SYAAINN Aug 14, 2025
89f5624
[CHORE/#317] ktlintFormat
SYAAINN Aug 14, 2025
f6584eb
Merge pull request #320 from Team-Clody/refactor/#319-upgrade-focus
SYAAINN Aug 14, 2025
53ceff5
[REFACTOR/#321] 점검 다이얼로그 string 추출을 통한 국제화를 진행합니다.
SYAAINN Aug 14, 2025
b6c5e39
[REFACTOR/#321] 점검 시작시간/종료시간을 "2025-08-11T18:00:00" 형태 그대로 반환합니다.
SYAAINN Aug 14, 2025
138c5ba
[REFACTOR/#321] 언어에 맞는 점검시간 포맷을 제공하는 함수를 구현합니다.
SYAAINN Aug 14, 2025
2a0c8e8
[REFACTOR/#321] 변경사항을 적용합니다.
SYAAINN Aug 14, 2025
d9026f8
[REFACTOR/#322] TimePicker 로직을 수정합니다. "오전"으로 하드코딩 되어있어서 TimePeriod가 바…
SYAAINN Aug 14, 2025
3b59e19
[REFACTOR/#321] 점검시간을 유저의 타임존에 맞춰 변환한 후 보여줄 수 있도록 합니다.
SYAAINN Aug 15, 2025
7194bfd
[CHORE/#321] 영어버전도 24시간 형식으로 통일합니다.
SYAAINN Aug 15, 2025
8581b98
[CHORE/#321] 리뷰 사항을 반영합니다.
SYAAINN Aug 26, 2025
c70281e
Merge pull request #324 from Team-Clody/feat/#321-inspection-i18n
SYAAINN Aug 26, 2025
c4d510e
Merge pull request #325 from Team-Clody/refactor/#322-notification-ti…
SYAAINN Aug 26, 2025
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
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ android {
applicationId = "com.sopt.clody"
minSdk = 28
targetSdk = 35
versionCode = 28
versionName = "1.4.0"
versionCode = 29
versionName = "1.5.0"
val kakaoApiKey: String = properties.getProperty("kakao.api.key")
val amplitudeApiKey: String = properties.getProperty("amplitude.api.key")
val googleAdmobAppId: String = properties.getProperty("GOOGLE_ADMOB_APP_ID", "")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package com.sopt.clody.presentation.ui.auth.timereminder

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
Expand Down Expand Up @@ -32,7 +27,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel
import com.sopt.clody.R
import com.sopt.clody.presentation.ui.auth.component.container.PickerBox
Expand All @@ -57,27 +51,6 @@ fun TimeReminderRoute(
var showDialog by remember { mutableStateOf(false) }
var dialogMessage by remember { mutableStateOf("") }

val isNotificationPermissionGranted = remember { mutableStateOf(false) }

val requestPermissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
) { isGranted: Boolean ->
isNotificationPermissionGranted.value = isGranted
}
// 알림 권한 요청
LaunchedEffect(Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val notificationPermission = Manifest.permission.POST_NOTIFICATIONS
if (ContextCompat.checkSelfPermission(context, notificationPermission) != PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(notificationPermission)
} else {
isNotificationPermissionGranted.value = true
}
} else {
isNotificationPermissionGranted.value = true
}
}

// 알림 권한 요청 결과에 따른 처리
LaunchedEffect(timeReminderState) {
when (val result = timeReminderState) {
Expand All @@ -103,14 +76,14 @@ fun TimeReminderRoute(
TimeReminderScreen(
onStartClick = {
viewModel.setSelectedTime(TimePeriod.PM, "9", "30")
viewModel.sendNotification(context, isNotificationPermissionGranted.value)
viewModel.sendNotification(context)
},
onTimeSelected = { period, hour, minute ->
viewModel.setSelectedTime(period, hour, minute)
},
onCompleteClick = {
AmplitudeUtils.trackEvent(eventName = AmplitudeConstraints.ONBOARDING_ALARM)
viewModel.sendNotification(context, isNotificationPermissionGranted.value)
viewModel.sendNotification(context)
},
isLoading = timeReminderState is TimeReminderState.Loading,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class TimeReminderViewModel @Inject constructor(
var selectedTime by mutableStateOf("21:30")
private set

fun sendNotification(context: Context, isPermissionGranted: Boolean) {
fun sendNotification(context: Context) {
viewModelScope.launch {
if (networkConnectivityObserver.networkStatus.first() == NetworkStatus.Unavailable) {
_timeReminderState.value = TimeReminderState.Failure(errorMessageProvider.getNetworkError())
Expand All @@ -47,9 +47,9 @@ class TimeReminderViewModel @Inject constructor(
}

val requestDto = SendNotificationRequestDto(
isDiaryAlarm = isPermissionGranted,
isDiaryAlarm = true,
isDraftAlarm = false,
isReplyAlarm = isPermissionGranted,
isReplyAlarm = true,
time = selectedTime,
fcmToken = fcmToken,
)
Comment on lines 49 to 55
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Hardcoded notification alarm settings may not reflect user preferences.

Setting isDiaryAlarm = true and isReplyAlarm = true unconditionally ignores the user's actual notification preferences. This could send unwanted notifications or override user settings.

Consider retrieving the current notification preferences before building the request:

-            val requestDto = SendNotificationRequestDto(
-                isDiaryAlarm = true,
-                isDraftAlarm = false,
-                isReplyAlarm = true,
-                time = selectedTime,
-                fcmToken = fcmToken,
-            )
+            val notificationInfo = notificationRepository.getNotificationInfo().getOrNull()
+            val requestDto = SendNotificationRequestDto(
+                isDiaryAlarm = notificationInfo?.isDiaryAlarm ?: true,
+                isDraftAlarm = false,
+                isReplyAlarm = notificationInfo?.isReplyAlarm ?: true,
+                time = selectedTime,
+                fcmToken = fcmToken,
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val requestDto = SendNotificationRequestDto(
isDiaryAlarm = isPermissionGranted,
isDiaryAlarm = true,
isDraftAlarm = false,
isReplyAlarm = isPermissionGranted,
isReplyAlarm = true,
time = selectedTime,
fcmToken = fcmToken,
)
val notificationInfo = notificationRepository.getNotificationInfo().getOrNull()
val requestDto = SendNotificationRequestDto(
isDiaryAlarm = notificationInfo?.isDiaryAlarm ?: true,
isDraftAlarm = false,
isReplyAlarm = notificationInfo?.isReplyAlarm ?: true,
time = selectedTime,
fcmToken = fcmToken,
)
🤖 Prompt for AI Agents
In
app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderViewModel.kt
around lines 49 to 55, the request DTO hardcodes isDiaryAlarm and isReplyAlarm
to true which ignores user preferences; replace the hardcoded values by fetching
the user's current notification preferences (via the existing preferences
repository/use-case or a new injected provider), read the diary/reply alarm
flags (with sensible fallbacks), and use those boolean values when constructing
SendNotificationRequestDto so the request reflects the user’s actual settings.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.sopt.clody.presentation.ui.home.screen

import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
Expand All @@ -26,6 +31,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sopt.clody.R
Expand All @@ -40,7 +46,6 @@ import com.sopt.clody.presentation.ui.component.dialog.ClodyDialog
import com.sopt.clody.presentation.ui.component.popup.ClodyPopupBottomSheet
import com.sopt.clody.presentation.ui.component.timepicker.YearMonthPicker
import com.sopt.clody.presentation.ui.component.toast.ClodyToastMessage
import com.sopt.clody.presentation.ui.home.calendar.model.DiaryDateData
import com.sopt.clody.presentation.ui.home.component.DiaryStateButton
import com.sopt.clody.presentation.ui.home.component.HomeTopAppBar
import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints
Expand All @@ -49,8 +54,6 @@ import com.sopt.clody.presentation.utils.extension.toLocalizedMonthLabel
import com.sopt.clody.presentation.utils.extension.toLocalizedYearLabel
import com.sopt.clody.presentation.utils.navigation.Route
import com.sopt.clody.ui.theme.ClodyTheme
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import java.time.LocalDate

@Composable
Expand Down Expand Up @@ -84,6 +87,12 @@ fun HomeRoute(
val showYearMonthPickerState by homeViewModel.showYearMonthPickerState.collectAsStateWithLifecycle()
val hasDraft by homeViewModel.hasDraft.collectAsStateWithLifecycle()

val requestPermissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
) { isGranted: Boolean ->
homeViewModel.sendNotification(isGranted)
}

LaunchedEffect(Unit) {
AmplitudeUtils.trackEvent(eventName = AmplitudeConstraints.HOME)

Expand All @@ -93,32 +102,33 @@ fun HomeRoute(
}
}

// 알림 권한 요청
LaunchedEffect(Unit) {
val year = selectedDiaryDate.year
val month = selectedDiaryDate.month
val day = selectedDate.dayOfMonth

try {
coroutineScope {
val calendarDeferred = async { homeViewModel.loadCalendarData(year, month) }
val dailyDeferred = async { homeViewModel.loadDailyDiariesData(year, month, day) }

calendarDeferred.await()
dailyDeferred.await()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val notificationPermission = Manifest.permission.POST_NOTIFICATIONS
if (ContextCompat.checkSelfPermission(context, notificationPermission) != PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(notificationPermission)
} else {
homeViewModel.sendNotification(true)
}
} catch (e: Exception) {
homeViewModel.setErrorState(true, "데이터를 불러오는데 실패했습니다.")
} else {
homeViewModel.sendNotification(true)
}
}

LaunchedEffect(Unit) {
homeViewModel.updateYearMonthAndLoadData(
selectedDiaryDate.year,
selectedDiaryDate.month,
selectedDate.dayOfMonth,
)
}

if (isError) {
FailureScreen(
message = errorMessage,
confirmAction = {
homeViewModel.refreshCalendarDataCalendarData(
selectedDiaryDate.year,
selectedDiaryDate.month,
)
homeViewModel.loadDailyDiariesData(
homeViewModel.updateYearMonthAndLoadData(
selectedDiaryDate.year,
selectedDiaryDate.month,
selectedDate.dayOfMonth,
Expand Down Expand Up @@ -273,10 +283,11 @@ fun HomeRoute(
confirmOption = stringResource(R.string.dialog_diary_delete_confirm),
dismissOption = stringResource(R.string.dialog_diary_delete_dismiss),
confirmAction = {
val d = homeViewModel.selectedDate.value
homeViewModel.deleteDailyDiary(
selectedDiaryDate.year,
selectedDiaryDate.month,
selectedDate.dayOfMonth,
d.year,
d.monthValue,
d.dayOfMonth,
)
homeViewModel.setShowDiaryDeleteDialog(false)
},
Expand Down Expand Up @@ -320,8 +331,7 @@ fun HomeScreen(
FailureScreen(
message = errorMessage,
confirmAction = {
homeViewModel.refreshCalendarDataCalendarData(selectedYear, selectedMonth)
homeViewModel.loadDailyDiariesData(selectedYear, selectedMonth, selectedDate.dayOfMonth)
homeViewModel.updateYearMonthAndLoadData(selectedYear, selectedMonth, selectedDate.dayOfMonth)
},
)
} else {
Expand Down Expand Up @@ -388,8 +398,7 @@ fun HomeScreen(
LoadingScreen()
}

is DeleteDiaryState.Success -> {
}
is DeleteDiaryState.Success -> {}

is DeleteDiaryState.Failure -> {
homeViewModel.setErrorState(true, stringResource(R.string.home_error_delete_diary))
Expand Down Expand Up @@ -433,8 +442,7 @@ fun HomeScreen(
selectedYear = selectedYear,
selectedMonth = selectedMonth,
onYearMonthSelected = { year, month ->
homeViewModel.updateSelectedDiaryDate(DiaryDateData(year, month))
homeViewModel.loadCalendarData(year, month)
homeViewModel.updateYearMonthAndLoadData(year, month)
},
)
}
Expand Down
Loading