Skip to content

User Login #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,46 @@ package com.byteutility.dev.leetcode.plus
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import com.byteutility.dev.leetcode.plus.ui.targetset.SetWeeklyTargetScreen
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.navigation.compose.rememberNavController
import com.byteutility.dev.leetcode.plus.data.datastore.UserDatastore
import com.byteutility.dev.leetcode.plus.ui.LeetCodePlusNavGraph
import com.byteutility.dev.leetcode.plus.ui.LeetCodePlusNavigationDestinations
import com.byteutility.dev.leetcode.plus.ui.theme.LeetcodePlusTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.first
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

@Inject
lateinit var userDatastore: UserDatastore

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LeetcodePlusTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
// TODO: Replace by host
SetWeeklyTargetScreen()
val navController = rememberNavController()

val startDestination =
remember { mutableStateOf(LeetCodePlusNavigationDestinations.LOGIN_ROUTE) }

LaunchedEffect(Unit) {
startDestination.value =
if (userDatastore.getUserBasicInfo()
.first()?.userName?.isNotEmpty() == true
) {
LeetCodePlusNavigationDestinations.USER_PROFILE_ROUTE
} else {
LeetCodePlusNavigationDestinations.LOGIN_ROUTE
}
}

LeetCodePlusNavGraph(navController, startDestination.value)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class UserDatastore @Inject constructor(
}
}

suspend fun getUserBasicInfo(): Flow<UserBasicInfo> {
fun getUserBasicInfo(): Flow<UserBasicInfo?> {
return context.userPreferencesDataStore.data
.catch { exception ->
emit(emptyPreferences())
Expand All @@ -59,7 +59,7 @@ class UserDatastore @Inject constructor(
}
}

fun getUserContestInfo(): Flow<UserContestInfo> {
fun getUserContestInfo(): Flow<UserContestInfo?> {
return context.userPreferencesDataStore.data
.catch { exception ->
emit(emptyPreferences())
Expand All @@ -79,7 +79,7 @@ class UserDatastore @Inject constructor(
}
}

fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo> {
fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo?> {
return context.userPreferencesDataStore.data
.catch { exception ->
emit(emptyPreferences())
Expand All @@ -99,7 +99,7 @@ class UserDatastore @Inject constructor(
}
}

fun getUserSubmissions(): Flow<List<UserSubmission>> {
fun getUserSubmissions(): Flow<List<UserSubmission>?> {
return context.userPreferencesDataStore.data
.catch { exception ->
emit(emptyPreferences())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import com.byteutility.dev.leetcode.plus.data.model.UserSubmission
import kotlinx.coroutines.flow.Flow

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

import android.content.Context
import com.byteutility.dev.leetcode.plus.data.datastore.UserDatastore
import com.byteutility.dev.leetcode.plus.data.model.UserBasicInfo
import com.byteutility.dev.leetcode.plus.data.model.UserContestInfo
import com.byteutility.dev.leetcode.plus.data.model.UserProblemSolvedInfo
import com.byteutility.dev.leetcode.plus.data.model.UserSubmission
import com.byteutility.dev.leetcode.plus.data.worker.UserDetailsSyncWorker
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class UserDetailsRepositoryImpl @Inject constructor(
@ApplicationContext private val context: Context,
private val userDatastore: UserDatastore,
) : UserDetailsRepository {

override suspend fun getUserBasicInfo(): Flow<UserBasicInfo> {
init {
UserDetailsSyncWorker.enqueueWork(context)
}

override suspend fun getUserBasicInfo(): Flow<UserBasicInfo?> {
return userDatastore.getUserBasicInfo()
}

override suspend fun getUserContestInfo(): Flow<UserContestInfo> {
override suspend fun getUserContestInfo(): Flow<UserContestInfo?> {
return userDatastore.getUserContestInfo()
}

override suspend fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo> {
override suspend fun getUserProblemSolvedInfo(): Flow<UserProblemSolvedInfo?> {
return userDatastore.getUserProblemSolvedInfo()
}

override suspend fun getUserRecentSubmissions(): Flow<List<UserSubmission>> {
override suspend fun getUserRecentSubmissions(): Flow<List<UserSubmission>?> {
return userDatastore.getUserSubmissions()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.byteutility.dev.leetcode.plus.utils.toInternalModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.supervisorScope

@HiltWorker
Expand All @@ -25,23 +26,26 @@ class UserDetailsSyncWorker @AssistedInject constructor(
override suspend fun doWork(): Result {
return try {
supervisorScope {
val userProfileDeferred =
async { restApiService.getUserProfile("Mazhar_MIK") }
val userContestDeferred =
async { restApiService.getUserContest("Mazhar_MIK") }
val userAcSubmissionDeferred =
async { restApiService.getAcSubmission("Mazhar_MIK", 20) }
val userSolvedDeferred =
async { restApiService.getSolved("Mazhar_MIK") }
val userName = userDatastore.getUserBasicInfo().first()?.userName
userName?.let {
val userProfileDeferred =
async { restApiService.getUserProfile(userName) }
val userContestDeferred =
async { restApiService.getUserContest(userName) }
val userAcSubmissionDeferred =
async { restApiService.getAcSubmission(userName, 20) }
val userSolvedDeferred =
async { restApiService.getSolved(userName) }

val userProfile = userProfileDeferred.await()
val userContest = userContestDeferred.await()
val userAcSubmission = userAcSubmissionDeferred.await()
val userSolved = userSolvedDeferred.await()
userDatastore.saveUserBasicInfo(userProfile.toInternalModel())
userDatastore.saveUserContestInfo(userContest.toInternalModel())
userDatastore.saveUserSubmissions(userAcSubmission.toInternalModel())
userDatastore.saveUserProblemSolvedInfo(userSolved.toInternalModel())
val userProfile = userProfileDeferred.await()
val userContest = userContestDeferred.await()
val userAcSubmission = userAcSubmissionDeferred.await()
val userSolved = userSolvedDeferred.await()
userDatastore.saveUserBasicInfo(userProfile.toInternalModel())
userDatastore.saveUserContestInfo(userContest.toInternalModel())
userDatastore.saveUserSubmissions(userAcSubmission.toInternalModel())
userDatastore.saveUserProblemSolvedInfo(userSolved.toInternalModel())
}
Result.success()
}
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.byteutility.dev.leetcode.plus.ui

import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.byteutility.dev.leetcode.plus.ui.login.UserLoginScreen
import com.byteutility.dev.leetcode.plus.ui.targetset.SetWeeklyTargetScreen
import com.byteutility.dev.leetcode.plus.ui.userdetails.UserProfileScreen


@Composable
fun LeetCodePlusNavGraph(
navController: NavHostController = rememberNavController(),
startDestination: String = LeetCodePlusNavigationDestinations.LOGIN_ROUTE
) {
NavHost(
navController = navController,
startDestination = startDestination
) {
val navigationActions = LeetCodePlusNavigation(navController)

composable(route = LeetCodePlusNavigationDestinations.LOGIN_ROUTE) {
UserLoginScreen {
navigationActions.navigateToUserProfile()
}
}

composable(route = LeetCodePlusNavigationDestinations.SET_GOAL_ROUTE) {
SetWeeklyTargetScreen()
}

composable(route = LeetCodePlusNavigationDestinations.USER_PROFILE_ROUTE) {
UserProfileScreen()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.byteutility.dev.leetcode.plus.ui

import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination


object LeetCodePlusNavigationDestinations {
const val USER_PROFILE_ROUTE = "user_profile"
const val SET_GOAL_ROUTE = "set_goal"
const val LOGIN_ROUTE = "login"
}

class LeetCodePlusNavigation(navController: NavController) {
val navigateToUserProfile: () -> Unit = {
navController.navigate(LeetCodePlusNavigationDestinations.USER_PROFILE_ROUTE) {
launchSingleTop = true
popUpTo(LeetCodePlusNavigationDestinations.LOGIN_ROUTE) {
inclusive = true
}
}
}

val navigateToSetGoal: () -> Unit = {
navController.navigate(LeetCodePlusNavigationDestinations.SET_GOAL_ROUTE) {
launchSingleTop = true
}
}


val navigateToLogin: () -> Unit = {
navController.navigate(LeetCodePlusNavigationDestinations.LOGIN_ROUTE) {
launchSingleTop = true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.byteutility.dev.leetcode.plus.ui.login

import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.byteutility.dev.leetcode.plus.R

private const val TAG = "UserLoginScreen"
@Composable
fun UserLoginScreen(
viewModel: UserLoginViewModel = hiltViewModel(),
onProceedClick: () -> Unit = {}
) {
LeetCodeUsernameScreen {
Log.e(TAG, "UserLoginScreen: $it", )
viewModel.saveUserName(it)
onProceedClick()
}
}


@Composable
fun LeetCodeUsernameScreen(
onProceedClick: (userName: String) -> Unit,
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.leetcode_logo),
contentDescription = "App Logo",
modifier = Modifier.size(100.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "LeetCode Plus",
fontWeight = FontWeight.Bold,
fontSize = 32.sp
)

Spacer(modifier = Modifier.height(16.dp))

var username by remember { mutableStateOf("") }

OutlinedTextField(
value = username,
onValueChange = { username = it },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

Spacer(modifier = Modifier.height(16.dp))

Button(onClick = {
onProceedClick(username)
}) {
Text(text = "Proceed")
}
}
}


@Preview(showBackground = true)
@Composable
fun PreviewLeetCodeLoginScreen() {
LeetCodeUsernameScreen {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.byteutility.dev.leetcode.plus.ui.login

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.byteutility.dev.leetcode.plus.data.datastore.UserDatastore
import com.byteutility.dev.leetcode.plus.data.model.UserBasicInfo
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject


@HiltViewModel
class UserLoginViewModel @Inject constructor(
private val userDatastore: UserDatastore,
) : ViewModel() {

fun saveUserName(userName: String) {
viewModelScope.launch {
userDatastore.saveUserBasicInfo(
userBasicInfo = UserBasicInfo(
userName = userName,
)
)
}
}
}
Loading