From b368c1dab522b1e29a1e52dbcf87086b84105596 Mon Sep 17 00:00:00 2001 From: MahmoudMabrok Date: Thu, 4 Jan 2024 22:24:12 +0200 Subject: [PATCH] feat: new ui and load avatar --- composeApp/build.gradle.kts | 6 +- .../components/BasicUserData.kt | 114 +++++++++++++----- .../githubactivity/components/LabeledData.kt | 23 +++- .../githubactivity/components/RepoItem.kt | 15 +-- .../mo3ta/githubactivity/model/UserData.kt | 40 +++--- .../screens/config/ConfigScreen.kt | 67 ++++++---- .../screens/userDetails/UserDetailsScreen.kt | 6 + gradle/libs.versions.toml | 5 +- 8 files changed, 186 insertions(+), 90 deletions(-) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 6e696ea..5fe3e8c 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -28,7 +28,7 @@ kotlin { iosX64(), iosArm64(), iosSimulatorArm64() - ).forEach { + ).forEach { it.binaries.framework { baseName = "ComposeApp" isStatic = true @@ -42,7 +42,7 @@ kotlin { implementation(compose.materialIconsExtended) implementation(libs.libres) implementation(libs.voyager.navigator) - implementation(libs.composeImageLoader) + implementation(libs.kamel.image) implementation(libs.kotlinx.coroutines.core) implementation(libs.moko.mvvm) implementation(libs.ktor.core) @@ -70,6 +70,7 @@ kotlin { implementation(compose.desktop.common) implementation(compose.desktop.currentOs) implementation(libs.ktor.client.okhttp) + implementation(libs.kotlinx.coroutines.swing) } jsMain.dependencies { @@ -133,3 +134,4 @@ libres { tasks.getByPath("jvmProcessResources").dependsOn("libresGenerateResources") tasks.getByPath("jvmSourcesJar").dependsOn("libresGenerateResources") tasks.getByPath("jsProcessResources").dependsOn("libresGenerateResources") + diff --git a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/BasicUserData.kt b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/BasicUserData.kt index b08442f..ab07861 100644 --- a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/BasicUserData.kt +++ b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/BasicUserData.kt @@ -1,46 +1,98 @@ package tools.mo3ta.githubactivity.components +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import io.kamel.image.KamelImage +import io.kamel.image.asyncPainterResource import tools.mo3ta.githubactivity.model.UserDetails + +@OptIn(ExperimentalLayoutApi::class) @Composable -fun BasicUserData(userDetails: UserDetails?, stars: Int?) { +fun BasicUserData(userDetails: UserDetails?, stars: Int? = 0) { Column { - userDetails?.name?.let { LabeledData("Name", it) } - userDetails?.bio?.let { LabeledData("Bio", it) } - userDetails?.email?.let { LabeledData("Email", it) } - userDetails?.company?.let { - LabeledData( - "Company", - it - ) + userDetails?.avatar_url?.let { + KamelImage( + asyncPainterResource(it), + "user avatar", + modifier = Modifier + .width(200.dp) + .aspectRatio(1f) + .clip(CircleShape) + .border(8.dp, Color.Gray, CircleShape) + .align(Alignment.CenterHorizontally) + ) + } + Spacer(modifier = Modifier.size(32.dp)) + userDetails?.name?.let { + Text( + it, + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.headlineLarge + ) } - userDetails?.blog?.let { LabeledData("Blog", it) } - userDetails?.location?.let { - LabeledData( - "Location", - it - ) + userDetails?.bio?.let { + Text( + it, + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.labelSmall + ) } - userDetails?.public_repos?.let { - LabeledData( - "Public Repo", - it.toString() - ) + userDetails?.email?.let { + Text( + it, + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.labelSmall + ) } - userDetails?.public_gists?.let { - LabeledData( - "Public Gists", - it.toString() - ) + userDetails?.blog?.let { + Text( + it, + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.labelSmall + ) } - userDetails?.followers?.let { - LabeledData( - "Followers", - it.toString() - ) + Spacer(modifier = Modifier.size(16.dp)) + FlowRow { + userDetails?.public_repos?.let { + LabeledData("Public Repo", it.toString(), modifier = Modifier.wrapContentSize()) + } + userDetails?.followers?.let { + LabeledData("Follower", it.toString(), modifier = Modifier.weight(1f)) + } + userDetails?.following?.let { + LabeledData("Following", it.toString(), modifier = Modifier.weight(1f)) + } + + stars?.let { LabeledData("Stars", it.toString(), modifier = Modifier.weight(1f)) } + + userDetails?.public_gists?.let { + LabeledData("Public Gists", it.toString(), modifier = Modifier.weight(1f)) + } + + userDetails?.company?.let { + LabeledData("Company", it, modifier = Modifier.weight(1f)) + } + + userDetails?.location?.let { + LabeledData("Location", it, modifier = Modifier.weight(1f)) + } } - stars?.let { LabeledData("Stars", it.toString()) } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/LabeledData.kt b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/LabeledData.kt index 6cb2c9d..fe02454 100644 --- a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/LabeledData.kt +++ b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/LabeledData.kt @@ -1,17 +1,30 @@ package tools.mo3ta.githubactivity.components -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @Composable -fun LabeledData(title:String , value: String) { +fun LabeledData(title: String, value: String, modifier: Modifier = Modifier) { - Row (modifier = Modifier.padding(horizontal = 16.dp , vertical = 4.dp)){ - Text("$title : ", modifier = Modifier.weight(1.5f)) - Text(value, modifier = Modifier.weight(4f)) + Column( + modifier = modifier.padding(horizontal = 16.dp, vertical = 4.dp) + ) { + Text( + value, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + Text( + title, + style = MaterialTheme.typography.titleSmall, + modifier = Modifier.align(Alignment.CenterHorizontally), + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/RepoItem.kt b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/RepoItem.kt index 949dd68..90f1f19 100644 --- a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/RepoItem.kt +++ b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/components/RepoItem.kt @@ -37,13 +37,7 @@ fun RepoItem(repoDetails: RepoDetails) { ) } Row { - Text( - text = repoDetails.language ?: "", - style = MaterialTheme.typography.bodySmall, - modifier = Modifier - .width(80.dp) - .align(Alignment.CenterVertically) - ) + repoDetails.stargazers_count?.let { LabelWithIcon( label = it.toString(), @@ -56,6 +50,13 @@ fun RepoItem(repoDetails: RepoDetails) { iconSource = Icons.Default.ForkRight ) } + Text( + text = repoDetails.language ?: "", + style = MaterialTheme.typography.bodySmall, + modifier = Modifier + .width(80.dp) + .align(Alignment.CenterVertically) + ) } diff --git a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/model/UserData.kt b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/model/UserData.kt index 47ce588..5b86817 100644 --- a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/model/UserData.kt +++ b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/model/UserData.kt @@ -4,23 +4,23 @@ import kotlinx.serialization.Serializable @Serializable data class UserDetails( - val avatar_url: String?, - val bio: String?, - val blog: String?, - val company: String?, - val created_at: String?, - val email: String?, - val followers: Int?, - val followers_url: String?, - val following: Int?, - val gravatar_id: String?, - val id: Int?, - val location: String?, - val login: String?, - val name: String?, - val node_id: String?, - val organizations_url: String?, - val public_gists: Int?, - val public_repos: Int?, - val twitter_username: String?, -) \ No newline at end of file + val avatar_url: String? = null, + val bio: String? = null, + val blog: String? = null, + val company: String? = null, + val created_at: String? = null, + val email: String? = null, + val followers: Int? = 0, + val followers_url: String? = null, + val following: Int? = 0, + val gravatar_id: String? = null, + val id: Int? = null, + val location: String? = null, + val login: String? = null, + val name: String? = null, + val node_id: String? = null, + val organizations_url: String? = null, + val public_gists: Int? = null, + val public_repos: Int? = 0, + val twitter_username: String? = null, + ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/screens/config/ConfigScreen.kt b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/screens/config/ConfigScreen.kt index 4cb3f02..ac6ec4a 100644 --- a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/screens/config/ConfigScreen.kt +++ b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/screens/config/ConfigScreen.kt @@ -4,13 +4,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.LightMode @@ -38,41 +35,55 @@ import tools.mo3ta.githubactivity.screens.userDetails.UserDetailsScreenData import tools.mo3ta.githubactivity.theme.LocalThemeIsDark - data object ConfigScreen : Screen { val settings = Settings() + @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow - var githubKey by rememberSaveable { mutableStateOf(settings.getString(Keys.API_KEY, ""))} - var userName by rememberSaveable { mutableStateOf(settings.getString(Keys.USER_NAME, ""))} - var isEnterprise by rememberSaveable { mutableStateOf(settings.getBoolean(Keys.IS_ENTERPRISE, false))} - var enterprise by rememberSaveable { mutableStateOf(settings.getString(Keys.ENTERPRISE, ""))} + var githubKey by rememberSaveable { mutableStateOf(settings.getString(Keys.API_KEY, "")) } + var userName by rememberSaveable { mutableStateOf(settings.getString(Keys.USER_NAME, "")) } + var isEnterprise by rememberSaveable { + mutableStateOf( + settings.getBoolean( + Keys.IS_ENTERPRISE, + false + ) + ) + } + var enterprise by rememberSaveable { + mutableStateOf( + settings.getString( + Keys.ENTERPRISE, + "" + ) + ) + } var isDark by LocalThemeIsDark.current Column(modifier = Modifier.fillMaxSize()) { Row( horizontalArrangement = Arrangement.Center - ) { + ) { Text( text = "Configurations", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleLarge, - ) + ) Spacer(modifier = Modifier.weight(1.0f)) IconButton( onClick = { isDark = !isDark } - ) { + ) { Icon( modifier = Modifier.padding(8.dp).size(20.dp), imageVector = if (isDark) Icons.Default.LightMode else Icons.Default.DarkMode, contentDescription = null - ) + ) } } @@ -82,7 +93,7 @@ data object ConfigScreen : Screen { label = { Text("User Name") }, singleLine = true, modifier = Modifier.fillMaxWidth().padding(16.dp) - ) + ) OutlinedTextField( value = githubKey, @@ -90,31 +101,33 @@ data object ConfigScreen : Screen { label = { Text("Token") }, singleLine = true, modifier = Modifier.fillMaxWidth().padding(16.dp) - ) + ) Row( horizontalArrangement = Arrangement.Center - ) { + ) { Text( text = "Is Enterprise", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleLarge, - ) + ) Spacer(modifier = Modifier.weight(1.0f)) - Checkbox(checked = isEnterprise, onCheckedChange = { state -> isEnterprise = state}) + Checkbox( + checked = isEnterprise, + onCheckedChange = { state -> isEnterprise = state }) } - if (isEnterprise){ + if (isEnterprise) { OutlinedTextField( value = enterprise, onValueChange = { enterprise = it }, label = { Text("Enterprise Name") }, singleLine = true, modifier = Modifier.fillMaxWidth().padding(16.dp) - ) + ) } Button( @@ -126,18 +139,26 @@ data object ConfigScreen : Screen { apiKey = githubKey, isEnterprise = isEnterprise, enterprise = enterprise, - userName = userName))) + userName = userName + ) + ) + ) }, modifier = Modifier.fillMaxWidth().padding(16.dp) - ) { + ) { Text("Fire") } } } - private fun saveData(userName: String, githubKey: String, isEnterprise: Boolean, enterprise: String) { - with(settings){ + private fun saveData( + userName: String, + githubKey: String, + isEnterprise: Boolean, + enterprise: String + ) { + with(settings) { putString(Keys.USER_NAME, userName) putString(Keys.API_KEY, githubKey) putString(Keys.ENTERPRISE, enterprise) diff --git a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/screens/userDetails/UserDetailsScreen.kt b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/screens/userDetails/UserDetailsScreen.kt index 6d2b5c7..5585e15 100644 --- a/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/screens/userDetails/UserDetailsScreen.kt +++ b/composeApp/src/commonMain/kotlin/tools/mo3ta/githubactivity/screens/userDetails/UserDetailsScreen.kt @@ -1,13 +1,16 @@ package tools.mo3ta.githubactivity.screens.userDetails import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen import dev.icerock.moko.mvvm.compose.getViewModel import dev.icerock.moko.mvvm.compose.viewModelFactory @@ -53,6 +56,9 @@ data class UserDetailsScreen( BasicUserData( uiState.userData, uiState.totalStars.takeIf { it > 0 }) + + Spacer(modifier = Modifier.size(16.dp)) + RepoList(uiState.repos) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e849fa9..10c9bde 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] +kamelImage = "0.6.0" kotlin = "1.9.21" agp = "8.2.0" compose = "1.5.11" @@ -8,7 +9,6 @@ androidx-activityCompose = "1.8.1" compose-uitooling = "1.5.4" libres = "1.2.2" voyager = "1.0.0" -composeImageLoader = "1.7.1" kotlinx-coroutines = "1.7.3" moko-mvvm = "0.16.1" ktor = "2.3.7" @@ -22,11 +22,12 @@ multiplatformSettings = "1.0.0" androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } compose-uitooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose-uitooling" } +kamel-image = { module = "media.kamel:kamel-image", version.ref = "kamelImage" } libres = { module = "io.github.skeptick.libres:libres-compose", version.ref = "libres" } voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } -composeImageLoader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "composeImageLoader" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } moko-mvvm = { module = "dev.icerock.moko:mvvm-compose", version.ref = "moko-mvvm" } ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }