Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,13 @@
package com.teamwable.data.mapper.toModel

import com.teamwable.model.news.CurationModel
import com.teamwable.network.dto.response.news.ResponseCurationInfoDto

internal fun ResponseCurationInfoDto.toCuration(): CurationModel =
CurationModel(
this.curationId,
this.curationLink,
this.curationTitle ?: "",
this.curationThumbnail ?: "",
this.time,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.teamwable.data.repository

import androidx.paging.PagingData
import com.teamwable.model.news.CurationModel
import com.teamwable.model.news.NewsInfoModel
import com.teamwable.model.news.NewsMatchModel
import com.teamwable.model.news.NewsRankModel
Expand All @@ -18,4 +19,8 @@ interface NewsRepository {
fun getNoticeInfo(): Flow<PagingData<NewsInfoModel>>

suspend fun getNumber(): Result<Map<String, Int>>

fun getCurationInfo(): Flow<PagingData<CurationModel>>

suspend fun getCurationNumber(): Result<Long>
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ interface UserInfoRepository {

fun getIsAdmin(): Flow<Boolean>

fun getNewsNumber(): Flow<Int>

fun getNoticeNumber(): Flow<Int>

fun getCurationNumber(): Flow<Long>

suspend fun saveAccessToken(accessToken: String)

suspend fun saveRefreshToken(refreshToken: String)
Expand All @@ -39,10 +39,10 @@ interface UserInfoRepository {

suspend fun saveIsAdmin(isAdmin: Boolean)

suspend fun saveNewsNumber(newsNumber: Int)

suspend fun saveNoticeNumber(noticeNumber: Int)

suspend fun saveCurationId(curationId: Long)

suspend fun clearAll()

suspend fun clearForRefreshToken()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.map
import com.teamwable.data.mapper.toModel.toCuration
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.data.util.runHandledCatching
import com.teamwable.model.news.CurationModel
import com.teamwable.model.news.NewsInfoModel
import com.teamwable.model.news.NewsMatchModel
import com.teamwable.model.news.NewsRankModel
Expand Down Expand Up @@ -66,8 +69,25 @@ internal class DefaultNewsRepository @Inject constructor(
return runCatching {
mapOf(
"news" to newsService.getNumber().data.newsNumber,
"notice" to newsService.getNumber().data.noticeNumber
"notice" to newsService.getNumber().data.noticeNumber,
)
}.onFailure { return it.handleThrowable() }
}

override fun getCurationInfo(): Flow<PagingData<CurationModel>> {
return Pager(PagingConfig(pageSize = 15, prefetchDistance = 1)) {
GenericPagingSource(
apiCall = { cursor -> newsService.getCurationInfo(cursor).data },
getNextCursor = { curations -> curations.lastOrNull()?.curationId },
)
}.flow.map { pagingData ->
pagingData.map { it.toCuration() }
}
}

override suspend fun getCurationNumber(): Result<Long> {
return runHandledCatching {
newsService.getCurationNumber().data.curationId
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ internal class DefaultUserInfoRepository @Inject constructor(
override fun getIsAdmin(): Flow<Boolean> =
wablePreferencesDataSource.isAdmin

override fun getNewsNumber(): Flow<Int> =
wablePreferencesDataSource.newsNumber

override fun getNoticeNumber(): Flow<Int> =
wablePreferencesDataSource.noticeNumber

override fun getCurationNumber(): Flow<Long> =
wablePreferencesDataSource.curationId

override suspend fun saveAccessToken(accessToken: String) {
wablePreferencesDataSource.updateAccessToken(accessToken)
}
Expand Down Expand Up @@ -70,14 +70,14 @@ internal class DefaultUserInfoRepository @Inject constructor(
wablePreferencesDataSource.updateIsAdmin(isAdmin)
}

override suspend fun saveNewsNumber(newsNumber: Int) {
wablePreferencesDataSource.updateNewsNumber(newsNumber)
}

override suspend fun saveNoticeNumber(noticeNumber: Int) {
wablePreferencesDataSource.updateNoticeNumber(noticeNumber)
}

override suspend fun saveCurationId(curationId: Long) {
wablePreferencesDataSource.updateCurationId(curationId)
}

override suspend fun clearAll() {
wablePreferencesDataSource.clear()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
Expand All @@ -26,8 +27,8 @@ class DefaultWablePreferenceDatasource @Inject constructor(
val MemberProfileUrl = stringPreferencesKey("memberProfileUrl")
val IsPushAlarmAllowed = booleanPreferencesKey("isPushAlarmAllowed")
val IsAdmin = booleanPreferencesKey("isAdmin")
val NewsNumber = intPreferencesKey("newsNumber")
val NoticeNumber = intPreferencesKey("noticeNumber")
val CurationId = longPreferencesKey("curationId")
}

override val accessToken: Flow<String> = dataStore.data
Expand Down Expand Up @@ -78,16 +79,16 @@ class DefaultWablePreferenceDatasource @Inject constructor(
preferences[PreferencesKeys.IsAdmin] ?: false
}

override val newsNumber: Flow<Int> = dataStore.data
override val noticeNumber: Flow<Int> = dataStore.data
.catch { handleError(it) }
.map { preferences ->
preferences[PreferencesKeys.NewsNumber] ?: -1
preferences[PreferencesKeys.NoticeNumber] ?: -1
}

override val noticeNumber: Flow<Int> = dataStore.data
override val curationId: Flow<Long> = dataStore.data
.catch { handleError(it) }
.map { preferences ->
preferences[PreferencesKeys.NoticeNumber] ?: -1
preferences[PreferencesKeys.CurationId] ?: -1
}

override suspend fun updateAccessToken(accessToken: String) {
Expand Down Expand Up @@ -138,15 +139,15 @@ class DefaultWablePreferenceDatasource @Inject constructor(
}
}

override suspend fun updateNewsNumber(newsNumber: Int) {
override suspend fun updateNoticeNumber(noticeNumber: Int) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.NewsNumber] = newsNumber
preferences[PreferencesKeys.NoticeNumber] = noticeNumber
}
}

override suspend fun updateNoticeNumber(noticeNumber: Int) {
override suspend fun updateCurationId(curationId: Long) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.NoticeNumber] = noticeNumber
preferences[PreferencesKeys.CurationId] = curationId
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ interface WablePreferencesDataSource {
val memberProfileUrl: Flow<String>
val isPushAlarmAllowed: Flow<Boolean>
val isAdmin: Flow<Boolean>
val newsNumber: Flow<Int>
val noticeNumber: Flow<Int>
val curationId: Flow<Long>

suspend fun updateAccessToken(accessToken: String)

Expand All @@ -30,10 +30,10 @@ interface WablePreferencesDataSource {

suspend fun updateIsAdmin(isAdmin: Boolean)

suspend fun updateNewsNumber(newsNumber: Int)

suspend fun updateNoticeNumber(noticeNumber: Int)

suspend fun updateCurationId(curationId: Long)

suspend fun clear()

suspend fun clearForRefreshToken()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.teamwable.model.news

data class CurationModel(
val curationId: Long,
val curationLink: String,
val curationTitle: String,
val curationThumbnail: String,
val time: String,
)
1 change: 1 addition & 0 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ dependencies {
implementation(project(":core:model"))
implementation(project(":core:datastore"))
implementation(libs.paging)
implementation(libs.androidx.annotation.experimental)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

annotation-experimental 의존성, 실제 사용 여부 점검(네트워크 모듈엔 불필요 가능성 큼)

이 모듈에서 androidx.annotation.Experimental 계열을 쓰지 않으면 제거하거나 실제 사용하는 UI/feature 모듈로 이동하는 게 좋습니다. 번들 최소화와 그래들 그래프 단순화에 도움이 됩니다.

다음 스크립트로 core/network 내 사용 여부를 빠르게 확인해 주세요:


🏁 Script executed:

#!/bin/bash
rg -nP '\bandroidx\.annotation\.Experimental\b|@Experimental\b' core/network || echo "No usages in core/network"

Length of output: 25


추가 검증을 위해 전체 코드베이스에서 @Experimental 사용 여부를 확인하겠습니다.


---

🏁 Script executed:

```shell
#!/bin/bash
rg -nP '@Experimental\b' --type=kt --type=java || echo "No @Experimental usages found in codebase"

Length of output: 68


core/network 모듈에서 androidx.annotation.experimental 의존성 제거 필요

검증 결과, @Experimental 어노테이션이 네트워크 모듈은 물론 전체 코드베이스에서 사용되지 않습니다. 이 의존성은 완전히 불필요하므로 삭제하시기 바랍니다.

    implementation(libs.androidx.annotation.experimental)  // 제거 필요

번들 크기 최소화와 Gradle 의존성 그래프 정리를 위해 이 라인을 삭제하세요.

🤖 Prompt for AI Agents
In core/network/build.gradle.kts around line 36, remove the unnecessary
dependency declaration for androidx.annotation.experimental; the network module
(and the whole codebase) does not use @Experimental annotations, so delete the
line "implementation(libs.androidx.annotation.experimental)" from the
dependencies block to reduce bundle size and simplify the Gradle dependency
graph.

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.teamwable.network.datasource

import com.teamwable.network.dto.response.main.ResponseNewsNumberDto
import com.teamwable.network.dto.response.news.ResponseCurationInfoDto
import com.teamwable.network.dto.response.news.ResponseGameTypeDto
import com.teamwable.network.dto.response.news.ResponseNewsCurationNumberDto
import com.teamwable.network.dto.response.news.ResponseNewsInfoDto
import com.teamwable.network.dto.response.news.ResponseNoticeInfoDto
import com.teamwable.network.dto.response.news.ResponseRankDto
Expand Down Expand Up @@ -32,4 +34,12 @@ interface NewsService {

@GET("api/v1/information/number")
suspend fun getNumber(): BaseResponse<ResponseNewsNumberDto>

@GET("api/v1/curation")
suspend fun getCurationInfo(
@Query(value = "cursor") contentId: Long = -1,
): BaseResponse<List<ResponseCurationInfoDto>>

@GET("api/v1/curation/number")
suspend fun getCurationNumber(): BaseResponse<ResponseNewsCurationNumberDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.teamwable.network.dto.response.news

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

@Serializable
data class ResponseCurationInfoDto(
@SerialName("curationId") val curationId: Long,
@SerialName("curationLink") val curationLink: String,
@SerialName("curationTitle") val curationTitle: String?,
@SerialName("curationThumbnail") val curationThumbnail: String?,
@SerialName("time") val time: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.teamwable.network.dto.response.news

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

@OptIn(InternalSerializationApi::class)
@Serializable
data class ResponseNewsCurationNumberDto(
@SerialName("curationId") val curationId: Long,
)
Comment on lines +7 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

@OptIn(InternalSerializationApi::class) 사용이 필요한지 확인해주세요.

단순한 데이터 클래스에 @OptIn(InternalSerializationApi::class)를 사용하고 있습니다. Internal API는 경고 없이 변경될 수 있어 안정성에 문제가 될 수 있습니다.

이 어노테이션을 제거하고도 정상적으로 직렬화가 동작하는지 확인해주세요. 일반적으로 @Serializable@SerialName만으로 충분합니다.


🏁 Script executed:

#!/bin/bash
# Description: Check if other DTOs in the project use @OptIn(InternalSerializationApi::class)

# Search for other DTO files to see the pattern
rg -n "@OptIn\(InternalSerializationApi" --type kotlin -C 3

Length of output: 1846


추가 검증을 위해 프로젝트의 다른 DTO 파일들을 확인하여 이 어노테이션 사용 패턴을 비교하겠습니다.

@OptIn(InternalSerializationApi::class) 제거 필요

이 어노테이션으로 표시된 API는 효과적으로 내부이며, kotlinx.serialization 외부에서 사용되어서는 안 되며, 호환성이 보장되지 않습니다. 단순한 데이터 클래스에서는 @serializable과 @SerialName 어노테이션만으로 충분합니다. 프로젝트의 다른 DTO 파일들을 확인하고 이 어노테이션 없이도 정상적으로 작동하는지 검증한 후 제거하시기 바랍니다.

@Serializable
data class ResponseNewsCurationNumberDto(
    @SerialName("curationId") val curationId: Long,
)
🤖 Prompt for AI Agents
In
core/network/src/main/java/com/teamwable/network/dto/response/news/ResponseNewsCurationNumberDto.kt
around lines 7 to 11, remove the unnecessary
@OptIn(InternalSerializationApi::class) annotation because this DTO only needs
@Serializable and @SerialName; update the file to leave @Serializable and
@SerialName on the data class, verify other DTOs follow the same pattern, and
run a build/tests to ensure serialization still works after removal.

28 changes: 26 additions & 2 deletions feature/main/src/main/java/com/teamwable/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import android.content.pm.ActivityInfo
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.NavDestination
Expand All @@ -19,12 +22,14 @@ import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.InstallStatus
import com.teamwable.common.uistate.UiState
import com.teamwable.common.util.AmplitudeHomeTag.CLICK_HOME_BOTNAVI
import com.teamwable.common.util.AmplitudeHomeTag.CLICK_MYPROFILE_BOTNAVI
import com.teamwable.common.util.AmplitudeHomeTag.CLICK_NEWS_BOTNAVI
import com.teamwable.common.util.AmplitudeUtil.trackEvent
import com.teamwable.home.HomeFragment
import com.teamwable.main.databinding.ActivityMainBinding
import com.teamwable.ui.extensions.colorOf
import com.teamwable.ui.extensions.setStatusBarColor
import com.teamwable.ui.extensions.showAlertDialog
import com.teamwable.ui.extensions.statusBarModeOf
Expand All @@ -33,6 +38,8 @@ import com.teamwable.ui.extensions.visible
import com.teamwable.ui.util.Navigation
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
import com.teamwable.viewit.R as viewitR
Expand All @@ -42,6 +49,7 @@ class MainActivity : AppCompatActivity(), Navigation {
private lateinit var binding: ActivityMainBinding
private lateinit var appUpdateHelper: AppUpdateHandler
private val appUpdateManager: AppUpdateManager by lazy { AppUpdateManagerFactory.create(this) }
private val viewModel: MainViewModel by viewModels()

private val activityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
Expand Down Expand Up @@ -100,6 +108,7 @@ class MainActivity : AppCompatActivity(), Navigation {

private fun initView() {
setBottomNavigation()
setupNewsRedDoteObserve()
}

private fun setBottomNavigation() {
Expand Down Expand Up @@ -206,14 +215,29 @@ class MainActivity : AppCompatActivity(), Navigation {
}
}

private fun setupNewsRedDoteObserve() {
viewModel.redDotUiState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).onEach { state ->
when (state) {
is UiState.Success -> setRedDotOnNews(state.data)
else -> Unit
}
}.launchIn(lifecycleScope)
}

private fun setRedDotOnNews(isVisible: Boolean) {
binding.bnvMain.getOrCreateBadge(com.teamwable.news.R.id.graph_news).apply {
this.isVisible = isVisible
horizontalOffset = 1
if (isVisible) backgroundColor = colorOf(com.teamwable.ui.R.color.error) else clearNumber()
}
}

private fun updateStatusBarColor(navController: NavController) {
// TODO : dark status bar 부분을 set에 넣어 주시면 됩니다!
val darkStatusBarDestinations = setOf(
com.teamwable.news.R.id.navigation_news_detail,
com.teamwable.news.R.id.navigation_news,
com.teamwable.community.R.id.navigation_community,
viewitR.id.navigation_view_it,
viewitR.id.navigation_view_it_posting,
com.teamwable.ui.R.id.navigation_feed_image_dialog,
com.teamwable.quiz.R.id.navigation_quiz_main,
)
Expand Down
Loading