Skip to content

Commit 984ce23

Browse files
committed
refactor:
- small update checker. - fix errors on many screens * I am quite confused as to how the API developers are thinking. changing data structures on endpoints used by not only my app, but theirs too. making both unusable for many students. I question myself a lot of times. do they know anything about actual good practices to follow when making any changes?
1 parent 7aa03a3 commit 984ce23

File tree

39 files changed

+380
-145
lines changed

39 files changed

+380
-145
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ plugins {
1212
alias(libs.plugins.sqldelight) apply false
1313
alias(libs.plugins.detekt) apply false
1414
alias(libs.plugins.gms) apply false
15+
alias(libs.plugins.kotlin.android) apply false
1516
}
1617

1718
buildscript {

composeApp/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
22
import io.gitlab.arturbosch.detekt.Detekt
3-
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
43
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
54

65
plugins {
@@ -53,6 +52,7 @@ kotlin {
5352
implementation(project(":i18n"))
5453
implementation(project(":domain"))
5554
implementation(project(":core"))
55+
implementation(project(":core:update-check"))
5656

5757
implementation(compose.ui)
5858
implementation(compose.runtime)
@@ -88,8 +88,8 @@ kotlin {
8888
}
8989
}
9090

91-
val appVersionName = "0.1.7"
92-
val appVersionCode = 12
91+
val appVersionName = libs.versions.app.version.name.get()
92+
val appVersionCode = libs.versions.app.version.code.get().toInt()
9393
val appPackageName = "mehiz.abdallah.progres"
9494

9595
android {
Lines changed: 38 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
1-
import androidx.compose.animation.animateContentSize
2-
import androidx.compose.foundation.background
3-
import androidx.compose.foundation.layout.Arrangement
4-
import androidx.compose.foundation.layout.Box
51
import androidx.compose.foundation.layout.Column
6-
import androidx.compose.foundation.layout.Row
72
import androidx.compose.foundation.layout.WindowInsets
3+
import androidx.compose.foundation.layout.consumeWindowInsets
84
import androidx.compose.foundation.layout.fillMaxWidth
95
import androidx.compose.foundation.layout.statusBars
10-
import androidx.compose.foundation.layout.windowInsetsPadding
11-
import androidx.compose.material3.MaterialTheme
12-
import androidx.compose.material3.Surface
13-
import androidx.compose.material3.Text
146
import androidx.compose.runtime.Composable
157
import androidx.compose.runtime.LaunchedEffect
168
import androidx.compose.runtime.collectAsState
179
import androidx.compose.runtime.getValue
10+
import androidx.compose.runtime.mutableStateOf
11+
import androidx.compose.runtime.remember
12+
import androidx.compose.runtime.setValue
1813
import androidx.compose.ui.Modifier
1914
import cafe.adriel.voyager.navigator.Navigator
2015
import cafe.adriel.voyager.transitions.SlideTransition
@@ -31,6 +26,7 @@ import org.koin.compose.koinInject
3126
import org.koin.core.context.loadKoinModules
3227
import org.koin.dsl.module
3328
import preferences.BasePreferences
29+
import presentation.AppStateBanners
3430
import presentation.theme.AppTheme
3531
import ui.home.HomeScreen
3632
import ui.onboarding.LoginScreen
@@ -40,36 +36,14 @@ fun App(onReady: () -> Unit, modifier: Modifier = Modifier) {
4036
val preferences = koinInject<BasePreferences>()
4137
val revealCanvasState = rememberRevealCanvasState()
4238
val toasterState = rememberToasterState()
43-
loadKoinModules(
44-
module {
45-
single { revealCanvasState }
46-
single { toasterState }
47-
},
48-
)
49-
AppTheme {
50-
RevealCanvas(revealCanvasState) {
51-
Column(modifier) {
52-
Surface(color = MaterialTheme.colorScheme.surfaceDim) { ConnectivityStatusBar() }
53-
Navigator(screen = if (preferences.isLoggedIn.get()) HomeScreen else LoginScreen) {
54-
onReady()
55-
SlideTransition(it)
56-
}
57-
}
58-
Toaster(toasterState, richColors = true)
59-
}
60-
}
61-
}
62-
63-
@Composable
64-
fun ConnectivityStatusBar(
65-
modifier: Modifier = Modifier,
66-
) {
39+
val appUpdateChecker = koinInject<AppUpdateCheck>()
40+
val isThereAnUpdate by appUpdateChecker.isThereAnUpdate.collectAsState()
6741
val state = rememberConnectivityState().apply { startMonitoring() }
6842
val httpConnectivity = Connectivity(
6943
options = HttpConnectivityOptions(
7044
urls = listOf(stringResource(MR.strings.progres_api_url)),
7145
pollingIntervalMs = 10 * 1000,
72-
timeoutMs = 10 * 1000
46+
timeoutMs = 10 * 1000,
7347
),
7448
)
7549
httpConnectivity.start()
@@ -79,24 +53,38 @@ fun ConnectivityStatusBar(
7953
httpConnectivity.stop()
8054
httpConnectivity.start()
8155
}
82-
Box(modifier = modifier.windowInsetsPadding(WindowInsets.statusBars))
83-
if (isHttpMonitoring && state.isMonitoring) {
84-
Row(
85-
modifier
86-
.fillMaxWidth()
87-
.animateContentSize()
88-
.background(MaterialTheme.colorScheme.errorContainer),
89-
horizontalArrangement = Arrangement.Center,
90-
) {
91-
if (state.isDisconnected || httpStatus.isDisconnected) {
92-
Text(
93-
stringResource(
94-
if (state.isDisconnected) MR.strings.connectivity_no_internet else MR.strings.connectivity_no_progres,
95-
),
96-
color = MaterialTheme.colorScheme.onErrorContainer,
97-
modifier = Modifier.windowInsetsPadding(WindowInsets.statusBars),
56+
loadKoinModules(
57+
module {
58+
single { revealCanvasState }
59+
single { toasterState }
60+
},
61+
)
62+
var isThereABannerVisible by remember { mutableStateOf(false) }
63+
LaunchedEffect(isThereAnUpdate, state.isConnected, httpStatus, isThereABannerVisible) {
64+
isThereABannerVisible = isThereAnUpdate || !state.isConnected || httpStatus !is Connectivity.Status.Connected
65+
}
66+
AppTheme {
67+
RevealCanvas(revealCanvasState) {
68+
Column(modifier) {
69+
AppStateBanners(
70+
newUpdate = isThereAnUpdate,
71+
noInternet = !state.isConnected && state.isMonitoring,
72+
cantReach = httpStatus !is Connectivity.Status.Connected && isHttpMonitoring,
73+
modifier = Modifier.fillMaxWidth(),
9874
)
75+
Navigator(screen = if (preferences.isLoggedIn.get()) HomeScreen else LoginScreen) {
76+
onReady()
77+
SlideTransition(
78+
it,
79+
modifier = if (isThereABannerVisible) {
80+
Modifier.consumeWindowInsets(WindowInsets.statusBars)
81+
} else {
82+
Modifier
83+
},
84+
)
85+
}
9986
}
87+
Toaster(toasterState, richColors = true)
10088
}
10189
}
10290
}

composeApp/src/commonMain/kotlin/di/InitKoin.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package di
22

3+
import UpdateCheckerModule
34
import mehiz.abdallah.progres.domain.di.DomainModule
45
import org.koin.dsl.module
56
import utils.CredentialManager
@@ -14,6 +15,7 @@ fun initKoin(
1415
PreferencesModule(datastorePath),
1516
DomainModule,
1617
ScreenModelsModule,
17-
ApplicationModule(credentialManager, platformUtils)
18+
ApplicationModule(credentialManager, platformUtils),
19+
UpdateCheckerModule,
1820
)
1921
}

composeApp/src/commonMain/kotlin/preferences/KVaultKeys.kt

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
Copyright © 2015 Javier Tomás
3+
Copyright © 2024 Mihon Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
package presentation
18+
19+
import androidx.compose.animation.AnimatedVisibility
20+
import androidx.compose.animation.expandVertically
21+
import androidx.compose.animation.shrinkVertically
22+
import androidx.compose.foundation.background
23+
import androidx.compose.foundation.clickable
24+
import androidx.compose.foundation.layout.WindowInsets
25+
import androidx.compose.foundation.layout.fillMaxWidth
26+
import androidx.compose.foundation.layout.padding
27+
import androidx.compose.foundation.layout.statusBars
28+
import androidx.compose.foundation.layout.windowInsetsPadding
29+
import androidx.compose.material3.MaterialTheme
30+
import androidx.compose.material3.Text
31+
import androidx.compose.runtime.Composable
32+
import androidx.compose.ui.Modifier
33+
import androidx.compose.ui.layout.SubcomposeLayout
34+
import androidx.compose.ui.platform.LocalDensity
35+
import androidx.compose.ui.text.style.TextAlign
36+
import androidx.compose.ui.unit.dp
37+
import androidx.compose.ui.util.fastForEach
38+
import androidx.compose.ui.util.fastMap
39+
import androidx.compose.ui.util.fastMaxBy
40+
import dev.icerock.moko.resources.compose.stringResource
41+
import mehiz.abdallah.progres.i18n.MR
42+
import org.koin.compose.koinInject
43+
import utils.PlatformUtils
44+
45+
@Composable
46+
fun AppStateBanners(
47+
newUpdate: Boolean,
48+
noInternet: Boolean,
49+
cantReach: Boolean,
50+
modifier: Modifier = Modifier,
51+
) {
52+
val density = LocalDensity.current
53+
val mainInsets = WindowInsets.statusBars
54+
val mainInsetsTop = mainInsets.getTop(density)
55+
SubcomposeLayout(modifier = modifier) { constraints ->
56+
val noInternetPlaceable = subcompose(0) {
57+
AnimatedVisibility(
58+
visible = noInternet || cantReach,
59+
enter = expandVertically(),
60+
exit = shrinkVertically(),
61+
) {
62+
NoInternet(
63+
modifier = Modifier.windowInsetsPadding(mainInsets),
64+
noInternet = noInternet,
65+
)
66+
}
67+
}.fastMap { it.measure(constraints) }
68+
val noInternetHeight = noInternetPlaceable.fastMaxBy { it.height }?.height ?: 0
69+
70+
val newUpdatePlaceable = subcompose(1) {
71+
AnimatedVisibility(
72+
visible = newUpdate,
73+
enter = expandVertically(),
74+
exit = shrinkVertically(),
75+
) {
76+
val top = (mainInsetsTop - noInternetHeight).coerceAtLeast(0)
77+
NewUpdateBanner(
78+
modifier = Modifier.windowInsetsPadding(WindowInsets(top = top)),
79+
)
80+
}
81+
}.fastMap { it.measure(constraints) }
82+
val newUpdateHeight = newUpdatePlaceable.fastMaxBy { it.height }?.height ?: 0
83+
84+
layout(constraints.maxWidth, noInternetHeight + newUpdateHeight) {
85+
noInternetPlaceable.fastForEach {
86+
it.place(0, 0)
87+
}
88+
newUpdatePlaceable.fastForEach {
89+
it.place(0, noInternetHeight)
90+
}
91+
}
92+
}
93+
}
94+
95+
@Composable
96+
private fun NewUpdateBanner(modifier: Modifier = Modifier) {
97+
// I don't care about injecting platform utils into this small composable.
98+
val platformUtils = koinInject<PlatformUtils>()
99+
Text(
100+
text = stringResource(MR.strings.new_update_available),
101+
modifier = Modifier
102+
.background(MaterialTheme.colorScheme.tertiary)
103+
.fillMaxWidth()
104+
.clickable { platformUtils.openURI(platformUtils.getString(MR.strings.repository_url) + "/releases/latest") }
105+
.padding(4.dp)
106+
.then(modifier),
107+
color = MaterialTheme.colorScheme.onTertiary,
108+
textAlign = TextAlign.Center,
109+
style = MaterialTheme.typography.labelMedium,
110+
)
111+
}
112+
113+
@Composable
114+
private fun NoInternet(
115+
noInternet: Boolean,
116+
modifier: Modifier = Modifier,
117+
) {
118+
Text(
119+
stringResource(
120+
if (noInternet) MR.strings.connectivity_no_internet else MR.strings.connectivity_no_progres,
121+
),
122+
color = MaterialTheme.colorScheme.onErrorContainer,
123+
textAlign = TextAlign.Center,
124+
style = MaterialTheme.typography.labelMedium,
125+
modifier = Modifier
126+
.background(MaterialTheme.colorScheme.errorContainer)
127+
.fillMaxWidth()
128+
.padding(4.dp)
129+
.then(modifier),
130+
)
131+
}

composeApp/src/commonMain/kotlin/ui/home/HomeScreen.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.Arrangement
1414
import androidx.compose.foundation.layout.Box
1515
import androidx.compose.foundation.layout.Column
1616
import androidx.compose.foundation.layout.Row
17-
import androidx.compose.foundation.layout.WindowInsets
1817
import androidx.compose.foundation.layout.aspectRatio
1918
import androidx.compose.foundation.layout.fillMaxSize
2019
import androidx.compose.foundation.layout.fillMaxWidth
@@ -175,7 +174,6 @@ object HomeScreen : Screen {
175174
Icon(Icons.Rounded.Settings, null)
176175
}
177176
},
178-
windowInsets = WindowInsets(0.dp),
179177
)
180178
},
181179
) { paddingValues ->

composeApp/src/commonMain/kotlin/ui/home/accommodation/AccommodationScreen.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Arrangement
77
import androidx.compose.foundation.layout.Column
88
import androidx.compose.foundation.layout.Row
99
import androidx.compose.foundation.layout.Spacer
10-
import androidx.compose.foundation.layout.WindowInsets
1110
import androidx.compose.foundation.layout.fillMaxWidth
1211
import androidx.compose.foundation.layout.height
1312
import androidx.compose.foundation.layout.padding
@@ -103,7 +102,6 @@ object AccommodationScreen : Screen {
103102
Icon(Icons.AutoMirrored.Rounded.ArrowBack, null)
104103
}
105104
},
106-
windowInsets = WindowInsets(0.dp),
107105
)
108106
},
109107
) { paddingValues ->

composeApp/src/commonMain/kotlin/ui/home/bacinfo/BacInfoScreen.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.compose.foundation.basicMarquee
66
import androidx.compose.foundation.layout.Arrangement
77
import androidx.compose.foundation.layout.Column
88
import androidx.compose.foundation.layout.Row
9-
import androidx.compose.foundation.layout.WindowInsets
109
import androidx.compose.foundation.layout.fillMaxWidth
1110
import androidx.compose.foundation.layout.heightIn
1211
import androidx.compose.foundation.layout.padding
@@ -60,7 +59,6 @@ object BacInfoScreen : Screen {
6059
Icon(Icons.AutoMirrored.Rounded.ArrowBack, null)
6160
}
6261
},
63-
windowInsets = WindowInsets(0.dp)
6462
)
6563
},
6664
) { paddingValues ->

composeApp/src/commonMain/kotlin/ui/home/ccgrades/CCGradesScreen.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Box
88
import androidx.compose.foundation.layout.Column
99
import androidx.compose.foundation.layout.Row
1010
import androidx.compose.foundation.layout.Spacer
11-
import androidx.compose.foundation.layout.WindowInsets
1211
import androidx.compose.foundation.layout.fillMaxSize
1312
import androidx.compose.foundation.layout.fillMaxWidth
1413
import androidx.compose.foundation.layout.height
@@ -107,7 +106,6 @@ object CCGradesScreen : Screen {
107106
Icon(Icons.AutoMirrored.Rounded.ArrowBack, null)
108107
}
109108
},
110-
windowInsets = WindowInsets(0.dp)
111109
)
112110
},
113111
) { paddingValues ->

0 commit comments

Comments
 (0)