Skip to content

Commit 97c1b94

Browse files
authored
Merge pull request #9 from nur-shuvo/user-submission
User Login
2 parents e71a357 + 5ddc6af commit 97c1b94

File tree

12 files changed

+276
-41
lines changed

12 files changed

+276
-41
lines changed

app/src/main/java/com/byteutility/dev/leetcode/plus/MainActivity.kt

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,46 @@ package com.byteutility.dev.leetcode.plus
33
import android.os.Bundle
44
import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
6-
import androidx.compose.foundation.layout.fillMaxSize
7-
import androidx.compose.material3.Scaffold
8-
import androidx.compose.ui.Modifier
9-
import com.byteutility.dev.leetcode.plus.ui.targetset.SetWeeklyTargetScreen
6+
import androidx.compose.runtime.LaunchedEffect
7+
import androidx.compose.runtime.mutableStateOf
8+
import androidx.compose.runtime.remember
9+
import androidx.navigation.compose.rememberNavController
10+
import com.byteutility.dev.leetcode.plus.data.datastore.UserDatastore
11+
import com.byteutility.dev.leetcode.plus.ui.LeetCodePlusNavGraph
12+
import com.byteutility.dev.leetcode.plus.ui.LeetCodePlusNavigationDestinations
1013
import com.byteutility.dev.leetcode.plus.ui.theme.LeetcodePlusTheme
1114
import dagger.hilt.android.AndroidEntryPoint
15+
import kotlinx.coroutines.flow.first
16+
import javax.inject.Inject
1217

1318
@AndroidEntryPoint
1419
class MainActivity : ComponentActivity() {
20+
21+
@Inject
22+
lateinit var userDatastore: UserDatastore
23+
1524
override fun onCreate(savedInstanceState: Bundle?) {
1625
super.onCreate(savedInstanceState)
1726
setContent {
1827
LeetcodePlusTheme {
19-
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
20-
// TODO: Replace by host
21-
SetWeeklyTargetScreen()
28+
val navController = rememberNavController()
29+
30+
val startDestination =
31+
remember { mutableStateOf(LeetCodePlusNavigationDestinations.LOGIN_ROUTE) }
32+
33+
LaunchedEffect(Unit) {
34+
startDestination.value =
35+
if (userDatastore.getUserBasicInfo()
36+
.first()?.userName?.isNotEmpty() == true
37+
) {
38+
LeetCodePlusNavigationDestinations.USER_PROFILE_ROUTE
39+
} else {
40+
LeetCodePlusNavigationDestinations.LOGIN_ROUTE
41+
}
2242
}
43+
44+
LeetCodePlusNavGraph(navController, startDestination.value)
2345
}
2446
}
2547
}
26-
}
48+
}

app/src/main/java/com/byteutility/dev/leetcode/plus/data/datastore/UserDatastore.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class UserDatastore @Inject constructor(
3939
}
4040
}
4141

42-
suspend fun getUserBasicInfo(): Flow<UserBasicInfo> {
42+
fun getUserBasicInfo(): Flow<UserBasicInfo?> {
4343
return context.userPreferencesDataStore.data
4444
.catch { exception ->
4545
emit(emptyPreferences())
@@ -59,7 +59,7 @@ class UserDatastore @Inject constructor(
5959
}
6060
}
6161

62-
fun getUserContestInfo(): Flow<UserContestInfo> {
62+
fun getUserContestInfo(): Flow<UserContestInfo?> {
6363
return context.userPreferencesDataStore.data
6464
.catch { exception ->
6565
emit(emptyPreferences())
@@ -79,7 +79,7 @@ class UserDatastore @Inject constructor(
7979
}
8080
}
8181

82-
fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo> {
82+
fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo?> {
8383
return context.userPreferencesDataStore.data
8484
.catch { exception ->
8585
emit(emptyPreferences())
@@ -99,7 +99,7 @@ class UserDatastore @Inject constructor(
9999
}
100100
}
101101

102-
fun getUserSubmissions(): Flow<List<UserSubmission>> {
102+
fun getUserSubmissions(): Flow<List<UserSubmission>?> {
103103
return context.userPreferencesDataStore.data
104104
.catch { exception ->
105105
emit(emptyPreferences())

app/src/main/java/com/byteutility/dev/leetcode/plus/data/repository/userDetails/UserDetailsRepository.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import com.byteutility.dev.leetcode.plus.data.model.UserSubmission
77
import kotlinx.coroutines.flow.Flow
88

99
interface UserDetailsRepository {
10-
suspend fun getUserBasicInfo(): Flow<UserBasicInfo>
11-
suspend fun getUserContestInfo(): Flow<UserContestInfo>
12-
suspend fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo>
13-
suspend fun getUserRecentSubmissions(): Flow<List<UserSubmission>>
10+
suspend fun getUserBasicInfo(): Flow<UserBasicInfo?>
11+
suspend fun getUserContestInfo(): Flow<UserContestInfo?>
12+
suspend fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo?>
13+
suspend fun getUserRecentSubmissions(): Flow<List<UserSubmission>?>
1414
}
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
package com.byteutility.dev.leetcode.plus.data.repository.userDetails
22

3+
import android.content.Context
34
import com.byteutility.dev.leetcode.plus.data.datastore.UserDatastore
45
import com.byteutility.dev.leetcode.plus.data.model.UserBasicInfo
56
import com.byteutility.dev.leetcode.plus.data.model.UserContestInfo
67
import com.byteutility.dev.leetcode.plus.data.model.UserProblemSolvedInfo
78
import com.byteutility.dev.leetcode.plus.data.model.UserSubmission
9+
import com.byteutility.dev.leetcode.plus.data.worker.UserDetailsSyncWorker
10+
import dagger.hilt.android.qualifiers.ApplicationContext
811
import kotlinx.coroutines.flow.Flow
912
import javax.inject.Inject
1013
import javax.inject.Singleton
1114

1215
@Singleton
1316
class UserDetailsRepositoryImpl @Inject constructor(
17+
@ApplicationContext private val context: Context,
1418
private val userDatastore: UserDatastore,
1519
) : UserDetailsRepository {
1620

17-
override suspend fun getUserBasicInfo(): Flow<UserBasicInfo> {
21+
init {
22+
UserDetailsSyncWorker.enqueueWork(context)
23+
}
24+
25+
override suspend fun getUserBasicInfo(): Flow<UserBasicInfo?> {
1826
return userDatastore.getUserBasicInfo()
1927
}
2028

21-
override suspend fun getUserContestInfo(): Flow<UserContestInfo> {
29+
override suspend fun getUserContestInfo(): Flow<UserContestInfo?> {
2230
return userDatastore.getUserContestInfo()
2331
}
2432

25-
override suspend fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo> {
33+
override suspend fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo?> {
2634
return userDatastore.getUserProblemSolvedInfo()
2735
}
2836

29-
override suspend fun getUserRecentSubmissions(): Flow<List<UserSubmission>> {
37+
override suspend fun getUserRecentSubmissions(): Flow<List<UserSubmission>?> {
3038
return userDatastore.getUserSubmissions()
3139
}
3240
}

app/src/main/java/com/byteutility/dev/leetcode/plus/data/worker/UserDetailsSyncWorker.kt

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.byteutility.dev.leetcode.plus.utils.toInternalModel
1212
import dagger.assisted.Assisted
1313
import dagger.assisted.AssistedInject
1414
import kotlinx.coroutines.async
15+
import kotlinx.coroutines.flow.first
1516
import kotlinx.coroutines.supervisorScope
1617

1718
@HiltWorker
@@ -25,23 +26,26 @@ class UserDetailsSyncWorker @AssistedInject constructor(
2526
override suspend fun doWork(): Result {
2627
return try {
2728
supervisorScope {
28-
val userProfileDeferred =
29-
async { restApiService.getUserProfile("Mazhar_MIK") }
30-
val userContestDeferred =
31-
async { restApiService.getUserContest("Mazhar_MIK") }
32-
val userAcSubmissionDeferred =
33-
async { restApiService.getAcSubmission("Mazhar_MIK", 20) }
34-
val userSolvedDeferred =
35-
async { restApiService.getSolved("Mazhar_MIK") }
29+
val userName = userDatastore.getUserBasicInfo().first()?.userName
30+
userName?.let {
31+
val userProfileDeferred =
32+
async { restApiService.getUserProfile(userName) }
33+
val userContestDeferred =
34+
async { restApiService.getUserContest(userName) }
35+
val userAcSubmissionDeferred =
36+
async { restApiService.getAcSubmission(userName, 20) }
37+
val userSolvedDeferred =
38+
async { restApiService.getSolved(userName) }
3639

37-
val userProfile = userProfileDeferred.await()
38-
val userContest = userContestDeferred.await()
39-
val userAcSubmission = userAcSubmissionDeferred.await()
40-
val userSolved = userSolvedDeferred.await()
41-
userDatastore.saveUserBasicInfo(userProfile.toInternalModel())
42-
userDatastore.saveUserContestInfo(userContest.toInternalModel())
43-
userDatastore.saveUserSubmissions(userAcSubmission.toInternalModel())
44-
userDatastore.saveUserProblemSolvedInfo(userSolved.toInternalModel())
40+
val userProfile = userProfileDeferred.await()
41+
val userContest = userContestDeferred.await()
42+
val userAcSubmission = userAcSubmissionDeferred.await()
43+
val userSolved = userSolvedDeferred.await()
44+
userDatastore.saveUserBasicInfo(userProfile.toInternalModel())
45+
userDatastore.saveUserContestInfo(userContest.toInternalModel())
46+
userDatastore.saveUserSubmissions(userAcSubmission.toInternalModel())
47+
userDatastore.saveUserProblemSolvedInfo(userSolved.toInternalModel())
48+
}
4549
Result.success()
4650
}
4751
} catch (e: Exception) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.byteutility.dev.leetcode.plus.ui
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.navigation.NavHostController
5+
import androidx.navigation.compose.NavHost
6+
import androidx.navigation.compose.composable
7+
import androidx.navigation.compose.rememberNavController
8+
import com.byteutility.dev.leetcode.plus.ui.login.UserLoginScreen
9+
import com.byteutility.dev.leetcode.plus.ui.targetset.SetWeeklyTargetScreen
10+
import com.byteutility.dev.leetcode.plus.ui.userdetails.UserProfileScreen
11+
12+
13+
@Composable
14+
fun LeetCodePlusNavGraph(
15+
navController: NavHostController = rememberNavController(),
16+
startDestination: String = LeetCodePlusNavigationDestinations.LOGIN_ROUTE
17+
) {
18+
NavHost(
19+
navController = navController,
20+
startDestination = startDestination
21+
) {
22+
val navigationActions = LeetCodePlusNavigation(navController)
23+
24+
composable(route = LeetCodePlusNavigationDestinations.LOGIN_ROUTE) {
25+
UserLoginScreen {
26+
navigationActions.navigateToUserProfile()
27+
}
28+
}
29+
30+
composable(route = LeetCodePlusNavigationDestinations.SET_GOAL_ROUTE) {
31+
SetWeeklyTargetScreen()
32+
}
33+
34+
composable(route = LeetCodePlusNavigationDestinations.USER_PROFILE_ROUTE) {
35+
UserProfileScreen()
36+
}
37+
}
38+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.byteutility.dev.leetcode.plus.ui
2+
3+
import androidx.navigation.NavController
4+
import androidx.navigation.NavGraph.Companion.findStartDestination
5+
6+
7+
object LeetCodePlusNavigationDestinations {
8+
const val USER_PROFILE_ROUTE = "user_profile"
9+
const val SET_GOAL_ROUTE = "set_goal"
10+
const val LOGIN_ROUTE = "login"
11+
}
12+
13+
class LeetCodePlusNavigation(navController: NavController) {
14+
val navigateToUserProfile: () -> Unit = {
15+
navController.navigate(LeetCodePlusNavigationDestinations.USER_PROFILE_ROUTE) {
16+
launchSingleTop = true
17+
popUpTo(LeetCodePlusNavigationDestinations.LOGIN_ROUTE) {
18+
inclusive = true
19+
}
20+
}
21+
}
22+
23+
val navigateToSetGoal: () -> Unit = {
24+
navController.navigate(LeetCodePlusNavigationDestinations.SET_GOAL_ROUTE) {
25+
launchSingleTop = true
26+
}
27+
}
28+
29+
30+
val navigateToLogin: () -> Unit = {
31+
navController.navigate(LeetCodePlusNavigationDestinations.LOGIN_ROUTE) {
32+
launchSingleTop = true
33+
}
34+
}
35+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.byteutility.dev.leetcode.plus.ui.login
2+
3+
import android.util.Log
4+
import androidx.compose.foundation.Image
5+
import androidx.compose.foundation.layout.Arrangement
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.height
11+
import androidx.compose.foundation.layout.padding
12+
import androidx.compose.foundation.layout.size
13+
import androidx.compose.material3.Button
14+
import androidx.compose.material3.OutlinedTextField
15+
import androidx.compose.material3.Text
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.getValue
18+
import androidx.compose.runtime.mutableStateOf
19+
import androidx.compose.runtime.remember
20+
import androidx.compose.runtime.setValue
21+
import androidx.compose.ui.Alignment
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.res.painterResource
24+
import androidx.compose.ui.text.font.FontWeight
25+
import androidx.compose.ui.tooling.preview.Preview
26+
import androidx.compose.ui.unit.dp
27+
import androidx.compose.ui.unit.sp
28+
import androidx.hilt.navigation.compose.hiltViewModel
29+
import com.byteutility.dev.leetcode.plus.R
30+
31+
private const val TAG = "UserLoginScreen"
32+
@Composable
33+
fun UserLoginScreen(
34+
viewModel: UserLoginViewModel = hiltViewModel(),
35+
onProceedClick: () -> Unit = {}
36+
) {
37+
LeetCodeUsernameScreen {
38+
Log.e(TAG, "UserLoginScreen: $it", )
39+
viewModel.saveUserName(it)
40+
onProceedClick()
41+
}
42+
}
43+
44+
45+
@Composable
46+
fun LeetCodeUsernameScreen(
47+
onProceedClick: (userName: String) -> Unit,
48+
) {
49+
Column(
50+
modifier = Modifier
51+
.fillMaxSize()
52+
.padding(16.dp),
53+
verticalArrangement = Arrangement.Center,
54+
horizontalAlignment = Alignment.CenterHorizontally
55+
) {
56+
Image(
57+
painter = painterResource(id = R.drawable.leetcode_logo),
58+
contentDescription = "App Logo",
59+
modifier = Modifier.size(100.dp)
60+
)
61+
Spacer(modifier = Modifier.height(16.dp))
62+
Text(
63+
text = "LeetCode Plus",
64+
fontWeight = FontWeight.Bold,
65+
fontSize = 32.sp
66+
)
67+
68+
Spacer(modifier = Modifier.height(16.dp))
69+
70+
var username by remember { mutableStateOf("") }
71+
72+
OutlinedTextField(
73+
value = username,
74+
onValueChange = { username = it },
75+
modifier = Modifier.fillMaxWidth(),
76+
singleLine = true
77+
)
78+
79+
Spacer(modifier = Modifier.height(16.dp))
80+
81+
Button(onClick = {
82+
onProceedClick(username)
83+
}) {
84+
Text(text = "Proceed")
85+
}
86+
}
87+
}
88+
89+
90+
@Preview(showBackground = true)
91+
@Composable
92+
fun PreviewLeetCodeLoginScreen() {
93+
LeetCodeUsernameScreen {}
94+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.byteutility.dev.leetcode.plus.ui.login
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
5+
import com.byteutility.dev.leetcode.plus.data.datastore.UserDatastore
6+
import com.byteutility.dev.leetcode.plus.data.model.UserBasicInfo
7+
import dagger.hilt.android.lifecycle.HiltViewModel
8+
import kotlinx.coroutines.launch
9+
import javax.inject.Inject
10+
11+
12+
@HiltViewModel
13+
class UserLoginViewModel @Inject constructor(
14+
private val userDatastore: UserDatastore,
15+
) : ViewModel() {
16+
17+
fun saveUserName(userName: String) {
18+
viewModelScope.launch {
19+
userDatastore.saveUserBasicInfo(
20+
userBasicInfo = UserBasicInfo(
21+
userName = userName,
22+
)
23+
)
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)