Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f253b46
[REFACTOR/#323] 홈화면의 UI 구조를 개편하여 컴포넌트 계층 구조를 간소화합니다.
SYAAINN Aug 15, 2025
196e0f5
[CHORE/#323] ReplyStatus를 presentation layer로 이동하고, DiaryCloverType -…
SYAAINN Aug 17, 2025
94588bb
[CHORE/#323] updateYearMonthAndLoadData 함수를 통일합니다.
SYAAINN Aug 17, 2025
acebc07
[MOVE/#323] domain type 패키지를 만듭니다.
SYAAINN Aug 17, 2025
c436359
[FEAT/#323] 일기를 재작성 할 시 진한 클로버로 표기됩니다.
SYAAINN Aug 17, 2025
3eb4945
[FEAT/#323] 클로버와 하단버튼에 대한 타입을 정의합니다.
SYAAINN Aug 18, 2025
9cb6ff4
[FEAT/#323] 연/월이 업데이트 되거나, 일자가 클릭되면 홈 전체의 정보를 업데이트하도록 변경합니다.
SYAAINN Aug 18, 2025
c870328
[FEAT/#323] 홈화면 일일 일기 상태에 대한 하단 버튼의 타입을 생성하고, 분기처리 로직을 추가합니다.
SYAAINN Aug 18, 2025
1292c5e
[FEAT/#323] 홈화면에서 사용할 월별 일기, 일일 일기에 대한 data class를 정의합니다.
SYAAINN Aug 19, 2025
9ad6900
[FEAT/#323] 새롭게 정의한 data class를 통해 클로버와 홈화면 하단 버튼 분기 로직을 구현합니다.
SYAAINN Aug 19, 2025
98d77e6
[REFACTOR/#323] 홈화면을 MVVM -> MVI (Mavericks)로 마이그레이션합니다.
SYAAINN Aug 19, 2025
f399ab5
[REFACTOR/#323] 부가기능(알림권한요청, 인앱리뷰, 이어쓰기 알림) 제외 기능 점검
SYAAINN Aug 22, 2025
5568ba8
[REFACTOR/#323] 부가기능(알림권한요청, 인앱리뷰, 이어쓰기 알림) 점검합니다.
SYAAINN Aug 22, 2025
264bca2
[CHORE/#323] ktlint Format
SYAAINN Aug 22, 2025
64cd81d
[CHORE/#323] 일기 삭제 후 임시저장한 일기를 보낼 경우, 삭제된 일기로 취급하도록 우선순위를 변경합니다.
SYAAINN Sep 10, 2025
e388bba
[FIX/#323] 만료된 임시저장 일기를 작성하려 하는데, 전날로 convert 되어 오류가 발생하는 문제를 해결했습니다.
SYAAINN Oct 9, 2025
e5c09d4
[REFACTOR/#323] 에러대응(다이얼로그, 에러스크린)을 추가합니다.
SYAAINN Oct 9, 2025
6e447fc
[FEAT/#323] AirBridge 딥링크 연결을 위한 세팅입니다.
SYAAINN Oct 9, 2025
8463278
[CHORE/#323] KtlintFormat
SYAAINN Oct 9, 2025
a48077e
[CHORE/#323] 코드래빗 리뷰 사항 반영
SYAAINN Oct 9, 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
34 changes: 34 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,40 @@
android:scheme="${kakaoRedirectUri}" />
</intent-filter>
</activity>

<activity
android:name=".ClodyDeeplinkActivity"
android:exported="true">

<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:scheme="clody" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="http" android:host="clody.abr.ge" />
<data android:scheme="https" android:host="clody.abr.ge" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="http" android:host="clody.airbridge.io" />
<data android:scheme="https" android:host="clody.airbridge.io" />
</intent-filter>

</activity>

</application>

</manifest>
23 changes: 23 additions & 0 deletions app/src/main/java/com/sopt/clody/ClodyDeeplinkActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.sopt.clody

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import co.ab180.airbridge.Airbridge

class ClodyDeeplinkActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
Comment on lines +7 to +11
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 | 🟠 Major

Set up proper activity behavior for a deep link handler.

This activity serves as a deep link entry point but doesn't set a content view or configure launch mode. Deep link handler activities typically:

  • Don't need a UI (no setContentView)
  • Should not remain in the back stack (consider android:noHistory="true" in manifest)
  • Should finish immediately after processing the link

Consider adding finish() after handling the deep link to prevent an empty activity from remaining in the back stack:

 override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
+    // No content view needed for deep link handler
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/java/com/sopt/clody/ClodyDeeplinkActivity.kt around lines 7–11,
the activity currently does nothing and can remain as an empty entry on the back
stack; update it to act as a no-UI deep link handler by: (1) not calling
setContentView, (2) immediately reading and handling the incoming intent/data in
onCreate (e.g., parse intent.data or extras and route to the correct place), (3)
calling finish() after processing to close the activity, and (4) set
android:noHistory="true" for this activity in AndroidManifest.xml so it is not
kept in the back stack (optionally add a transition override if you want no
animation).


override fun onResume() {
super.onResume()

val isFirstCalled = Airbridge.handleDeferredDeeplink { uri ->
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unused variable: isFirstCalled is never referenced.

The isFirstCalled boolean indicates whether this is the first deferred deep link call after app install, which could be useful for analytics or conditional logic. Either use this variable or remove it to reduce code noise.

Apply this diff if the variable is not needed:

-        val isFirstCalled = Airbridge.handleDeferredDeeplink { uri ->
+        Airbridge.handleDeferredDeeplink { uri ->
             // when handleDeferredDeeplink is called firstly after install
             if (uri != null) {
                 // show proper content using uri (YOUR_SCHEME://...)
📝 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 isFirstCalled = Airbridge.handleDeferredDeeplink { uri ->
Airbridge.handleDeferredDeeplink { uri ->
// when handleDeferredDeeplink is called firstly after install
if (uri != null) {
// show proper content using uri (YOUR_SCHEME://...)
🤖 Prompt for AI Agents
In app/src/main/java/com/sopt/clody/ClodyDeeplinkActivity.kt around line 16, the
local boolean isFirstCalled returned from Airbridge.handleDeferredDeeplink is
assigned but never used; either remove the assignment to eliminate the unused
variable or use isFirstCalled where appropriate (e.g., pass it to analytics or
conditional logic). Update the code by deleting the "val isFirstCalled =" part
if you don't need the flag, or by referencing isFirstCalled in subsequent logic
to record or act on first-call behavior.

// when handleDeferredDeeplink is called firstly after install
if (uri != null) {
// show proper content using uri (YOUR_SCHEME://...)
}
}
Comment on lines +16 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Implement URI handling and finish the Activity.

The callback contains only a placeholder comment. Without actual URI processing and navigation logic, this Activity cannot fulfill its deep link handling purpose. Additionally, the Activity does not call finish(), which means it will remain in the back stack as an invisible screen after handling the deep link.

Consider implementing:

  1. Parse the URI and extract relevant parameters (e.g., screen destination, query params)
  2. Navigate to the appropriate screen using an Intent (e.g., MainActivity with extras)
  3. Call finish() to remove this Activity from the back stack

Example:

         Airbridge.handleDeferredDeeplink { uri ->
-            // when handleDeferredDeeplink is called firstly after install
             if (uri != null) {
-                // show proper content using uri (YOUR_SCHEME://...)
+                // Parse URI and navigate to the appropriate screen
+                val intent = Intent(this, MainActivity::class.java).apply {
+                    data = uri
+                    // Add any necessary extras based on URI
+                }
+                startActivity(intent)
+            } else {
+                // No deferred deep link, navigate to main screen
+                startActivity(Intent(this, MainActivity::class.java))
             }
+            finish()
         }
🤖 Prompt for AI Agents
In app/src/main/java/com/sopt/clody/ClodyDeeplinkActivity.kt around lines 16 to
21, the Airbridge.handleDeferredDeeplink callback only contains a placeholder
and never finishes the Activity; implement URI processing: check for uri !=
null, parse path and query parameters (e.g., destination and any ids), build an
Intent targeting the appropriate Activity (e.g., MainActivity) with the parsed
parameters added as extras, call startActivity(intent) and then finish() to
remove this Activity from the back stack; also handle the null case by either
directing to a default screen or simply calling finish().

}
Comment on lines +13 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Complete the deep link URI handling implementation.

The URI callback is incomplete with only a placeholder comment. This leaves the deep link functionality non-functional.

The current implementation:

  1. Doesn't handle the URI when received
  2. Doesn't navigate to any destination
  3. Doesn't call finish() to remove the activity from the stack

Apply this diff to add navigation and cleanup:

     override fun onResume() {
         super.onResume()
 
         val isFirstCalled = Airbridge.handleDeferredDeeplink { uri ->
             // when handleDeferredDeeplink is called firstly after install
             if (uri != null) {
-                // show proper content using uri (YOUR_SCHEME://...)
+                // TODO: Parse uri and navigate to appropriate screen
+                // Example: NavigationUtil.handleDeepLink(this, uri)
             }
+            // Finish this activity after handling the deep link
+            finish()
         }
+        
+        // If not first call, also finish immediately
+        if (!isFirstCalled) {
+            finish()
+        }
     }

Would you like me to help implement the URI parsing and navigation logic?

📝 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
override fun onResume() {
super.onResume()
val isFirstCalled = Airbridge.handleDeferredDeeplink { uri ->
// when handleDeferredDeeplink is called firstly after install
if (uri != null) {
// show proper content using uri (YOUR_SCHEME://...)
}
}
}
override fun onResume() {
super.onResume()
val isFirstCalled = Airbridge.handleDeferredDeeplink { uri ->
// when handleDeferredDeeplink is called firstly after install
if (uri != null) {
// TODO: Parse uri and navigate to appropriate screen
// Example: NavigationUtil.handleDeepLink(this, uri)
}
// Finish this activity after handling the deep link
finish()
}
// If not first call, also finish immediately
if (!isFirstCalled) {
finish()
}
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sopt.clody.data.remote.dto.response

import com.sopt.clody.domain.model.ReplyStatus
import com.sopt.clody.domain.type.ReplyStatus
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sopt.clody.data.remote.dto.response

import com.sopt.clody.domain.model.ReplyStatus
import com.sopt.clody.domain.type.ReplyStatus
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

API 변경 시 Domain 영향 최소화하려면 ReplyStatusDto - Mapper로 나중에 바꾸는 것도 좋아보입니다.
클린 아키텍처에 따르면 말이죠

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.sopt.clody.domain.model

import com.sopt.clody.domain.type.ReplyStatus
import java.time.LocalDate
import java.time.ZoneId

/**
* 홈 화면의 월별 달력에서 사용되는 정보
*
* @property totalCloverCount 지금까지 모은 클로버(답장)의 개수. 연 단위로 카운트
* @property calendarDailyInfoList 해당 월의 일별 정보
*
* */
data class CalendarMonthlyInfo(
val totalCloverCount: Int = 0,
val calendarDailyInfoList: List<CalendarDailyInfo> = listOf(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

불변성을 보장한다면 emptyList()로 바꾸는 게 좋습니다

) {
/**
* 홈 화면 월별 달력을 구성하는 일별 일기 정보
*
* @property diaryCount 해당 일에 작성한 일기의 개수
* @property replyStatus 해당 일에 작성한 일기의 답장 상태
* @property date 해당 일의 날짜, "2025-08-21" 형식
* @property isDeleted 해당 일에 작성한 일기가 삭제 이력의 여부
*
* */
data class CalendarDailyInfo(
val diaryCount: Int = 0,
val replyStatus: ReplyStatus = ReplyStatus.UNREADY,
val date: String = "",
val isDeleted: Boolean = false,
) {
fun isToday(): Boolean = date == LocalDate.now().toString()

fun enableWriteDiary(): Boolean {
val userTimeZone = ZoneId.systemDefault().id
val today = LocalDate.now().toString()
val yesterday = LocalDate.now().minusDays(1).toString()
val isAvailableDay = if (userTimeZone == "Asia/Seoul") {
date == today || date == yesterday
} else {
date == today
}
return diaryCount == 0 && isAvailableDay
}
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/com/sopt/clody/domain/model/DailyDiaryInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.sopt.clody.domain.model

/**
* 홈 화면에서 월별 달력 하단에 일별 일기 정보를 위한 데이터 클래스
*
* @property diaryList 해당 일에 작성한 일기의 내용
* @property isDraft 해당 일에 임시 저장 일기의 존재 여부
*
*/
data class DailyDiaryInfo(
val diaryList: List<String> = listOf(),
val isDraft: Boolean = false,
)
6 changes: 0 additions & 6 deletions app/src/main/java/com/sopt/clody/domain/model/ExampleModel.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sopt.clody.domain
package com.sopt.clody.domain.type

enum class Notification {
DIARY, DRAFT, REPLY
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sopt.clody.domain.model
package com.sopt.clody.domain.type

import kotlinx.serialization.Serializable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.airbnb.mvrx.hilt.AssistedViewModelFactory
import com.airbnb.mvrx.hilt.MavericksViewModelComponent
import com.airbnb.mvrx.hilt.ViewModelKey
import com.sopt.clody.presentation.ui.auth.signup.SignUpViewModel
import com.sopt.clody.presentation.ui.home.screen.HomeViewModel
import com.sopt.clody.presentation.ui.login.LoginViewModel
import com.sopt.clody.presentation.ui.splash.SplashViewModel
import dagger.Binds
Expand Down Expand Up @@ -35,4 +36,11 @@ interface ViewModelsModule {
fun bindSignUpViewModelFactory(
factory: SignUpViewModel.Factory,
): AssistedViewModelFactory<*, *>

@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class)
fun bindHomeViewModelFactory(
factory: HomeViewModel.Factory,
): AssistedViewModelFactory<*, *>
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
Expand All @@ -25,7 +26,6 @@ import androidx.compose.ui.unit.dp
import com.sopt.clody.R
import com.sopt.clody.presentation.ui.auth.component.checkbox.CustomCheckbox
import com.sopt.clody.presentation.ui.component.button.ClodyButton
import com.sopt.clody.presentation.ui.home.calendar.component.HorizontalDivider
import com.sopt.clody.presentation.utils.base.BasePreview
import com.sopt.clody.presentation.utils.base.ClodyPreview
import com.sopt.clody.presentation.utils.extension.heightForScreenPercentage
Expand Down Expand Up @@ -107,7 +107,11 @@ fun TermsOfServicePage(
)
}
Spacer(modifier = Modifier.height(18.dp))
HorizontalDivider(color = ClodyTheme.colors.gray07, thickness = 1.dp)
HorizontalDivider(
color = ClodyTheme.colors.gray07,
thickness = 1.dp,
modifier = Modifier.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(16.dp))
TermsCheckboxRow(
text = stringResource(R.string.terms_service_use),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -35,7 +36,6 @@ import com.sopt.clody.presentation.ui.component.LoadingScreen
import com.sopt.clody.presentation.ui.component.button.ClodyButton
import com.sopt.clody.presentation.ui.component.dialog.FailureDialog
import com.sopt.clody.presentation.ui.component.popup.ClodyPopupBottomSheet
import com.sopt.clody.presentation.ui.home.calendar.component.HorizontalDivider
import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints
import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils
import com.sopt.clody.presentation.utils.extension.TimePeriod
Expand Down Expand Up @@ -166,7 +166,11 @@ fun TimeReminderScreen(
modifier = Modifier.fillMaxWidth(),
onClick = { showBottomSheet = true },
)
HorizontalDivider(color = ClodyTheme.colors.gray07, thickness = 1.dp)
HorizontalDivider(
color = ClodyTheme.colors.gray07,
thickness = 1.dp,
modifier = Modifier.fillMaxWidth(),
)
}

if (showBottomSheet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.sopt.clody.R
import com.sopt.clody.data.remote.dto.response.MonthlyDiaryResponseDto
import com.sopt.clody.domain.model.ReplyStatus
import com.sopt.clody.domain.type.ReplyStatus
import com.sopt.clody.presentation.ui.diarylist.screen.DiaryListViewModel
import com.sopt.clody.ui.theme.ClodyTheme

Expand All @@ -47,7 +47,7 @@ fun DailyDiaryCard(
val iconRes = when {
dailyDiary.replyStatus == ReplyStatus.READY_NOT_READ && dailyDiary.diaryCount > 0 -> R.drawable.ic_home_ungiven_clover
dailyDiary.replyStatus == ReplyStatus.UNREADY && dailyDiary.diaryCount > 0 -> R.drawable.ic_home_ungiven_clover
dailyDiary.replyStatus == ReplyStatus.INVALID_DRAFT -> R.drawable.ic_home_expired_written_clover
dailyDiary.replyStatus == ReplyStatus.INVALID_DRAFT -> R.drawable.ic_home_disabled_reply_clover
dailyDiary.diaryCount == 0 -> R.drawable.ic_home_ungiven_clover
dailyDiary.diaryCount in 1..2 -> R.drawable.ic_home_bottom_clover
dailyDiary.diaryCount in 3..4 -> R.drawable.ic_home_mid_clover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.sopt.clody.data.remote.dto.response.MonthlyDiaryResponseDto
import com.sopt.clody.domain.model.ReplyStatus
import com.sopt.clody.domain.type.ReplyStatus
import com.sopt.clody.presentation.ui.diarylist.screen.DiaryListViewModel
import com.sopt.clody.presentation.utils.extension.getDayOfWeek

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.sopt.clody.domain.model.ReplyStatus
import com.sopt.clody.domain.type.ReplyStatus
import com.sopt.clody.presentation.ui.diarylist.screen.DiaryListRoute
import com.sopt.clody.presentation.utils.navigation.Route

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.sopt.clody.R
import com.sopt.clody.domain.model.ReplyStatus
import com.sopt.clody.domain.type.ReplyStatus
import com.sopt.clody.presentation.ui.component.FailureScreen
import com.sopt.clody.presentation.ui.component.LoadingScreen
import com.sopt.clody.presentation.ui.component.bottomsheet.DiaryDeleteSheet
Expand Down

This file was deleted.

Loading