Skip to content

AppLinkers/ExhibitionDoT-Android

Repository files navigation

image

전시회닷 - 세상의 전시를 잇다

로그인 홈 - 필터링 홈 - 검색
이벤트 추가 상세 - 애니메이션 상세 - 댓글쓰기



아키텍처

전시회닷 앱 구조



고려사항

1. 이미지 다운샘플링


2. runCathing을 이용한 예외 넘기기

class SignInUseCase @Inject constructor(
    private val userRepository: UserRepository,
    private val preferenceRepository: PreferenceRepository,
) {
    suspend operator fun invoke(email: String): Result<Unit> = runCatching {
        val userId = userRepository.signIn(email)
            .getOrElse { exception ->
                when (exception) {
                    is NetworkFailException -> throw exception
                    else -> throw SignInFailException(exception)
                }
            }
        preferenceRepository.updateUserId(userId)
    }
}

3. Filter interface 분리를 통한 확장성 확보

// Filter.kt (domain)
sealed interface Filter {
    sealed interface SingleFilter : Filter
    sealed interface MultiFilter : Filter
}

sealed class Region(val key: String, val name: String) : Filter.SingleFilter

sealed class Category(val key: String) : Filter.MultiFilter

sealed class EventType(val key: String) : Filter.MultiFilter

// FilterState.kt (presentation)
interface IFilterState<T : Filter> {
    val filterList: List<T>
    fun selectFilter(filter: T)
    fun resetAll()
}

interface ISingleFilterState<T : Filter.SingleFilter> : IFilterState<T> {
    var selectedFilter: T?
    fun setFilter(filter: T?)
}

interface IMultiFilerState<T : Filter.MultiFilter> : IFilterState<T> {
    var selectedFilterList: List<T>
    fun setFilter(filterList: List<T>)
}

4. 전략 패턴을 활용한 유연한 DateFormat 매핑

// EventMapper.kt (presentation)
fun EventDetail.toUiModel() =
    EventDetailUiModel(
        id = id,
        name = name,
        imgUrl = imgUrl,
        region = region.name,
        categoryTags = categoryList.joinToString(" ", transform = Category::toTag),
        eventTypeTags = eventTypeList.joinToString(" ", transform = EventType::toTag),
        date = format(DateFormatStrategy.FullDate(date)),
        createdAt = format(DateFormatStrategy.RelativeTime(createdAt)),
        likeCount = likeCount,
        isLike = isLike,
        owner = owner
    )

5. derivedState을 통해 애니메이션 flag의 Recomposition 줄이기

@Composable
private fun EventDetailScreen(
    modifier: Modifier,
    eventDetail: EventDetailUiModel,
    ...
) {
    val lazyListState = rememberLazyListState()
    val skipImage by remember {
        derivedStateOf {
            (lazyListState.firstVisibleItemIndex == 0 &&
                    lazyListState.firstVisibleItemScrollOffset < 1100).not()
        }
    }
    ...
    EventDetailTopBar(     
        skipImage = skipImage,
        ...
    )
}

@Composable
fun EventDetailTopBar(
    skipImage: Boolean,
    ...
) {
    val containerColor by animateColorAsState(
        targetValue = if (skipImage) {
            MaterialTheme.colorScheme.background
        } else {
            Color.Transparent
        },
        label = "event-detail-top-bar-container-color-anim"
    )
    val iconColor by animateColorAsState(
        targetValue = if (skipImage) {
            MaterialTheme.colorScheme.surfaceContainerHigh
        } else {
            MaterialTheme.colorScheme.background
        },
        label = "event-detail-top-bar-icon-color-anim"
    )
    ...
}

About

전시회닷 - 세상의 전시를 잇다

Resources

Stars

Watchers

Forks

Languages