Skip to content

Commit

Permalink
Merge pull request #60 from kw-notice/develop
Browse files Browse the repository at this point in the history
2.1.0
  • Loading branch information
yjyoon-dev committed Aug 1, 2022
2 parents 67b569e + 15915d3 commit fa54197
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 59 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ https://play.google.com/store/apps/details?id=dev.yjyoon.kwnotice

# Architectures
- MVVM + Clean Architecture
- Mutli Modules
- Multi Modules
- app
- domain
- data
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ android {
applicationId = "dev.yjyoon.kwnotice"
minSdk = 24
targetSdk = 32
versionCode = 5
versionName = "2.0.0"
versionCode = 7
versionName = "2.1.0"
}

buildTypes {
Expand Down
7 changes: 7 additions & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import org.jetbrains.kotlin.konan.properties.Properties

plugins {
id("com.android.library")
id("dagger.hilt.android.plugin")
kotlin("android")
kotlin("kapt")
}

val properties = Properties()
properties.load(project.rootProject.file("local.properties").inputStream())

android {
namespace = "dev.yjyoon.kwnotice.data"
compileSdk = 32

defaultConfig {
minSdk = 24
targetSdk = 32

buildConfigField("String", "BASE_URL", properties["base_url"] as String)
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
Expand Down
3 changes: 2 additions & 1 deletion data/src/main/java/dev/yjyoon/kwnotice/data/di/DataModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dev.yjyoon.kwnotice.data.BuildConfig
import dev.yjyoon.kwnotice.data.local.dao.FavoriteDao
import dev.yjyoon.kwnotice.data.local.db.FavoriteDatabase
import dev.yjyoon.kwnotice.data.remote.api.NoticeService
Expand All @@ -26,7 +27,7 @@ internal object DataModule {
@Provides
@Singleton
@Named("BaseUrl")
fun provideBaseUrl(): String = "https://n6f11u33jf.execute-api.ap-northeast-2.amazonaws.com/v1/"
fun provideBaseUrl(): String = BuildConfig.BASE_URL

@Provides
@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import java.time.format.DateTimeFormatter
fun FavoriteCard(
favorite: Favorite,
onClickFavorite: (String) -> Unit,
onUnbookmark: (Favorite) -> Unit
onUnbookmark: (Favorite) -> Unit,
showUndoSnackbar: (Favorite) -> Unit
) {
ElevatedCard(
onClick = { onClickFavorite(favorite.url) },
Expand Down Expand Up @@ -61,7 +62,12 @@ fun FavoriteCard(
}
FilledIconToggleButton(
checked = true,
onCheckedChange = { onUnbookmark(favorite) },
onCheckedChange = {
favorite.let {
onUnbookmark(it)
showUndoSnackbar(it)
}
},
modifier = Modifier.padding(end = 12.dp)
) {
Icon(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ fun FavoriteContent(
onClickFavorite: (String) -> Unit,
onUnbookmark: (Favorite) -> Unit,
onTypeFilterChange: (String?) -> Unit,
onMonthFilterChange: (String?) -> Unit
onMonthFilterChange: (String?) -> Unit,
showUndoSnackbar: (Favorite) -> Unit
) {
Column(Modifier.fillMaxSize()) {
Row(
Expand Down Expand Up @@ -56,7 +57,8 @@ fun FavoriteContent(
FavoriteCard(
favorite = it,
onClickFavorite = onClickFavorite,
onUnbookmark = onUnbookmark
onUnbookmark = onUnbookmark,
showUndoSnackbar = showUndoSnackbar
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,46 @@ package dev.yjyoon.kwnotice.presentation.ui.favorite

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
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.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarData
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import dev.yjyoon.kwnotice.domain.model.Favorite
import dev.yjyoon.kwnotice.presentation.R
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeLoading
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeSearchTopAppBar
import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTheme
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

@Composable
fun FavoriteScreen(
Expand All @@ -30,7 +59,8 @@ fun FavoriteScreen(
onUnbookmark = viewModel::deleteFromFavorite,
onTypeFilterChange = viewModel::setTypeFilter,
onMonthFilterChange = viewModel::setMonthFilter,
onInitFilter = viewModel::initFilter
onInitFilter = viewModel::initFilter,
addToFavorite = viewModel::addToFavorite
)
}

Expand All @@ -43,37 +73,136 @@ fun FavoriteScreen(
onUnbookmark: (Favorite) -> Unit,
onTypeFilterChange: (String?) -> Unit,
onMonthFilterChange: (String?) -> Unit,
onInitFilter: () -> Unit
onInitFilter: () -> Unit,
addToFavorite: (Favorite) -> Unit
) {
Column(
Modifier.fillMaxSize()
) {
KwNoticeSearchTopAppBar(
titleText = stringResource(id = R.string.navigation_favorite),
onSearch = onSearch,
onCloseSearh = onInitFilter
)
Box(
Modifier.weight(1f)
) {
when (uiState) {
is FavoriteUiState.Success -> {
FavoriteContent(
uiState = uiState,
filterState = filterState,
onClickFavorite = onClickFavorite,
onUnbookmark = onUnbookmark,
onTypeFilterChange = onTypeFilterChange,
onMonthFilterChange = onMonthFilterChange
)
val scope = rememberCoroutineScope()
var job: Job? by remember { mutableStateOf(null) }
val snackbarHostState = remember { SnackbarHostState() }

val showUndoSnackbar: (Favorite) -> Unit = { favorite ->
job?.cancel()
job = scope.launch {
val snackbarResult = snackbarHostState.showSnackbar(
message = "즐겨찾기에서 삭제했습니다",
actionLabel = "되돌리기"
)
when (snackbarResult) {
SnackbarResult.ActionPerformed -> {
addToFavorite(favorite)
}
FavoriteUiState.Loading -> {
KwNoticeLoading()
SnackbarResult.Dismissed -> {}
}
}
}

Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState) { data ->
FavoriteSnackbar(data)
}
}
) { innerPadding ->
Column(
Modifier
.fillMaxSize()
.padding(innerPadding)
) {
KwNoticeSearchTopAppBar(
titleText = stringResource(id = R.string.navigation_favorite),
onSearch = onSearch,
onCloseSearh = onInitFilter
)
Box(
Modifier.weight(1f)
) {
when (uiState) {
is FavoriteUiState.Success -> {
FavoriteContent(
uiState = uiState,
filterState = filterState,
onClickFavorite = onClickFavorite,
onUnbookmark = onUnbookmark,
onTypeFilterChange = onTypeFilterChange,
onMonthFilterChange = onMonthFilterChange,
showUndoSnackbar = showUndoSnackbar
)
}
FavoriteUiState.Empty -> {
FavoriteEmpty(
Modifier
.align(Alignment.Center)
.fillMaxWidth()
)
}
FavoriteUiState.Loading -> {
KwNoticeLoading()
}
FavoriteUiState.Failure -> {
// no-op
}
}
FavoriteUiState.Failure -> {
}
}
}
}

@Composable
fun FavoriteSnackbar(data: SnackbarData) {
Snackbar(
Modifier.padding(18.dp),
action = {
data.visuals.actionLabel?.let {
TextButton(
onClick = { data.performAction() },
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.inversePrimary
)
) {
Text(it)
}
}
}
) {
Text(data.visuals.message)
}
}

@Composable
fun FavoriteEmpty(modifier: Modifier = Modifier) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(32.dp)
)
Spacer(Modifier.height(12.dp))
Text(
text = stringResource(id = R.string.favorite_empty),
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
}

@Preview(showBackground = true)
@Composable
private fun FavoriteScreenPreview() {
KwNoticeTheme {
FavoriteScreen(
uiState = FavoriteUiState.Empty,
filterState = FavoriteFilterState.Unspecified,
onSearch = {},
onInitFilter = {},
onMonthFilterChange = {},
onTypeFilterChange = {},
onUnbookmark = {},
onClickFavorite = {},
addToFavorite = {}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ sealed interface FavoriteUiState {
val months: List<Int>
) : FavoriteUiState

object Empty : FavoriteUiState
object Loading : FavoriteUiState
object Failure : FavoriteUiState
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dev.yjyoon.kwnotice.presentation.ui.favorite
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.yjyoon.kwnotice.domain.model.Favorite
import dev.yjyoon.kwnotice.domain.usecase.favorite.AddFavoriteUseCase
import dev.yjyoon.kwnotice.domain.usecase.favorite.DeleteFavoriteUseCase
import dev.yjyoon.kwnotice.domain.usecase.favorite.GetAllFavoriteListUseCase
import dev.yjyoon.kwnotice.presentation.ui.base.BaseViewModel
Expand All @@ -18,15 +19,20 @@ import javax.inject.Inject
@HiltViewModel
class FavoriteViewModel @Inject constructor(
getAllFavoriteListUseCase: GetAllFavoriteListUseCase,
private val addFavoriteUseCase: AddFavoriteUseCase,
private val deleteFavoriteUseCase: DeleteFavoriteUseCase
) : BaseViewModel() {

val uiState = getAllFavoriteListUseCase().map {
FavoriteUiState.Success(
favorites = it,
types = it.map { favorite -> favorite.type }.distinct(),
months = it.map { favorite -> favorite.date.monthValue }.distinct()
)
if (it.isNotEmpty()) {
FavoriteUiState.Success(
favorites = it,
types = it.map { favorite -> favorite.type }.distinct(),
months = it.map { favorite -> favorite.date.monthValue }.distinct()
)
} else {
FavoriteUiState.Empty
}
}
.stateIn(
viewModelScope,
Expand All @@ -37,6 +43,11 @@ class FavoriteViewModel @Inject constructor(
private val _filterState = MutableStateFlow(FavoriteFilterState.Unspecified)
val filterState: StateFlow<FavoriteFilterState> = _filterState.asStateFlow()

fun addToFavorite(favorite: Favorite) {
launch {
addFavoriteUseCase(favorite).getOrThrow()
}
}

fun deleteFromFavorite(favorite: Favorite) {
launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ class NoticeViewModel @Inject constructor(

fun addFavorite(notice: Notice) {
launch {
addFavoriteUseCase(notice.toFavorite())
addFavoriteUseCase(notice.toFavorite()).getOrThrow()
}
}

fun deleteFavorite(notice: Notice) {
launch {
deleteFavoriteUseCase(notice.toFavorite())
deleteFavoriteUseCase(notice.toFavorite()).getOrThrow()
}
}

Expand Down
Loading

0 comments on commit fa54197

Please sign in to comment.