Skip to content

Commit eef0e03

Browse files
committed
feat(profile): Implement login functionality and enhance account UI
This commit adds support for navigating to the authentication screen from the profile and significantly improves the profile's account section UI, including support for unauthenticated states. - **feat(profile)**: Added `OnLoginClick` action to `ProfileAction` and updated `ProfileViewModel` and `ProfileRoot` to handle navigation to the authentication screen. - **feat(profile)**: Redesigned `AccountSection` to show a "Sign in to GitHub" prompt with a login button when the user is not logged in. - **feat(profile)**: Enhanced the logged-in profile view with a larger avatar, statistics cards (Repos, Followers, Following), and improved typography for the user's name, username, and bio. - **refactor(profile)**: Updated `UserProfile` domain model to make the `bio` field nullable. - **refactor(navigation)**: Integrated `onNavigateToAuthentication` callback into `AppNavigation` for the `ProfileRoot`.
1 parent c744ce9 commit eef0e03

6 files changed

Lines changed: 208 additions & 14 deletions

File tree

composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/navigation/AppNavigation.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ fun AppNavigation(
213213
ProfileRoot(
214214
onNavigateBack = {
215215
navController.navigateUp()
216+
},
217+
onNavigateToAuthentication = {
218+
navController.navigate(GithubStoreGraph.AuthenticationScreen)
216219
}
217220
)
218221
}

feature/profile/domain/src/commonMain/kotlin/zed/rainxch/profile/domain/model/UserProfile.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ data class UserProfile(
55
val imageUrl: String,
66
val name: String,
77
val username: String,
8-
val bio: String,
8+
val bio: String?,
99
val repositoryCount: Int,
1010
val followers: Int,
1111
val following: Int,

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileAction.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ sealed interface ProfileAction {
1212
data object OnLogoutConfirmClick : ProfileAction
1313
data object OnLogoutDismiss : ProfileAction
1414
data object OnHelpClick : ProfileAction
15+
data object OnLoginClick : ProfileAction
1516
data class OnFontThemeSelected(val fontTheme: FontTheme) : ProfileAction
1617
data class OnProxyTypeSelected(val type: ProxyType) : ProfileAction
1718
data class OnProxyHostChanged(val host: String) : ProfileAction

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileRoot.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import zed.rainxch.profile.presentation.components.sections.settings
4949
@Composable
5050
fun ProfileRoot(
5151
onNavigateBack: () -> Unit,
52+
onNavigateToAuthentication: () -> Unit,
5253
viewModel: ProfileViewModel = koinViewModel()
5354
) {
5455
val state by viewModel.state.collectAsStateWithLifecycle()
@@ -93,6 +94,10 @@ fun ProfileRoot(
9394
onNavigateBack()
9495
}
9596

97+
ProfileAction.OnLoginClick -> {
98+
onNavigateToAuthentication()
99+
}
100+
96101
else -> {
97102
viewModel.onAction(action)
98103
}

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ class ProfileViewModel(
193193
/* Handed in composable */
194194
}
195195

196+
ProfileAction.OnLoginClick -> {
197+
/* Handed in composable */
198+
}
199+
196200
is ProfileAction.OnFontThemeSelected -> {
197201
viewModelScope.launch {
198202
themesRepository.setFontTheme(action.fontTheme)
Lines changed: 194 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,214 @@
11
package zed.rainxch.profile.presentation.components.sections
22

3+
import androidx.compose.foundation.BorderStroke
34
import androidx.compose.foundation.background
45
import androidx.compose.foundation.layout.Arrangement
56
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.Row
8+
import androidx.compose.foundation.layout.Spacer
9+
import androidx.compose.foundation.layout.fillMaxSize
610
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.height
12+
import androidx.compose.foundation.layout.padding
713
import androidx.compose.foundation.layout.size
14+
import androidx.compose.foundation.layout.width
815
import androidx.compose.foundation.lazy.LazyColumn
916
import androidx.compose.foundation.lazy.LazyListScope
1017
import androidx.compose.foundation.shape.CircleShape
18+
import androidx.compose.foundation.shape.RoundedCornerShape
1119
import androidx.compose.material.icons.Icons
20+
import androidx.compose.material.icons.filled.AccountCircle
1221
import androidx.compose.material.icons.outlined.AccountCircle
22+
import androidx.compose.material3.Card
23+
import androidx.compose.material3.CardDefaults
24+
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
25+
import androidx.compose.material3.Icon
1326
import androidx.compose.material3.MaterialTheme
27+
import androidx.compose.material3.Text
1428
import androidx.compose.runtime.Composable
1529
import androidx.compose.ui.Alignment
1630
import androidx.compose.ui.Modifier
1731
import androidx.compose.ui.draw.clip
32+
import androidx.compose.ui.text.style.TextAlign
33+
import androidx.compose.ui.text.style.TextOverflow
1834
import androidx.compose.ui.unit.dp
1935
import org.jetbrains.compose.ui.tooling.preview.Preview
2036
import zed.rainxch.core.presentation.components.GitHubStoreImage
37+
import zed.rainxch.core.presentation.components.GithubStoreButton
2138
import zed.rainxch.core.presentation.theme.GithubStoreTheme
39+
import zed.rainxch.profile.domain.model.UserProfile
2240
import zed.rainxch.profile.presentation.ProfileAction
2341
import zed.rainxch.profile.presentation.ProfileState
2442

43+
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
2544
fun LazyListScope.accountSection(
2645
state: ProfileState,
2746
onAction: (ProfileAction) -> Unit,
2847
) {
2948
item {
30-
Column (
49+
Column(
3150
modifier = Modifier.fillMaxWidth(),
3251
verticalArrangement = Arrangement.spacedBy(4.dp),
3352
horizontalAlignment = Alignment.CenterHorizontally
3453
) {
35-
GitHubStoreImage(
36-
imageModel = {
37-
if (state.userProfile == null) {
38-
Icons.Outlined.AccountCircle
39-
} else {
54+
if (state.userProfile == null) {
55+
Icon(
56+
imageVector = Icons.Filled.AccountCircle,
57+
contentDescription = null,
58+
modifier = Modifier
59+
.size(100.dp)
60+
.clip(CircleShape)
61+
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
62+
.padding(20.dp),
63+
tint = MaterialTheme.colorScheme.onSurface
64+
)
65+
} else {
66+
GitHubStoreImage(
67+
imageModel = {
4068
state.userProfile.imageUrl
41-
}
42-
},
43-
modifier = Modifier
44-
.size(100.dp)
45-
.clip(CircleShape)
46-
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
47-
)
69+
},
70+
modifier = Modifier
71+
.size(128.dp)
72+
.clip(CircleShape)
73+
.background(MaterialTheme.colorScheme.surfaceContainerHigh),
74+
)
75+
76+
Spacer(Modifier.height(8.dp))
77+
}
78+
79+
if (state.userProfile?.name != null) {
80+
Text(
81+
text = state.userProfile.name,
82+
style = MaterialTheme.typography.titleLargeEmphasized,
83+
color = MaterialTheme.colorScheme.onBackground,
84+
textAlign = TextAlign.Center
85+
)
86+
87+
Text(
88+
text = "@${state.userProfile.username}",
89+
style = MaterialTheme.typography.titleMedium,
90+
color = MaterialTheme.colorScheme.onSurfaceVariant,
91+
textAlign = TextAlign.Center
92+
)
93+
94+
state.userProfile.bio?.let { bio ->
95+
Text(
96+
text = bio,
97+
style = MaterialTheme.typography.titleMedium,
98+
color = MaterialTheme.colorScheme.onSurfaceVariant,
99+
textAlign = TextAlign.Center
100+
)
101+
}
102+
} else {
103+
Spacer(Modifier.height(8.dp))
104+
105+
Text(
106+
text = "Sign in to GitHub",
107+
style = MaterialTheme.typography.titleLarge,
108+
color = MaterialTheme.colorScheme.onBackground,
109+
textAlign = TextAlign.Center
110+
)
111+
112+
Spacer(Modifier.height(4.dp))
113+
114+
Text(
115+
text = "Unlock the full experience. Manage your apps, sync your preference, and browser faster.",
116+
style = MaterialTheme.typography.bodyLarge,
117+
color = MaterialTheme.colorScheme.onSurfaceVariant,
118+
textAlign = TextAlign.Center
119+
)
120+
}
121+
122+
if (state.userProfile != null) {
123+
Spacer(Modifier.height(16.dp))
48124

125+
Row(
126+
modifier = Modifier.fillMaxSize(),
127+
verticalAlignment = Alignment.CenterVertically,
128+
horizontalArrangement = Arrangement.spacedBy(12.dp)
129+
) {
130+
StatCard(
131+
label = "Repos",
132+
value = "24",
133+
modifier = Modifier.weight(1f)
134+
)
49135

136+
StatCard(
137+
label = "Followers",
138+
value = "1.2K",
139+
modifier = Modifier.weight(1f)
140+
)
141+
142+
StatCard(
143+
label = "Following",
144+
value = "56",
145+
modifier = Modifier.weight(1f)
146+
)
147+
}
148+
}
149+
150+
if (state.userProfile == null) {
151+
Spacer(Modifier.height(8.dp))
152+
153+
GithubStoreButton(
154+
text = "Login",
155+
onClick = {
156+
onAction(ProfileAction.OnLoginClick)
157+
},
158+
modifier = Modifier
159+
.width(480.dp)
160+
.padding(horizontal = 8.dp)
161+
)
162+
}
50163
}
51164

52165
}
53166
}
54167

168+
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
169+
@Composable
170+
private fun StatCard(
171+
label: String,
172+
value: String,
173+
modifier: Modifier = Modifier
174+
) {
175+
Card(
176+
modifier = modifier,
177+
colors = CardDefaults.elevatedCardColors(
178+
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
179+
contentColor = MaterialTheme.colorScheme.onSurface
180+
),
181+
shape = RoundedCornerShape(32.dp),
182+
border = BorderStroke(
183+
width = 1.dp,
184+
color = MaterialTheme.colorScheme.secondary
185+
)
186+
) {
187+
Column(
188+
modifier = Modifier
189+
.fillMaxWidth()
190+
.padding(12.dp),
191+
horizontalAlignment = Alignment.CenterHorizontally
192+
) {
193+
Text(
194+
text = value,
195+
maxLines = 1,
196+
style = MaterialTheme.typography.titleLargeEmphasized,
197+
overflow = TextOverflow.Ellipsis,
198+
color = MaterialTheme.colorScheme.onSurface
199+
)
200+
201+
Text(
202+
text = label,
203+
maxLines = 1,
204+
style = MaterialTheme.typography.bodyLargeEmphasized,
205+
overflow = TextOverflow.Ellipsis,
206+
color = MaterialTheme.colorScheme.onSurfaceVariant
207+
)
208+
}
209+
}
210+
}
211+
55212
@Preview(showBackground = true)
56213
@Composable
57214
fun AccountSectionPreview() {
@@ -63,4 +220,28 @@ fun AccountSectionPreview() {
63220
)
64221
}
65222
}
223+
}
224+
225+
@Preview(showBackground = true)
226+
@Composable
227+
fun AccountSectionUserPreview() {
228+
GithubStoreTheme {
229+
LazyColumn {
230+
accountSection(
231+
state = ProfileState(
232+
userProfile = UserProfile(
233+
id = 1,
234+
imageUrl = "",
235+
name = "Octocat",
236+
username = "the_octocat",
237+
bio = " Language Savant. If your repository's language is being reported incorrectly, send us a pull request! ",
238+
repositoryCount = 8,
239+
followers = 21900,
240+
following = 9
241+
)
242+
),
243+
onAction = { }
244+
)
245+
}
246+
}
66247
}

0 commit comments

Comments
 (0)