diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 74dd639..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/PiggyBankApiService.kt b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/PiggyBankApiService.kt
index cdb77cf..1869db2 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/PiggyBankApiService.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/PiggyBankApiService.kt
@@ -3,6 +3,7 @@ package com.ssafy.tiggle.data.datasource.remote
import com.ssafy.tiggle.data.model.BaseResponse
import com.ssafy.tiggle.data.model.EmptyResponse
import com.ssafy.tiggle.data.model.piggybank.request.CreatePiggyBankRequestDto
+import com.ssafy.tiggle.data.model.piggybank.request.PiggyBankSettingRequestDto
import com.ssafy.tiggle.data.model.piggybank.request.PrimaryAccountRequestDto
import com.ssafy.tiggle.data.model.piggybank.request.SendSMSRequestDto
import com.ssafy.tiggle.data.model.piggybank.request.VerificationCheckRequestDto
@@ -10,12 +11,18 @@ import com.ssafy.tiggle.data.model.piggybank.request.VerificationRequestDto
import com.ssafy.tiggle.data.model.piggybank.request.VerifySMSRequestDto
import com.ssafy.tiggle.data.model.piggybank.response.AccountHolderResponseDto
import com.ssafy.tiggle.data.model.piggybank.response.CreatePiggyBankResponseDto
+import com.ssafy.tiggle.data.model.piggybank.response.MainAccountResponseDto
+import com.ssafy.tiggle.data.model.piggybank.response.PiggyBankAccountResponseDto
+import com.ssafy.tiggle.data.model.piggybank.response.PiggyBankSettingResponseDto
import com.ssafy.tiggle.data.model.piggybank.response.VerificationCheckResponseDto
import com.ssafy.tiggle.data.model.piggybank.response.VerifySMSResponseDto
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
+import retrofit2.http.PATCH
import retrofit2.http.POST
+import retrofit2.http.PUT
+import retrofit2.http.Path
import retrofit2.http.Query
interface PiggyBankApiService {
@@ -53,4 +60,20 @@ interface PiggyBankApiService {
suspend fun verifySMS(
@Body body: VerifySMSRequestDto
): BaseResponse
+
+ @GET("accounts/primary")
+ suspend fun getMainAccount(): BaseResponse
+
+ @GET("piggybank/summary")
+ suspend fun getPiggyBankAccount(): BaseResponse
+
+ @PATCH("piggybank/settings")
+ suspend fun setPiggyBankSetting(
+ @Body body: PiggyBankSettingRequestDto
+ ): BaseResponse
+
+ @PUT("piggybank/category/{categoryId}")
+ suspend fun setEsgCategory(
+ @Path("categoryId") categoryId: Int
+ ): BaseResponse
}
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/request/PiggyBankSettingRequestDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/request/PiggyBankSettingRequestDto.kt
new file mode 100644
index 0000000..20ef894
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/request/PiggyBankSettingRequestDto.kt
@@ -0,0 +1,9 @@
+package com.ssafy.tiggle.data.model.piggybank.request
+
+data class PiggyBankSettingRequestDto(
+ val name: String? = null,
+ val targetAmount: Long? = null,
+ val autoDonation: Boolean? = null,
+ val autoSaving: Boolean? = null,
+ val esgCategoryId: Int? = null,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/MainAccountResponseDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/MainAccountResponseDto.kt
new file mode 100644
index 0000000..ec4cb78
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/MainAccountResponseDto.kt
@@ -0,0 +1,17 @@
+package com.ssafy.tiggle.data.model.piggybank.response
+
+import com.ssafy.tiggle.domain.entity.piggybank.AccountHolder
+import com.ssafy.tiggle.domain.entity.piggybank.MainAccount
+
+data class MainAccountResponseDto(
+ val accountName:String="",
+ val accountNo:String="",
+ val balance:String=""
+)
+
+fun MainAccountResponseDto.toDomain(): MainAccount =
+ MainAccount(
+ accountName = accountName,
+ accountNo= accountNo,
+ balance=balance
+ )
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/PiggyBankAccountResponseDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/PiggyBankAccountResponseDto.kt
new file mode 100644
index 0000000..6dc7ff1
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/PiggyBankAccountResponseDto.kt
@@ -0,0 +1,16 @@
+package com.ssafy.tiggle.data.model.piggybank.response
+
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBankAccount
+
+data class PiggyBankAccountResponseDto (
+ val name:String="",
+ val currentAmount:Long=0L,
+ val lastWeekSavedAmount:Long=0L
+)
+
+fun PiggyBankAccountResponseDto.toDomain():PiggyBankAccount=
+ PiggyBankAccount(
+ name=name,
+ currentAmount=currentAmount,
+ lastWeekSavedAmount=lastWeekSavedAmount
+ )
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/PiggyBankSettingResponseDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/PiggyBankSettingResponseDto.kt
new file mode 100644
index 0000000..80eb140
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/piggybank/response/PiggyBankSettingResponseDto.kt
@@ -0,0 +1,46 @@
+package com.ssafy.tiggle.data.model.piggybank.response
+
+import com.ssafy.tiggle.domain.entity.piggybank.EsgCategory
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBank
+
+data class PiggyBankSettingResponseDto(
+ val id: Int,
+ val name: String,
+ val currentAmount: Long,
+ val targetAmount: Long,
+ val savingCount: Int,
+ val donationCount: Int,
+ val donationTotalAmount: Long,
+ val autoDonation: Boolean,
+ val autoSaving: Boolean,
+ val esgCategory: EsgCategoryDto
+)
+
+data class EsgCategoryDto(
+ val id: Int,
+ val name: String,
+ val description: String,
+ val characterName: String
+)
+
+fun PiggyBankSettingResponseDto.toDomain(): PiggyBank =
+ PiggyBank(
+ id = id,
+ name = name,
+ currentAmount = currentAmount,
+ targetAmount = targetAmount,
+ savingCount = savingCount,
+ donationCount = donationCount,
+ donationTotalAmount = donationTotalAmount,
+ autoDonation = autoDonation,
+ autoSaving = autoSaving,
+ esgCategory = esgCategory.toDomain()
+ )
+
+fun EsgCategoryDto.toDomain(): EsgCategory =
+ EsgCategory(
+ id = id,
+ name = name,
+ description = description,
+ characterName = characterName
+ )
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/repository/PiggyBankRepositoryImpl.kt b/app/src/main/java/com/ssafy/tiggle/data/repository/PiggyBankRepositoryImpl.kt
index 850ac65..915dcb1 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/repository/PiggyBankRepositoryImpl.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/repository/PiggyBankRepositoryImpl.kt
@@ -2,6 +2,7 @@ package com.ssafy.tiggle.data.repository
import com.ssafy.tiggle.data.datasource.remote.PiggyBankApiService
import com.ssafy.tiggle.data.model.piggybank.request.CreatePiggyBankRequestDto
+import com.ssafy.tiggle.data.model.piggybank.request.PiggyBankSettingRequestDto
import com.ssafy.tiggle.data.model.piggybank.request.PrimaryAccountRequestDto
import com.ssafy.tiggle.data.model.piggybank.request.SendSMSRequestDto
import com.ssafy.tiggle.data.model.piggybank.request.VerificationCheckRequestDto
@@ -10,6 +11,9 @@ import com.ssafy.tiggle.data.model.piggybank.request.VerifySMSRequestDto
import com.ssafy.tiggle.data.model.piggybank.response.VerifySMSResponseDto
import com.ssafy.tiggle.data.model.piggybank.response.toDomain
import com.ssafy.tiggle.domain.entity.piggybank.AccountHolder
+import com.ssafy.tiggle.domain.entity.piggybank.MainAccount
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBank
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBankAccount
import com.ssafy.tiggle.domain.repository.PiggyBankRepository
import javax.inject.Inject
import javax.inject.Singleton
@@ -147,4 +151,77 @@ class PiggyBankRepositoryImpl @Inject constructor(
}
}
+ override suspend fun getMainAccount(): Result {
+ return try {
+ val response = piggyBankApiService.getMainAccount()
+
+ if (response.result && response.data != null) {
+ Result.success(response.data.toDomain())
+ } else {
+ Result.failure(Exception(response.message ?: "주계좌 정보를 불러오기에 실패했습니다."))
+ }
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
+
+ override suspend fun getPiggyBankAccount(): Result {
+ return try {
+ val response = piggyBankApiService.getPiggyBankAccount()
+
+ if (response.result && response.data != null) {
+ Result.success(response.data.toDomain())
+ } else {
+ Result.failure(Exception(response.message ?: "저금통 계좌 정보를 불러오기에 실패했습니다."))
+ }
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
+
+ override suspend fun setPiggyBankSetting(
+ name: String?,
+ targetAmount: Long?,
+ autoDonation: Boolean?,
+ autoSaving: Boolean?,
+ esgCategory: Int?
+ ): Result {
+ return try {
+ val response = piggyBankApiService.setPiggyBankSetting(
+ PiggyBankSettingRequestDto(
+ name,
+ targetAmount,
+ autoDonation,
+ autoSaving,
+ esgCategory
+ )
+ )
+
+ if (response.result && response.data != null) {
+ Result.success(response.data.toDomain())
+ } else {
+ Result.failure(Exception(response.message ?: "저금통 설정에 실패했습니다."))
+ }
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+
+ }
+
+ override suspend fun setEsgCategory(categoryId: Int): Result {
+ return try {
+ val response = piggyBankApiService.setEsgCategory(
+ categoryId = categoryId
+ )
+
+ if (response.result && response.data != null) {
+ Result.success(response.data.toDomain())
+ } else {
+ Result.failure(Exception(response.message ?: "카테고리 설정에 실패했습니다."))
+ }
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/MainAccount.kt b/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/MainAccount.kt
new file mode 100644
index 0000000..100fd76
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/MainAccount.kt
@@ -0,0 +1,7 @@
+package com.ssafy.tiggle.domain.entity.piggybank
+
+data class MainAccount (
+ val accountName:String="",
+ val accountNo:String="",
+ val balance:String=""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/PiggyBank.kt b/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/PiggyBank.kt
new file mode 100644
index 0000000..fed055f
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/PiggyBank.kt
@@ -0,0 +1,21 @@
+package com.ssafy.tiggle.domain.entity.piggybank
+
+data class PiggyBank(
+ val id: Int = 0,
+ val name: String = "",
+ val currentAmount: Long = 0L,
+ val targetAmount: Long = 0L,
+ val savingCount: Int = 0,
+ val donationCount: Int = 0,
+ val donationTotalAmount: Long = 0L,
+ val autoDonation: Boolean = false,
+ val autoSaving: Boolean = false,
+ val esgCategory: EsgCategory? = null,
+)
+
+data class EsgCategory(
+ val id: Int = 0,
+ val name: String = "",
+ val description: String = "",
+ val characterName: String = ""
+)
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/PiggyBankAccount.kt b/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/PiggyBankAccount.kt
new file mode 100644
index 0000000..7990474
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/entity/piggybank/PiggyBankAccount.kt
@@ -0,0 +1,7 @@
+package com.ssafy.tiggle.domain.entity.piggybank
+
+data class PiggyBankAccount (
+ val name:String="",
+ val currentAmount:Long=0L,
+ val lastWeekSavedAmount:Long=0L
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/repository/PiggyBankRepository.kt b/app/src/main/java/com/ssafy/tiggle/domain/repository/PiggyBankRepository.kt
index fe33336..c5a4b15 100644
--- a/app/src/main/java/com/ssafy/tiggle/domain/repository/PiggyBankRepository.kt
+++ b/app/src/main/java/com/ssafy/tiggle/domain/repository/PiggyBankRepository.kt
@@ -2,6 +2,9 @@ package com.ssafy.tiggle.domain.repository
import com.ssafy.tiggle.data.model.piggybank.response.VerifySMSResponseDto
import com.ssafy.tiggle.domain.entity.piggybank.AccountHolder
+import com.ssafy.tiggle.domain.entity.piggybank.MainAccount
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBank
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBankAccount
interface PiggyBankRepository {
suspend fun getAccountHolder(accountNo: String): Result
@@ -14,5 +17,21 @@ interface PiggyBankRepository {
suspend fun createPiggyBank(name: String, targetAmount: Long, esgCategoryId: Int): Result
suspend fun sendSMS(phone: String, purpose: String): Result
- suspend fun verifySMS(phone: String, code: String, purpose: String): Result
+ suspend fun verifySMS(
+ phone: String,
+ code: String,
+ purpose: String
+ ): Result
+
+ suspend fun getMainAccount(): Result
+ suspend fun getPiggyBankAccount(): Result
+ suspend fun setPiggyBankSetting(
+ name: String?,
+ targetAmount: Long?,
+ autoDonation: Boolean?,
+ autoSaving: Boolean?,
+ esgCategory: Int?
+ ): Result
+
+ suspend fun setEsgCategory(categoryId: Int): Result
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/GetMainAccountUseCase.kt b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/GetMainAccountUseCase.kt
new file mode 100644
index 0000000..f93ff3d
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/GetMainAccountUseCase.kt
@@ -0,0 +1,14 @@
+package com.ssafy.tiggle.domain.usecase.piggybank
+
+import com.ssafy.tiggle.domain.entity.piggybank.AccountHolder
+import com.ssafy.tiggle.domain.entity.piggybank.MainAccount
+import com.ssafy.tiggle.domain.repository.PiggyBankRepository
+import javax.inject.Inject
+
+class GetMainAccountUseCase @Inject constructor(
+ private val repository: PiggyBankRepository
+) {
+ suspend operator fun invoke():Result {
+ return repository.getMainAccount()
+ }
+}
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/GetPiggyBankAccountUseCase.kt b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/GetPiggyBankAccountUseCase.kt
new file mode 100644
index 0000000..3dde78c
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/GetPiggyBankAccountUseCase.kt
@@ -0,0 +1,14 @@
+package com.ssafy.tiggle.domain.usecase.piggybank
+
+import com.ssafy.tiggle.domain.entity.piggybank.MainAccount
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBankAccount
+import com.ssafy.tiggle.domain.repository.PiggyBankRepository
+import javax.inject.Inject
+
+class GetPiggyBankAccountUseCase @Inject constructor(
+ private val repository: PiggyBankRepository
+) {
+ suspend operator fun invoke():Result {
+ return repository.getPiggyBankAccount()
+ }
+}
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/PiggyBankUseCases.kt b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/PiggyBankUseCases.kt
index aa9bc46..6324800 100644
--- a/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/PiggyBankUseCases.kt
+++ b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/PiggyBankUseCases.kt
@@ -9,5 +9,9 @@ data class PiggyBankUseCases @Inject constructor(
val registerPrimaryAccountUseCase: RegisterPrimaryAccountUseCase,
val createPiggyBankUseCase: CreatePiggyBankUseCase,
val sendSMSUseCase: SendSMSUseCase,
- val verifySMSUseCase: VerifySMSUseCase
+ val verifySMSUseCase: VerifySMSUseCase,
+ val getMainAccountUseCase: GetMainAccountUseCase,
+ val getPiggyBankAccountUseCase: GetPiggyBankAccountUseCase,
+ val setPiggyBankSettingUseCase: SetPiggyBankSettingUseCase,
+ val setEsgCategoryUseCase: SetEsgCategoryUseCase
)
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/SetEsgCategoryUseCase.kt b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/SetEsgCategoryUseCase.kt
new file mode 100644
index 0000000..c1dd322
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/SetEsgCategoryUseCase.kt
@@ -0,0 +1,13 @@
+package com.ssafy.tiggle.domain.usecase.piggybank
+
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBank
+import com.ssafy.tiggle.domain.repository.PiggyBankRepository
+import javax.inject.Inject
+
+class SetEsgCategoryUseCase @Inject constructor(
+ private val repository: PiggyBankRepository
+) {
+ suspend operator fun invoke(categoryId: Int): Result {
+ return repository.setEsgCategory(categoryId)
+ }
+}
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/SetPiggyBankSettingUseCase.kt b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/SetPiggyBankSettingUseCase.kt
new file mode 100644
index 0000000..1abb8ba
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/usecase/piggybank/SetPiggyBankSettingUseCase.kt
@@ -0,0 +1,25 @@
+package com.ssafy.tiggle.domain.usecase.piggybank
+
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBank
+import com.ssafy.tiggle.domain.repository.PiggyBankRepository
+import javax.inject.Inject
+
+class SetPiggyBankSettingUseCase @Inject constructor(
+ private val repository: PiggyBankRepository
+) {
+ suspend operator fun invoke(
+ name: String? = null,
+ targetAmount: Long? = null,
+ autoDonation: Boolean? = null,
+ autoSaving: Boolean? = null,
+ esgCategory: Int? = null
+ ): Result {
+ return repository.setPiggyBankSetting(
+ name,
+ targetAmount,
+ autoDonation,
+ autoSaving,
+ esgCategory
+ )
+ }
+}
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/EsgCategoryBottomSheet.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/EsgCategoryBottomSheet.kt
new file mode 100644
index 0000000..ba06a22
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/EsgCategoryBottomSheet.kt
@@ -0,0 +1,143 @@
+package com.ssafy.tiggle.presentation.ui.piggybank
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.ssafy.tiggle.R
+import com.ssafy.tiggle.presentation.ui.theme.AppTypography
+import com.ssafy.tiggle.presentation.ui.theme.TiggleBlue
+import com.ssafy.tiggle.presentation.ui.theme.TiggleGrayText
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun EsgCategoryBottomSheet(
+ show: Boolean,
+ selectedId: Int?,
+ onPick: (Int) -> Unit,
+ onConfirm: () -> Unit,
+ onDismiss: () -> Unit
+) {
+ if (!show) return
+
+ ModalBottomSheet(
+ onDismissRequest = onDismiss,
+ shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
+ containerColor = Color.White
+ ) {
+ Column(Modifier.padding(horizontal = 20.dp, vertical = 16.dp)) {
+ Text("자동 기부 분야 설정", style = AppTypography.headlineLarge, fontSize = 18.sp)
+ Spacer(Modifier.height(6.dp))
+ Text(
+ "자동 기부 설정 시 기부할 분야를 선택해주세요.",
+ style = AppTypography.bodySmall,
+ color = TiggleGrayText
+ )
+
+ Spacer(Modifier.height(16.dp))
+
+ // 간단한 3옵션 선택 (Planet / People / Prosperity)
+ Box(modifier = Modifier.align(Alignment.CenterHorizontally)) {
+ Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
+ EsgChoiceChip(
+ "Planet",
+ id = 1,
+ selected = selectedId == 1,
+ imageUrl = R.drawable.icon_planet,
+ onClick = { onPick(1) })
+ EsgChoiceChip(
+ "People",
+ id = 2,
+ selected = selectedId == 2,
+ imageUrl = R.drawable.icon_people,
+ onClick = { onPick(2) })
+ EsgChoiceChip(
+ "Prosperity",
+ id = 3,
+ selected = selectedId == 3,
+ imageUrl = R.drawable.icon_prosperity,
+ onClick = { onPick(3) })
+ }
+ }
+
+ Spacer(Modifier.height(20.dp))
+
+ Button(
+ onClick = onConfirm,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(48.dp),
+ shape = RoundedCornerShape(12.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = TiggleBlue,
+ contentColor = Color.White
+ ),
+ enabled = selectedId != null
+ ) { Text("확인", style = AppTypography.bodyLarge) }
+
+ Spacer(Modifier.height(12.dp))
+ }
+ }
+}
+
+@Composable
+private fun EsgChoiceChip(
+ label: String,
+ id: Int,
+ selected: Boolean,
+ imageUrl: Int,
+ onClick: () -> Unit
+) {
+ val border = if (selected) TiggleBlue else TiggleGrayText
+ val CHIP_WIDTH = 110.dp
+ val CHIP_HEIGHT = 100.dp
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .size(width = CHIP_WIDTH, height = CHIP_HEIGHT)
+ .clip(RoundedCornerShape(12.dp))
+ .background(Color.White)
+ .border(1.dp, border, RoundedCornerShape(12.dp))
+ .clickable(onClick = onClick)
+ ) {
+ Image(
+ painter = painterResource(id = imageUrl),
+ contentDescription = "아이콘",
+ modifier = Modifier.size(25.dp)
+ )
+ Text(
+ label,
+ color = TiggleGrayText,
+ style = AppTypography.bodyMedium,
+ fontSize = 15.sp,
+ textAlign = TextAlign.Center
+ )
+ }
+}
+
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankScreen.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankScreen.kt
index f3ab88c..70fd8ac 100644
--- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankScreen.kt
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankScreen.kt
@@ -6,7 +6,6 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -42,7 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.hilt.navigation.compose.hiltViewModel
import com.ssafy.tiggle.R
import com.ssafy.tiggle.core.utils.Formatter
import com.ssafy.tiggle.presentation.ui.components.TiggleScreenLayout
@@ -60,7 +59,7 @@ fun PiggyBankScreen(
onRegisterAccountClick: () -> Unit = {},
onStartDutchPayClick: () -> Unit = {},
onBackClick: () -> Unit = {},
- viewModel: PiggyBankViewModel = viewModel()
+ viewModel: PiggyBankViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
@@ -87,14 +86,12 @@ fun PiggyBankScreen(
fontSize = 13.sp,
style = AppTypography.bodySmall
)
- Spacer(Modifier.height(30.dp))
+ Spacer(Modifier.height(50.dp))
//계좌 존재 여부에 따라
if (uiState.hasPiggyBank) {
TodaySavingBanner(
- amount = uiState.todaySaving,
- lastWeek = uiState.lastWeekRemainder,
- rounded = uiState.lastWeekRounded
+ uiState = uiState
)
} else {
DottedActionCard(
@@ -106,10 +103,7 @@ fun PiggyBankScreen(
Spacer(Modifier.height(10.dp))
if (uiState.hasLinkedAccount) {
AccountCard(
- bank = uiState.accountBank.orEmpty(),
- name = uiState.accountName.orEmpty(),
- number = uiState.accountNumberMasked.orEmpty(),
- balance = uiState.accountBalance ?: 0
+ uiState = uiState
)
} else {
DottedActionCard(
@@ -126,14 +120,14 @@ fun PiggyBankScreen(
onStart = onStartDutchPayClick
)
}
- Spacer(Modifier.height(18.dp))
+ Spacer(Modifier.height(25.dp))
// 스위치 섹션
TiggleSwitchRow(
- title = "잔돈 자동 기부",
- subtitle = "매일 잔정에 1,000원 미만 잔돈을 자동으로 기부합니다",
- checked = uiState.changeLeftoverDonate,
- onCheckedChange = viewModel::setChangeLeftoverDonate
+ title = "저금통 자동 기부",
+ subtitle = "일정 금액의 티끌이 쌓이면 기부 단체에 자동으로 기부됩니다.",
+ checked = uiState.piggyBank.autoDonation,
+ onCheckedChange = viewModel::onToggleAutoDonation
)
HorizontalDivider(
@@ -144,12 +138,21 @@ fun PiggyBankScreen(
// 스위치 섹션 2
TiggleSwitchRow(
- title = "목표 달성 자동 기부",
- subtitle = "더치페이 할 때 남는 자투리 금액을 기부할 수 있습니다",
- checked = uiState.achieveGoalDonate,
- onCheckedChange = viewModel::setAchieveGoalDonate
+ title = "잔돈 자동 저금",
+ subtitle = "매일 자정에 1,000원 미만 잔돈을 자동으로 저금합니다.",
+ checked = uiState.piggyBank.autoSaving,
+ onCheckedChange = viewModel::onToggleAutoSaving
)
+ if (uiState.showEsgCategorySheet) {
+ EsgCategoryBottomSheet( // <- 네가 만든 컴포넌트 이름
+ show = uiState.showEsgCategorySheet,
+ selectedId = uiState.piggyBank.esgCategory?.id,
+ onPick = viewModel::onPickEsgCategory, // 카테고리 탭
+ onConfirm = viewModel::onConfirmAutoDonation, // 확인 버튼
+ onDismiss = viewModel::onDismissEsgSheet // 바깥 터치/뒤로
+ )
+ }
HorizontalDivider(
color = TiggleGray,
thickness = 0.5.dp,
@@ -245,11 +248,12 @@ private fun PlusIcon(color: Color) {
}
@Composable
-private fun TodaySavingBanner(amount: Int, lastWeek: Int, rounded: Int) {
+private fun TodaySavingBanner(uiState: PiggyBankState) {
val radius = 18.dp
Box(
modifier = Modifier
.fillMaxWidth()
+ .height(130.dp)
.clip(RoundedCornerShape(radius))
.background(
brush = Brush.horizontalGradient(
@@ -264,17 +268,23 @@ private fun TodaySavingBanner(amount: Int, lastWeek: Int, rounded: Int) {
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f)) {
- Text("오늘 모인 띠끌", color = Color(0xCCFFFFFF), style = AppTypography.bodySmall)
+ Text(
+ text = uiState.piggyBankAccount.name,
+ color = Color(0xCCFFFFFF),
+ style = AppTypography.bodySmall
+ )
Spacer(Modifier.height(6.dp))
Text(
- "${amount}원",
+ "${uiState.piggyBankAccount.currentAmount}원",
color = Color.White,
fontSize = 34.sp,
fontWeight = FontWeight.ExtraBold
)
- Spacer(Modifier.height(6.dp))
+ Spacer(Modifier.height(10.dp))
Text(
- "지난주 잔액 ${Formatter.formatCurrency(lastWeek.toLong())} → ${Formatter.formatCurrency(rounded.toLong())}",
+ "+ 지난주에 ${
+ Formatter.formatCurrency(uiState.piggyBankAccount.lastWeekSavedAmount)
+ }이 저금 됐어요",
color = Color(0xE6FFFFFF),
style = AppTypography.bodySmall
)
@@ -295,11 +305,12 @@ private fun TodaySavingBanner(amount: Int, lastWeek: Int, rounded: Int) {
}
@Composable
-private fun AccountCard(bank: String, name: String, number: String, balance: Int) {
+private fun AccountCard(uiState: PiggyBankState) {
val radius = 14.dp
Column(
modifier = Modifier
.fillMaxWidth()
+ .height(130.dp)
.clip(RoundedCornerShape(radius))
.background(Color.White)
.border(1.dp, Color(0x11000000), RoundedCornerShape(radius))
@@ -318,13 +329,17 @@ private fun AccountCard(bank: String, name: String, number: String, balance: Int
Spacer(Modifier.width(10.dp))
Column(Modifier.weight(1f)) {
Text(
- "$name",
+ "${uiState.mainAccount.accountName}",
color = Color.Black,
style = AppTypography.bodyLarge,
fontSize = 15.sp
)
Spacer(Modifier.height(2.dp))
- Text("$bank $number", color = TiggleGrayText, style = AppTypography.bodySmall)
+ Text(
+ "신한 ${uiState.mainAccount.accountNo}",
+ color = TiggleGrayText,
+ style = AppTypography.bodySmall
+ )
}
Box() {
Image(
@@ -335,10 +350,11 @@ private fun AccountCard(bank: String, name: String, number: String, balance: Int
}
}
Spacer(Modifier.height(16.dp))
- Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
+ Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.End) {
Text("잔액", color = TiggleGrayText, style = AppTypography.bodySmall)
+ Spacer(Modifier.height(5.dp))
Text(
- Formatter.formatCurrency(balance.toLong()),
+ "${uiState.mainAccount.balance}원",
color = Color.Black,
fontSize = 22.sp,
fontWeight = FontWeight.SemiBold
@@ -349,6 +365,7 @@ private fun AccountCard(bank: String, name: String, number: String, balance: Int
@Composable
private fun DutchButtonsRow(onStatus: () -> Unit, onStart: () -> Unit) {
+ Spacer(Modifier.height(20.dp))
Row(Modifier.fillMaxWidth()) {
OutlinedButton(
onClick = onStatus,
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankState.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankState.kt
index 1637153..1621d58 100644
--- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankState.kt
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankState.kt
@@ -1,23 +1,23 @@
package com.ssafy.tiggle.presentation.ui.piggybank
+import com.ssafy.tiggle.domain.entity.piggybank.MainAccount
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBank
+import com.ssafy.tiggle.domain.entity.piggybank.PiggyBankAccount
+
data class PiggyBankState(
- val isLoading: Boolean = false,
+ val piggyBankAccount: PiggyBankAccount = PiggyBankAccount(),
+ val mainAccount: MainAccount = MainAccount(),
+ val piggyBank: PiggyBank = PiggyBank(),
//계좌 여부
val hasPiggyBank: Boolean = false,
val hasLinkedAccount: Boolean = false,
- //배너/ 계좌 카드 데이터 (임시로 만들었음 수정 예정)
- val todaySaving: Int = 0, // 오늘 모인 띠끌
- val lastWeekRemainder: Int = 0, // 지난주 잔액 예: 15847
- val lastWeekRounded: Int = 0, // 지난주 목표 반올림 금액 예: 15000
- val accountBank: String? = null, // 예: "신한"
- val accountName: String? = null, // 예: "쏠편한 입출금통장(저축예금)"
- val accountNumberMasked: String? = null, // 예: "123-456-789000"
- val accountBalance: Int? = null, // 예: 100000
- // 토글
- val changeLeftoverDonate: Boolean = false,
- val achieveGoalDonate: Boolean = false,
+ //바텀 시트
+ val showEsgCategorySheet: Boolean = false,
+ val tempSelectedCategoryId: Int? = null, // 시트에서 임시 선택
+
+ val isLoading: Boolean = false,
// 전체 에러 메시지
- val generalError: String? = null
+ val errorMessage: String? = null
)
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankViewModel.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankViewModel.kt
index 5c9e393..efc8a22 100644
--- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankViewModel.kt
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/piggybank/PiggyBankViewModel.kt
@@ -1,44 +1,254 @@
package com.ssafy.tiggle.presentation.ui.piggybank
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.ssafy.tiggle.domain.entity.piggybank.EsgCategory
+import com.ssafy.tiggle.domain.usecase.piggybank.PiggyBankUseCases
+import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import retrofit2.HttpException
+import javax.inject.Inject
-class PiggyBankViewModel : ViewModel() {
- private val _uiState = MutableStateFlow(
- PiggyBankState(
- hasPiggyBank = true,
- hasLinkedAccount = true,
- todaySaving = 847,
- lastWeekRemainder = 15847,
- lastWeekRounded = 15000,
- accountBank = "신한",
- accountName = "쏠편한 입출금통장(저축예금)",
- accountNumberMasked = "123-456-789000",
- accountBalance = 100_000,
- changeLeftoverDonate = true,
- achieveGoalDonate = true
-
-
- )
- )
+@HiltViewModel
+class PiggyBankViewModel @Inject constructor(
+ val useCases: PiggyBankUseCases
+) : ViewModel() {
+ private val _uiState = MutableStateFlow(PiggyBankState())
val uiState: StateFlow = _uiState.asStateFlow()
- fun setChangeLeftoverDonate(value: Boolean) {
- _uiState.update { it.copy(changeLeftoverDonate = value) }
+ init {
+ setPiggyBankAccount()
+ setMainAccount()
+ loadPiggyBankSettings()
}
- fun setAchieveGoalDonate(value: Boolean) {
- _uiState.update { it.copy(achieveGoalDonate = value) }
+ //저금통 계좌 확인
+ fun setPiggyBankAccount() {
+
+ viewModelScope.launch {
+ _uiState.update { it.copy(isLoading = true, errorMessage = null) }
+
+ val result = useCases.getPiggyBankAccountUseCase()
+
+ result
+ .onSuccess { account ->
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ piggyBankAccount = account,
+ hasPiggyBank = true
+ )
+ }
+ }
+ .onFailure { e ->
+ val isNotFound = (e is HttpException && e.code() == 404)
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ hasPiggyBank = !isNotFound,
+ errorMessage = if (isNotFound) null
+ else e.message ?: "저금통 계좌 가져오기에 실패했습니다."
+ )
+ }
+ }
+ }
}
- fun setHasPiggyBank(v: Boolean) {
- _uiState.update { it.copy(hasPiggyBank = v) }
+ //주계좌 확인
+ fun setMainAccount() {
+
+ viewModelScope.launch {
+ _uiState.update { it.copy(isLoading = true, errorMessage = null) }
+
+ val result = useCases.getMainAccountUseCase()
+
+ result
+ .onSuccess { account ->
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ mainAccount = account,
+ hasLinkedAccount = true
+ )
+ }
+ }
+ .onFailure { e ->
+ val isNotFound = (e is HttpException && e.code() == 404)
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ hasLinkedAccount = !isNotFound,
+ errorMessage = if (isNotFound) null
+ else e.message ?: "주계좌 가져오기에 실패했습니다."
+ )
+ }
+ }
+ }
}
- fun setHasLinkedAccount(v: Boolean) {
- _uiState.update { it.copy(hasLinkedAccount = v) }
+
+ fun onToggleAutoDonation(checked: Boolean) {
+ if (checked) {
+ // UI만 켜고 시트 띄움 (서버 호출은 확인 시에)
+ _uiState.update {
+ it.copy(
+ piggyBank = it.piggyBank.copy(autoDonation = true),
+ showEsgCategorySheet = true,
+ errorMessage = null
+ )
+ }
+ } else {
+ // 바로 서버 반영 (카테고리는 그대로)
+ val before = _uiState.value.piggyBank
+ _uiState.update { it.copy(piggyBank = it.piggyBank.copy(autoDonation = false)) }
+
+ viewModelScope.launch {
+ val result = useCases.setPiggyBankSettingUseCase(
+ autoDonation = false
+ )
+ result.onSuccess { updated ->
+ _uiState.update { it.copy(piggyBank = updated) }
+ }.onFailure { e ->
+ // 롤백
+ _uiState.update {
+ it.copy(
+ piggyBank = before,
+ errorMessage = e.message ?: "자동 기부 해제 실패"
+ )
+ }
+ }
+ }
+ }
+ }
+
+ /** 바텀시트에서 카테고리 버튼 눌렀을 때: PiggyBank 안의 id만 바꿔둠(임시) */
+ fun onPickEsgCategory(categoryId: Int) {
+ _uiState.update {
+ val cur = it.piggyBank
+ it.copy(
+ piggyBank = cur.copy(
+ esgCategory = (cur.esgCategory ?: EsgCategory()).copy(id = categoryId)
+ )
+ )
+ }
}
+
+ fun onConfirmAutoDonation() {
+ val pb = _uiState.value.piggyBank
+ val categoryId = pb.esgCategory?.id
+ if (categoryId == null) {
+ _uiState.update { it.copy(errorMessage = "카테고리를 선택해주세요.") }
+ return
+ }
+
+ val before = pb
+ _uiState.update { it.copy(isLoading = true, errorMessage = null) }
+
+ viewModelScope.launch {
+ // 1) 카테고리 먼저 확정
+ val r1 = useCases.setEsgCategoryUseCase(categoryId)
+ r1.onFailure { e ->
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ // 토글 롤백
+ piggyBank = before.copy(autoDonation = false),
+ showEsgCategorySheet = false,
+ errorMessage = e.message ?: "카테고리 설정 실패"
+ )
+ }
+ return@launch
+ }
+
+ // 2) 자동 기부 ON (서버에 켜기)
+ val r2 = useCases.setPiggyBankSettingUseCase(autoDonation = true)
+ r2.onSuccess { updated ->
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ piggyBank = updated,
+ showEsgCategorySheet = false
+ )
+ }
+ }.onFailure { e ->
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ piggyBank = before.copy(autoDonation = false), // 실패 시 OFF로 되돌림
+ showEsgCategorySheet = false,
+ errorMessage = e.message ?: "자동 기부 설정 실패"
+ )
+ }
+ }
+ }
+ }
+
+ /** 바텀시트 닫기(취소): 서버 호출 없이 토글만 다시 꺼짐 */
+ fun onDismissEsgSheet() {
+ _uiState.update {
+ it.copy(
+ showEsgCategorySheet = false,
+ piggyBank = it.piggyBank.copy(autoDonation = false)
+ )
+ }
+ }
+
+ fun onToggleAutoSaving(checked: Boolean) {
+
+ val before = _uiState.value.piggyBank.autoSaving
+ // 낙관적 업데이트
+ _uiState.update { it.copy(piggyBank = it.piggyBank.copy(autoSaving = checked)) }
+
+ viewModelScope.launch {
+ val result = useCases.setPiggyBankSettingUseCase(autoSaving = checked)
+ result
+ .onSuccess { updated ->
+ _uiState.update { it.copy(piggyBank = updated) } // 서버 값으로 동기화
+ }
+ .onFailure { e ->
+ _uiState.update {
+ it.copy(
+ piggyBank = it.piggyBank.copy(autoSaving = before),
+ errorMessage = e.message ?: "자동 저금 설정 변경 실패"
+ )
+ }
+ }
+ }
+ }
+
+ fun loadPiggyBankSettings() {
+ viewModelScope.launch {
+ // 초기 진입 시 로딩 인디케이터를 꼭 보여주고 싶지 않다면 isLoading은 빼도 됨
+ _uiState.update { it.copy(isLoading = true, errorMessage = null) }
+
+ val result = useCases.setPiggyBankSettingUseCase() // ← 프로젝트에 맞는 이름으로 교체
+ result
+ .onSuccess { settings ->
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ piggyBank = settings,
+ hasPiggyBank = true
+ )
+ }
+ }
+ .onFailure { e ->
+ val isNotFound = (e is HttpException && e.code() == 404)
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ // 404면 설정이 아직 없다는 뜻 → 토글은 false로 유지, hasPiggyBank는 false로
+ hasPiggyBank = if (isNotFound) false else it.hasPiggyBank,
+ errorMessage = if (isNotFound) null
+ else e.message ?: "저금통 설정을 불러오지 못했습니다."
+ )
+ }
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/icon_people.png b/app/src/main/res/drawable/icon_people.png
new file mode 100644
index 0000000..742fc3c
Binary files /dev/null and b/app/src/main/res/drawable/icon_people.png differ
diff --git a/app/src/main/res/drawable/icon_planet.png b/app/src/main/res/drawable/icon_planet.png
new file mode 100644
index 0000000..9196bd2
Binary files /dev/null and b/app/src/main/res/drawable/icon_planet.png differ
diff --git a/app/src/main/res/drawable/icon_prosperity.png b/app/src/main/res/drawable/icon_prosperity.png
new file mode 100644
index 0000000..6709325
Binary files /dev/null and b/app/src/main/res/drawable/icon_prosperity.png differ