diff --git a/app/src/main/java/com/jiachian/nbatoday/MainActivity.kt b/app/src/main/java/com/jiachian/nbatoday/MainActivity.kt index 4ea604fd..7b292c28 100644 --- a/app/src/main/java/com/jiachian/nbatoday/MainActivity.kt +++ b/app/src/main/java/com/jiachian/nbatoday/MainActivity.kt @@ -64,6 +64,9 @@ class MainActivity : ComponentActivity() { toastEventManager.eventFlow.collectWithLifecycle(this::onToastEvent) } + /** + * Handles navigation events received from the ViewModel. + */ private fun onNavigationEvent(event: NavigationController.Event?) { runOnUiThread { when (event) { @@ -102,6 +105,9 @@ class MainActivity : ComponentActivity() { viewModel.consumeNavigationEvent(event) } + /** + * Handles toast events received from the toast event manager. + */ private fun onToastEvent(event: ToastEvent?) { when (event) { ToastEvent.OnError -> showErrorToast() diff --git a/app/src/main/java/com/jiachian/nbatoday/MainApplication.kt b/app/src/main/java/com/jiachian/nbatoday/MainApplication.kt index ddbb4d68..6d420947 100644 --- a/app/src/main/java/com/jiachian/nbatoday/MainApplication.kt +++ b/app/src/main/java/com/jiachian/nbatoday/MainApplication.kt @@ -9,9 +9,9 @@ import org.koin.core.context.startKoin class MainApplication : Application() { companion object { - private var instance: Application? = null + private lateinit var instance: Application val context: Context - get() = instance!!.applicationContext + get() = instance.applicationContext } override fun onCreate() { diff --git a/app/src/main/java/com/jiachian/nbatoday/MainViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/MainViewModel.kt index de8f73cc..be238987 100644 --- a/app/src/main/java/com/jiachian/nbatoday/MainViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/MainViewModel.kt @@ -28,6 +28,15 @@ class MainViewModel( loadData() } + /** + * Loads data asynchronously. + * 1. update schedules + * 2. update teams + * 3. update theme colors + * 4. log in the user (if available) + * + * Finally, navigate to the home screen after loading data + */ private fun loadData() { viewModelScope.launch(dispatcherProvider.io) { val updateScheduleDeferred = async { diff --git a/app/src/main/java/com/jiachian/nbatoday/annotation/ExcludeFromJacocoGeneratedReport.kt b/app/src/main/java/com/jiachian/nbatoday/annotation/ExcludeFromJacocoGeneratedReport.kt index b40c04e8..a80f5571 100644 --- a/app/src/main/java/com/jiachian/nbatoday/annotation/ExcludeFromJacocoGeneratedReport.kt +++ b/app/src/main/java/com/jiachian/nbatoday/annotation/ExcludeFromJacocoGeneratedReport.kt @@ -1,5 +1,10 @@ package com.jiachian.nbatoday.annotation +/** + * This annotation is used to mark functions or classes that should be excluded from code coverage analysis + * performed by Jacoco. It is particularly useful when you want to skip specific parts of your codebase + * from being included in the coverage report. + */ @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) annotation class ExcludeFromJacocoGeneratedReport diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/coil/SvgRequest.kt b/app/src/main/java/com/jiachian/nbatoday/compose/coil/SvgRequest.kt index 7951315b..c5241ff3 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/coil/SvgRequest.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/coil/SvgRequest.kt @@ -4,6 +4,18 @@ import android.content.Context import coil.decode.SvgDecoder import coil.request.ImageRequest +/** + * A builder class for creating [ImageRequest] instances specifically for loading SVG images using Coil. + * + * This class provides a convenient way to construct [ImageRequest] instances tailored for SVG images. + * + * @usage: + * AsyncImage( + * model = SvgRequest.Builder(LocalContext.current) + * .data(`svg data source`) + * .build() + * ) + */ class SvgRequest private constructor() { class Builder(context: Context) { diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/ComposeViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/ComposeViewModel.kt index 1c5d7f7a..c1722bc7 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/ComposeViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/ComposeViewModel.kt @@ -5,11 +5,21 @@ import com.jiachian.nbatoday.navigation.NavigationController import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel +/** + * Base ViewModel for Composable in this App. + * + * @property coroutineScope The coroutine scope associated with the ViewModel. + * @property navigationController The navigation controller responsible for screen navigation. + * @property route The route representing the current screen, nullable if not applicable. + */ open class ComposeViewModel( protected val coroutineScope: CoroutineScope, protected val navigationController: NavigationController, private val route: MainRoute?, ) { + /** + * Closes the current screen by canceling the coroutine scope and navigating back if a route is specified. + */ open fun close() { coroutineScope.cancel() route?.also { diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetDialog.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetDialog.kt index efd2edef..fde87ed5 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetDialog.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetDialog.kt @@ -113,7 +113,7 @@ private fun BetDialogDetail( homePoints: Long, awayPoints: Long ) { - val remainedPoints by viewModel.remainedPoints.collectAsState() + val remainingPoint by viewModel.remainingPoints.collectAsState() Column( modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally @@ -146,7 +146,7 @@ private fun BetDialogDetail( modifier = Modifier .testTag(BetTestTag.BetDialogDetail_Text_Remainder) .padding(top = 16.dp, start = 16.dp, end = 16.dp), - text = stringResource(R.string.bet_remain, remainedPoints), + text = stringResource(R.string.bet_remain, remainingPoint), color = MaterialTheme.colors.primary, fontSize = 12.sp, ) diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetDialogViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetDialogViewModel.kt index ee924e84..30ac4183 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetDialogViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetDialogViewModel.kt @@ -12,29 +12,42 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +/** + * ViewModel for handling business logic related to [BetDialog]. + * + * @property gameAndBets The data class containing information about the game and associated bets. + * @property userPoints The total points of the user available for betting. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class BetDialogViewModel( val gameAndBets: GameAndBets, val userPoints: Long, dispatcherProvider: DispatcherProvider = DefaultDispatcherProvider, coroutineScope: CoroutineScope = CoroutineScope(dispatcherProvider.unconfined), ) { + // the warning state in the UI private val warningImp = MutableStateFlow(false) val warning = warningImp.asStateFlow() + // the selected home points in the UI private val homePointsImp = MutableStateFlow(0L) val homePoints = homePointsImp.asStateFlow() + // the selected away points in the UI private val awayPointsImp = MutableStateFlow(0L) val awayPoints = awayPointsImp.asStateFlow() - val remainedPoints = combine( + // the remaining points available for betting + val remainingPoints = combine( homePoints, awayPoints ) { home, away -> userPoints - home - away }.stateIn(coroutineScope, SharingStarted.Lazily, userPoints) - val enabled = remainedPoints.map { points -> + // Determine if the bet button should be enabled based on [remainingPoints]. + val enabled = remainingPoints.map { points -> points != userPoints && points >= 0 }.stateIn(coroutineScope, SharingStarted.Lazily, false) diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetViewModel.kt index cc7a0102..250ad630 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/bet/BetViewModel.kt @@ -41,6 +41,15 @@ private const val ThirdSectorMaxAngle = 269f private const val MaxMagnification = 4 +/** + * ViewModel for handling business logic related to [BetScreen]. + * + * @property account The user account associated with the logged-in user. + * @property repository The repository for interacting with [BetAndGame]. + * @property navigationController The controller for navigation within the app. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class BetViewModel( account: String, private val repository: BetRepository, @@ -52,26 +61,37 @@ class BetViewModel( navigationController = navigationController, route = MainRoute.Bet ) { + // state representing the stats of [BetAndGame] val betsAndGamesState = repository .getBetsAndGames(account) .map { UIState.Loaded(it) } .stateIn(coroutineScope, SharingStarted.Lazily, UIState.Loading()) + // the current points on the turn table private val turnTablePointsImp = MutableStateFlow(null) val turnTablePoints = turnTablePointsImp.asStateFlow() + // whether the turn table is currently running private val turnTableRunningImp = MutableStateFlow(false) val turnTableRunning = turnTableRunningImp.asStateFlow() + // the current angle of the turn table private val turnTableAngleImp = MutableStateFlow(0f) val turnTableAngle = turnTableAngleImp.asStateFlow() + // the visibility of the turn table private val turnTableVisibleImp = MutableStateFlow(false) val turnTableVisible = turnTableVisibleImp.asStateFlow() + // the points rewarded after the turn table animation private val rewardedPointsImp = MutableStateFlow(null) val rewardedPoints = rewardedPointsImp.asStateFlow() + /** + * Handles the click event on a specific [BetAndGame]. + * + * @param betAndGame The clicked [BetAndGame]. + */ fun clickBetAndGame(betAndGame: BetAndGame) { when (betAndGame.game.gameStatus) { GameStatus.COMING_SOON -> { @@ -107,6 +127,11 @@ class BetViewModel( turnTableVisibleImp.value = true } + /** + * Starts the turn table animation with the specified points configuration. + * + * @param turnTablePoints The points configuration for the turn table. + */ fun startTurnTable(turnTablePoints: TurnTablePoints) { coroutineScope.launch(dispatcherProvider.io) { turnTableRunningImp.value = true diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/calendar/CalendarViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/calendar/CalendarViewModel.kt index 2e85e7d4..323d0b6f 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/calendar/CalendarViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/calendar/CalendarViewModel.kt @@ -31,6 +31,16 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +/** + * ViewModel for handling business logic related to [CalendarScreen]. + * + * @property dateTime The initial date and time for the calendar. + * @property repository The repository for interacting with [GameAndBets]. + * @property navigationController The controller for navigation within the app. + * @property composeViewModelProvider The provider for creating ComposeViewModel instances. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class CalendarViewModel( dateTime: Long, private val repository: GameRepository, @@ -48,12 +58,15 @@ class CalendarViewModel( private val selectedDateImp = MutableStateFlow(Date(dateTime)) val selectedDate = selectedDateImp.asStateFlow() + // the list of games for the selected date private val selectedGamesImp = MutableStateFlow(emptyList()) val selectedGames = selectedGamesImp.asStateFlow() + // the loading status of [selectedGames] private val loadingGamesImp = MutableStateFlow(false) val loadingGames = loadingGamesImp.asStateFlow() + // the loading status of [calendarDates] private val loadingCalendar = MutableStateFlow(false) private val lastGameDate = repository.getLastGameDateTime() @@ -61,6 +74,7 @@ class CalendarViewModel( private val firstGameDate = repository.getFirstGameDateTime() .stateIn(coroutineScope, SharingStarted.Lazily, Date(dateTime)) + // Initialize the [currentCalendar] based on the provided date and time. init { currentCalendar = DateUtils.getCalendar().let { it.timeInMillis = dateTime @@ -147,6 +161,9 @@ class CalendarViewModel( selectedDateImp.value = date } + /** + * Moves to the next month in the calendar. + */ fun nextMonth() { if (hasNextMonth()) { currentCalendar.value = DateUtils.getCalendar().apply { @@ -156,6 +173,9 @@ class CalendarViewModel( } } + /** + * Moves to the previous month in the calendar. + */ fun lastMonth() { if (hasLastMonth()) { currentCalendar.value = DateUtils.getCalendar().apply { @@ -165,6 +185,12 @@ class CalendarViewModel( } } + /** + * Retrieves the list of dates for the current calendar month. + * + * @param calendar The calendar instance representing the current month. + * @return List of CalendarDate objects representing each day in the month. + */ private fun getCalendarDates(calendar: Calendar): List { val year = calendar.get(Calendar.YEAR) val month = calendar.get(Calendar.MONTH) @@ -200,6 +226,11 @@ class CalendarViewModel( } } + /** + * Click event handler for a game card. + * + * @param game The selected game. + */ fun clickGameCard(game: Game) { if (game.gamePlayed) { navigationController.navigateToBoxScore(game.gameId) diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/card/GameCardViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/card/GameCardViewModel.kt index 6a9d8318..dcfc81f7 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/card/GameCardViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/card/GameCardViewModel.kt @@ -16,6 +16,15 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +/** + * ViewModel for handling business logic related to [GameCard]. + * + * @property gameAndBets The GameAndBets instance associated with the game card. + * @property betRepository The repository for interacting with [GameAndBets]. + * @property userRepository The repository for interacting with [User]. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class GameCardViewModel( val gameAndBets: GameAndBets, private val betRepository: BetRepository, @@ -23,21 +32,27 @@ class GameCardViewModel( private val dispatcherProvider: DispatcherProvider = DefaultDispatcherProvider, private val coroutineScope: CoroutineScope = CoroutineScope(dispatcherProvider.unconfined), ) { + // logged-in user. private val user = userRepository.user + // the user's points private val userPoints = user.map { it?.points ?: 0 }.stateIn(coroutineScope, SharingStarted.Eagerly, 0) + // whether the user is logged in val login = user .map { it != null } .stateIn(coroutineScope, SharingStarted.Lazily, false) + // the expanded status of the game card private val expandedImp = MutableStateFlow(false) val expanded = expandedImp.asStateFlow() + // whether the game has been played val gamePlayed = gameAndBets.game.gamePlayed + // whether the user has placed a bet on the game val hasBet = user.map { user -> user ?: return@map false gameAndBets.bets.any { it.account == user.account } @@ -45,6 +60,7 @@ class GameCardViewModel( .flowOn(dispatcherProvider.io) .stateIn(coroutineScope, SharingStarted.Lazily, true) + // whether the user can place a bet on the game val betAvailable = hasBet.map { !gamePlayed && !it }.stateIn(coroutineScope, SharingStarted.Lazily, true) @@ -54,21 +70,40 @@ class GameCardViewModel( val homeLeader = leaders?.homeLeader ?: GameLeaders.GameLeader.default() val awayLeader = leaders?.awayLeader ?: GameLeaders.GameLeader.default() + // the visibility of the bet dialog private val betDialogVisibleImp = MutableStateFlow(false) val betDialogVisible = betDialogVisibleImp.asStateFlow() + /** + * Performs user login with the provided account and password. + * + * @param account The user account. + * @param password The user password. + */ fun login(account: String, password: String) { coroutineScope.launch(dispatcherProvider.io) { userRepository.login(account, password) } } + /** + * Performs user registration with the provided account and password. + * + * @param account The user account. + * @param password The user password. + */ fun register(account: String, password: String) { coroutineScope.launch(dispatcherProvider.io) { userRepository.register(account, password) } } + /** + * Places a bet on the game with the specified home and away points. + * + * @param homePoints The number of points to bet on the home team. + * @param awayPoints The number of points to bet on the away team. + */ fun bet(homePoints: Long, awayPoints: Long) { coroutineScope.launch(dispatcherProvider.io) { betRepository.insertBet(gameAndBets.game.gameId, homePoints, awayPoints) diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/HomeViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/HomeViewModel.kt index 5b583398..c0d5b74d 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/HomeViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/HomeViewModel.kt @@ -9,6 +9,14 @@ import com.jiachian.nbatoday.navigation.NavigationController import com.jiachian.nbatoday.utils.ComposeViewModelProvider import kotlinx.coroutines.CoroutineScope +/** + * ViewModel for handling business logic related to [HomeScreen]. + * + * @property composeViewModelProvider The provider for creating ComposeViewModel instances. + * @property navigationController The controller for navigation within the app. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class HomeViewModel( private val composeViewModelProvider: ComposeViewModelProvider, navigationController: NavigationController, @@ -19,6 +27,7 @@ class HomeViewModel( navigationController = navigationController, route = MainRoute.Home ) { + // list of pages in the home screen val pages = HomePage.values() val schedulePageViewModel by lazy { diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/schedule/SchedulePageViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/schedule/SchedulePageViewModel.kt index 9df77781..c40e8e2f 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/schedule/SchedulePageViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/schedule/SchedulePageViewModel.kt @@ -27,6 +27,16 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +/** + * ViewModel for handling business logic related to [SchedulePage]. + * + * @property scheduleRepository The repository for interacting with [GameAndBets]. + * @property gameRepository The repository for interacting with [GameAndBets]. + * @property navigationController The controller for navigation within the app. + * @property composeViewModelProvider The provider for creating ComposeViewModel instances. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class SchedulePageViewModel( private val scheduleRepository: ScheduleRepository, private val gameRepository: GameRepository, @@ -35,10 +45,13 @@ class SchedulePageViewModel( private val dispatcherProvider: DispatcherProvider = DefaultDispatcherProvider, private val coroutineScope: CoroutineScope = CoroutineScope(dispatcherProvider.unconfined) ) { + // the date data for the schedule page on the TabBar val dateData = createDateData() + // selected date for the schedule page private var selectedDate = dateData[dateData.size / 2] + // the list of games within the specified date range private val games = DateUtils.getCalendar().run { set(Calendar.HOUR, 0) set(Calendar.MINUTE, 0) @@ -49,6 +62,7 @@ class SchedulePageViewModel( ) } + // the grouped games based on their date val groupedGamesState = games .map { UIState.Loaded(getGroupedGames(it)) } .flowOn(dispatcherProvider.io) @@ -59,6 +73,11 @@ class SchedulePageViewModel( private val gameCardViewModelMap = mutableMapOf() + /** + * Creates a list of date data based on the specified date range. + * + * @return List of DateData instances. + */ private fun createDateData(): List { val range = ScheduleDateRange * 2 + 1 return DateUtils.getCalendar().run { @@ -75,6 +94,12 @@ class SchedulePageViewModel( } } + /** + * Groups the games based on their date. + * + * @param games List of GameAndBets instances. + * @return Map with DateData as keys and lists of GameAndBets as values. + */ private fun getGroupedGames(games: List): Map> { return DateUtils.getCalendar().run { games.groupBy { game -> @@ -92,6 +117,9 @@ class SchedulePageViewModel( selectedDate = dateData } + /** + * Updates the selected schedule based on the chosen date. + */ fun updateSelectedSchedule() { if (refreshing.value) return coroutineScope.launch(dispatcherProvider.io) { @@ -105,6 +133,11 @@ class SchedulePageViewModel( } } + /** + * Handles click event for a game, navigating to the team or box score screen. + * + * @param game The selected GameAndBets instance. + */ fun onClickGame(game: GameAndBets) { if (!game.game.gamePlayed) { navigationController.navigateToTeam(game.game.homeTeam.team.teamId) @@ -113,6 +146,9 @@ class SchedulePageViewModel( } } + /** + * Handles click event for the calendar, navigating to the selected date on the calendar screen. + */ @SuppressLint("SimpleDateFormat") fun onClickCalendar() { SimpleDateFormat("yyyy/MM/dd").let { format -> diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/standing/StandingPageViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/standing/StandingPageViewModel.kt index 7264ecdf..38146478 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/standing/StandingPageViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/standing/StandingPageViewModel.kt @@ -23,6 +23,14 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +/** + * ViewModel for handling business logic related to [StandingPage]. + * + * @property repository The repository for interacting with [Team]. + * @property navigationController The controller for navigation within the app. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class StandingPageViewModel( private val repository: TeamRepository, private val navigationController: NavigationController, @@ -32,14 +40,17 @@ class StandingPageViewModel( private val eastTeams = repository.getTeams(NBATeam.Conference.EAST) private val westTeams = repository.getTeams(NBATeam.Conference.WEST) + // list of available labels for the standing page val labels = StandingLabel.values() val conferences = NBATeam.Conference.values() + // the sorting criteria for the Eastern and Western conferences private val eastSortingImp = MutableStateFlow(StandingSorting.WINP) private val westSortingImp = MutableStateFlow(StandingSorting.WINP) val eastSorting = eastSortingImp.asStateFlow() val westSorting = westSortingImp.asStateFlow() + // the row data for the Eastern and Western conferences private val eastRowData = eastTeams.map { teams -> teams.toRowData() }.flowOn(dispatcherProvider.io) @@ -47,6 +58,7 @@ class StandingPageViewModel( teams.toRowData() }.flowOn(dispatcherProvider.io) + // the sorted row data for the Eastern and Western conference. val sortedEastRowDataState = combine( eastRowData, eastSorting @@ -64,11 +76,15 @@ class StandingPageViewModel( .flowOn(dispatcherProvider.io) .stateIn(coroutineScope, SharingStarted.Lazily, UIState.Loading()) + // the refreshing status of the standing page private val refreshingImp = MutableStateFlow(false) val refreshing = refreshingImp.asStateFlow() private var selectedConference = NBATeam.Conference.EAST + /** + * Updates team statistics by fetching the latest data from the repository. + */ fun updateTeamStats() { if (refreshing.value) return coroutineScope.launch(dispatcherProvider.io) { @@ -82,6 +98,11 @@ class StandingPageViewModel( selectedConference = conference } + /** + * Updates the sorting criteria based on the selected conference. + * + * @param sorting The selected sorting criteria. + */ fun updateSorting(sorting: StandingSorting) { when (selectedConference) { NBATeam.Conference.EAST -> eastSortingImp.value = sorting @@ -89,6 +110,11 @@ class StandingPageViewModel( } } + /** + * Handles click event for a team, navigating to the team details screen. + * + * @param team The selected NBATeam instance. + */ fun onClickTeam(team: NBATeam) { navigationController.navigateToTeam(team.teamId) } diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/user/UserPageViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/user/UserPageViewModel.kt index d7682299..99ae337f 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/user/UserPageViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/home/user/UserPageViewModel.kt @@ -15,6 +15,15 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +/** + * ViewModel for handling business logic related to [UserPage]. + * + * @property repository The repository for interacting with [User]. + * @property dataStore The data store for managing user preferences. + * @property navigationController The controller for navigation within the app. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class UserPageViewModel( private val repository: UserRepository, private val dataStore: BaseDataStore, @@ -22,8 +31,10 @@ class UserPageViewModel( private val dispatcherProvider: DispatcherProvider = DefaultDispatcherProvider, private val coroutineScope: CoroutineScope = CoroutineScope(dispatcherProvider.unconfined) ) { + // logged-in user private val user = repository.user + // the UIState of user val userState = user.map { user -> UIState.Loaded(user) }.stateIn(coroutineScope, SharingStarted.Lazily, UIState.Loading()) @@ -32,16 +43,25 @@ class UserPageViewModel( it?.account }.stateIn(coroutineScope, SharingStarted.Eagerly, null) + // list of available NBA teams for theming val teams: List = mutableListOf().apply { add(teamOfficial) addAll(NBATeam.nbaTeams) } + /** + * Handles click event for navigating to the bet page. + */ fun onBetClick() { val account = account.value ?: return navigationController.navigateToBet(account) } + /** + * Updates the app theme based on the selected NBA team's colors. + * + * @param team The selected NBA team. + */ fun updateTheme(team: NBATeam) { coroutineScope.launch(dispatcherProvider.io) { updateColors(team.colors) diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/label/LabelHelper.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/label/LabelHelper.kt index 9a412dae..8b644b5b 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/label/LabelHelper.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/label/LabelHelper.kt @@ -14,6 +14,9 @@ import com.jiachian.nbatoday.models.local.team.Team import com.jiachian.nbatoday.models.local.team.TeamPlayer import com.jiachian.nbatoday.utils.decimalFormat +/** + * Helper object for obtaining values based on different labels for various UI components. + */ object LabelHelper { fun getValueByLabel( label: StandingLabel, diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/player/PlayerViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/player/PlayerViewModel.kt index ab52b79c..5a094ed8 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/player/PlayerViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/player/PlayerViewModel.kt @@ -24,6 +24,15 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +/** + * ViewModel for handling business logic related to [PlayerScreen]. + * + * @param playerId The ID of the player for whom details are displayed. + * @param repository The repository for interacting with [Player]. + * @property navigationController The controller for navigation within the app. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class PlayerViewModel( private val playerId: Int, private val repository: PlayerRepository, @@ -35,6 +44,7 @@ class PlayerViewModel( navigationController = navigationController, route = MainRoute.Player ) { + // Update player data into the repository init { coroutineScope.launch(dispatcherProvider.io) { repository.insertPlayer(playerId) @@ -46,6 +56,7 @@ class PlayerViewModel( private val sortingImp = MutableStateFlow(PlayerStatsSorting.TIME_FRAME) val sorting = sortingImp.asStateFlow() + // labels for player stats and info val statsLabels = PlayerStatsLabel.values() private val tableLabels = PlayerTableLabel.values() @@ -111,6 +122,11 @@ class PlayerViewModel( UIState.Loaded(playerUI) }.stateIn(coroutineScope, SharingStarted.Lazily, UIState.Loading()) + /** + * Update the sorting criteria for player stats. + * + * @param sorting The new sorting criteria. + */ fun updateSorting(sorting: PlayerStatsSorting) { sortingImp.value = sorting } diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/score/BoxScoreViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/score/BoxScoreViewModel.kt index f5fb87f0..9b5d609a 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/score/BoxScoreViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/score/BoxScoreViewModel.kt @@ -26,6 +26,15 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +/** + * ViewModel for handling business logic related to [BoxScoreScreen]. + * + * @param gameId The ID of the game for which the box score is displayed. + * @param repository The repository for interacting with [Game]. + * @property navigationController The controller for navigation within the app. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class BoxScoreViewModel( private val gameId: String, private val repository: GameRepository, @@ -37,12 +46,14 @@ class BoxScoreViewModel( navigationController = navigationController, route = MainRoute.BoxScore ) { + // Update box score data into the repository init { coroutineScope.launch(dispatcherProvider.io) { repository.insertBoxScore(gameId) } } + // labels for box score player, team, and leader sections val playerLabels = BoxScorePlayerLabel.values() private val teamLabels = BoxScoreTeamLabel.values() private val leaderLabels = BoxScoreLeaderLabel.values() @@ -52,6 +63,7 @@ class BoxScoreViewModel( it?.boxScore } + // date of the game (e.g. 2023-1-1) val date = boxScore.map { it?.gameDate ?: "" }.stateIn(coroutineScope, SharingStarted.Lazily, "") @@ -196,6 +208,9 @@ class BoxScoreViewModel( selectedPlayerLabelImp.value = label } + /** + * Navigate to the player screen + */ fun openPlayerInfo(playerId: Int) { navigationController.navigateToPlayer(playerId) } diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/state/UIState.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/state/UIState.kt index dd918859..e2358e41 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/state/UIState.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/state/UIState.kt @@ -1,5 +1,11 @@ package com.jiachian.nbatoday.compose.screen.state +/** + * Make it clear and easy to handle loading and loaded states. + * + * @usage + * Used in conjunction with [UIStateScreen] + */ sealed class UIState { class Loading : UIState() class Loaded(val data: T?) : UIState() diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/screen/team/TeamViewModel.kt b/app/src/main/java/com/jiachian/nbatoday/compose/screen/team/TeamViewModel.kt index cb47ba78..16065aa6 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/screen/team/TeamViewModel.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/screen/team/TeamViewModel.kt @@ -32,6 +32,17 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +/** + * ViewModel for handling business logic related to [TeamScreen]. + * + * @param teamId The ID of the team for which details are displayed. + * @param teamRepository The repository for interacting with [Team]. + * @param gameRepository The repository for interacting with [Game]. + * @property navigationController The controller for navigation within the app. + * @property composeViewModelProvider The provider for creating ComposeViewModel instances. + * @property dispatcherProvider The provider for obtaining dispatchers for coroutines (default is [DefaultDispatcherProvider]). + * @property coroutineScope The coroutine scope for managing coroutines (default is [CoroutineScope] with unconfined dispatcher). + */ class TeamViewModel( teamId: Int, private val teamRepository: TeamRepository, @@ -48,6 +59,7 @@ class TeamViewModel( private val team = NBATeam.getTeamById(teamId) val colors = team.colors + // Asynchronous initialization to update teams and team players. init { coroutineScope.launch(dispatcherProvider.io) { val deferred1 = async { teamRepository.insertTeams() } @@ -58,6 +70,9 @@ class TeamViewModel( val labels = TeamPlayerLabel.values() + /** + * the games before/after today. + */ val gamesBefore = gameRepository.getGamesAndBetsBefore( team.teamId, DateUtils.getCalendar().apply { @@ -134,14 +149,29 @@ class TeamViewModel( private val gameCardViewModelMap = mutableMapOf() + /** + * Update the player sorting based on the provided [sorting] criteria. + * + * @param sorting The sorting criteria for team players. + */ fun updatePlayerSorting(sorting: TeamPlayerSorting) { playerSortingImp.value = sorting } + /** + * Handle the click event on a game card and navigates to the box score screen. + * + * @param game The clicked game. + */ fun onGameCardClick(game: Game) { navigationController.navigateToBoxScore(game.gameId) } + /** + * Handle the click event on a player and navigates to the player screen. + * + * @param playerId The ID of the clicked player. + */ fun onPlayerClick(playerId: Int) { navigationController.navigateToPlayer(playerId) } diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/theme/NBAColors.kt b/app/src/main/java/com/jiachian/nbatoday/compose/theme/NBAColors.kt index f47444e6..3ddb8882 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/theme/NBAColors.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/theme/NBAColors.kt @@ -4,6 +4,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color +/** + * Indicate the current app theme color scheme and apply it to MaterialTheme.colors. + * + * Refer to [Color.kt] to check which colors are available. + */ class NBAColors( primary: Color, secondary: Color, diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/theme/Theme.kt b/app/src/main/java/com/jiachian/nbatoday/compose/theme/Theme.kt index ca8b916a..964eb767 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/theme/Theme.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/theme/Theme.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue var ColorPalette by mutableStateOf(LakersColors) + private set @VisibleForTesting get @Composable diff --git a/app/src/main/java/com/jiachian/nbatoday/compose/widget/CommonWidget.kt b/app/src/main/java/com/jiachian/nbatoday/compose/widget/CommonWidget.kt index d1f9e236..070c65ab 100644 --- a/app/src/main/java/com/jiachian/nbatoday/compose/widget/CommonWidget.kt +++ b/app/src/main/java/com/jiachian/nbatoday/compose/widget/CommonWidget.kt @@ -52,6 +52,13 @@ import com.jiachian.nbatoday.utils.NBAUtils import com.jiachian.nbatoday.utils.color import com.jiachian.nbatoday.utils.noRippleClickable +/** + * Displays a loading screen with a circular progress indicator. + * + * @param modifier The modifier for the loading screen. + * @param color The color of the circular progress indicator. + * @param interceptBack Whether to intercept the back button press. + */ @Composable fun LoadingScreen( modifier: Modifier = Modifier, @@ -67,6 +74,9 @@ fun LoadingScreen( BackHandler(interceptBack) {} } +/** + * Custom implementation of an outlined text field with additional customization options. + */ @Composable fun CustomOutlinedTextField( value: String, @@ -185,6 +195,12 @@ fun IconButton( } } +/** + * Displays the team logo image using an AsyncImage. + * + * @param modifier The modifier for the team logo image. + * @param team The NBA team for which to display the logo. + */ @Composable fun TeamLogoImage( modifier: Modifier = Modifier, @@ -201,6 +217,12 @@ fun TeamLogoImage( ) } +/** + * Displays the player image using an AsyncImage. + * + * @param modifier The modifier for the player image. + * @param playerId The ID of the player for which to display the image. + */ @Composable fun PlayerImage( modifier: Modifier = Modifier, @@ -217,6 +239,13 @@ fun PlayerImage( ) } +/** + * Conditional rendering based on nullability of data. + * + * @param data The data to be checked for null. + * @param ifNull Content to be displayed if data is null. + * @param ifNotNull Content to be displayed if data is not null. + */ @Composable fun NullCheckScreen( data: T?, @@ -228,6 +257,14 @@ fun NullCheckScreen( } ?: ifNull?.invoke() } +/** + * Conditional rendering based on the state of UI data. + * + * @param state The UIState representing the state of the data. + * @param loading Content to be displayed when data is in a loading state. + * @param ifNull Content to be displayed when data is null. + * @param ifNotNull Content to be displayed when data is not null. + */ @Composable fun UIStateScreen( state: UIState, @@ -238,13 +275,22 @@ fun UIStateScreen( when (state) { is UIState.Loading -> loading?.invoke() is UIState.Loaded -> { - state.data?.let { data -> - ifNotNull?.invoke(data) - } ?: ifNull?.invoke() + NullCheckScreen( + data = state.data, + ifNull = ifNull, + ifNotNull = ifNotNull + ) } } } +/** + * Animate the expansion of content. + * + * @param modifier The modifier for the AnimatedExpand. + * @param visible Whether the content is currently visible. + * @param content The content to be animated. + */ @Composable fun AnimatedExpand( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/jiachian/nbatoday/database/converter/NBATeamTypeAdapter.kt b/app/src/main/java/com/jiachian/nbatoday/database/converter/NBATeamTypeAdapter.kt index d7b24b72..7d39654d 100644 --- a/app/src/main/java/com/jiachian/nbatoday/database/converter/NBATeamTypeAdapter.kt +++ b/app/src/main/java/com/jiachian/nbatoday/database/converter/NBATeamTypeAdapter.kt @@ -12,6 +12,9 @@ import com.google.gson.JsonSerializer import com.jiachian.nbatoday.models.local.team.NBATeam import java.lang.reflect.Type +/** + * Gson instance with a custom TypeAdapter for serializing and deserializing NBATeam objects. + */ val typeAdapterGson: Gson = GsonBuilder() .registerTypeAdapter(NBATeam::class.java, NBATeamTypeAdapter()) .create() diff --git a/app/src/main/java/com/jiachian/nbatoday/database/dao/BetDao.kt b/app/src/main/java/com/jiachian/nbatoday/database/dao/BetDao.kt index 404bb35a..4c9837a8 100644 --- a/app/src/main/java/com/jiachian/nbatoday/database/dao/BetDao.kt +++ b/app/src/main/java/com/jiachian/nbatoday/database/dao/BetDao.kt @@ -9,17 +9,41 @@ import com.jiachian.nbatoday.models.local.bet.Bet import com.jiachian.nbatoday.models.local.bet.BetAndGame import kotlinx.coroutines.flow.Flow +/** + * Handle operations related to the Bet entity in the database. + */ @Dao interface BetDao { + /** + * Retrieves a flow of all bets and associated games from the database. + * + * @return Flow emitting a list of [BetAndGame]. + */ @Query("SELECT * FROM bet") fun getBetsAndGames(): Flow> + /** + * Retrieves a flow of bets and associated games for a specific account from the database. + * + * @param account The account for which to retrieve bets. + * @return Flow emitting a list of [BetAndGame]. + */ @Query("SELECT * FROM bet WHERE bet_account == :account") fun getBetsAndGames(account: String): Flow> + /** + * Inserts a new bet into the database, ignoring conflicts. + * + * @param bet The [Bet] object to be inserted. + */ @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insertBet(bet: Bet) + /** + * Deletes a bet from the database. + * + * @param bet The [Bet] object to be deleted. + */ @Delete suspend fun deleteBet(bet: Bet) } diff --git a/app/src/main/java/com/jiachian/nbatoday/database/dao/BoxScoreDao.kt b/app/src/main/java/com/jiachian/nbatoday/database/dao/BoxScoreDao.kt index 5e840a3f..c747ed29 100644 --- a/app/src/main/java/com/jiachian/nbatoday/database/dao/BoxScoreDao.kt +++ b/app/src/main/java/com/jiachian/nbatoday/database/dao/BoxScoreDao.kt @@ -8,11 +8,25 @@ import com.jiachian.nbatoday.models.local.score.BoxScore import com.jiachian.nbatoday.models.local.score.BoxScoreAndGame import kotlinx.coroutines.flow.Flow +/** + * Handle operations related to the BoxScore entity in the database. + */ @Dao interface BoxScoreDao { + /** + * Retrieves a flow of BoxScoreAndGame for a specific game ID from the database. + * + * @param gameId The ID of the game for which to retrieve the BoxScoreAndGame. + * @return Flow emitting a nullable [BoxScoreAndGame]. + */ @Query("SELECT * FROM score WHERE game_id == :gameId") fun getBoxScoreAndGame(gameId: String): Flow + /** + * Inserts or replaces a BoxScore entry in the database. + * + * @param boxScore The [BoxScore] object to be inserted or replaced. + */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertBoxScore(boxScore: BoxScore) } diff --git a/app/src/main/java/com/jiachian/nbatoday/database/dao/GameDao.kt b/app/src/main/java/com/jiachian/nbatoday/database/dao/GameDao.kt index 48dfe17f..385a7788 100644 --- a/app/src/main/java/com/jiachian/nbatoday/database/dao/GameDao.kt +++ b/app/src/main/java/com/jiachian/nbatoday/database/dao/GameDao.kt @@ -12,35 +12,94 @@ import com.jiachian.nbatoday.models.local.game.GameUpdateData import java.util.Date import kotlinx.coroutines.flow.Flow +/** + * Handle operations related to the Game entity in the database. + */ @Dao interface GameDao { + /** + * Retrieves a flow of all games and associated bets from the database. + * + * @return Flow emitting a list of [GameAndBets]. + */ @Query("SELECT * FROM game") fun getGamesAndBets(): Flow> + /** + * Retrieves a flow of games and associated bets before a specific date for a given team from the database. + * + * @param teamId The ID of the team for which to retrieve games. + * @param from The starting date for filtering games. + * @return Flow emitting a list of [GameAndBets]. + */ @Query("SELECT * FROM game WHERE game_date <= :from AND (home_team_id == :teamId OR away_team_id == :teamId)") fun getGamesAndBetsBefore(teamId: Int, from: Long): Flow> + /** + * Retrieves a flow of games and associated bets after a specific date for a given team from the database. + * + * @param teamId The ID of the team for which to retrieve games. + * @param from The starting date for filtering games. + * @return Flow emitting a list of [GameAndBets]. + */ @Query("SELECT * FROM game WHERE game_date > :from AND (home_team_id == :teamId OR away_team_id == :teamId)") fun getGamesAndBetsAfter(teamId: Int, from: Long): Flow> + /** + * Retrieves a flow of games and associated bets within a specific date range from the database. + * + * @param from The starting date for filtering games. + * @param to The ending date for filtering games. + * @return Flow emitting a list of [GameAndBets]. + */ @Query("SELECT * FROM game WHERE game_date >= :from AND game_date <= :to") fun getGamesAndBetsDuring(from: Long, to: Long): Flow> + /** + * Retrieves the maximum game date from the database. + * + * @return Flow emitting a nullable [Date]. + */ @Query("SELECT MAX(game_date_time) FROM game") fun getLastGameDateTime(): Flow + /** + * Retrieves the minimum game date from the database. + * + * @return Flow emitting a nullable [Date]. + */ @Query("SELECT MIN(game_date_time) FROM game") fun getFirstGameDateTime(): Flow + /** + * Checks if there are any games in the database. + * + * @return True if games exist; false otherwise. + */ @Query("SELECT EXISTS (SELECT 1 FROM game)") suspend fun gameExists(): Boolean + /** + * Inserts a list of games into the database, replacing any existing entries on conflict. + * + * @param games The list of [Game] objects to be inserted. + */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertGames(games: List) + /** + * Updates a list of games in the database. + * + * @param games The list of [GameUpdateData] objects containing updated game information. + */ @Update(entity = Game::class, onConflict = OnConflictStrategy.REPLACE) suspend fun updateGames(games: List) + /** + * Updates the scores of a list of games in the database. + * + * @param games The list of [GameScoreUpdateData] objects containing updated game scores. + */ @Update(entity = Game::class, onConflict = OnConflictStrategy.REPLACE) suspend fun updateGameScores(games: List) } diff --git a/app/src/main/java/com/jiachian/nbatoday/database/dao/PlayerDao.kt b/app/src/main/java/com/jiachian/nbatoday/database/dao/PlayerDao.kt index 6c3939e4..6c58f53b 100644 --- a/app/src/main/java/com/jiachian/nbatoday/database/dao/PlayerDao.kt +++ b/app/src/main/java/com/jiachian/nbatoday/database/dao/PlayerDao.kt @@ -7,11 +7,25 @@ import androidx.room.Query import com.jiachian.nbatoday.models.local.player.Player import kotlinx.coroutines.flow.Flow +/** + * Handle operations related to the Player entity in the database. + */ @Dao interface PlayerDao { + /** + * Retrieves a flow of a player with a specific player ID from the database. + * + * @param playerId The ID of the player to retrieve. + * @return Flow emitting a nullable [Player]. + */ @Query("SELECT * FROM player WHERE player_id == :playerId") fun getPlayer(playerId: Int): Flow + /** + * Inserts a player into the database, replacing any existing entry on conflict. + * + * @param player The [Player] object to be inserted. + */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertPlayer(player: Player) } diff --git a/app/src/main/java/com/jiachian/nbatoday/database/dao/TeamDao.kt b/app/src/main/java/com/jiachian/nbatoday/database/dao/TeamDao.kt index 09213942..a1c3c6f3 100644 --- a/app/src/main/java/com/jiachian/nbatoday/database/dao/TeamDao.kt +++ b/app/src/main/java/com/jiachian/nbatoday/database/dao/TeamDao.kt @@ -10,14 +10,36 @@ import com.jiachian.nbatoday.models.local.team.TeamAndPlayers import com.jiachian.nbatoday.models.local.team.TeamPlayer import kotlinx.coroutines.flow.Flow +/** + * Handle operations related to the Team entity in the database. + */ @Dao interface TeamDao { + /** + * Retrieves a flow of teams based on the conference from the database. + * + * @param conference The [NBATeam.Conference] for which to retrieve teams. + * @return Flow emitting a list of [Team]. + */ @Query("SELECT * FROM team WHERE team_conference == :conference") fun getTeams(conference: NBATeam.Conference): Flow> + /** + * Retrieves a flow of TeamAndPlayers for a specific team ID from the database. + * + * @param teamId The ID of the team for which to retrieve TeamAndPlayers. + * @return Flow emitting a nullable [TeamAndPlayers]. + */ @Query("SELECT * FROM team WHERE team_id == :teamId") fun getTeamAndPlayers(teamId: Int): Flow + /** + * Retrieves the standing rank of a team within a specific conference from the database. + * + * @param teamId The ID of the team for which to retrieve the standing rank. + * @param conference The [NBATeam.Conference] in which to determine the standing rank. + * @return Flow emitting the standing rank of the team. + */ @Query( """ SELECT ( @@ -32,6 +54,12 @@ interface TeamDao { ) fun getTeamStanding(teamId: Int, conference: NBATeam.Conference): Flow + /** + * Retrieves the points rank of a team from the database. + * + * @param teamId The ID of the team for which to retrieve the points rank. + * @return Flow emitting the points rank of the team. + */ @Query( """ SELECT ( @@ -50,6 +78,12 @@ interface TeamDao { ) fun getPointsRank(teamId: Int): Flow + /** + * Retrieves the rebounds rank of a team from the database. + * + * @param teamId The ID of the team for which to retrieve the rebounds rank. + * @return Flow emitting the rebounds rank of the team. + */ @Query( """ SELECT ( @@ -68,6 +102,12 @@ interface TeamDao { ) fun getReboundsRank(teamId: Int): Flow + /** + * Retrieves the assists rank of a team from the database. + * + * @param teamId The ID of the team for which to retrieve the assists rank. + * @return Flow emitting the assists rank of the team. + */ @Query( """ SELECT ( @@ -86,6 +126,12 @@ interface TeamDao { ) fun getAssistsRank(teamId: Int): Flow + /** + * Retrieves the plus-minus rank of a team from the database. + * + * @param teamId The ID of the team for which to retrieve the plus-minus rank. + * @return Flow emitting the plus-minus rank of the team. + */ @Query( """ SELECT ( @@ -104,12 +150,28 @@ interface TeamDao { ) fun getPlusMinusRank(teamId: Int): Flow + /** + * Inserts a list of teams into the database, replacing any existing entries on conflict. + * + * @param stats The list of [Team] objects to be inserted. + */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTeams(stats: List) + /** + * Inserts a list of team players into the database, replacing any existing entries on conflict. + * + * @param stats The list of [TeamPlayer] objects to be inserted. + */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTeamPlayers(stats: List) + /** + * Deletes team players associated with a specific team and player IDs from the database. + * + * @param teamId The ID of the team for which to delete team players. + * @param playerIds The list of player IDs to be deleted. + */ @Query("DELETE FROM team_player WHERE team_id == :teamId AND player_id IN (:playerIds)") suspend fun deleteTeamPlayers(teamId: Int, playerIds: List) } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/local/bet/BetLocalSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/local/bet/BetLocalSource.kt index 3fd92170..2471ab6f 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/local/bet/BetLocalSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/local/bet/BetLocalSource.kt @@ -4,10 +4,29 @@ import com.jiachian.nbatoday.models.local.bet.Bet import com.jiachian.nbatoday.models.local.bet.BetAndGame import kotlinx.coroutines.flow.Flow +/** + * The local data source for bet-related operations. + */ abstract class BetLocalSource { + /** + * Retrieves a flow of [BetAndGame] objects associated with a specific account. + * + * @param account The account for which to retrieve bets and games. + * @return A [Flow] emitting a list of [BetAndGame] objects. + */ abstract fun getBetsAndGames(account: String): Flow> + /** + * Inserts a [Bet] object into the local data source. + * + * @param bet The [Bet] object to be inserted. + */ abstract suspend fun insertBet(bet: Bet) + /** + * Deletes a [Bet] object from the local data source. + * + * @param bet The [Bet] object to be deleted. + */ abstract suspend fun deleteBet(bet: Bet) } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/local/boxscore/BoxScoreLocalSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/local/boxscore/BoxScoreLocalSource.kt index 0778c20e..9947ed49 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/local/boxscore/BoxScoreLocalSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/local/boxscore/BoxScoreLocalSource.kt @@ -4,8 +4,22 @@ import com.jiachian.nbatoday.models.local.score.BoxScore import com.jiachian.nbatoday.models.local.score.BoxScoreAndGame import kotlinx.coroutines.flow.Flow +/** + * The local data source for box score-related operations. + */ abstract class BoxScoreLocalSource { + /** + * Retrieves a flow of [BoxScoreAndGame] objects associated with a specific game ID. + * + * @param gameId The ID of the game for which to retrieve the box score and game. + * @return A [Flow] emitting a nullable [BoxScoreAndGame] object. + */ abstract fun getBoxScoreAndGame(gameId: String): Flow + /** + * Inserts a [BoxScore] object into the local data source. + * + * @param boxScore The [BoxScore] object to be inserted. + */ abstract suspend fun insertBoxScore(boxScore: BoxScore) } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/local/game/GameLocalSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/local/game/GameLocalSource.kt index e4666613..a36cf13b 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/local/game/GameLocalSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/local/game/GameLocalSource.kt @@ -7,17 +7,83 @@ import com.jiachian.nbatoday.models.local.game.GameUpdateData import java.util.Date import kotlinx.coroutines.flow.Flow +/** + * The local data source for game-related operations. + */ abstract class GameLocalSource { + /** + * Retrieves a flow of [GameAndBets] objects representing games and associated bets. + * + * @return A [Flow] emitting a list of [GameAndBets] objects. + */ abstract fun getGamesAndBets(): Flow> + + /** + * Retrieves a flow of [GameAndBets] objects representing games and associated bets within a specified date range. + * + * @param from The start of the date range (in milliseconds since epoch). + * @param to The end of the date range (in milliseconds since epoch). + * @return A [Flow] emitting a list of [GameAndBets] objects. + */ abstract fun getGamesAndBetsDuring(from: Long, to: Long): Flow> + + /** + * Retrieves a flow of [GameAndBets] objects representing games and associated bets before a specific date for a given team. + * + * @param teamId The ID of the team. + * @param from The reference date (in milliseconds since epoch). + * @return A [Flow] emitting a list of [GameAndBets] objects. + */ abstract fun getGamesAndBetsBefore(teamId: Int, from: Long): Flow> + + /** + * Retrieves a flow of [GameAndBets] objects representing games and associated bets after a specific date for a given team. + * + * @param teamId The ID of the team. + * @param from The reference date (in milliseconds since epoch). + * @return A [Flow] emitting a list of [GameAndBets] objects. + */ abstract fun getGamesAndBetsAfter(teamId: Int, from: Long): Flow> + + /** + * Retrieves a flow of [Date] representing the last game date and time in the local data source. + * + * @return A [Flow] emitting a nullable [Date] object. + */ abstract fun getLastGameDateTime(): Flow + + /** + * Retrieves a flow of [Date] representing the first game date and time in the local data source. + * + * @return A [Flow] emitting a nullable [Date] object. + */ abstract fun getFirstGameDateTime(): Flow + /** + * Inserts a list of [Game] objects into the local data source. + * + * @param games The list of [Game] objects to be inserted. + */ abstract suspend fun insertGames(games: List) + + /** + * Updates a list of [Game] objects in the local data source. + * + * @param games The list of [GameUpdateData] objects containing the updated game information. + */ abstract suspend fun updateGames(games: List) + + /** + * Updates the scores of a list of [Game] objects in the local data source. + * + * @param games The list of [GameScoreUpdateData] objects containing the updated game scores. + */ abstract suspend fun updateGameScores(games: List) + /** + * Checks if there are existing games in the local data source. + * + * @return `true` if there are existing games, `false` otherwise. + */ abstract suspend fun gameExists(): Boolean } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/local/player/PlayerLocalSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/local/player/PlayerLocalSource.kt index 6056052d..090a8316 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/local/player/PlayerLocalSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/local/player/PlayerLocalSource.kt @@ -3,8 +3,22 @@ package com.jiachian.nbatoday.datasource.local.player import com.jiachian.nbatoday.models.local.player.Player import kotlinx.coroutines.flow.Flow +/** + * The local data source for player-related operations. + */ abstract class PlayerLocalSource { + /** + * Retrieves a [Flow] of [Player] representing a player with the specified player ID. + * + * @param playerId The ID of the player. + * @return A [Flow] emitting a nullable [Player] object. + */ abstract fun getPlayer(playerId: Int): Flow + /** + * Inserts a [Player] object into the local data source. + * + * @param player The [Player] object to be inserted. + */ abstract suspend fun insertPlayer(player: Player) } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/local/team/TeamLocalSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/local/team/TeamLocalSource.kt index c7d61d4e..0ed4a897 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/local/team/TeamLocalSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/local/team/TeamLocalSource.kt @@ -7,12 +7,54 @@ import com.jiachian.nbatoday.models.local.team.TeamPlayer import com.jiachian.nbatoday.models.local.team.TeamRank import kotlinx.coroutines.flow.Flow +/** + * The local data source for team-related operations. + */ abstract class TeamLocalSource { + /** + * Retrieves a [Flow] of [Team] objects representing teams within the specified [NBATeam.Conference]. + * + * @param conference The conference to filter teams by. + * @return A [Flow] emitting a list of [Team] objects. + */ abstract fun getTeams(conference: NBATeam.Conference): Flow> + + /** + * Retrieves a [Flow] of [TeamAndPlayers] representing team information and its players with the specified team ID. + * + * @param teamId The ID of the team. + * @return A [Flow] emitting a nullable [TeamAndPlayers] object. + */ abstract fun getTeamAndPlayers(teamId: Int): Flow + + /** + * Retrieves a [Flow] of [TeamRank] representing the rank of a team within the specified conference. + * + * @param teamId The ID of the team. + * @param conference The conference in which the rank is determined. + * @return A [Flow] emitting a [TeamRank] object. + */ abstract fun getTeamRank(teamId: Int, conference: NBATeam.Conference): Flow + /** + * Inserts a list of [Team] objects into the local data source. + * + * @param teams The list of [Team] objects to be inserted. + */ abstract suspend fun insertTeams(teams: List) + + /** + * Inserts a list of [TeamPlayer] objects into the local data source. + * + * @param teamPlayers The list of [TeamPlayer] objects to be inserted. + */ abstract suspend fun insertTeamPlayers(teamPlayers: List) + + /** + * Deletes team players from the local data source. + * + * @param teamId The ID of the team. + * @param playerIds The list of player IDs to be deleted. + */ abstract suspend fun deleteTeamPlayers(teamId: Int, playerIds: List) } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/RemoteSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/RemoteSource.kt index d27717d1..6b533ae4 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/RemoteSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/RemoteSource.kt @@ -7,6 +7,9 @@ import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +/** + * Creating and managing Retrofit instances for remote data sources. + */ object RemoteSource { private const val ConnectTimeOut = 5L private const val ReadTimeOut = 20L diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/game/GameRemoteSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/game/GameRemoteSource.kt index 134fad50..d5584ee1 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/game/GameRemoteSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/game/GameRemoteSource.kt @@ -5,9 +5,35 @@ import com.jiachian.nbatoday.models.remote.game.RemoteSchedule import com.jiachian.nbatoday.models.remote.score.RemoteBoxScore import retrofit2.Response +/** + * The remote data source for game-related operations. + */ abstract class GameRemoteSource { + /** + * Retrieves the schedule of NBA games from the remote data source. + * + * @return A [Response] containing a [RemoteSchedule] object. + */ abstract suspend fun getSchedule(): Response + + /** + * Retrieves information about a specific NBA game from the remote data source. + * + * @param leagueId The ID of the NBA league. + * @param gameDate The date of the game. + * @return A [Response] containing a [RemoteGame] object. + */ abstract suspend fun getGame(leagueId: String, gameDate: String): Response + + /** + * Retrieves a list of NBA games for a specific date range from the remote data source. + * + * @param year The year of the date range. + * @param month The month of the date range. + * @param day The day of the date range. + * @param total The total number of games to retrieve. + * @return A [Response] containing a list of [RemoteGame] objects. + */ abstract suspend fun getGames( year: Int, month: Int, @@ -15,5 +41,11 @@ abstract class GameRemoteSource { total: Int ): Response> + /** + * Retrieves the box score of a specific NBA game from the remote data source. + * + * @param gameId The ID of the NBA game. + * @return A [Response] containing a [RemoteBoxScore] object. + */ abstract suspend fun getBoxScore(gameId: String): Response } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/player/PlayerRemoteSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/player/PlayerRemoteSource.kt index ad3f69dd..52e7bfce 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/player/PlayerRemoteSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/player/PlayerRemoteSource.kt @@ -3,6 +3,15 @@ package com.jiachian.nbatoday.datasource.remote.player import com.jiachian.nbatoday.models.remote.player.RemotePlayer import retrofit2.Response +/** + * The remote data source for player-related operations. + */ abstract class PlayerRemoteSource { + /** + * Retrieves information about a specific NBA player from the remote data source. + * + * @param playerId The ID of the NBA player. + * @return A [Response] containing a [RemotePlayer] object. + */ abstract suspend fun getPlayer(playerId: Int): Response } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/team/TeamRemoteSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/team/TeamRemoteSource.kt index bd3221c8..85a21d2e 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/team/TeamRemoteSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/team/TeamRemoteSource.kt @@ -4,8 +4,30 @@ import com.jiachian.nbatoday.models.remote.team.RemoteTeam import com.jiachian.nbatoday.models.remote.team.RemoteTeamPlayer import retrofit2.Response +/** + * The remote data source for team-related operations. + */ abstract class TeamRemoteSource { + /** + * Retrieves information about NBA teams from the remote data source. + * + * @return A [Response] containing a [RemoteTeam] object. + */ abstract suspend fun getTeam(): Response + + /** + * Retrieves information about a specific NBA team from the remote data source. + * + * @param teamId The ID of the NBA team. + * @return A [Response] containing a [RemoteTeam] object. + */ abstract suspend fun getTeam(teamId: Int): Response + + /** + * Retrieves information about team players of a specific NBA team from the remote data source. + * + * @param teamId The ID of the NBA team. + * @return A [Response] containing a [RemoteTeamPlayer] object. + */ abstract suspend fun getTeamPlayer(teamId: Int): Response } diff --git a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/user/UserRemoteSource.kt b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/user/UserRemoteSource.kt index 3f2b3bd8..8bb9be21 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datasource/remote/user/UserRemoteSource.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datasource/remote/user/UserRemoteSource.kt @@ -3,9 +3,45 @@ package com.jiachian.nbatoday.datasource.remote.user import com.jiachian.nbatoday.models.remote.user.RemoteUser import retrofit2.Response +/** + * The remote data source for user-related operations. + */ abstract class UserRemoteSource { + /** + * Attempts to log in a user using the provided account and password. + * + * @param account The user's account. + * @param password The user's password. + * @return A [Response] containing a [RemoteUser] object. + */ abstract suspend fun login(account: String, password: String): Response + + /** + * Registers a new user with the provided account and password. + * + * @param account The user's account. + * @param password The user's password. + * @return A [Response] containing a [RemoteUser] object. + */ abstract suspend fun register(account: String, password: String): Response + + /** + * Updates the password for a user account. + * + * @param account The user's account. + * @param password The new password. + * @param token The authentication token. + * @return A [Response] containing a success message. + */ abstract suspend fun updatePassword(account: String, password: String, token: String): Response + + /** + * Updates the points for a user account. + * + * @param account The user's account. + * @param points The new points value. + * @param token The authentication token. + * @return A [Response] containing a success message. + */ abstract suspend fun updatePoints(account: String, points: Long, token: String): Response } diff --git a/app/src/main/java/com/jiachian/nbatoday/datastore/NBADataStore.kt b/app/src/main/java/com/jiachian/nbatoday/datastore/NBADataStore.kt index 3b044e77..40e4e63e 100644 --- a/app/src/main/java/com/jiachian/nbatoday/datastore/NBADataStore.kt +++ b/app/src/main/java/com/jiachian/nbatoday/datastore/NBADataStore.kt @@ -38,12 +38,14 @@ class NBADataStore( application.applicationContext.dataStore } + // Retrieve the last accessed date from DataStore override val lastAccessedDate by lazy { dataStore.data.map { pref -> pref[LAST_ACCESSED_DATE] ?: DateUtils.formatDate(1990, 1, 1) } } + // Retrieve theme colors based on the team ID from DataStore override val themeColors by lazy { dataStore.data.map { pref -> pref[THEME_COLORS_TEAM_ID]?.let { teamId -> @@ -52,6 +54,7 @@ class NBADataStore( } } + // Retrieve user information from DataStore override val user by lazy { dataStore.data.map { pref -> pref[USER].let { user -> diff --git a/app/src/main/java/com/jiachian/nbatoday/event/EventManager.kt b/app/src/main/java/com/jiachian/nbatoday/event/EventManager.kt index fa435c83..ca9f7775 100644 --- a/app/src/main/java/com/jiachian/nbatoday/event/EventManager.kt +++ b/app/src/main/java/com/jiachian/nbatoday/event/EventManager.kt @@ -6,12 +6,23 @@ import java.util.concurrent.ConcurrentLinkedQueue import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +/** + * Manages and broadcasts events of type [T]. + * + * @param T The type of events to be managed. + */ class EventManager : EventBroadcaster { private val queue = ConcurrentLinkedQueue() private val eventFlowImp = MutableStateFlow(null) override val eventFlow: StateFlow = eventFlowImp private var currentEvent: T? = null + /** + * Sends an event to be broadcast. If there is no ongoing event, it is immediately broadcast. + * Otherwise, the event is added to the queue. + * + * @param event The event to be broadcast. + */ @Synchronized fun send(event: T) { if (currentEvent == null) { @@ -22,6 +33,12 @@ class EventManager : EventBroadcaster { } } + /** + * Callback method to be invoked when an event is consumed. It updates the current event and + * broadcasts the next event from the queue if available. + * + * @param event The event that was consumed. + */ @Synchronized override fun onEventConsumed(event: T?) { if (event == null || event != currentEvent) return diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/bet/BetAndGame.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/bet/BetAndGame.kt index 09830d37..cce7a446 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/bet/BetAndGame.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/bet/BetAndGame.kt @@ -28,6 +28,9 @@ data class BetAndGame( val awayWin get() = game.awayWin + /** + * Gets the text representation of home team points, considering the game's status and result. + */ fun getHomePointsText(): String { return when { !gameFinal -> "${bet.homePoints}" @@ -36,6 +39,9 @@ data class BetAndGame( } } + /** + * Gets the text representation of away team points, considering the game's status and result. + */ fun getAwayPointsText(): String { return when { !gameFinal -> "${bet.awayPoints}" @@ -44,6 +50,9 @@ data class BetAndGame( } } + /** + * Gets the color of home team points text, based on the game's status and result. + */ @Composable fun getHomePointsTextColor(): Color { return when { @@ -53,6 +62,9 @@ data class BetAndGame( } } + /** + * Gets the color of away team points text, based on the game's status and result. + */ @Composable fun getAwayPointsTextColor(): Color { return when { @@ -62,6 +74,9 @@ data class BetAndGame( } } + /** + * Gets the text representation of the bet status based on the associated game's status. + */ fun getBetStatusText(): String { return when (game.gameStatus) { GameStatus.COMING_SOON -> game.gameStatusText.replaceFirst(" ", "\n") + "\n1:1" @@ -70,10 +85,16 @@ data class BetAndGame( }.trim() } + /** + * Gets the number of points won in the bet. + */ fun getWonPoints(): Long { return if (homeWin) bet.homePoints else bet.awayPoints } + /** + * Gets the number of points lost in the bet. + */ fun getLostPoints(): Long { return if (homeWin) bet.awayPoints else bet.homePoints } diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/game/Game.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/game/Game.kt index b6e83aab..e0c4e24f 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/game/Game.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/game/Game.kt @@ -14,31 +14,31 @@ data class Game( @ColumnInfo(name = "away_team") val awayTeam: GameTeam, @PrimaryKey @ColumnInfo(name = "game_id") - val gameId: String, // 比賽id, e.g. 0022200089 + val gameId: String, // e.g. 0022200089 @ColumnInfo(name = "game_status") val gameStatus: GameStatus, @ColumnInfo(name = "game_status_text") - val gameStatusText: String, // 比賽狀態(Final或開始時間), e.g. Final or 3:00 pm ET + val gameStatusText: String, // e.g. Final or 3:00 pm ET @ColumnInfo(name = "home_team") val homeTeam: GameTeam, @ColumnInfo(name = "game_date") - val gameDate: Date, // 比賽開始日期(固定為13:00), e.g. 2022-11-20T13:00:00Z (GMT+8) + val gameDate: Date, // Start date of the game (the time is always 13:00), e.g. 2022-11-20T13:00:00Z (GMT+8) @ColumnInfo(name = "game_date_time") - val gameDateTime: Date, // 比賽開始時間, e.g. 2022-11-20T15:30:00Z (GMT+8) + val gameDateTime: Date, // Start time of the game, e.g. 2022-11-20T15:30:00Z (GMT+8) @ColumnInfo(name = "points_leader") - val pointsLeaders: List, + val pointsLeaders: List, // Leaders in scoring @ColumnInfo(name = "game_leaders") - val gameLeaders: GameLeaders?, + val gameLeaders: GameLeaders?, // Leaders in this game @ColumnInfo(name = "team_leaders") - val teamLeaders: GameLeaders? + val teamLeaders: GameLeaders? // Leaders in the team ) { data class PointsLeader( @ColumnInfo(name = "player_id") - val playerId: Int, // 球員id, e.g. 1628983 + val playerId: Int, // e.g. 1628983 @ColumnInfo(name = "points") - val points: Double, // 球員當場得分, e.g. 38.0 + val points: Double, // e.g. 38.0 @ColumnInfo(name = "team_id") - val teamId: Int, // 球員所屬球隊id, e.g. 1610612760 + val teamId: Int, // e.g. 1610612760 ) val gameFinal: Boolean diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameLeaders.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameLeaders.kt index f2db9e15..31460cd3 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameLeaders.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameLeaders.kt @@ -21,11 +21,11 @@ data class GameLeaders( @ColumnInfo(name = "team_tri_code") val teamTricode: String, // e.g. CHA @ColumnInfo(name = "person_points") - val points: Double, // e.g. 如果比賽已經結束: 22,否則:22.2 + val points: Double, // e.g. If the game has finished: 22, otherwise:22.2 @ColumnInfo(name = "person_rebounds") - val rebounds: Double, // e.g. 如果比賽已經結束: 22,否則:22.2 + val rebounds: Double, // e.g. f the game has finished:: 22, otherwise:22.2 @ColumnInfo(name = "person_assists") - val assists: Double // e.g. 如果比賽已經結束: 22,否則:22.2 + val assists: Double // e.g. f the game has finished:: 22, otherwise:22.2 ) { companion object { fun default(): GameLeader = GameLeader( diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameScoreUpdateData.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameScoreUpdateData.kt index 77a446d7..33ec415b 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameScoreUpdateData.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameScoreUpdateData.kt @@ -4,11 +4,11 @@ import androidx.room.ColumnInfo data class GameScoreUpdateData( @ColumnInfo(name = "game_id") - val gameId: String, // 比賽id, e.g. 0022200089 + val gameId: String, // e.g. 0022200089 @ColumnInfo(name = "game_status") val gameStatus: GameStatus, @ColumnInfo(name = "game_status_text") - val gameStatusText: String, // 比賽狀態(Final或開始時間), e.g. Final or 3:00 pm ET + val gameStatusText: String, // e.g. Final or 3:00 pm ET @ColumnInfo(name = "home_team") val homeTeam: GameTeam, @ColumnInfo(name = "away_team") diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameStatus.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameStatus.kt index 12a102de..ce21a452 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameStatus.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameStatus.kt @@ -8,11 +8,11 @@ private const val FinalStatusCode = 3 enum class GameStatus(val code: Int) { @SerializedName(ComingSoonStatusCode.toString()) - COMING_SOON(ComingSoonStatusCode), // 比賽尚未開始 + COMING_SOON(ComingSoonStatusCode), // The game is coming @SerializedName(PlayingStatusCode.toString()) - PLAYING(PlayingStatusCode), // 比賽進行中 + PLAYING(PlayingStatusCode), // The game is in progress @SerializedName(FinalStatusCode.toString()) - FINAL(FinalStatusCode) // 比賽已經結束 + FINAL(FinalStatusCode) // The game has finished } diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameTeam.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameTeam.kt index e204bd97..c52ec67a 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameTeam.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameTeam.kt @@ -7,9 +7,9 @@ data class GameTeam( @ColumnInfo(name = "team") val team: NBATeam, @ColumnInfo(name = "losses") - val losses: Int, // 敗場場次(從這場之前), e.g. 2 + val losses: Int, // e.g. 2 @ColumnInfo(name = "score") - val score: Int, // 比分, e.g. 100 + val score: Int, // e.g. 100 @ColumnInfo(name = "wins") - val wins: Int, // 勝場場次(從這場之前), e.g. 2 + val wins: Int, // e.g. 2 ) diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameUpdateData.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameUpdateData.kt index c4c8b7c7..1ff4f507 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameUpdateData.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/game/GameUpdateData.kt @@ -3,20 +3,22 @@ package com.jiachian.nbatoday.models.local.game import androidx.room.ColumnInfo /** - * 每場比賽的數據,需要透過兩個不同的api才能彙整一份資料, - * 1. 透過`static/json/staticData/scheduleLeagueV2_32.json`取得賽季的賽程表(當中會包含簡單的數據) - * 2. 透過`stats/scoreboardv3`取得特定日期裡各場比賽的數據(包含leaders數據) + * Data for each game needs to be aggregated through two different APIs, + * 1. Fetch the season schedule from `static/json/staticData/scheduleLeagueV2_32.json` (which contains basic data about the schedule). + * 2. Retrieve specific game data, including leaders' statistics, for each game on a given date through `stats/scoreboardv3`. * - * 因此會在初始時(App尚未取得任何數據)先透過(1)初始化資料庫,再透過根據當日且推算前幾天後幾天,透過(2)取得更詳細的資料, - * 而此Data Class就是用來將(2)的資料更新於先前的(1)的資料中 + * Therefore, during the initialization phase (when the app has not obtained any data yet), the database is initialized through (1). + * Subsequently, more detailed data is obtained by using (2) based on the current date and estimating a few days before and after. + * This Data Class is designed to update the data from (2) into the previously fetched data from (1). */ + data class GameUpdateData( @ColumnInfo(name = "game_id") - val gameId: String, // 比賽id, e.g. 0022200089 + val gameId: String, // e.g. 0022200089 @ColumnInfo(name = "game_status") val gameStatus: GameStatus, @ColumnInfo(name = "game_status_text") - val gameStatusText: String, // 比賽狀態(Final或開始時間), e.g. Final or 3:00 pm ET + val gameStatusText: String, // e.g. Final or 3:00 pm ET @ColumnInfo(name = "home_team") val homeTeam: GameTeam, @ColumnInfo(name = "away_team") diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/player/Player.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/player/Player.kt index 8e2e23b5..d9b4e5e6 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/player/Player.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/player/Player.kt @@ -35,7 +35,7 @@ data class Player( @ColumnInfo(name = "points") val points: Double, @ColumnInfo(name = "assists") val assists: Double, @ColumnInfo(name = "rebounds") val rebounds: Double, - @ColumnInfo(name = "impact_estimate") val impact: Double // 球員影響值(%) + @ColumnInfo(name = "impact_estimate") val impact: Double // Player Impact Percentage ) val points: Double @@ -73,7 +73,7 @@ data class Player( @ColumnInfo(name = "game_played") val gamePlayed: Int, @ColumnInfo(name = "win") val win: Int, @ColumnInfo(name = "lose") val lose: Int, - @ColumnInfo(name = "win_percentage") val winPercentage: Double, // 勝敗場百分比, e.g. 50.0 + @ColumnInfo(name = "win_percentage") val winPercentage: Double, // Win-Loss Percentage, e.g. 50.0 @ColumnInfo(name = "field_goals_made") val fieldGoalsMade: Int, @ColumnInfo(name = "field_goals_attempted") val fieldGoalsAttempted: Int, @ColumnInfo(name = "field_goals_percentage") val fieldGoalsPercentage: Double, diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/score/BoxScore.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/score/BoxScore.kt index 32d821a6..1420a860 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/score/BoxScore.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/score/BoxScore.kt @@ -50,7 +50,7 @@ data class BoxScore( data class Period( @ColumnInfo(name = "period_label") - val periodLabel: String, // 第幾節標籤, e.g. 1st or OT1 + val periodLabel: String, // Quarter Label, e.g. 1st or OT1 @ColumnInfo(name = "score") val score: Int // e.g. 33 ) @@ -59,13 +59,13 @@ data class BoxScore( @ColumnInfo(name = "status") val status: PlayerActiveStatus, @ColumnInfo(name = "not_playing_reason") - val notPlayingReason: String, // status為INACTIVE時才有值, e.g. INACTIVE_INJURY + val notPlayingReason: String, // e.g. INACTIVE_INJURY, or empty if the status is not INACTIVE @ColumnInfo(name = "player_id") val playerId: Int, // e.g. 1626162 @ColumnInfo(name = "position") val position: String, // e.g. SF @ColumnInfo(name = "starter") - val starter: Boolean, // 是否為先發 + val starter: Boolean, // Is starting player @ColumnInfo(name = "statistics") val statistics: Statistics, @ColumnInfo(name = "name_abbr") @@ -73,57 +73,57 @@ data class BoxScore( ) { data class Statistics( @ColumnInfo(name = "assists") - val assists: Int, // 助攻數 + val assists: Int, @ColumnInfo(name = "blocks") - val blocks: Int, // 阻攻數, + val blocks: Int, @ColumnInfo(name = "blocks_received") - val blocksReceived: Int, // 被阻攻數 + val blocksReceived: Int, @ColumnInfo(name = "field_goals_attempted") - val fieldGoalsAttempted: Int, // 出手次數 + val fieldGoalsAttempted: Int, @ColumnInfo(name = "field_goals_made") - val fieldGoalsMade: Int, // 進球數 + val fieldGoalsMade: Int, @ColumnInfo(name = "field_goals_percentage") - val fieldGoalsPercentage: Double, // 41.2 + val fieldGoalsPercentage: Double, @ColumnInfo(name = "fouls_offensive") - val foulsOffensive: Int, // 進攻犯規數 + val foulsOffensive: Int, @ColumnInfo(name = "fouls_personal") - val foulsPersonal: Int, // 防守犯規數 + val foulsPersonal: Int, @ColumnInfo(name = "fouls_technical") - val foulsTechnical: Int, // 技術犯規數 + val foulsTechnical: Int, @ColumnInfo(name = "free_throws_attempted") - val freeThrowsAttempted: Int, // 罰球出手次數 + val freeThrowsAttempted: Int, @ColumnInfo(name = "free_throws_made") - val freeThrowsMade: Int, // 罰球進球數 + val freeThrowsMade: Int, @ColumnInfo(name = "free_throws_percentage") - val freeThrowsPercentage: Double, // 66.6 + val freeThrowsPercentage: Double, @ColumnInfo(name = "minutes") - val minutes: String, // 上場總時間, e.g. 36:12 (分:秒) + val minutes: String, // e.g. 36:12 (分:秒) @ColumnInfo(name = "plus_minus_points") - val plusMinusPoints: Int, // 加減評分 + val plusMinusPoints: Int, @ColumnInfo(name = "points") - val points: Int, // 得分數 + val points: Int, @ColumnInfo(name = "rebounds_defensive") - val reboundsDefensive: Int, // 防守籃板數 + val reboundsDefensive: Int, @ColumnInfo(name = "rebounds_offensive") - val reboundsOffensive: Int, // 進攻籃板數 + val reboundsOffensive: Int, @ColumnInfo(name = "rebounds_total") - val reboundsTotal: Int, // 總籃板數 + val reboundsTotal: Int, @ColumnInfo(name = "steals") - val steals: Int, // 抄截數 + val steals: Int, @ColumnInfo(name = "three_pointers_attempted") - val threePointersAttempted: Int, // 三分出手次數 + val threePointersAttempted: Int, @ColumnInfo(name = "three_pointers_made") - val threePointersMade: Int, // 三分進球次數 + val threePointersMade: Int, @ColumnInfo(name = "three_pointers_percentage") - val threePointersPercentage: Double, // 66.6 + val threePointersPercentage: Double, @ColumnInfo(name = "turnovers") - val turnovers: Int, // 失誤次數 + val turnovers: Int, @ColumnInfo(name = "two_pointers_attempted") - val twoPointersAttempted: Int, // 兩分出手數 + val twoPointersAttempted: Int, @ColumnInfo(name = "two_pointers_made") - val twoPointersMade: Int, // 兩分進球數 + val twoPointersMade: Int, @ColumnInfo(name = "two_pointers_percentage") - val twoPointersPercentage: Double // 66.6 + val twoPointersPercentage: Double ) { private val indicator get() = points + reboundsTotal + assists + steals + blocks @@ -157,59 +157,59 @@ data class BoxScore( data class Statistics( @ColumnInfo(name = "assists") - val assists: Int, // 球隊總助攻數 + val assists: Int, @ColumnInfo(name = "blocks") - val blocks: Int, // 球隊總阻攻數 + val blocks: Int, @ColumnInfo(name = "field_goals_attempted") - val fieldGoalsAttempted: Int, // 球隊出手次數 + val fieldGoalsAttempted: Int, @ColumnInfo(name = "field_goals_made") - val fieldGoalsMade: Int, // 球隊進球次數 + val fieldGoalsMade: Int, @ColumnInfo(name = "field_goals_percentage") - val fieldGoalsPercentage: Double, // 66.6 + val fieldGoalsPercentage: Double, @ColumnInfo(name = "fouls_personal") - val foulsPersonal: Int, // 球隊防守犯規總數 + val foulsPersonal: Int, @ColumnInfo(name = "fouls_technical") - val foulsTechnical: Int, // 球隊技術犯規數 + val foulsTechnical: Int, @ColumnInfo(name = "free_throws_attempted") - val freeThrowsAttempted: Int, // 球隊罰球出手次數 + val freeThrowsAttempted: Int, @ColumnInfo(name = "free_throws_made") - val freeThrowsMade: Int, // 球隊罰球進球次數 + val freeThrowsMade: Int, @ColumnInfo(name = "free_throws_percentage") - val freeThrowsPercentage: Double, // 66.6 + val freeThrowsPercentage: Double, @ColumnInfo(name = "points") - val points: Int, // 球隊得分 + val points: Int, @ColumnInfo(name = "rebounds_defensive") - val reboundsDefensive: Int, // 球隊防守籃板總數 + val reboundsDefensive: Int, @ColumnInfo(name = "rebounds_offensive") - val reboundsOffensive: Int, // 球隊進攻籃板總數 + val reboundsOffensive: Int, @ColumnInfo(name = "rebounds_total") - val reboundsTotal: Int, // 球隊籃板總數 (total) + val reboundsTotal: Int, @ColumnInfo(name = "steals") - val steals: Int, // 球隊抄截總數 + val steals: Int, @ColumnInfo(name = "three_pointers_attempted") - val threePointersAttempted: Int, // 球隊三分出手次數 + val threePointersAttempted: Int, @ColumnInfo(name = "three_pointers_made") - val threePointersMade: Int, // 球隊三分進球次數 + val threePointersMade: Int, @ColumnInfo(name = "three_pointers_percentage") - val threePointersPercentage: Double, // 66.6 + val threePointersPercentage: Double, @ColumnInfo(name = "turnovers") - val turnovers: Int, // 個人失誤總數 + val turnovers: Int, @ColumnInfo(name = "two_pointers_attempted") - val twoPointersAttempted: Int, // 球隊兩分出手總數 + val twoPointersAttempted: Int, @ColumnInfo(name = "two_pointers_made") - val twoPointersMade: Int, // 球隊兩分進球總數 + val twoPointersMade: Int, @ColumnInfo(name = "two_pointers_percentage") - val twoPointersPercentage: Double, // 66.6 + val twoPointersPercentage: Double, @ColumnInfo(name = "points_fast_break") - val pointsFastBreak: Int, // 快攻得分, e.g. 10 + val pointsFastBreak: Int, @ColumnInfo(name = "points_from_turnovers") - val pointsFromTurnovers: Int, // 因對方失誤得分, e.g 10 + val pointsFromTurnovers: Int, @ColumnInfo(name = "points_in_the_paint") - val pointsInThePaint: Int, // 禁區得分, e.g. 10 + val pointsInThePaint: Int, @ColumnInfo(name = "points_second_chance") - val pointsSecondChance: Int, // 二次進攻得分, e.g. 10 + val pointsSecondChance: Int, @ColumnInfo(name = "bench_points") - val benchPoints: Int, // 板凳得分, e.g. 10 + val benchPoints: Int, ) { val fieldGoalsFormat: String get() = "$fieldGoalsMade/$fieldGoalsAttempted($fieldGoalsPercentage%)" diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/score/PlayerActiveStatus.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/score/PlayerActiveStatus.kt index 86ca78e8..aa637192 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/score/PlayerActiveStatus.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/score/PlayerActiveStatus.kt @@ -4,8 +4,8 @@ import com.google.gson.annotations.SerializedName enum class PlayerActiveStatus { @SerializedName("ACTIVE") - ACTIVE, // 出賽 + ACTIVE, // Playing status @SerializedName("INACTIVE") - INACTIVE // 未出賽 + INACTIVE // Not playing status } diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/team/Team.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/team/Team.kt index 7a3e67d4..3870d7db 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/team/Team.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/team/Team.kt @@ -11,10 +11,10 @@ data class Team( @ColumnInfo(name = "team_id") @PrimaryKey val teamId: Int, @ColumnInfo(name = "team") val team: NBATeam, @ColumnInfo(name = "team_conference") val teamConference: NBATeam.Conference, - @ColumnInfo(name = "game_played") val gamePlayed: Int, // 目前打了幾場比賽 + @ColumnInfo(name = "game_played") val gamePlayed: Int, @ColumnInfo(name = "win") val win: Int, @ColumnInfo(name = "lose") val lose: Int, - @ColumnInfo(name = "win_percentage") val winPercentage: Double, // 勝敗場百分比, e.g. 50.0 + @ColumnInfo(name = "win_percentage") val winPercentage: Double, @ColumnInfo(name = "field_goals_percentage") val fieldGoalsPercentage: Double, @ColumnInfo(name = "three_pointers_percentage") val threePointersPercentage: Double, @ColumnInfo(name = "free_throws_percentage") val freeThrowsPercentage: Double, diff --git a/app/src/main/java/com/jiachian/nbatoday/models/local/team/TeamPlayer.kt b/app/src/main/java/com/jiachian/nbatoday/models/local/team/TeamPlayer.kt index a0ebe61b..0c2b9b73 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/local/team/TeamPlayer.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/local/team/TeamPlayer.kt @@ -10,10 +10,10 @@ data class TeamPlayer( @ColumnInfo(name = "player_id") @PrimaryKey val playerId: Int, @ColumnInfo(name = "team_id") val teamId: Int, @ColumnInfo(name = "player_name") val playerName: String, // A.J. Lawson - @ColumnInfo(name = "game_played") val gamePlayed: Int, // 目前打了幾場比賽 + @ColumnInfo(name = "game_played") val gamePlayed: Int, @ColumnInfo(name = "win") val win: Int, @ColumnInfo(name = "lose") val lose: Int, - @ColumnInfo(name = "win_percentage") val winPercentage: Double, // 勝敗場百分比, e.g. 50.0 + @ColumnInfo(name = "win_percentage") val winPercentage: Double, @ColumnInfo(name = "field_goals_made") val fieldGoalsMade: Int, @ColumnInfo(name = "field_goals_attempted") val fieldGoalsAttempted: Int, @ColumnInfo(name = "field_goals_percentage") val fieldGoalsPercentage: Double, diff --git a/app/src/main/java/com/jiachian/nbatoday/models/remote/game/RemoteGame.kt b/app/src/main/java/com/jiachian/nbatoday/models/remote/game/RemoteGame.kt index a3cb68ae..d360cc64 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/remote/game/RemoteGame.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/remote/game/RemoteGame.kt @@ -35,9 +35,9 @@ data class RemoteGame( val jerseyNum: String, // e.g. 3 val position: String, // e.g. G val teamTricode: String, // e.g. CHA - val points: Double, // e.g. 如果比賽已經結束: 22,否則:22.2 - val rebounds: Double, // e.g. 如果比賽已經結束: 22,否則:22.2 - val assists: Double // e.g. 如果比賽已經結束: 22,否則:22.2 + val points: Double, // e.g. If the game has finished: 22, otherwise:22.2 + val rebounds: Double, // e.g. If the game has finished: 22, otherwise:22.2 + val assists: Double // e.g. If the game has finished: 22, otherwise:22.2 ) } diff --git a/app/src/main/java/com/jiachian/nbatoday/models/remote/game/RemoteSchedule.kt b/app/src/main/java/com/jiachian/nbatoday/models/remote/game/RemoteSchedule.kt index d6c5f5d6..d4df6b5d 100644 --- a/app/src/main/java/com/jiachian/nbatoday/models/remote/game/RemoteSchedule.kt +++ b/app/src/main/java/com/jiachian/nbatoday/models/remote/game/RemoteSchedule.kt @@ -14,26 +14,26 @@ data class RemoteSchedule( ) { data class RemoteGame( @SerializedName("awayTeam") val awayTeam: RemoteTeam?, - @SerializedName("gameId") val gameId: String?, // 比賽id, e.g. 0022200089 + @SerializedName("gameId") val gameId: String?, // e.g. 0022200089 @SerializedName("gameStatus") val gameStatus: GameStatus?, @SerializedName("gameStatusText") val gameStatusText: String?, - @SerializedName("gameSequence") val gameSequence: Int?, // 今天的第幾場比賽(起始為1), e.g. 1 + @SerializedName("gameSequence") val gameSequence: Int?, // Number of the current game today (starting from 1) @SerializedName("homeTeam") val homeTeam: RemoteTeam?, @SerializedName("gameDateEst") val gameDateEst: String?, - @SerializedName("gameDateTimeEst") val gameDateTimeEst: String?, // 比賽開始時間, e.g. 2022-10-30T12:00:00Z + @SerializedName("gameDateTimeEst") val gameDateTimeEst: String?, // e.g. 2022-10-30T12:00:00Z @SerializedName("pointsLeaders") val pointsLeaders: List?, ) { data class RemoteTeam( - @SerializedName("losses") val losses: Int?, // 敗場場次(從這場之前), e.g. 2 - @SerializedName("score") val score: Int?, // 比分, e.g. 100 - @SerializedName("teamId") val teamId: Int?, // 隊伍id, e.g. 1610612746 - @SerializedName("wins") val wins: Int? // 勝場場次(從這場之前), e.g. 2 + @SerializedName("losses") val losses: Int?, // Team's losses (prior to this game) + @SerializedName("score") val score: Int?, + @SerializedName("teamId") val teamId: Int?, // e.g. 1610612746 + @SerializedName("wins") val wins: Int? // Team's wins (prior to this game) ) data class RemotePointsLeader( - @SerializedName("personId") val playerId: Int?, // 球員id, e.g. 1628983 - @SerializedName("points") val points: Double?, // 球員當場得分, e.g. 38.0 - @SerializedName("teamId") val teamId: Int?, // 球員所屬球隊id, e.g. 1610612760 + @SerializedName("personId") val playerId: Int?, // e.g. 1628983 + @SerializedName("points") val points: Double?, // e.g. 38.0 + @SerializedName("teamId") val teamId: Int?, // e.g. 1610612760 ) } } diff --git a/app/src/main/java/com/jiachian/nbatoday/navigation/MainRoute.kt b/app/src/main/java/com/jiachian/nbatoday/navigation/MainRoute.kt index 6d30699b..11ed50cf 100644 --- a/app/src/main/java/com/jiachian/nbatoday/navigation/MainRoute.kt +++ b/app/src/main/java/com/jiachian/nbatoday/navigation/MainRoute.kt @@ -1,5 +1,11 @@ package com.jiachian.nbatoday.navigation +/** + * Different routes in the main navigation flow. + * + * @param path The path for the route. + * @param param The optional parameter associated with the route. + */ sealed class MainRoute( val path: String, val param: String = "", @@ -12,5 +18,6 @@ sealed class MainRoute( object Calendar : MainRoute("calendar", "dateTime") object Bet : MainRoute("bet", "account") + // The full route string, including the parameter if present val route = if (param.isEmpty()) path else "$path/{$param}" } diff --git a/app/src/main/java/com/jiachian/nbatoday/navigation/NavigationController.kt b/app/src/main/java/com/jiachian/nbatoday/navigation/NavigationController.kt index 84452647..e27b9c56 100644 --- a/app/src/main/java/com/jiachian/nbatoday/navigation/NavigationController.kt +++ b/app/src/main/java/com/jiachian/nbatoday/navigation/NavigationController.kt @@ -3,6 +3,11 @@ package com.jiachian.nbatoday.navigation import com.jiachian.nbatoday.event.EventBroadcaster import com.jiachian.nbatoday.event.EventManager +/** + * Handle navigation events within the app. + * + * @param eventManager The event manager used for broadcasting navigation events. + */ class NavigationController( private val eventManager: EventManager = EventManager() ) : EventBroadcaster by eventManager { diff --git a/app/src/main/java/com/jiachian/nbatoday/repository/BaseRepository.kt b/app/src/main/java/com/jiachian/nbatoday/repository/BaseRepository.kt index 2f759f20..47118c0f 100644 --- a/app/src/main/java/com/jiachian/nbatoday/repository/BaseRepository.kt +++ b/app/src/main/java/com/jiachian/nbatoday/repository/BaseRepository.kt @@ -6,15 +6,35 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import retrofit2.Response +/** + * Provide common functionality for other repositories. + */ abstract class BaseRepository { + /** + * A Flow representing the loading state of the repository. + */ private val loadingImp = MutableStateFlow(false) val loading = loadingImp.asStateFlow() + /** + * A volatile variable to track the number of ongoing asynchronous tasks. + */ @Volatile private var enqueueJobAmount = 0 + /** + * Checks if a Retrofit response indicates an error (either unsuccessful or with a null body). + * + * @return `true` if the response is an error, `false` otherwise. + */ protected fun Response<*>.isError() = !isSuccessful || body() == null + /** + * Wraps an asynchronous operation with loading state management. + * + * @param runnable The asynchronous operation to be executed. + * @return The result of the asynchronous operation. + */ protected suspend fun loading(runnable: suspend () -> T): T { return try { loadingImp.value = true @@ -28,6 +48,9 @@ abstract class BaseRepository { } } + /** + * Sends a toast event indicating an error. + */ protected fun onError() { ToastEvent.OnError.send() } diff --git a/app/src/main/java/com/jiachian/nbatoday/repository/bet/BetRepository.kt b/app/src/main/java/com/jiachian/nbatoday/repository/bet/BetRepository.kt index 49caf9f6..454ede1d 100644 --- a/app/src/main/java/com/jiachian/nbatoday/repository/bet/BetRepository.kt +++ b/app/src/main/java/com/jiachian/nbatoday/repository/bet/BetRepository.kt @@ -5,11 +5,46 @@ import com.jiachian.nbatoday.models.local.bet.BetAndGame import com.jiachian.nbatoday.repository.BaseRepository import kotlinx.coroutines.flow.Flow +/** + * Manage bet-related to games. + */ abstract class BetRepository : BaseRepository() { + /** + * Inserts a new bet for a specified game. + * + * @param gameId The ID of the game for which the bet is placed. + * @param homePoints The points predicted for the home team. + * @param awayPoints The points predicted for the away team. + */ abstract suspend fun insertBet(gameId: String, homePoints: Long, awayPoints: Long) + + /** + * Deletes a bet from the repository. + * + * @param bet The bet to be deleted. + */ abstract suspend fun deleteBet(bet: Bet) + + /** + * Settles a bet and updates the points based on the outcome of the associated game. + * + * @param betAndGame The combination of bet and game to be settled. + * @return A pair representing the points won and lost in the bet. + */ abstract suspend fun settleBet(betAndGame: BetAndGame): Pair + + /** + * Adds points to the user's account. + * + * @param points The number of points to be added. + */ abstract suspend fun addPoints(points: Long) + /** + * Retrieves a flow of bets and associated games for a specific user account. + * + * @param account The user account for which to retrieve bets. + * @return A Flow emitting a list of BetAndGame objects. + */ abstract fun getBetsAndGames(account: String): Flow> } diff --git a/app/src/main/java/com/jiachian/nbatoday/repository/game/GameRepository.kt b/app/src/main/java/com/jiachian/nbatoday/repository/game/GameRepository.kt index 1c27d307..f7e5cae2 100644 --- a/app/src/main/java/com/jiachian/nbatoday/repository/game/GameRepository.kt +++ b/app/src/main/java/com/jiachian/nbatoday/repository/game/GameRepository.kt @@ -6,14 +6,70 @@ import com.jiachian.nbatoday.repository.BaseRepository import java.util.Date import kotlinx.coroutines.flow.Flow +/** + * Manage game-related data. + */ abstract class GameRepository : BaseRepository() { + /** + * Inserts the box score for a specified game. + * + * @param gameId The ID of the game for which the box score is inserted. + */ abstract suspend fun insertBoxScore(gameId: String) + /** + * Retrieves a flow of games and associated bets that occurred within a specified time range. + * + * @param from The start date-time of the time range. + * @param to The end date-time of the time range. + * @return A Flow emitting a list of GameAndBets objects. + */ abstract fun getGamesAndBetsDuring(from: Long, to: Long): Flow> + + /** + * Retrieves a flow of box score and game information for a specific game. + * + * @param gameId The ID of the game for which to retrieve box score and game information. + * @return A Flow emitting a BoxScoreAndGame object, or null if no information is available. + */ abstract fun getBoxScoreAndGame(gameId: String): Flow + + /** + * Retrieves a flow of games and associated bets. + * + * @return A Flow emitting a list of GameAndBets objects. + */ abstract fun getGamesAndBets(): Flow> + + /** + * Retrieves a flow of games and associated bets that occurred before a specified date-time for a specific team. + * + * @param teamId The ID of the team for which to retrieve games and bets. + * @param from The date-time before which the games occurred. + * @return A Flow emitting a list of GameAndBets objects. + */ abstract fun getGamesAndBetsBefore(teamId: Int, from: Long): Flow> + + /** + * Retrieves a flow of games and associated bets that occurred after a specified date-time for a specific team. + * + * @param teamId The ID of the team for which to retrieve games and bets. + * @param from The date-time after which the games occurred. + * @return A Flow emitting a list of GameAndBets objects. + */ abstract fun getGamesAndBetsAfter(teamId: Int, from: Long): Flow> + + /** + * Retrieves a flow of the date-time of the last recorded game. + * + * @return A Flow emitting the date-time of the last recorded game. + */ abstract fun getLastGameDateTime(): Flow + + /** + * Retrieves a flow of the date-time of the first recorded game. + * + * @return A Flow emitting the date-time of the first recorded game. + */ abstract fun getFirstGameDateTime(): Flow } diff --git a/app/src/main/java/com/jiachian/nbatoday/repository/player/PlayerRepository.kt b/app/src/main/java/com/jiachian/nbatoday/repository/player/PlayerRepository.kt index e26dc940..e84e2700 100644 --- a/app/src/main/java/com/jiachian/nbatoday/repository/player/PlayerRepository.kt +++ b/app/src/main/java/com/jiachian/nbatoday/repository/player/PlayerRepository.kt @@ -4,8 +4,22 @@ import com.jiachian.nbatoday.models.local.player.Player import com.jiachian.nbatoday.repository.BaseRepository import kotlinx.coroutines.flow.Flow +/** + * Manage player-related data. + */ abstract class PlayerRepository : BaseRepository() { + /** + * Inserts a player with the specified player ID. + * + * @param playerId The ID of the player to be inserted. + */ abstract suspend fun insertPlayer(playerId: Int) + /** + * Retrieves a flow of player information for a specific player ID. + * + * @param playerId The ID of the player for which to retrieve information. + * @return A Flow emitting a Player object, or null if no information is available. + */ abstract fun getPlayer(playerId: Int): Flow } diff --git a/app/src/main/java/com/jiachian/nbatoday/repository/schedule/ScheduleRepository.kt b/app/src/main/java/com/jiachian/nbatoday/repository/schedule/ScheduleRepository.kt index f716f6af..62f97917 100644 --- a/app/src/main/java/com/jiachian/nbatoday/repository/schedule/ScheduleRepository.kt +++ b/app/src/main/java/com/jiachian/nbatoday/repository/schedule/ScheduleRepository.kt @@ -2,7 +2,21 @@ package com.jiachian.nbatoday.repository.schedule import com.jiachian.nbatoday.repository.BaseRepository +/** + * Manage schedule-related data. + */ abstract class ScheduleRepository : BaseRepository() { + /** + * Updates the schedule data. + */ abstract suspend fun updateSchedule() + + /** + * Updates the schedule data for a specific date. + * + * @param year The year of the schedule date. + * @param month The month of the schedule date. + * @param day The day of the schedule date. + */ abstract suspend fun updateSchedule(year: Int, month: Int, day: Int) } diff --git a/app/src/main/java/com/jiachian/nbatoday/repository/team/TeamRepository.kt b/app/src/main/java/com/jiachian/nbatoday/repository/team/TeamRepository.kt index 413c7549..eb1b0667 100644 --- a/app/src/main/java/com/jiachian/nbatoday/repository/team/TeamRepository.kt +++ b/app/src/main/java/com/jiachian/nbatoday/repository/team/TeamRepository.kt @@ -7,12 +7,51 @@ import com.jiachian.nbatoday.models.local.team.TeamRank import com.jiachian.nbatoday.repository.BaseRepository import kotlinx.coroutines.flow.Flow +/** + * Manage team-related data. + */ abstract class TeamRepository : BaseRepository() { + /** + * Inserts all teams. + */ abstract suspend fun insertTeams() + + /** + * Inserts a specific team. + * + * @param teamId The ID of the team to be inserted. + */ abstract suspend fun insertTeam(teamId: Int) + + /** + * Updates the players of a specific team. + * + * @param teamId The ID of the team whose players need to be updated. + */ abstract suspend fun updateTeamPlayers(teamId: Int) + /** + * Retrieves a flow of teams based on their conference. + * + * @param conference The conference of the teams to retrieve. + * @return A Flow emitting a list of Team objects. + */ abstract fun getTeams(conference: NBATeam.Conference): Flow> + + /** + * Retrieves a flow of detailed information about a specific team, including its players. + * + * @param teamId The ID of the team for which to retrieve information. + * @return A Flow emitting a TeamAndPlayers object, or null if no information is available. + */ abstract fun getTeamAndPlayers(teamId: Int): Flow + + /** + * Retrieves a flow of the rank information for a specific team within a given conference. + * + * @param teamId The ID of the team for which to retrieve rank information. + * @param conference The conference of the teams in which to calculate the rank. + * @return A Flow emitting a TeamRank object. + */ abstract fun getTeamRank(teamId: Int, conference: NBATeam.Conference): Flow } diff --git a/app/src/main/java/com/jiachian/nbatoday/repository/user/UserRepository.kt b/app/src/main/java/com/jiachian/nbatoday/repository/user/UserRepository.kt index e81945dd..d22bfe05 100644 --- a/app/src/main/java/com/jiachian/nbatoday/repository/user/UserRepository.kt +++ b/app/src/main/java/com/jiachian/nbatoday/repository/user/UserRepository.kt @@ -4,13 +4,47 @@ import com.jiachian.nbatoday.models.local.user.User import com.jiachian.nbatoday.repository.BaseRepository import kotlinx.coroutines.flow.Flow +/** + * Manage user-related data. + */ abstract class UserRepository : BaseRepository() { + /** + * A Flow representing the current user information. + */ abstract val user: Flow + /** + * Performs a user login operation. + * + * @param account The user account for login. + * @param password The password associated with the account. + */ abstract suspend fun login(account: String, password: String) + + /** + * Performs a user logout operation. + */ abstract suspend fun logout() + + /** + * Performs a user registration operation. + * + * @param account The user account for registration. + * @param password The password associated with the account. + */ abstract suspend fun register(account: String, password: String) + /** + * Updates the points for the currently logged-in user. + * + * @param points The new points value. + */ abstract suspend fun updatePoints(points: Long) + + /** + * Adds points to the currently logged-in user. + * + * @param points The points to be added. + */ abstract suspend fun addPoints(points: Long) } diff --git a/app/src/main/java/com/jiachian/nbatoday/utils/ComposeViewModelProvider.kt b/app/src/main/java/com/jiachian/nbatoday/utils/ComposeViewModelProvider.kt index a5c79cb3..9c7d63e0 100644 --- a/app/src/main/java/com/jiachian/nbatoday/utils/ComposeViewModelProvider.kt +++ b/app/src/main/java/com/jiachian/nbatoday/utils/ComposeViewModelProvider.kt @@ -22,11 +22,17 @@ import com.jiachian.nbatoday.navigation.NavigationController import com.jiachian.nbatoday.repository.RepositoryProvider import kotlinx.coroutines.CoroutineScope +/** + * A provider class for creating and managing ComposeViewModel instances. + */ class ComposeViewModelProvider( private val repositoryProvider: RepositoryProvider, private val dataStore: BaseDataStore, private val navigationController: NavigationController, ) { + /** + * A map to store created ComposeViewModel instances for reuse. + */ private val viewModelMap = mutableMapOf() fun getHomeViewModel( @@ -179,6 +185,11 @@ class ComposeViewModelProvider( ) } + /** + * Removes a ComposeViewModel instance from the map based on the specified route. + * + * @param route The MainRoute associated with the view model to be removed. + */ fun removeViewModel(route: MainRoute) { viewModelMap.remove(route) } diff --git a/app/src/main/java/com/jiachian/nbatoday/utils/DateUtils.kt b/app/src/main/java/com/jiachian/nbatoday/utils/DateUtils.kt index 4f48c082..2e727fd1 100644 --- a/app/src/main/java/com/jiachian/nbatoday/utils/DateUtils.kt +++ b/app/src/main/java/com/jiachian/nbatoday/utils/DateUtils.kt @@ -24,6 +24,9 @@ object DateUtils { private const val MONTH_OCT = 9 private const val MONTH_NOV = 10 + /** + * Gets a Calendar instance set to the current date and time in the "EST" time zone. + */ fun getCalendar(): Calendar { return Calendar.getInstance().apply { timeZone = TimeZone.getTimeZone("EST") @@ -35,10 +38,16 @@ object DateUtils { return formatDate(year, month, day) } + /** + * Formats a date as a string in the format "yyyy-MM-dd". + */ fun formatDate(year: Int, month: Int, day: Int): String { return String.format(Locale.US, "%d-%d-%d", year, month, day) } + /** + * Parses a date represented by the given year, month, and day into a Date object. + */ @SuppressLint("SimpleDateFormat") fun parseDate(year: Int, month: Int, day: Int): Date? { return SimpleDateFormat("yyyy-MM-dd").let { @@ -47,6 +56,9 @@ object DateUtils { } } + /** + * Parses a date represented by the provided date string in "yyyy-MM-dd" format into a Date object. + */ @SuppressLint("SimpleDateFormat") fun parseDate(dateInFormat: String): Date? { return SimpleDateFormat("yyyy-MM-dd").let { @@ -55,6 +67,9 @@ object DateUtils { } } + /** + * Parses a date represented by the given date string using the provided date format. + */ fun parseDate(dateString: String?, dateFormat: SimpleDateFormat): Date? { return dateString?.let { time -> try {