Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#117 news notice api paging #120

Merged
merged 15 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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 @@ -6,6 +6,7 @@ import com.teamwable.model.news.NewsMatchScoreModel
import com.teamwable.model.news.NewsRankModel
import com.teamwable.network.dto.response.news.ResponseGameDto
import com.teamwable.network.dto.response.news.ResponseNewsInfoDto
import com.teamwable.network.dto.response.news.ResponseNoticeInfoDto
import com.teamwable.network.dto.response.news.ResponseRankDto
import com.teamwable.network.dto.response.news.ResponseScheduleDto

Expand Down Expand Up @@ -43,3 +44,12 @@ internal fun ResponseNewsInfoDto.toNewsInfoModel(): NewsInfoModel =
newsText = newsText,
time = time,
)

internal fun ResponseNoticeInfoDto.toNoticeInfoModel(): NewsInfoModel =
NewsInfoModel(
newsId = noticeId,
newsTitle = noticeTitle,
newsImage = noticeImage,
newsText = noticeText,
time = time
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ interface NewsRepository {
suspend fun getRank(): Result<List<NewsRankModel>>

fun getNewsInfo(): Flow<PagingData<NewsInfoModel>>

fun getNoticeInfo(): Flow<PagingData<NewsInfoModel>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.paging.map
import com.teamwable.data.mapper.toModel.toNewsInfoModel
import com.teamwable.data.mapper.toModel.toNewsMatchModel
import com.teamwable.data.mapper.toModel.toNewsRankModel
import com.teamwable.data.mapper.toModel.toNoticeInfoModel
import com.teamwable.data.repository.NewsRepository
import com.teamwable.model.news.NewsInfoModel
import com.teamwable.model.news.NewsMatchModel
Expand Down Expand Up @@ -49,4 +50,15 @@ internal class DefaultNewsRepository @Inject constructor(
pagingData.map { it.toNewsInfoModel() }
}
}

override fun getNoticeInfo(): Flow<PagingData<NewsInfoModel>> {
return Pager(PagingConfig(pageSize = 15, prefetchDistance = 1)) {
GenericPagingSource(
apiCall = { cursor -> newsService.getNoticeInfo(cursor).data },
getNextCursor = { feeds -> feeds.lastOrNull()?.noticeId },
)
}.flow.map { pagingData ->
pagingData.map { it.toNoticeInfoModel() }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.teamwable.network.datasource

import com.teamwable.network.dto.response.news.ResponseGameTypeDto
import com.teamwable.network.dto.response.news.ResponseNewsInfoDto
import com.teamwable.network.dto.response.news.ResponseNoticeInfoDto
import com.teamwable.network.dto.response.news.ResponseRankDto
import com.teamwable.network.dto.response.news.ResponseScheduleDto
import com.teamwable.network.util.BaseResponse
Expand All @@ -22,4 +23,9 @@ interface NewsService {
suspend fun getNewsInfo(
@Query(value = "cursor") contentId: Long = -1,
): BaseResponse<List<ResponseNewsInfoDto>>

@GET("api/v1/information/notice")
suspend fun getNoticeInfo(
@Query(value = "cursor") contentId: Long = -1,
): BaseResponse<List<ResponseNoticeInfoDto>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.teamwable.network.dto.response.news

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseNoticeInfoDto(
@SerialName("noticeId")
val noticeId: Long,
@SerialName("noticeTitle")
val noticeTitle: String,
@SerialName("noticeText")
val noticeText: String,
@SerialName("noticeImage")
val noticeImage: String?,
@SerialName("time")
val time: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package com.teamwable.ui.extensions

import android.content.Intent
import android.net.Uri
import android.provider.Settings
import android.view.View
import android.widget.Toast
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
Expand Down Expand Up @@ -55,3 +55,9 @@ fun Fragment.statusBarColorOf(
fun Fragment.openUri(uri: String) {
Intent(Intent.ACTION_VIEW, Uri.parse(uri)).also { startActivity(it) }
}

fun Fragment.statusBarModeOf(isLightStatusBar: Boolean = true) {
requireActivity().window.apply {
WindowInsetsControllerCompat(this, decorView).isAppearanceLightStatusBars = isLightStatusBar
}
}
Eonji-sw marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 4 additions & 0 deletions feature/news/src/main/java/com/teamwable/news/NewsFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import com.teamwable.news.databinding.FragmentNewsBinding
import com.teamwable.ui.base.BindingFragment
import com.teamwable.ui.extensions.colorOf
import com.teamwable.ui.extensions.statusBarColorOf
import com.teamwable.ui.extensions.statusBarModeOf
import com.teamwable.ui.extensions.stringOf
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class NewsFragment : BindingFragment<FragmentNewsBinding>(FragmentNewsBinding::inflate) {
override fun initView() {
statusBarColorOf(com.teamwable.ui.R.color.black)
statusBarModeOf(false)

initNewsViewPagerAdapter()
initTabClickListener()

Expand Down Expand Up @@ -74,5 +77,6 @@ class NewsFragment : BindingFragment<FragmentNewsBinding>(FragmentNewsBinding::i
override fun onDestroyView() {
super.onDestroyView()
statusBarColorOf(com.teamwable.ui.R.color.white)
statusBarModeOf()
}
}
12 changes: 0 additions & 12 deletions feature/news/src/main/java/com/teamwable/news/NewsViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.teamwable.common.uistate.UiState
import com.teamwable.data.repository.NewsRepository
import com.teamwable.model.news.NewsInfoModel
import com.teamwable.model.news.NewsMatchModel
import com.teamwable.model.news.NewsRankModel
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -25,17 +24,6 @@ class NewsViewModel
private val _rankUiState = MutableStateFlow<UiState<List<NewsRankModel>>>(UiState.Loading)
val rankUiState = _rankUiState.asStateFlow()

val dummyNotice = listOf(
NewsInfoModel(1, "์™€๋ธ” ์ปค๋ฎค๋‹ˆํ‹ฐ ์—…๋ฐ์ดํŠธ ์•ˆ๋‚ด", "๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค ๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค ๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค ๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค ๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค ๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค ๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค ๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค ๋ณธ๋ฌธ์ž…๋‹ˆ๋‹ค", "www.11", "2024-01-10 11:47:18"),
NewsInfoModel(2, "์ œ๋ชฉ2", "๋‚ด์šฉ2", null, "2024-06-12 20:00:37"),
NewsInfoModel(3, "์ œ๋ชฉ3", "๋‚ด์šฉ3", "www.33", "2024-11-22 04:50:26"),
NewsInfoModel(4, "์ œ๋ชฉ4", "๋‚ด์šฉ4", "www.33", "2024-11-22 04:50:26"),
NewsInfoModel(5, "์ œ๋ชฉ5", "๋‚ด์šฉ5", "www.33", "2024-11-22 04:50:26"),
NewsInfoModel(6, "์ œ๋ชฉ6", "๋‚ด์šฉ6", "www.33", "2024-11-22 04:50:26"),
NewsInfoModel(7, "์ œ๋ชฉ7", "๋‚ด์šฉ7", "www.33", "2024-11-22 04:50:26"),
NewsInfoModel(8, "์ œ๋ชฉ8", "๋‚ด์šฉ8", "www.33", "2024-11-22 04:50:26"),
)

init {
getGameType()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.teamwable.news.notice

import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.navigation.fragment.findNavController
import com.teamwable.designsystem.theme.WableTheme
Expand All @@ -13,18 +11,16 @@ import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class NewsNoticeFragment : BindingFragment<FragmentNewsNoticeBinding>(FragmentNewsNoticeBinding::inflate) {
@RequiresApi(Build.VERSION_CODES.O)
override fun initView() {
initComposeView()
}

@RequiresApi(Build.VERSION_CODES.O)
private fun initComposeView() {
binding.composeNewsNotice.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
WableTheme {
NewsNoticeRoute(navigateToDetail = { notice -> navigateToDetail(notice) })
NewsNoticeRoute(navigateToDetail = ::navigateToDetail)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.teamwable.news.notice

import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
Expand All @@ -17,26 +14,24 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.teamwable.designsystem.theme.WableTheme
import com.teamwable.model.news.NewsInfoModel
import com.teamwable.ui.util.CalculateTime
import com.teamwable.news.news.component.WableNewsTimeText

@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun NewsNoticeItem(
context: Context,
data: NewsInfoModel,
navigateToDetail: (NewsInfoModel) -> Unit
onItemClick: (NewsInfoModel) -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(WableTheme.colors.white)
.clickable { navigateToDetail(data) }
.clickable { onItemClick(data) }
.padding(vertical = 12.dp, horizontal = 20.dp)
) {
Row {
Text(text = data.newsTitle, style = WableTheme.typography.body01)
Spacer(modifier = Modifier.weight(1f))
Text(text = CalculateTime().getCalculateTime(context, data.time), color = WableTheme.colors.gray500, style = WableTheme.typography.caption04)
WableNewsTimeText(data.time)
}
Spacer(modifier = Modifier.height(2.dp))
Text(text = data.newsText, color = WableTheme.colors.gray600, maxLines = 2, style = WableTheme.typography.body04)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,108 @@
package com.teamwable.news.notice

import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.flowWithLifecycle
import androidx.paging.LoadState
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.teamwable.designsystem.component.paging.WablePagingSpinner
import com.teamwable.designsystem.component.screen.LoadingScreen
import com.teamwable.designsystem.component.screen.NewsNoticeEmptyScreen
import com.teamwable.designsystem.theme.WableTheme
import com.teamwable.designsystem.type.ContentType
import com.teamwable.model.news.NewsInfoModel
import com.teamwable.news.NewsViewModel
import com.teamwable.news.R
import com.teamwable.news.news.model.NewsInfoSideEffect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flowOf

@RequiresApi(Build.VERSION_CODES.O)
@Composable
internal fun NewsNoticeRoute(
viewModel: NewsViewModel = hiltViewModel(),
viewModel: NewsNoticeViewModel = hiltViewModel(),
navigateToDetail: (NewsInfoModel) -> Unit
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val newsItems = viewModel.noticePagingFlow.collectAsLazyPagingItems()

viewModel.dummyNotice.apply {
if (this.isNotEmpty()) {
NewsNoticeScreen(context = context, notices = this, navigateToDetail = navigateToDetail)
} else {
NewsNoticeEmptyScreen(R.string.tv_news_notice_empty)
}
LaunchedEffect(lifecycleOwner) {
viewModel.sideEffect.flowWithLifecycle(lifecycleOwner.lifecycle)
.collectLatest { sideEffect ->
when (sideEffect) {
is NewsInfoSideEffect.NavigateToDetail -> navigateToDetail(sideEffect.newsInfoModel)
else -> Unit
}
}
}
NewsNoticeScreen(
noticeItems = newsItems,
onItemClick = viewModel::onItemClick
)
}

@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun NewsNoticeScreen(
context: Context,
notices: List<NewsInfoModel>,
navigateToDetail: (NewsInfoModel) -> Unit
noticeItems: LazyPagingItems<NewsInfoModel>,
onItemClick: (NewsInfoModel) -> Unit
) {
val isLoading = noticeItems.loadState.refresh is LoadState.Loading
val isEmpty = noticeItems.itemCount == 0 && !isLoading

LazyColumn(modifier = Modifier.fillMaxSize()) {
items(items = notices, key = { item -> item.newsId }) { notice ->
NewsNoticeItem(context, notice, navigateToDetail)
HorizontalDivider(
thickness = 1.dp,
color = WableTheme.colors.gray200,
)
when {
isLoading -> item { LoadingScreen() }
isEmpty -> item { NewsNoticeEmptyScreen(emptyTxt = R.string.tv_news_notice_empty) }
else -> {
Eonji-sw marked this conversation as resolved.
Show resolved Hide resolved
items(
count = noticeItems.itemCount,
key = noticeItems.itemKey { it.newsId },
contentType = noticeItems.itemContentType { ContentType.Item.name },
) { idx ->
noticeItems[idx]?.let {
NewsNoticeItem(it, onItemClick)
HorizontalDivider(
thickness = 1.dp,
color = WableTheme.colors.gray200,
)
}
}
item(
key = ContentType.Spinner.name,
contentType = noticeItems.itemContentType { ContentType.Spinner.name },
) {
if (noticeItems.loadState.append is LoadState.Loading) {
WablePagingSpinner(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
)
}
}
}
}
}
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
WableTheme {
NewsNoticeScreen(
onItemClick = {},
noticeItems = flowOf(PagingData.from(emptyList<NewsInfoModel>())).collectAsLazyPagingItems(),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.teamwable.news.notice

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
import com.teamwable.data.repository.NewsRepository
import com.teamwable.model.news.NewsInfoModel
import com.teamwable.news.news.model.NewsInfoSideEffect
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class NewsNoticeViewModel @Inject constructor(
newsRepository: NewsRepository,
) : ViewModel() {
private val _sideEffect = MutableSharedFlow<NewsInfoSideEffect>()
val sideEffect: SharedFlow<NewsInfoSideEffect> get() = _sideEffect.asSharedFlow()

val noticePagingFlow = newsRepository.getNoticeInfo().cachedIn(viewModelScope)

fun onItemClick(newsInfoModel: NewsInfoModel) {
viewModelScope.launch {
_sideEffect.emit(NewsInfoSideEffect.NavigateToDetail(newsInfoModel))
}
}
}