diff --git a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt index 1f6eed6..7111fb4 100644 --- a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt +++ b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt @@ -90,6 +90,7 @@ import com.whyranoid.presentation.screens.mypage.editprofile.EditProfileViewMode import com.whyranoid.presentation.screens.mypage.following.FollowingViewModel import com.whyranoid.presentation.screens.setting.SettingViewModel import com.whyranoid.presentation.viewmodel.AddPostViewModel +import com.whyranoid.presentation.util.ApiResponseDialog import com.whyranoid.presentation.viewmodel.CommunityScreenViewModel import com.whyranoid.presentation.viewmodel.RunningEditViewModel import com.whyranoid.presentation.viewmodel.RunningViewModel @@ -104,6 +105,7 @@ import com.whyranoid.presentation.viewmodel.challenge.ChallengeDetailViewModel import com.whyranoid.presentation.viewmodel.challenge.ChallengeExitViewModel import com.whyranoid.presentation.viewmodel.challenge.ChallengeMainViewModel import com.whyranoid.walkie.walkiedialog.DialogViewModel +import com.whyranoid.walkie.walkiedialog.NetworkInterceptor import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response @@ -120,7 +122,19 @@ val viewModelModule = viewModel { ChallengeMainViewModel(get(), get(), get(), get(), get()) } viewModel { ChallengeDetailViewModel(get(), get()) } viewModel { ChallengeExitViewModel(get(), get()) } - viewModel { UserPageViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get()) } + viewModel { + UserPageViewModel( + get(), + get(), + get(), + get(), + get(), + get(), + get(), + get(), + get() + ) + } viewModel { RunningViewModel(get(), get(), get(), get(), get(), get()) } viewModel { RunningEditViewModel() } viewModel { SplashViewModel(get()) } @@ -200,7 +214,7 @@ val useCaseModule = single { GetMyFollowingUseCase(get(), get()) } single { SendCommentUseCase(get(), get()) } single { GetUserUseCase(get()) } - single { ChangeChallengeStatusUseCase(get(), get())} + single { ChangeChallengeStatusUseCase(get(), get()) } single { GetUserPostsUseCase(get(), get()) } } @@ -242,6 +256,17 @@ val networkModule = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }, + ).addInterceptor( + NetworkInterceptor( + onRequest = { ApiResponseDialog.startLoading() }, + onResponse = { ApiResponseDialog.finishLoad(it) }, + excludedUrls = listOf( + Regex("/api/follow/(\\d+)/following"), // polling 방식 업데이트 + Regex("/api/follow/(\\d+)/walking-followings"), // polling 방식 업데이트 + Regex("/api/community/listup-post"), // 커뮤니티 탭, 자체 로딩바 있음 + Regex("/api/community/upload-post") // 게시글 업로드, 자체 로딩바 있음 + ) + ) ) .build() } diff --git a/app/src/main/java/com/whyranoid/walkie/walkiedialog/NetworkInterceptor.kt b/app/src/main/java/com/whyranoid/walkie/walkiedialog/NetworkInterceptor.kt new file mode 100644 index 0000000..a410201 --- /dev/null +++ b/app/src/main/java/com/whyranoid/walkie/walkiedialog/NetworkInterceptor.kt @@ -0,0 +1,38 @@ +package com.whyranoid.walkie.walkiedialog + +import android.util.Log +import okhttp3.Interceptor +import okhttp3.Response + +class NetworkInterceptor( + private val onRequest: () -> Unit, + private val onResponse: (isSuccessFul: Boolean) -> Unit, + private val excludedUrls: List, +) : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + Log.d("ju0828", request.url.toString()) + + Log.d("ju0828", "${request.url} validate = ${excludedUrls.all { request.url.toString().contains(it).not() }}") + + // 요청 URL이 제외할 URL과 같지 않으면 콜백 호출 + if (excludedUrls.all { request.url.toString().contains(it).not() }) { + onRequest() // 요청 전 콜백 호출 + Log.d("ju0828", "${request.url} request called") + + } + + return try { + val response = chain.proceed(request) + + // 응답 후 콜백 호출 + if (excludedUrls.all { request.url.toString().contains(it).not() }) { + onResponse(response.isSuccessful) // 응답 후 콜백 호출 + } + response + } catch (e: Exception) { + onResponse(false) + throw e + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/whyranoid/presentation/reusable/CircleProgressWithText.kt b/presentation/src/main/java/com/whyranoid/presentation/reusable/CircleProgressWithText.kt index e90bbb4..d57904e 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/reusable/CircleProgressWithText.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/reusable/CircleProgressWithText.kt @@ -1,6 +1,7 @@ package com.whyranoid.presentation.reusable import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -17,6 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.whyranoid.presentation.theme.WalkieColor @@ -28,16 +30,31 @@ fun CircleProgressWithText( modifier: Modifier = Modifier, text: String, ) { - Box(modifier = modifier.fillMaxSize().alpha(0.5f).background(WalkieColor.GrayDefault)) + Box( + modifier = modifier + .fillMaxSize() + .alpha(0.5f) + .background(WalkieColor.GrayDefault) + .pointerInput(Unit) { // 터치 이벤트 소비 + detectTapGestures(onPress = { + // 아무것도 하지 않음, 터치 이벤트 소비 + }) + }) Box(modifier = Modifier.fillMaxSize()) { Column( - modifier = modifier.width(200.dp).height(160.dp).align(Alignment.Center) - .clip(RoundedCornerShape(20.dp)).background(Color.White), + modifier = modifier + .width(200.dp) + .height(160.dp) + .align(Alignment.Center) + .clip(RoundedCornerShape(20.dp)) + .background(Color.White), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text(text, style = WalkieTypography.SubTitle) - Spacer(modifier = Modifier.height(20.dp)) + if (text.isNotEmpty()) { + Text(text, style = WalkieTypography.SubTitle) + Spacer(modifier = Modifier.height(20.dp)) + } CircularProgressIndicator() } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt index e40e834..1e73ace 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigationItem import androidx.compose.material.Icon @@ -13,6 +14,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource @@ -27,6 +29,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.whyranoid.domain.model.post.Post +import com.whyranoid.presentation.reusable.CircleProgressWithText import com.whyranoid.presentation.screens.Screen.Companion.bottomNavigationItems import com.whyranoid.presentation.screens.challenge.ChallengeCompleteScreen import com.whyranoid.presentation.screens.challenge.ChallengeDetailScreen @@ -47,6 +50,8 @@ import com.whyranoid.presentation.screens.signin.SignInScreen import com.whyranoid.presentation.screens.splash.SplashScreen import com.whyranoid.presentation.theme.WalkieColor import com.whyranoid.presentation.theme.WalkieTypography +import com.whyranoid.presentation.util.CustomDialog +import com.whyranoid.presentation.util.ApiResponseDialog import com.whyranoid.presentation.viewmodel.SplashState import com.whyranoid.presentation.viewmodel.SplashViewModel import org.koin.androidx.compose.koinViewModel @@ -249,5 +254,18 @@ fun AppScreenContent( ) } } + + val isLoading = ApiResponseDialog.isLoading.collectAsStateWithLifecycle() + val isError = ApiResponseDialog.isShowError.collectAsStateWithLifecycle() + if (isLoading.value) { + CircleProgressWithText(text = "") + } else if (isError.value) { + CustomDialog( + title = "네트워크 연결 실패", + description = "네트워크 연결이 끊겼거나 속도가 느립니다.\n다시 시도해주세요.", + onAction = { ApiResponseDialog.closeErrorDialog() }, + Modifier.clip(RoundedCornerShape(20.dp)) + ) + } } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/MyPageScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/MyPageScreen.kt index 8b7c5f0..6c8af2a 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/MyPageScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/MyPageScreen.kt @@ -75,7 +75,9 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel import org.orbitmvi.orbit.compose.collectAsState +import java.time.Instant import java.time.LocalDate +import java.time.ZoneId @Composable fun MyPageScreen( @@ -165,7 +167,7 @@ fun UserPageContent( nickname: String? = null, // 상대방 페이지인 경우에 존재, 마이페이지일 경우 null state: UserPageState, onTotalBadgePageClicked: () -> Unit = {}, - onPostPreviewClicked: (uid: Long, postId: Long) -> Unit = { _, _ ->}, + onPostPreviewClicked: (uid: Long, postId: Long) -> Unit = { _, _ -> }, onPostCreateClicked: () -> Unit = {}, onProfileEditClicked: () -> Unit = {}, onSettingsClicked: () -> Unit = {}, @@ -406,7 +408,13 @@ fun UserPageContent( Column( modifier = Modifier.verticalScroll(rememberScrollState()) ) { - HistoryPage(onDayClicked = onDateClicked) + HistoryPage( + runningHistories = state.userPostPreviewsState.getDataOrNull() + ?.map { + Instant.ofEpochMilli(it.date).atZone(ZoneId.systemDefault()) + .toLocalDate() + }, onDayClicked = onDateClicked + ) state.calendarPreviewsState.getDataOrNull() ?.let { postPreviews -> postPreviews.forEach { postPreview -> diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/addpost/SelectHistoryScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/addpost/SelectHistoryScreen.kt index 314d2c9..c38dc2a 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/addpost/SelectHistoryScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/addpost/SelectHistoryScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxSize 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.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.grid.GridCells @@ -23,8 +24,12 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowLeft +import androidx.compose.material.icons.filled.KeyboardArrowRight import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults.buttonColors +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -61,6 +66,7 @@ fun SelectHistoryScreen( val viewModel = koinViewModel() val historyListState = viewModel.historyList.collectAsStateWithLifecycle() val selectedState = viewModel.selectedHistory.collectAsStateWithLifecycle() + val allRunningHistory = viewModel.allRunningHistory.collectAsStateWithLifecycle() val calendarState = rememberSelectableCalendarState( initialMonth = YearMonth.now(), @@ -71,6 +77,7 @@ fun SelectHistoryScreen( LaunchedEffect(calendarState.selectionState.selection.first()) { val date = calendarState.selectionState.selection.first() viewModel.getHistoryList(date.year, date.month.value, date.dayOfMonth) + viewModel.getAllRunningHistory() } Column( @@ -83,77 +90,115 @@ fun SelectHistoryScreen( Text( style = WalkieTypography.Title, text = "러닝 기록", - modifier = Modifier.align(Alignment.Center).padding(bottom = 24.dp), + modifier = Modifier + .align(Alignment.Center) + .padding(bottom = 24.dp), ) } SelectableCalendar( calendarState = calendarState, horizontalSwipeEnabled = true, monthHeader = { monthState -> - Row( - modifier = Modifier - .padding(bottom = 12.dp) - .fillMaxWidth() - .height(40.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - val context = LocalContext.current - Box( + Column { + Row( modifier = Modifier - .weight(1f) - .fillMaxHeight() - .clip(RoundedCornerShape(12.dp)) - .border( - width = 1.dp, - color = WalkieColor.GrayDefault, - shape = RoundedCornerShape(12.dp), - ) - .background(color = WalkieColor.GrayDefault) - .clickable { - datePicker( - context, - monthState.currentMonth.year, - monthState.currentMonth.month.value - 1, - calendarState.selectionState.selection[0].dayOfMonth, - ) { year, month, _ -> - monthState.currentMonth.withMonth(month) - calendarState.monthState.currentMonth = - YearMonth.of(year, month + 1) - } - }, - contentAlignment = Alignment.Center, + .padding(bottom = 12.dp) + .fillMaxWidth() + .height(40.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, ) { - Text( - text = "${monthState.currentMonth.year}년 ${monthState.currentMonth.month.value}월", - style = WalkieTypography.Body1_ExtraBold, - ) - } + val context = LocalContext.current + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .clip(RoundedCornerShape(12.dp)) + .border( + width = 1.dp, + color = WalkieColor.GrayDefault, + shape = RoundedCornerShape(12.dp), + ) + .background(color = WalkieColor.GrayDefault) + .clickable { + datePicker( + context, + monthState.currentMonth.year, + monthState.currentMonth.month.value - 1, + calendarState.selectionState.selection[0].dayOfMonth, + ) { year, month, _ -> + monthState.currentMonth.withMonth(month) + calendarState.monthState.currentMonth = + YearMonth.of(year, month + 1) + } + }, + contentAlignment = Alignment.Center, + ) { + Text( + text = "${monthState.currentMonth.year}년 ${monthState.currentMonth.month.value}월", + style = WalkieTypography.Body1_ExtraBold, + ) + } - Spacer(modifier = Modifier.width(12.dp)) + Spacer(modifier = Modifier.width(12.dp)) - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .clip(RoundedCornerShape(12.dp)) - .border( - width = 1.dp, - color = WalkieColor.GrayDefault, - shape = RoundedCornerShape(12.dp), + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .clip(RoundedCornerShape(12.dp)) + .border( + width = 1.dp, + color = WalkieColor.GrayDefault, + shape = RoundedCornerShape(12.dp), + ) + .background(color = if (selectedState.value == null) Color.White else WalkieColor.GrayDefault), + contentAlignment = Alignment.Center, + ) { + Text( + text = if (selectedState.value == null) { + "달린시간" + } else { + SimpleDateFormat("HH:mm").format( + requireNotNull(selectedState.value).finishedAt, + ) + }, + style = WalkieTypography.Body1_ExtraBold, ) - .background(color = if (selectedState.value == null) Color.White else WalkieColor.GrayDefault), - contentAlignment = Alignment.Center, + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, ) { + Icon( + imageVector = Icons.Default.KeyboardArrowLeft, + contentDescription = "이전 달", + modifier = Modifier + .size(32.dp) + .clickable { + calendarState.monthState.currentMonth = + calendarState.monthState.currentMonth.minusMonths(1) + }, + ) + Spacer(Modifier.width(8.dp)) Text( - text = if (selectedState.value == null) { - "달린시간" - } else { - SimpleDateFormat("HH:mm").format( - requireNotNull(selectedState.value).finishedAt, - ) - }, - style = WalkieTypography.Body1_ExtraBold, + text = "${monthState.currentMonth.year}년 ${monthState.currentMonth.month.value}월", + style = WalkieTypography.Title, + ) + Spacer(Modifier.width(8.dp)) + Icon( + imageVector = Icons.Default.KeyboardArrowRight, + contentDescription = "다음 달", + modifier = Modifier + .size(32.dp) + .clickable { + calendarState.monthState.currentMonth = + calendarState.monthState.currentMonth.plusMonths(1) + }, ) } } @@ -195,12 +240,16 @@ fun SelectHistoryScreen( Box( contentAlignment = Alignment.Center, modifier = Modifier - .padding(2.dp) + .padding(4.dp) .fillMaxWidth() .aspectRatio(1.2f) .aspectRatio(ratio = 1f, matchHeightConstraintsFirst = true) .clip(CircleShape) - .background(if (isSelected) WalkieColor.Primary else Color.Transparent) + .background( + if (isSelected) WalkieColor.Primary + else if (viewModel.curDay.dayOfYear == dateState.date.dayOfYear) WalkieColor.GrayDefault + else Color.Transparent + ) .clickable { calendarState.selectionState.selection = listOf(dateState.date) }, @@ -211,6 +260,19 @@ fun SelectHistoryScreen( style = WalkieTypography.Body1, color = color, ) + val hasHistory = allRunningHistory.value.any { localDate -> + localDate.year == dateState.date.year && localDate.dayOfYear == dateState.date.dayOfYear + } + if (isSelected.not() && hasHistory) { + Box( + modifier = Modifier + .padding(bottom = 2.dp) + .align(Alignment.BottomCenter) + .size(6.dp) + .clip(CircleShape) + .background(WalkieColor.GrayDefault) + ) + } } }, monthContainer = { container -> @@ -246,7 +308,10 @@ fun SelectHistoryScreen( val history = historyListState.value[index] HistoryWithTime( isSelected = selectedState.value == history, - modifier = Modifier.fillMaxWidth().height(40.dp).clip(RoundedCornerShape(12.dp)) + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .clip(RoundedCornerShape(12.dp)) .clickable { viewModel.selectHistory(history) }, @@ -257,11 +322,18 @@ fun SelectHistoryScreen( } selectedState.value.let { runningHistory -> - Box(modifier = Modifier.fillMaxSize().padding(20.dp)) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(20.dp) + ) { Button( enabled = runningHistory != null, shape = RoundedCornerShape(12.dp), - modifier = Modifier.fillMaxWidth().height(48.dp).align(Alignment.BottomCenter), + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .align(Alignment.BottomCenter), onClick = { onHistorySelected(requireNotNull(runningHistory)) }, colors = buttonColors(containerColor = WalkieColor.Primary), ) { diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/tabs/HistoryPage.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/tabs/HistoryPage.kt index d2e09d4..3affeff 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/tabs/HistoryPage.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/tabs/HistoryPage.kt @@ -1,5 +1,6 @@ package com.whyranoid.presentation.screens.mypage.tabs +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -39,12 +40,17 @@ import java.time.format.TextStyle import java.util.* @Composable -fun HistoryPage(modifier: Modifier = Modifier, onDayClicked: (LocalDate) -> Unit) { +fun HistoryPage( + modifier: Modifier = Modifier, + runningHistories: List?, + onDayClicked: (LocalDate) -> Unit +) { val calendarState = rememberSelectableCalendarState( initialMonth = YearMonth.now(), initialSelection = listOf(LocalDate.now()), initialSelectionMode = SelectionMode.Single, ) + val curDay = LocalDate.now() onDayClicked(calendarState.selectionState.selection[0]) Column( @@ -131,12 +137,12 @@ fun HistoryPage(modifier: Modifier = Modifier, onDayClicked: (LocalDate) -> Unit Box( contentAlignment = Alignment.Center, modifier = Modifier - .padding(2.dp) + .padding(4.dp) .fillMaxWidth() .aspectRatio(1.2f) .aspectRatio(ratio = 1f, matchHeightConstraintsFirst = true) .clip(CircleShape) - .background(if (isSelected) WalkieColor.Primary else Color.Transparent) + .background(if (isSelected) WalkieColor.Primary else if (curDay.dayOfYear == dateState.date.dayOfYear) WalkieColor.GrayDefault else Color.Transparent) .clickable { onDayClicked(dateState.date) calendarState.selectionState.selection = listOf(dateState.date) @@ -148,6 +154,20 @@ fun HistoryPage(modifier: Modifier = Modifier, onDayClicked: (LocalDate) -> Unit style = WalkieTypography.Body1, color = color, ) + val hasHistory = runningHistories?.any { localDate -> + localDate.year == dateState.date.year && localDate.dayOfYear == dateState.date.dayOfYear + } ?: false + + if (isSelected.not() && hasHistory) { + Box( + modifier = Modifier + .padding(bottom = 2.dp) + .align(Alignment.BottomCenter) + .size(6.dp) + .clip(CircleShape) + .background(WalkieColor.GrayDefault) + ) + } } }, monthContainer = { container -> diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInUserNameScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInUserNameScreen.kt index 0d40da2..dc8e7db 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInUserNameScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInUserNameScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape @@ -52,7 +53,7 @@ fun SignInUserNameScreen(onSuccess: () -> Unit) { val userNameState = signInState.value as SignInState.UserNameState Surface( - modifier = Modifier.background(Color.White).padding(20.dp), + modifier = Modifier.background(Color.White).padding(20.dp).systemBarsPadding(), ) { Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.Start) { Spacer(modifier = Modifier.height(68.dp)) diff --git a/presentation/src/main/java/com/whyranoid/presentation/util/ApiResponseManager.kt b/presentation/src/main/java/com/whyranoid/presentation/util/ApiResponseManager.kt new file mode 100644 index 0000000..885722c --- /dev/null +++ b/presentation/src/main/java/com/whyranoid/presentation/util/ApiResponseManager.kt @@ -0,0 +1,33 @@ +package com.whyranoid.presentation.util + + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import java.util.concurrent.atomic.AtomicInteger + +object ApiResponseDialog { + private val loadingCount = AtomicInteger(0) + + private val _isLaoding = MutableStateFlow(false) + val isLoading get() = _isLaoding.asStateFlow() + + private val _isShowError = MutableStateFlow(false) + + val isShowError get() = _isShowError.asStateFlow() + + fun startLoading() { + if (loadingCount.incrementAndGet() > 0) _isLaoding.value = true + } + + fun finishLoad(isSuccessFul: Boolean) { + if (loadingCount.decrementAndGet() == 0) _isLaoding.value = false + if (isSuccessFul.not()) { + _isLaoding.value = false + _isShowError.value = true + } + } + + fun closeErrorDialog() { + _isShowError.value = false + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/whyranoid/presentation/util/CustomDialog.kt b/presentation/src/main/java/com/whyranoid/presentation/util/CustomDialog.kt new file mode 100644 index 0000000..414b9c1 --- /dev/null +++ b/presentation/src/main/java/com/whyranoid/presentation/util/CustomDialog.kt @@ -0,0 +1,63 @@ +package com.whyranoid.presentation.util + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.AlertDialog +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.whyranoid.presentation.theme.WalkieColor +import com.whyranoid.presentation.theme.WalkieTypography + +@Composable +fun CustomDialog( + title: String, + description: String, + onAction: () -> Unit, + modifier: Modifier = Modifier, +) { + AlertDialog( + onDismissRequest = onAction, + buttons = { + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = "확인", + style = WalkieTypography.SubTitle.copy(color = WalkieColor.Primary), + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + ) { + onAction() + } + .padding(bottom = 20.dp) + .padding(horizontal = 20.dp), + ) + } + }, + title = { + Text( + modifier = Modifier.fillMaxWidth(), + text = title, style = WalkieTypography.SubTitle, + textAlign = TextAlign.Center, + ) + }, + text = { + Text( + modifier = Modifier.fillMaxWidth(), + text = description, + style = WalkieTypography.Body1_Normal, + textAlign = TextAlign.Center, + ) + }, + modifier = modifier, + ) +} diff --git a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/SelectHistoryViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/SelectHistoryViewModel.kt index 5d22c6d..ac66bc8 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/SelectHistoryViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/SelectHistoryViewModel.kt @@ -7,6 +7,9 @@ import com.whyranoid.domain.repository.RunningHistoryRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId class SelectHistoryViewModel( private val runningHistoryRepository: RunningHistoryRepository, @@ -17,6 +20,12 @@ class SelectHistoryViewModel( private val _selectedHistory: MutableStateFlow = MutableStateFlow(null) val selectedHistory get() = _selectedHistory.asStateFlow() + private val _allRunningHistory: MutableStateFlow> = + MutableStateFlow(emptyList()) + val allRunningHistory get() = _allRunningHistory.asStateFlow() + + val curDay = LocalDate.now() + fun getHistoryList(year: Int, month: Int, day: Int) { viewModelScope.launch { runningHistoryRepository.getByDate(year, month, day).onSuccess { data -> @@ -28,4 +37,14 @@ class SelectHistoryViewModel( fun selectHistory(runningHistory: RunningHistory) { _selectedHistory.value = runningHistory } + + fun getAllRunningHistory() { + viewModelScope.launch { + runningHistoryRepository.getAll().onSuccess { historys -> + _allRunningHistory.value = historys.map { + Instant.ofEpochMilli(it.finishedAt).atZone(ZoneId.systemDefault()).toLocalDate() + } + } + } + } }