From 9e1508e364aff8fccfc10d0156badcfac9ea7df3 Mon Sep 17 00:00:00 2001 From: I-Info Date: Fri, 3 Nov 2023 22:44:18 +0800 Subject: [PATCH] feat: query electricity balance --- app/build.gradle | 8 +- .../ijh/ui/component/ElectricityStatusCard.kt | 114 ++++++++++++++++++ .../com/zjutjh/ijh/ui/screen/HomeScreen.kt | 15 +++ .../zjutjh/ijh/ui/viewmodel/HomeViewModel.kt | 26 +++- .../kotlin/com/zjutjh/ijh/util/LoadResult.kt | 31 +++-- app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + build.gradle | 2 +- .../zjutjh/ijh/model/ElectricityBalance.kt | 13 ++ .../zjutjh/ijh/data/ElectricityRepository.kt | 8 ++ .../com/zjutjh/ijh/data/di/DataModule.kt | 9 +- .../data/impl/ElectricityRepositoryImpl.kt | 14 +++ .../model/NetworkElectricityBalance.kt | 40 ++++-- 13 files changed, 255 insertions(+), 29 deletions(-) create mode 100644 app/src/main/kotlin/com/zjutjh/ijh/ui/component/ElectricityStatusCard.kt create mode 100644 core/common/src/main/kotlin/com/zjutjh/ijh/model/ElectricityBalance.kt create mode 100644 core/data/src/main/kotlin/com/zjutjh/ijh/data/ElectricityRepository.kt create mode 100644 core/data/src/main/kotlin/com/zjutjh/ijh/data/impl/ElectricityRepositoryImpl.kt diff --git a/app/build.gradle b/app/build.gradle index f242164..a84e39c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { minSdk min_sdk targetSdk target_sdk - versionCode 2 - versionName '0.1.0' + versionCode 3 + versionName '0.2.0-alpha' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -78,7 +78,7 @@ dependencies { implementation 'androidx.compose.material3:material3:1.1.2' implementation 'androidx.compose.material:material-icons-extended:1.5.4' implementation 'androidx.core:core-splashscreen:1.0.1' - implementation "androidx.navigation:navigation-compose:2.7.4" + implementation 'androidx.navigation:navigation-compose:2.7.5' implementation 'androidx.profileinstaller:profileinstaller:1.3.1' implementation 'com.google.android.material:material:1.10.0' implementation "com.google.accompanist:accompanist-systemuicontroller:0.32.0" @@ -101,7 +101,7 @@ dependencies { // Dependency injection (Hilt) implementation "com.google.dagger:hilt-android:$hilt_version" kapt "com.google.dagger:hilt-compiler:$hilt_version" - implementation "androidx.hilt:hilt-navigation-compose:1.0.0" + implementation 'androidx.hilt:hilt-navigation-compose:1.1.0' testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/kotlin/com/zjutjh/ijh/ui/component/ElectricityStatusCard.kt b/app/src/main/kotlin/com/zjutjh/ijh/ui/component/ElectricityStatusCard.kt new file mode 100644 index 0000000..b828236 --- /dev/null +++ b/app/src/main/kotlin/com/zjutjh/ijh/ui/component/ElectricityStatusCard.kt @@ -0,0 +1,114 @@ +package com.zjutjh.ijh.ui.component + +import android.content.Context +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ElectricBolt +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.zjutjh.ijh.R +import com.zjutjh.ijh.model.ElectricityBalance +import com.zjutjh.ijh.ui.theme.IJhTheme +import com.zjutjh.ijh.util.LoadResult +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle + +@Composable +fun ElectricityStatusCard( + modifier: Modifier = Modifier, + balance: LoadResult, +) { + val context = LocalContext.current + val subtitle = remember(balance) { + prompt(context, if (balance is LoadResult.Loading) null else LocalDateTime.now()) + } + + GlanceCard( + modifier = modifier, + title = stringResource(id = R.string.dorm_electricity), + subtitle = subtitle, + icon = Icons.Default.ElectricBolt + ) { + AnimatedContent( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + targetState = balance, + contentAlignment = Alignment.Center, + label = "Loading", + ) { + when (it) { + is LoadResult.Loading -> Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator() + Text( + text = stringResource(id = R.string.loading), + textAlign = TextAlign.Center + ) + } + + is LoadResult.Ready -> { + val text = if (it.data == null) { + "N/A" + } else { + "${it.data.total} KW•h" + } + + Text( + modifier = Modifier.padding(vertical = 8.dp), + color = MaterialTheme.colorScheme.primary, + text = text, + style = MaterialTheme.typography.displaySmall, + textAlign = TextAlign.Center, + ) + } + } + } + } +} + +private fun prompt(context: Context, syncTime: LocalDateTime? = null): String = buildString { + val divider = " • " + append(context.getString(R.string.balance)) + append(divider) + if (syncTime == null) { + append(context.getString(R.string.unknown)) + } else { + append(DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM).format(syncTime)) + } +} + + +@Preview +@Composable +private fun ElectricityStatusCardPreview() { + IJhTheme { + ElectricityStatusCard( + balance = LoadResult.Ready( + ElectricityBalance( + total = 200f, + totalAmount = 100f, + subsidy = 100f, + subsidyAmount = 50f, + surplus = 100f, + surplusAmount = 50f + ) + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/zjutjh/ijh/ui/screen/HomeScreen.kt b/app/src/main/kotlin/com/zjutjh/ijh/ui/screen/HomeScreen.kt index 5d662db..4b8c624 100644 --- a/app/src/main/kotlin/com/zjutjh/ijh/ui/screen/HomeScreen.kt +++ b/app/src/main/kotlin/com/zjutjh/ijh/ui/screen/HomeScreen.kt @@ -51,8 +51,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zjutjh.ijh.R import com.zjutjh.ijh.data.mock.CourseRepositoryMock import com.zjutjh.ijh.model.Course +import com.zjutjh.ijh.model.ElectricityBalance import com.zjutjh.ijh.model.Term import com.zjutjh.ijh.ui.component.CampusCardInfoCard +import com.zjutjh.ijh.ui.component.ElectricityStatusCard import com.zjutjh.ijh.ui.component.IJhScaffold import com.zjutjh.ijh.ui.component.ScheduleCard import com.zjutjh.ijh.ui.model.TermDayState @@ -79,6 +81,7 @@ fun HomeRoute( val coursesLastSyncState by viewModel.courseLastSyncState.collectAsStateWithLifecycle() val cardBalanceState by viewModel.cardBalanceState.collectAsStateWithLifecycle() val cardBalanceLastSyncState by viewModel.cardBalanceLastSyncState.collectAsStateWithLifecycle() + val electricityBalance by viewModel.electricityState.collectAsStateWithLifecycle() val isLoggedIn = when (loginState) { null -> false @@ -103,6 +106,7 @@ fun HomeRoute( coursesLastSync = coursesLastSyncState, cardBalance = cardBalanceState, cardBalanceLastSync = cardBalanceLastSyncState, + electricityBalance = electricityBalance, onRefresh = viewModel::refreshAll, onNavigateToLogin = onNavigateToLogin, onNavigateToProfile = onNavigateToProfile, @@ -120,6 +124,7 @@ private fun HomeScreen( coursesLastSync: Duration?, cardBalance: LoadResult, cardBalanceLastSync: LoadResult, + electricityBalance: LoadResult, onRefresh: () -> Unit, onNavigateToLogin: () -> Unit, onNavigateToProfile: () -> Unit, @@ -161,6 +166,7 @@ private fun HomeScreen( balance = cardBalance, lastSync = cardBalanceLastSync, ) + ElectricityStatusCard(modifier = modifier, balance = electricityBalance) } PullRefreshIndicator( @@ -347,6 +353,14 @@ private fun NavigationDrawerPreview() { private fun HomeScreenPreview() { val courses = CourseRepositoryMock.getCourses() val termDay = TermDayState(2023, Term.FIRST, 1, true, DayOfWeek.MONDAY) + val electricityBalance = ElectricityBalance( + total = 200f, + totalAmount = 100f, + subsidy = 100f, + subsidyAmount = 50f, + surplus = 100f, + surplusAmount = 50f + ) IJhTheme { HomeScreen( refreshing = false, @@ -356,6 +370,7 @@ private fun HomeScreenPreview() { coursesLastSync = Duration.ofDays(1), cardBalance = LoadResult.Ready("123"), cardBalanceLastSync = LoadResult.Ready(Duration.ofDays(2)), + electricityBalance = LoadResult.Ready(electricityBalance), onRefresh = ::emptyFun, onNavigateToAbout = ::emptyFun, onNavigateToCalendar = ::emptyFun, diff --git a/app/src/main/kotlin/com/zjutjh/ijh/ui/viewmodel/HomeViewModel.kt b/app/src/main/kotlin/com/zjutjh/ijh/ui/viewmodel/HomeViewModel.kt index 73a1aa4..9769672 100644 --- a/app/src/main/kotlin/com/zjutjh/ijh/ui/viewmodel/HomeViewModel.kt +++ b/app/src/main/kotlin/com/zjutjh/ijh/ui/viewmodel/HomeViewModel.kt @@ -6,8 +6,10 @@ import androidx.lifecycle.viewModelScope import com.zjutjh.ijh.data.CampusRepository import com.zjutjh.ijh.data.CardRepository import com.zjutjh.ijh.data.CourseRepository +import com.zjutjh.ijh.data.ElectricityRepository import com.zjutjh.ijh.data.WeJhUserRepository import com.zjutjh.ijh.model.Course +import com.zjutjh.ijh.model.ElectricityBalance import com.zjutjh.ijh.model.Session import com.zjutjh.ijh.model.Term import com.zjutjh.ijh.ui.model.TermDayState @@ -29,6 +31,7 @@ class HomeViewModel @Inject constructor( private val courseRepository: CourseRepository, private val campusRepository: CampusRepository, private val cardRepository: CardRepository, + private val electricityRepository: ElectricityRepository, ) : ViewModel() { private val timerFlow: Flow = flow { @@ -79,7 +82,7 @@ class HomeViewModel @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) val coursesState: StateFlow>> = termDayState .dropWhile(LoadResult<*>::isLoading) - .distinctUntilChanged(LoadResult<*>::isEqual) + .distinctUntilChanged() .flatMapLatest { state -> if (state is LoadResult.Ready && state.data != null) { val day = state.data @@ -112,6 +115,11 @@ class HomeViewModel @Inject constructor( started = SharingStarted.WhileSubscribed(5_000) ) + private val _electricityState: MutableStateFlow> = + MutableStateFlow(LoadResult.Loading) + val electricityState: StateFlow> = + _electricityState.asStateFlow() + init { viewModelScope.launch(Dispatchers.Default) { // Check session state, renew if needed. TODO: move to application scope @@ -166,7 +174,8 @@ class HomeViewModel @Inject constructor( val task2 = scope.async { refreshCard() } - mutableListOf(task1, task2) + val task3 = scope.async { refreshElectricity() } + mutableListOf(task1, task2, task3) } else { mutableListOf() } @@ -216,4 +225,17 @@ class HomeViewModel @Inject constructor( } } + private suspend fun refreshElectricity() { + runCatching { + electricityRepository.getBalance() + }.fold({ balance -> + _electricityState.emit(LoadResult.Ready(balance)) + Log.i("Home", "Sync Electricity succeed.") + }) { + _electricityState.update { state -> + if (state is LoadResult.Loading) LoadResult.Ready(null) else state + } + Log.e("Home", "Sync Electricity failed.", it) + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/zjutjh/ijh/util/LoadResult.kt b/app/src/main/kotlin/com/zjutjh/ijh/util/LoadResult.kt index 7735ed8..89f9cca 100644 --- a/app/src/main/kotlin/com/zjutjh/ijh/util/LoadResult.kt +++ b/app/src/main/kotlin/com/zjutjh/ijh/util/LoadResult.kt @@ -15,25 +15,32 @@ import kotlinx.coroutines.flow.stateIn import kotlin.coroutines.CoroutineContext @Stable -sealed interface LoadResult { - data object Loading : LoadResult - class Ready(val data: T) : LoadResult +sealed class LoadResult { + data object Loading : LoadResult() + class Ready(val data: T) : LoadResult() - fun isEqual(v: LoadResult): Boolean = - if (this is Ready && v is Ready) { - this.data == v.data - } else this is Loading && v is Loading - - fun isEqual(v: LoadResult, areEquivalent: (left: T, right: R) -> Boolean): Boolean = - if (this is Ready && v is Ready) { - areEquivalent(this.data, v.data) - } else this is Loading && v is Loading + override fun equals(other: Any?): Boolean { + if (other !is LoadResult<*>) { + return false + } + return when (this) { + is Loading -> other is Loading + is Ready<*> -> other is Ready<*> && this.data == other.data + } + } fun map(transform: (T) -> R): LoadResult = when (this) { is Loading -> Loading is Ready -> Ready(transform(data)) } + + override fun hashCode(): Int { + return when (this) { + is Loading -> javaClass.hashCode() + is Ready<*> -> data.hashCode() + } + } } fun LoadResult.isLoading(): Boolean = this is LoadResult.Loading diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b80df50..8bf2dc5 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -70,4 +70,6 @@ 更多 余额 校园卡 + 寝室电费 + 状态 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 85094d1..8092c72 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,4 +74,6 @@ More Balance Campus Card + Dorm Electricity + Satus \ No newline at end of file diff --git a/build.gradle b/build.gradle index 318980a..ffed78a 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { compose_version = '1.5.4' hilt_version = '2.48.1' retrofit_version = '2.9.0' - protobuf_version = '3.24.4' + protobuf_version = '3.25.0' room_version = '2.6.0' work_version = '2.8.1' } diff --git a/core/common/src/main/kotlin/com/zjutjh/ijh/model/ElectricityBalance.kt b/core/common/src/main/kotlin/com/zjutjh/ijh/model/ElectricityBalance.kt new file mode 100644 index 0000000..21074e7 --- /dev/null +++ b/core/common/src/main/kotlin/com/zjutjh/ijh/model/ElectricityBalance.kt @@ -0,0 +1,13 @@ +package com.zjutjh.ijh.model + +import androidx.compose.runtime.Stable + +@Stable +data class ElectricityBalance( + val total: Float, + val totalAmount: Float, + val subsidy: Float, + val subsidyAmount: Float, + val surplus: Float, + val surplusAmount: Float +) \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/zjutjh/ijh/data/ElectricityRepository.kt b/core/data/src/main/kotlin/com/zjutjh/ijh/data/ElectricityRepository.kt new file mode 100644 index 0000000..c9a8c13 --- /dev/null +++ b/core/data/src/main/kotlin/com/zjutjh/ijh/data/ElectricityRepository.kt @@ -0,0 +1,8 @@ +package com.zjutjh.ijh.data + +import com.zjutjh.ijh.model.ElectricityBalance + +interface ElectricityRepository { + + suspend fun getBalance(): ElectricityBalance +} \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/zjutjh/ijh/data/di/DataModule.kt b/core/data/src/main/kotlin/com/zjutjh/ijh/data/di/DataModule.kt index 62e1cd3..db612b8 100644 --- a/core/data/src/main/kotlin/com/zjutjh/ijh/data/di/DataModule.kt +++ b/core/data/src/main/kotlin/com/zjutjh/ijh/data/di/DataModule.kt @@ -3,10 +3,12 @@ package com.zjutjh.ijh.data.di import com.zjutjh.ijh.data.CampusRepository import com.zjutjh.ijh.data.CardRepository import com.zjutjh.ijh.data.CourseRepository +import com.zjutjh.ijh.data.ElectricityRepository import com.zjutjh.ijh.data.WeJhUserRepository import com.zjutjh.ijh.data.impl.CampusRepositoryImpl import com.zjutjh.ijh.data.impl.CardRepositoryImpl import com.zjutjh.ijh.data.impl.CourseRepositoryImpl +import com.zjutjh.ijh.data.impl.ElectricityRepositoryImpl import com.zjutjh.ijh.data.impl.WeJhUserRepositoryImpl import dagger.Binds import dagger.Module @@ -24,8 +26,11 @@ interface DataModule { fun bindWeJhUserRepository(impl: WeJhUserRepositoryImpl): WeJhUserRepository @Binds - fun bindWeJhInfoRepository(impl: CampusRepositoryImpl): CampusRepository + fun bindCampusRepository(impl: CampusRepositoryImpl): CampusRepository @Binds - fun bindCardInfoRepository(impl: CardRepositoryImpl): CardRepository + fun bindCardRepository(impl: CardRepositoryImpl): CardRepository + + @Binds + fun bindElectricityRepository(impl: ElectricityRepositoryImpl): ElectricityRepository } \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/zjutjh/ijh/data/impl/ElectricityRepositoryImpl.kt b/core/data/src/main/kotlin/com/zjutjh/ijh/data/impl/ElectricityRepositoryImpl.kt new file mode 100644 index 0000000..d9265d2 --- /dev/null +++ b/core/data/src/main/kotlin/com/zjutjh/ijh/data/impl/ElectricityRepositoryImpl.kt @@ -0,0 +1,14 @@ +package com.zjutjh.ijh.data.impl + +import com.zjutjh.ijh.data.ElectricityRepository +import com.zjutjh.ijh.model.ElectricityBalance +import com.zjutjh.ijh.network.ElectricityNetworkDataSource +import com.zjutjh.ijh.network.model.asExternalModel +import javax.inject.Inject + +class ElectricityRepositoryImpl @Inject constructor( + private val network: ElectricityNetworkDataSource +) : ElectricityRepository { + + override suspend fun getBalance(): ElectricityBalance = network.getBalance().asExternalModel() +} \ No newline at end of file diff --git a/core/network/src/main/kotlin/com/zjutjh/ijh/network/model/NetworkElectricityBalance.kt b/core/network/src/main/kotlin/com/zjutjh/ijh/network/model/NetworkElectricityBalance.kt index 8d710c4..bc37360 100644 --- a/core/network/src/main/kotlin/com/zjutjh/ijh/network/model/NetworkElectricityBalance.kt +++ b/core/network/src/main/kotlin/com/zjutjh/ijh/network/model/NetworkElectricityBalance.kt @@ -1,22 +1,46 @@ package com.zjutjh.ijh.network.model +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import com.zjutjh.ijh.model.ElectricityBalance @JsonClass(generateAdapter = true) data class NetworkElectricityBalance( - val areaID: String, + @Json(name = "area_id") + val areaId: String, + @Json(name = "building_code") val buildingCode: String, + @Json(name = "display_room_name") val displayRoomName: String, + @Json(name = "floor_code") val floorCode: String, + @Json(name = "md_name") val mdName: String, + @Json(name = "md_type") val mdType: String, + @Json(name = "room_code") val roomCode: String, + @Json(name = "room_status") val roomStatus: String, + @Json(name = "school_code") val schoolCode: String, - val soc: Double, - val socAmount: Double, - val subsidy: Long, - val subsidyAmount: Long, - val surplus: Double, - val surplusAmount: Double -) \ No newline at end of file + val soc: Float, + @Json(name = "soc_amount") + val socAmount: Float, + val subsidy: Float, + @Json(name = "subsidy_amount") + val subsidyAmount: Float, + val surplus: Float, + @Json(name = "surplus_amount") + val surplusAmount: Float +) + +fun NetworkElectricityBalance.asExternalModel(): ElectricityBalance = + ElectricityBalance( + total = soc, + totalAmount = socAmount, + subsidy = subsidy, + subsidyAmount = subsidyAmount, + surplus = surplus, + surplusAmount = surplusAmount + ) \ No newline at end of file