Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2589ba1
feat: 확인, 확인/취소 Custom Dialog 추가
PeraSite Oct 16, 2025
3ac0fa5
feat: cancellable 파라미터 추가
PeraSite Oct 16, 2025
dcaa269
refactor: Dialog 옵션 DSL 구조로 변경
PeraSite Oct 16, 2025
127de15
refactor: 강제 업데이트 Custom Dialog 적용
PeraSite Oct 16, 2025
a089334
feat: Destructive Dialog 추가
PeraSite Oct 16, 2025
1ffacc0
refactor: Destructive Flag로 변경
PeraSite Oct 16, 2025
ac58048
refactor: 로그아웃 Custom Dialog 적용
PeraSite Oct 16, 2025
6e3a35f
refactor: 내 리뷰 페이지에서 삭제 Custom Dialog 적용
PeraSite Oct 16, 2025
e5c58b7
feat: Dialog 객체를 반환하고 초기에 열리지 않을 수 있는 옵션 추가
PeraSite Oct 16, 2025
9f0ea55
refactor: 네트워크 연결 안되었을 때 Custom Dialog 적용
PeraSite Oct 16, 2025
b4ba1e2
feat: Custom Toast 관련 resource 추가
PeraSite Oct 16, 2025
623ebc2
feat: ToastUtil 추가
PeraSite Oct 16, 2025
4ffd262
feat: StringRes로 토스트 보낼 수 있게 수정
PeraSite Oct 16, 2025
3cfe7bc
feat: 토큰 관련 Custom Toast 적용
PeraSite Oct 16, 2025
0a5519f
feat: 로그인 토스트 수정
PeraSite Oct 16, 2025
800f0fa
feat: 메인 토스트 수정
PeraSite Oct 16, 2025
89c5f43
chore: ContextUtil 삭제, Fragment에 토스트 확장 추가
PeraSite Oct 16, 2025
19d383a
feat: 마이페이지 토스트 적용
PeraSite Oct 16, 2025
bd145e3
feat: 토스트 관련 string 이름 변경, 토큰 관련 토스트 적용
PeraSite Oct 16, 2025
e8c3c62
feat: 제휴지도 토스트 수정
PeraSite Oct 16, 2025
53e9335
chore: 토스트 코드만 수정
PeraSite Oct 16, 2025
93619ef
feat: 회원탈퇴 토스트 수정
PeraSite Oct 16, 2025
aa0a7df
Merge branch 'develop' into feat/tf-dialog
PeraSite Nov 24, 2025
f08a036
fix: 환영 메시지 삭제
PeraSite Nov 24, 2025
688e65e
refactor: 기존 Toast 호출 코드 개선
PeraSite Nov 24, 2025
d3e40ce
Merge branch 'develop' into feat/tf-dialog
PeraSite Dec 12, 2025
1a8e91f
fix: ToastType 분리
PeraSite Dec 12, 2025
8c19d55
fix: 모든 Toast 호출 정리
PeraSite Dec 12, 2025
b2b895a
feat: onDismiss handler 추가
PeraSite Dec 12, 2025
ef3e155
refactor: 기존 AlertDialog 개선
PeraSite Dec 12, 2025
2d9ea2e
fix: 토스트 디자인 줄바꿈 개선
PeraSite Dec 12, 2025
7d71f39
chore: dismiss 추가
PeraSite Dec 14, 2025
9c5fff6
refactor: DialogBuilder의 기본 버튼 텍스트 값이 Context에서 가져오도록 수정
PeraSite Dec 14, 2025
a0ba703
chore: 리뷰 삭제 완료 toast를 Succes로 변경
PeraSite Dec 14, 2025
54132e6
chore: unused import 삭제
PeraSite Dec 14, 2025
ce369a1
fix: Toast가 화면 가로 길이 채우게 수정
PeraSite Dec 22, 2025
8b8767a
refactor: 중복된 confirm 설정 삭제
PeraSite Dec 23, 2025
ca02ee5
refactor: 하드코딩 토스트 메시지 수정
PeraSite Dec 23, 2025
18da734
refactor: 제미니 수정 사항 반영
PeraSite Dec 23, 2025
cd8031a
refactor: xml id 컨벤션
PeraSite Dec 23, 2025
39c5dc6
refactor: Layout Binding 적용
PeraSite Dec 23, 2025
3a1a60a
refactor: core 모듈로 Toast 관련 리소스 이동
PeraSite Dec 23, 2025
8d2c5a0
refactor: 토스트 아이콘 벡터로 변환
PeraSite Dec 23, 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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.eatssu.android.presentation.base.BaseActivity
import com.eatssu.android.presentation.login.LoginActivity
import com.eatssu.android.presentation.mypage.MyPageViewModel
import com.eatssu.android.presentation.mypage.userinfo.UserInfoActivity
import com.eatssu.android.presentation.util.showInfoToast
import com.eatssu.android.presentation.util.showToast
import com.eatssu.android.presentation.util.startActivity
import com.eatssu.common.UiEvent
Expand Down Expand Up @@ -114,11 +115,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>(
if (requestCode == 1000) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 권한이 승인됨
showToast("EAT-SSU 알림 수신을 동의하였습니다.")
showInfoToast("EAT-SSU 알림 수신을 동의하였습니다.")
myPageViewModel.setNotificationOn() //바로 알림 받도록 설정
} else {
// 권한이 거부됨
showToast("EAT-SSU 알림 수신을 거부하였습니다.\n$dateFormat")
showInfoToast("EAT-SSU 알림 수신을 거부하였습니다.\n$dateFormat")
myPageViewModel.setNotificationOff() //바로 알림 받도록 설정
}
}
Expand Down Expand Up @@ -171,8 +172,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(
private fun collectUiEvents() {
lifecycleScope.launch {
mainViewModel.uiEvent.collectLatest { event ->
if (event is UiEvent.ShowToast) {
showToast(event.message)
when (event) {
is UiEvent.ShowToast -> showToast(event)
}
}
}
Expand Down
24 changes: 13 additions & 11 deletions app/src/main/java/com/eatssu/android/presentation/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.eatssu.android.domain.usecase.user.GetUserNickNameUseCase
import com.eatssu.android.domain.usecase.user.SetUserCollegeDepartmentUseCase
import com.eatssu.common.UiEvent
import com.eatssu.common.UiState
import com.eatssu.common.enums.ToastType
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableSharedFlow
Expand Down Expand Up @@ -67,28 +68,24 @@ class MainViewModel @Inject constructor(
// 1) 닉네임 없음
if (nickname.isBlank()) {
_uiState.value = UiState.Success(MainState.NicknameNull)
_uiEvent.emit(UiEvent.ShowToast(context.getString(R.string.set_nickname)))
_uiEvent.emit(UiEvent.ShowToast(context.getString(R.string.set_nickname), ToastType.ERROR))
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Using ToastType.ERROR for a missing nickname seems too severe. A missing nickname is more of a required input prompt rather than an error condition. Consider using ToastType.INFO or ToastType.WARNING instead, as this is guiding the user to complete their profile rather than indicating a system error.

Suggested change
_uiEvent.emit(UiEvent.ShowToast(context.getString(R.string.set_nickname), ToastType.ERROR))
_uiEvent.emit(UiEvent.ShowToast(context.getString(R.string.set_nickname), ToastType.INFO))

Copilot uses AI. Check for mistakes.
return@launch
}

// 2) 정상 닉네임
_uiState.value = UiState.Success(MainState.NicknameExists(nickname))
_uiEvent.emit(
UiEvent.ShowToast(
String.format(
context.getString(R.string.hello_user),
nickname
)
)
)
}
}

fun logOut() {
viewModelScope.launch {
logoutUseCase()
_uiState.value = UiState.Success(MainState.LoggedOut)
_uiEvent.emit(UiEvent.ShowToast("로그아웃 되었습니다."))
_uiEvent.emit(
UiEvent.ShowToast(
context.getString(R.string.toast_logout_success), ToastType.SUCCESS
)
)
}
}

Expand Down Expand Up @@ -121,7 +118,12 @@ class MainViewModel @Inject constructor(
viewModelScope.launch {
val (college, department) = userRepository.getUserCollegeDepartment() ?: run {
_uiState.value = UiState.Error
_uiEvent.emit(UiEvent.ShowToast("정보를 불러올 수 없습니다."))
_uiEvent.emit(
UiEvent.ShowToast(
context.getString(R.string.not_found),
ToastType.ERROR
)
)
return@launch
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
Expand All @@ -21,6 +20,7 @@ import com.eatssu.android.R
import com.eatssu.android.presentation.common.NetworkConnection
import com.eatssu.android.presentation.login.LoginActivity
import com.eatssu.android.presentation.util.observeNetworkError
import com.eatssu.android.presentation.util.showInfoToast
import com.eatssu.common.EventLogger
import com.eatssu.common.enums.ScreenId
import com.google.android.material.card.MaterialCardView
Expand Down Expand Up @@ -107,17 +107,14 @@ abstract class BaseActivity<B : ViewBinding>(
private fun observeTokenExpiration() {
lifecycleScope.launch {
TokenEventBus.tokenExpired.collect {
Toast.makeText(this@BaseActivity,
getString(R.string.token_expired), Toast.LENGTH_SHORT).show()
showInfoToast(R.string.toast_token_expired)
navigateToLogin()
}
}

lifecycleScope.launch {
TokenEventBus.tokenServerError.collect {
Toast.makeText(this@BaseActivity,
getString(R.string.token_server_error), Toast.LENGTH_SHORT)
.show()
showInfoToast(R.string.toast_token_server_error)
navigateToLogin()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ fun ReviewListScreen(

when (uiEvent) {
is UiEvent.ShowToast -> {
context.showToast((uiEvent as UiEvent.ShowToast).message)
context.showToast(uiEvent as UiEvent.ShowToast)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.eatssu.android.domain.usecase.review.GetReviewListUseCase
import com.eatssu.common.UiEvent
import com.eatssu.common.UiState
import com.eatssu.common.enums.MenuType
import com.eatssu.common.enums.ToastType
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -53,7 +54,7 @@ class ReviewListViewModel @Inject constructor(
_uiState.value = UiState.Success(ReviewListState(reviewInfo, reviewList))
} catch (e: Exception) {
_uiState.value = UiState.Error
_uiEvent.emit(UiEvent.ShowToast("리뷰를 불러오지 못했습니다."))
_uiEvent.emit(UiEvent.ShowToast("리뷰를 불러오지 못했습니다.", ToastType.ERROR))
}
}

Expand All @@ -63,12 +64,12 @@ class ReviewListViewModel @Inject constructor(
val success = deleteReviewUseCase(reviewId)

if (!success) {
_uiEvent.emit(UiEvent.ShowToast("리뷰 삭제에 실패했습니다."))
_uiEvent.emit(UiEvent.ShowToast("리뷰 삭제에 실패했습니다.", ToastType.ERROR))
Copy link
Member

Choose a reason for hiding this comment

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

지금 드는 생각인데 하드코딩이 아니라 리소스에서 string을 한번에 관리하려면 뷰모델에서는 리소스 접근이 불가하니 event 유형만 넘겨주고 뷰에서 string 호출을 해야하지 않을까..? 싶네영

근데 그 방법이 좋을지는 몰르겠어요...

Copy link
Member Author

Choose a reason for hiding this comment

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

https://medium.com/@muhammetemingundogar53/accessing-string-resource-file-in-viewmodel-65bf2ad8b39
이런 예시처럼 ViewModel에서는 R 문자열 ID만 지정해서 emit하고, View 단에서 context로 해당 ID를 resolve하는 방식이 있더라구요!

다만 궁극적으로 ViewModel이 R 자체를 몰라도 되게끔 I18nProvider 같은 외부 서비스가 enum/String을 집어넣으면 알아서 현재 context에서 resolve해주는 형태로 가야할 것 같아요!

말씀해주신 부분은 이벤트 유형 케이스가 너무..많아서 유지보수가 어려울 것 같네용

Copy link
Member

Choose a reason for hiding this comment

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

뷰모델에서 한테 context 주고 리소스 id를 호출하는게 최선일 것 같슴다

return@launch
}

// 삭제 성공 시
_uiEvent.emit(UiEvent.ShowToast("리뷰를 삭제했습니다."))
_uiEvent.emit(UiEvent.ShowToast("리뷰를 삭제했습니다.", ToastType.SUCCESS))
val type = lastMenuType
val id = lastItemId
if (type != null && id != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ fun ModifyReviewScreen(
when (event) {
is UiEvent.NavigateBack -> onBack()
is UiEvent.ShowToast -> {
context.showToast(event.message)
context.showToast(event)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.eatssu.android.domain.model.Review
import com.eatssu.android.domain.usecase.review.ModifyReviewUseCase
import com.eatssu.common.UiEvent
import com.eatssu.common.UiState
import com.eatssu.common.enums.ToastType
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -65,11 +66,11 @@ class ModifyViewModel @Inject constructor(
)
if (!success) {
_uiState.value = UiState.Success(editing)
_uiEvent.emit(UiEvent.ShowToast("리뷰 수정이 실패했습니다."))
_uiEvent.emit(UiEvent.ShowToast("리뷰 수정이 실패했습니다.", ToastType.ERROR))
}

_uiEvent.emit(UiEvent.NavigateBack)
_uiEvent.emit(UiEvent.ShowToast("리뷰를 수정했습니다."))
_uiEvent.emit(UiEvent.ShowToast("리뷰를 수정했습니다.", ToastType.SUCCESS))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.eatssu.android.presentation.base.BaseActivity
import com.eatssu.android.presentation.util.showToast
import com.eatssu.common.enums.ReportType
import com.eatssu.common.enums.ScreenId
import com.eatssu.common.enums.ToastType
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -68,7 +69,10 @@ class ReportActivity : BaseActivity<ActivityReportBinding>(

lifecycleScope.launch {
reportViewModel.uiState.collectLatest {
showToast(it.toastMessage)
showToast(
it.toastMessage,
if (it.isDone) ToastType.SUCCESS else ToastType.ERROR
)
if (it.isDone) {
finish()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fun WriteReviewScreen(
when (event) {
is UiEvent.NavigateBack -> onBack()
is UiEvent.ShowToast -> {
context.showToast(event.message)
context.showToast(event)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.eatssu.android.domain.usecase.review.WriteReviewUseCase
import com.eatssu.common.UiEvent
import com.eatssu.common.UiState
import com.eatssu.common.enums.MenuType
import com.eatssu.common.enums.ToastType
import dagger.hilt.android.lifecycle.HiltViewModel
import id.zelory.compressor.Compressor
import kotlinx.coroutines.flow.MutableSharedFlow
Expand Down Expand Up @@ -111,24 +112,24 @@ class WriteReviewViewModel @Inject constructor(
val compressedFile = compressImage(context, originalFile)
if (compressedFile != null && compressedFile.exists()) {
imageUrl = getImageUrlUseCase(compressedFile)
_uiEvent.emit(UiEvent.ShowToast("이미지가 업로드되었습니다."))
_uiEvent.emit(UiEvent.ShowToast("이미지가 업로드되었습니다.", ToastType.SUCCESS))

// 원본 파일 삭제 (압축된 파일만 유지)
originalFile.delete()
} else {
_uiState.value = UiState.Success(editing) // 되돌림
_uiEvent.emit(UiEvent.ShowToast("이미지 압축에 실패하였습니다."))
_uiEvent.emit(UiEvent.ShowToast("이미지 압축에 실패하였습니다.", ToastType.ERROR))
return@launch
}
} else {
_uiState.value = UiState.Success(editing) // 되돌림
_uiEvent.emit(UiEvent.ShowToast("이미지 파일을 찾을 수 없습니다."))
_uiEvent.emit(UiEvent.ShowToast("이미지 파일을 찾을 수 없습니다.", ToastType.ERROR))
return@launch
}
} catch (e: Exception) {
Timber.e(e, "이미지 업로드 실패")
_uiState.value = UiState.Success(editing) // 되돌림
_uiEvent.emit(UiEvent.ShowToast("이미지 업로드에 실패하였습니다."))
_uiEvent.emit(UiEvent.ShowToast("이미지 업로드에 실패하였습니다.", ToastType.ERROR))
return@launch
}
}
Expand All @@ -145,11 +146,11 @@ class WriteReviewViewModel @Inject constructor(

if (!success) {
_uiState.value = UiState.Success(editing) // 되돌림
_uiEvent.emit(UiEvent.ShowToast("리뷰 작성에 실패하였습니다."))
_uiEvent.emit(UiEvent.ShowToast("리뷰 작성에 실패하였습니다.", ToastType.ERROR))
return@launch
}

_uiEvent.emit(UiEvent.ShowToast("리뷰가 작성되었습니다."))
_uiEvent.emit(UiEvent.ShowToast("리뷰가 작성되었습니다.", ToastType.SUCCESS))
_uiEvent.emit(UiEvent.NavigateBack)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.eatssu.android.presentation.common


import android.app.AlertDialog
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

import com.eatssu.android.presentation.util.showDialog

class AndroidMessageDialogActivity : AppCompatActivity() {

Expand All @@ -15,23 +13,20 @@ class AndroidMessageDialogActivity : AppCompatActivity() {
}

private fun showDialog() {
val builder = AlertDialog.Builder(this)

builder.setTitle("공지")
val message = intent.getStringExtra("message")
Log.d("message",message.toString())
builder.setMessage(intent.getStringExtra("message"))

builder.setPositiveButton("확인") { dialog, which ->
// Google Play Store의 앱 페이지로 이동하여 업데이트를 다운로드합니다.

// 다이얼로그를 종료합니다.
finish()
Log.d("message", message.toString())

showDialog(
title = "공지",
description = message ?: ""
) {
confirmText = "확인"
showCancelButton = false
cancellable = false
onConfirm { dialog ->
dialog.dismiss()
finish()
}
}

builder.setCancelable(false) // 사용자가 다이얼로그를 취소할 수 없도록 설정

val dialog = builder.create()
dialog.show()
}
}
Loading