diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/ActiveFriendResponse.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/ActiveFriendResponse.kt index c7a86e3..0968f0f 100644 --- a/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/ActiveFriendResponse.kt +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/ActiveFriendResponse.kt @@ -1,5 +1,10 @@ package com.dscvit.vitty.network.api.community.responses.user data class ActiveFriendResponse( - val data: List?, + val data: List?, +) + +data class ActiveFriendItem( + val friend_username: String?, + val hide: Boolean?, ) diff --git a/app/src/main/java/com/dscvit/vitty/ui/connect/ConnectScreenContent.kt b/app/src/main/java/com/dscvit/vitty/ui/connect/ConnectScreenContent.kt index 6697bdc..1b05ffb 100644 --- a/app/src/main/java/com/dscvit/vitty/ui/connect/ConnectScreenContent.kt +++ b/app/src/main/java/com/dscvit/vitty/ui/connect/ConnectScreenContent.kt @@ -168,6 +168,7 @@ fun ConnectScreenContent( val username = sharedPreferences.getString(Constants.COMMUNITY_USERNAME, "") ?: "" if (token.isNotEmpty()) { + connectViewModel.fetchActiveFriends(token, prefs = sharedPreferences) connectViewModel.refreshFriendList(token, username) connectViewModel.getFriendRequest(token) } diff --git a/app/src/main/java/com/dscvit/vitty/ui/connect/ConnectViewModel.kt b/app/src/main/java/com/dscvit/vitty/ui/connect/ConnectViewModel.kt index 1e31f21..6d28cdc 100644 --- a/app/src/main/java/com/dscvit/vitty/ui/connect/ConnectViewModel.kt +++ b/app/src/main/java/com/dscvit/vitty/ui/connect/ConnectViewModel.kt @@ -18,6 +18,7 @@ import com.dscvit.vitty.network.api.community.responses.circle.CircleRequestsRes import com.dscvit.vitty.network.api.community.responses.circle.CreateCircleResponse import com.dscvit.vitty.network.api.community.responses.circle.JoinCircleResponse import com.dscvit.vitty.network.api.community.responses.requests.RequestsResponse +import com.dscvit.vitty.network.api.community.responses.user.ActiveFriendItem import com.dscvit.vitty.network.api.community.responses.user.ActiveFriendResponse import com.dscvit.vitty.network.api.community.responses.user.CircleResponse import com.dscvit.vitty.network.api.community.responses.user.FriendResponse @@ -48,7 +49,7 @@ class ConnectViewModel : ViewModel() { private val _sentCircleRequests = MutableLiveData() private val _isCircleRequestsLoading = MutableLiveData() private val _circleActionResponse = MutableLiveData() - private val _activeFriends = MutableLiveData??>() + private val _activeFriends = MutableLiveData??>() private val _ghostModeResponse = MutableLiveData() val ghostModeResponse: MutableLiveData = _ghostModeResponse @@ -71,7 +72,7 @@ class ConnectViewModel : ViewModel() { val sentCircleRequests: MutableLiveData = _sentCircleRequests val isCircleRequestsLoading: MutableLiveData = _isCircleRequestsLoading val circleActionResponse: MutableLiveData = _circleActionResponse - val activeFriends: MutableLiveData??> = _activeFriends + val activeFriends: MutableLiveData??> = _activeFriends fun getFriendList( token: String, @@ -774,7 +775,7 @@ class ConnectViewModel : ViewModel() { _ghostModeResponse.postValue(null) } - fun updateActiveFriendsList(data: List?) { + fun updateActiveFriendsList(data: List?) { _activeFriends.postValue(data) } diff --git a/app/src/main/java/com/dscvit/vitty/ui/connect/FriendDetailScreenContent.kt b/app/src/main/java/com/dscvit/vitty/ui/connect/FriendDetailScreenContent.kt index 086e683..e363d8f 100644 --- a/app/src/main/java/com/dscvit/vitty/ui/connect/FriendDetailScreenContent.kt +++ b/app/src/main/java/com/dscvit/vitty/ui/connect/FriendDetailScreenContent.kt @@ -68,6 +68,7 @@ import coil.compose.AsyncImage import com.dscvit.vitty.R import com.dscvit.vitty.model.PeriodDetails import com.dscvit.vitty.network.api.community.responses.timetable.TimetableResponse +import com.dscvit.vitty.network.api.community.responses.user.ActiveFriendItem import com.dscvit.vitty.network.api.community.responses.user.UserResponse import com.dscvit.vitty.theme.Accent import com.dscvit.vitty.theme.Background @@ -119,7 +120,10 @@ fun FriendDetailScreenContent( LaunchedEffect(activeFriends) { Timber.d("Active friends: $activeFriends") - activeFriends?.let { activeList -> isFriendGhosted = !activeList.contains(friend.username) } + activeFriends?.let { activeList -> + val friendItem = activeList.find { it.friend_username == friend.username } + isFriendGhosted = friendItem?.hide ?: false + } } LaunchedEffect(ghostModeResponse) { @@ -130,15 +134,16 @@ fun FriendDetailScreenContent( context.getSharedPreferences(Constants.USER_INFO, Context.MODE_PRIVATE) if (ghostModeResponse!!.success) { - val currentActiveFriends = activeFriends?.toMutableList() ?: mutableListOf() + val currentActiveFriends = activeFriends?.toMutableList() ?: mutableListOf() val gson = Gson() - if (isFriendGhosted) { - currentActiveFriends.removeAll { it == friend.username } + val existingFriendIndex = currentActiveFriends.indexOfFirst { it.friend_username == friend.username } + + if (existingFriendIndex != -1) { + currentActiveFriends[existingFriendIndex] = + currentActiveFriends[existingFriendIndex].copy(hide = isFriendGhosted) } else { - if (!currentActiveFriends.contains(friend.username)) { - currentActiveFriends.add(friend.username) - } + currentActiveFriends.add(ActiveFriendItem(friend_username = friend.username, hide = isFriendGhosted)) } val activeFriendsJson = gson.toJson(currentActiveFriends) diff --git a/app/src/main/java/com/dscvit/vitty/ui/main/MainComposeApp.kt b/app/src/main/java/com/dscvit/vitty/ui/main/MainComposeApp.kt index 58d4ad1..2593f15 100644 --- a/app/src/main/java/com/dscvit/vitty/ui/main/MainComposeApp.kt +++ b/app/src/main/java/com/dscvit/vitty/ui/main/MainComposeApp.kt @@ -74,6 +74,7 @@ import androidx.navigation.compose.rememberNavController import coil.compose.AsyncImage import com.dscvit.vitty.R import com.dscvit.vitty.activity.SettingsActivity +import com.dscvit.vitty.network.api.community.responses.user.ActiveFriendItem import com.dscvit.vitty.network.api.community.responses.user.UserResponse import com.dscvit.vitty.theme.Accent import com.dscvit.vitty.theme.Background @@ -97,6 +98,7 @@ import com.dscvit.vitty.ui.coursepage.CoursePageContent import com.dscvit.vitty.ui.coursepage.CoursePageViewModel import com.dscvit.vitty.ui.coursepage.models.Note import com.dscvit.vitty.ui.emptyclassrooms.EmptyClassroomsContent +import com.dscvit.vitty.ui.maintenance.MaintenanceBannerDialog import com.dscvit.vitty.ui.notes.NoteScreenContent import com.dscvit.vitty.ui.schedule.ScheduleScreenContent import com.dscvit.vitty.ui.schedule.ScheduleViewModel @@ -106,16 +108,15 @@ import com.dscvit.vitty.util.LogoutHelper import com.dscvit.vitty.util.MaintenanceChecker import com.dscvit.vitty.util.SemesterUtils import com.dscvit.vitty.util.UtilFunctions -import com.dscvit.vitty.ui.maintenance.MaintenanceBannerDialog import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import java.net.URLDecoder -import java.net.URLEncoder -import java.nio.charset.StandardCharsets import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber +import java.net.URLDecoder +import java.net.URLEncoder +import java.nio.charset.StandardCharsets @Composable fun MainComposeApp() { @@ -132,19 +133,19 @@ fun MainComposeApp() { var campus by remember { mutableStateOf(prefs.getString(Constants.COMMUNITY_CAMPUS, "") ?: "") } var showCampusDialog by remember { mutableStateOf(campus.isEmpty()) } - + // Maintenance banner state var showMaintenanceBanner by remember { mutableStateOf(false) } DisposableEffect(Unit) { val listener = - SharedPreferences.OnSharedPreferenceChangeListener { _, key -> - if (key == Constants.COMMUNITY_CAMPUS) { - val newCampus = prefs.getString(Constants.COMMUNITY_CAMPUS, "") ?: "" - campus = newCampus - showCampusDialog = newCampus.isEmpty() - } + SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + if (key == Constants.COMMUNITY_CAMPUS) { + val newCampus = prefs.getString(Constants.COMMUNITY_CAMPUS, "") ?: "" + campus = newCampus + showCampusDialog = newCampus.isEmpty() } + } prefs.registerOnSharedPreferenceChangeListener(listener) onDispose { prefs.unregisterOnSharedPreferenceChangeListener(listener) } @@ -155,38 +156,38 @@ fun MainComposeApp() { LaunchedEffect(currentRoute) { bottomNavVisible = - currentRoute?.let { route -> - !route.startsWith("course_page") && - !route.startsWith("note_screen") && - route != "empty_classrooms" && - !route.startsWith("friend_detail") && - !route.startsWith("circle_detail") && - route != "add_friend" && - route != "friend_requests" && - route != "circle_requests" && - !route.startsWith("circle_member_detail") && - !route.startsWith("add_participants") - } - ?: true + currentRoute?.let { route -> + !route.startsWith("course_page") && + !route.startsWith("note_screen") && + route != "empty_classrooms" && + !route.startsWith("friend_detail") && + !route.startsWith("circle_detail") && + route != "add_friend" && + route != "friend_requests" && + route != "circle_requests" && + !route.startsWith("circle_member_detail") && + !route.startsWith("add_participants") + } + ?: true currentRoute?.let { route -> val screenName = - when { - route == "academics" -> "Academics Screen" - route == "schedule" -> "Schedule Screen" - route == "connect" -> "Connect Screen" - route.startsWith("course_page") -> "Course Detail Screen" - route.startsWith("note_screen") -> "Note Editor Screen" - route == "empty_classrooms" -> "Empty Classrooms Screen" - route.startsWith("friend_detail") -> "Friend Detail Screen" - route.startsWith("circle_detail") -> "Circle Detail Screen" - route == "add_friend" -> "Add Friend Screen" - route == "friend_requests" -> "Friend Requests Screen" - route == "circle_requests" -> "Circle Requests Screen" - route.startsWith("circle_member_detail") -> "Circle Member Detail Screen" - route.startsWith("add_participants") -> "Add Participants Screen" - else -> route - } + when { + route == "academics" -> "Academics Screen" + route == "schedule" -> "Schedule Screen" + route == "connect" -> "Connect Screen" + route.startsWith("course_page") -> "Course Detail Screen" + route.startsWith("note_screen") -> "Note Editor Screen" + route == "empty_classrooms" -> "Empty Classrooms Screen" + route.startsWith("friend_detail") -> "Friend Detail Screen" + route.startsWith("circle_detail") -> "Circle Detail Screen" + route == "add_friend" -> "Add Friend Screen" + route == "friend_requests" -> "Friend Requests Screen" + route == "circle_requests" -> "Circle Requests Screen" + route.startsWith("circle_member_detail") -> "Circle Member Detail Screen" + route.startsWith("add_participants") -> "Add Participants Screen" + else -> route + } Analytics.appNavigation(screenName) } } @@ -195,12 +196,12 @@ fun MainComposeApp() { val activeFriendsFetched = prefs.getBoolean(Constants.ACTIVE_FRIENDS_FETCHED, false) val activeFriendsList = prefs.getString(Constants.ACTIVE_FRIENDS_LIST, "") val listType = object : TypeToken>() {}.type - val cachedList: List = - try { - Gson().fromJson(activeFriendsList, listType) ?: emptyList() - } catch (e: Exception) { - emptyList() - } + val cachedList: List = + try { + Gson().fromJson(activeFriendsList, listType) ?: emptyList() + } catch (e: Exception) { + emptyList() + } if (!activeFriendsFetched) { val token = prefs.getString(Constants.COMMUNITY_TOKEN, null) @@ -227,396 +228,396 @@ fun MainComposeApp() { VittyTheme { ModalNavigationDrawer( - drawerState = drawerState, - drawerContent = { - DrawerContent( - navController = navController, - onCloseDrawer = { scope.launch { drawerState.close() } } - ) - }, + drawerState = drawerState, + drawerContent = { + DrawerContent( + navController = navController, + onCloseDrawer = { scope.launch { drawerState.close() } }, + ) + }, ) { Box( - modifier = - Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background), + modifier = + Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background), ) { NavHost( - navController = navController, - startDestination = "schedule", - modifier = Modifier.fillMaxSize(), + navController = navController, + startDestination = "schedule", + modifier = Modifier.fillMaxSize(), ) { composable( - "academics", - enterTransition = { - slideInHorizontally( - initialOffsetX = { -it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + fadeIn(animationSpec = tween(300)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { -it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + fadeOut(animationSpec = tween(200)) - }, + "academics", + enterTransition = { + slideInHorizontally( + initialOffsetX = { -it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + fadeIn(animationSpec = tween(300)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { -it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + fadeOut(animationSpec = tween(200)) + }, ) { AcademicsComposeScreen( - navController = navController, - onOpenDrawer = { scope.launch { drawerState.open() } }, - academicsViewModel, - coursePageViewModel, + navController = navController, + onOpenDrawer = { scope.launch { drawerState.open() } }, + academicsViewModel, + coursePageViewModel, ) } composable( - "schedule", - enterTransition = { - when (initialState.destination.route) { - "academics" -> - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + fadeIn(animationSpec = tween(300)) - "connect" -> - slideInHorizontally( - initialOffsetX = { -it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + fadeIn(animationSpec = tween(300)) - else -> fadeIn(animationSpec = tween(300)) - } - }, - exitTransition = { - when (targetState.destination.route) { - "academics" -> - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + fadeOut(animationSpec = tween(200)) - "connect" -> - slideOutHorizontally( - targetOffsetX = { -it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + fadeOut(animationSpec = tween(200)) - else -> fadeOut(animationSpec = tween(200)) - } - }, + "schedule", + enterTransition = { + when (initialState.destination.route) { + "academics" -> + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + fadeIn(animationSpec = tween(300)) + "connect" -> + slideInHorizontally( + initialOffsetX = { -it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + fadeIn(animationSpec = tween(300)) + else -> fadeIn(animationSpec = tween(300)) + } + }, + exitTransition = { + when (targetState.destination.route) { + "academics" -> + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + fadeOut(animationSpec = tween(200)) + "connect" -> + slideOutHorizontally( + targetOffsetX = { -it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + fadeOut(animationSpec = tween(200)) + else -> fadeOut(animationSpec = tween(200)) + } + }, ) { ScheduleComposeScreen( - navController = navController, - onOpenDrawer = { scope.launch { drawerState.open() } }, + navController = navController, + onOpenDrawer = { scope.launch { drawerState.open() } }, ) } composable( - "connect", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + fadeIn(animationSpec = tween(300)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + fadeOut(animationSpec = tween(200)) - }, + "connect", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + fadeIn(animationSpec = tween(300)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + fadeOut(animationSpec = tween(200)) + }, ) { ConnectComposeScreen( - navController = navController, - connectViewModel = connectViewModel, + navController = navController, + connectViewModel = connectViewModel, ) } composable( - "friend_detail/{friendData}", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "friend_detail/{friendData}", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { backStackEntry -> val encodedFriendData = - backStackEntry.arguments?.getString("friendData") ?: "" + backStackEntry.arguments?.getString("friendData") ?: "" val friendData = - URLDecoder.decode( - encodedFriendData, - StandardCharsets.UTF_8.toString(), - ) + URLDecoder.decode( + encodedFriendData, + StandardCharsets.UTF_8.toString(), + ) val friend = - try { - Gson().fromJson(friendData, UserResponse::class.java) - } catch (e: Exception) { - null - } + try { + Gson().fromJson(friendData, UserResponse::class.java) + } catch (e: Exception) { + null + } if (friend != null) { FriendDetailScreenContent( - friend = friend, - onBackClick = { navController.popBackStack() }, - connectViewModel = connectViewModel, + friend = friend, + onBackClick = { navController.popBackStack() }, + connectViewModel = connectViewModel, ) } else { LaunchedEffect(Unit) { navController.popBackStack() } } } composable( - "add_friend", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "add_friend", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { AddFriendScreenContent( - onBackClick = { navController.popBackStack() }, + onBackClick = { navController.popBackStack() }, ) } composable( - "friend_requests", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "friend_requests", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { FriendRequestsScreenContent( - onBackClick = { navController.popBackStack() }, - connectViewModel = connectViewModel, + onBackClick = { navController.popBackStack() }, + connectViewModel = connectViewModel, ) } composable( - "circle_requests", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "circle_requests", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { CircleRequestsScreenContent( - onBackClick = { navController.popBackStack() }, - connectViewModel = connectViewModel, + onBackClick = { navController.popBackStack() }, + connectViewModel = connectViewModel, ) } composable( - "add_participants/{circleId}", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "add_participants/{circleId}", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { backStackEntry -> val circleId = backStackEntry.arguments?.getString("circleId") ?: "" AddParticipantsScreenContent( - navController = navController, - circleId = circleId, + navController = navController, + circleId = circleId, ) } composable( - "course_page/{courseTitle}/{courseCode}", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "course_page/{courseTitle}/{courseCode}", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { backStackEntry -> val encodedCourseTitle = - backStackEntry.arguments?.getString("courseTitle") ?: "" + backStackEntry.arguments?.getString("courseTitle") ?: "" val encodedCourseCode = - backStackEntry.arguments?.getString("courseCode") ?: "" + backStackEntry.arguments?.getString("courseCode") ?: "" val courseTitle = - URLDecoder.decode( - encodedCourseTitle, - StandardCharsets.UTF_8.toString(), - ) + URLDecoder.decode( + encodedCourseTitle, + StandardCharsets.UTF_8.toString(), + ) val courseCode = - URLDecoder.decode( - encodedCourseCode, - StandardCharsets.UTF_8.toString(), - ) + URLDecoder.decode( + encodedCourseCode, + StandardCharsets.UTF_8.toString(), + ) CoursePageContent( - courseTitle = courseTitle, - courseCode = courseCode, - onBackClick = { navController.popBackStack() }, - onNavigateToNote = { courseCodeParam, noteId, _ -> - val encodedCourseCodeParam = - URLEncoder.encode( - courseCodeParam, - StandardCharsets.UTF_8.toString(), - ) - val encodedNoteId = - noteId?.let { - URLEncoder.encode( - it, - StandardCharsets.UTF_8.toString(), - ) - } - ?: "new" - navController.navigate( - "note_screen/$encodedCourseCodeParam/$encodedNoteId", + courseTitle = courseTitle, + courseCode = courseCode, + onBackClick = { navController.popBackStack() }, + onNavigateToNote = { courseCodeParam, noteId, _ -> + val encodedCourseCodeParam = + URLEncoder.encode( + courseCodeParam, + StandardCharsets.UTF_8.toString(), ) - }, + val encodedNoteId = + noteId?.let { + URLEncoder.encode( + it, + StandardCharsets.UTF_8.toString(), + ) + } + ?: "new" + navController.navigate( + "note_screen/$encodedCourseCodeParam/$encodedNoteId", + ) + }, ) } composable( - "note_screen/{courseCode}/{noteId}", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "note_screen/{courseCode}/{noteId}", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { backStackEntry -> val encodedCourseCode = - backStackEntry.arguments?.getString("courseCode") ?: "" + backStackEntry.arguments?.getString("courseCode") ?: "" val encodedNoteId = backStackEntry.arguments?.getString("noteId") ?: "new" val courseCode = + URLDecoder.decode( + encodedCourseCode, + StandardCharsets.UTF_8.toString(), + ) + val noteId = + if (encodedNoteId == "new") { + null + } else { URLDecoder.decode( - encodedCourseCode, - StandardCharsets.UTF_8.toString(), + encodedNoteId, + StandardCharsets.UTF_8.toString(), ) - val noteId = - if (encodedNoteId == "new") { - null - } else { - URLDecoder.decode( - encodedNoteId, - StandardCharsets.UTF_8.toString(), - ) - } + } val viewModel: CoursePageViewModel = viewModel() var noteToEdit by remember { mutableStateOf(null) } @@ -628,182 +629,182 @@ fun MainComposeApp() { } NoteScreenContent( - onBackClick = { navController.popBackStack() }, - noteToEdit = noteToEdit, - onSaveNote = { title, content -> - viewModel.setCourseId(courseCode) - if (noteToEdit != null) { - viewModel.updateExistingNote( - noteId = noteToEdit!!.id.toString(), - title = title, - content = content, - isStarred = noteToEdit!!.isStarred, - ) - } else { - viewModel.addTextNote(title, content) - } - navController.popBackStack() - }, + onBackClick = { navController.popBackStack() }, + noteToEdit = noteToEdit, + onSaveNote = { title, content -> + viewModel.setCourseId(courseCode) + if (noteToEdit != null) { + viewModel.updateExistingNote( + noteId = noteToEdit!!.id.toString(), + title = title, + content = content, + isStarred = noteToEdit!!.isStarred, + ) + } else { + viewModel.addTextNote(title, content) + } + navController.popBackStack() + }, ) } composable( - "empty_classrooms", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "empty_classrooms", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { EmptyClassroomsContent( - onBackClick = { navController.popBackStack() }, + onBackClick = { navController.popBackStack() }, ) } composable( - "circle_detail/{circleData}/{circleMembersData}", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "circle_detail/{circleData}/{circleMembersData}", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { backStackEntry -> val encodedCircleData = - backStackEntry.arguments?.getString("circleData") ?: "" + backStackEntry.arguments?.getString("circleData") ?: "" val encodedCircleMembersData = - backStackEntry.arguments?.getString("circleMembersData") + backStackEntry.arguments?.getString("circleMembersData") val circleData = - URLDecoder.decode( - encodedCircleData, - StandardCharsets.UTF_8.toString(), - ) + URLDecoder.decode( + encodedCircleData, + StandardCharsets.UTF_8.toString(), + ) val circleMembersData = - URLDecoder.decode( - encodedCircleMembersData, - StandardCharsets.UTF_8.toString(), - ) + URLDecoder.decode( + encodedCircleMembersData, + StandardCharsets.UTF_8.toString(), + ) val circle = - try { - Gson().fromJson( - circleData, - com.dscvit.vitty.network.api.community.responses - .user.CircleItem::class - .java, - ) - } catch (e: Exception) { - null - } + try { + Gson().fromJson( + circleData, + com.dscvit.vitty.network.api.community.responses + .user.CircleItem::class + .java, + ) + } catch (e: Exception) { + null + } val circleMembers = - try { - Gson().fromJson( - circleMembersData, - com.dscvit.vitty.network.api.community.responses - .user.FriendResponse::class - .java, - ) - } catch (e: Exception) { - null - } + try { + Gson().fromJson( + circleMembersData, + com.dscvit.vitty.network.api.community.responses + .user.FriendResponse::class + .java, + ) + } catch (e: Exception) { + null + } if (circle != null) { CircleDetailScreenContent( - circle = circle, - circleMembers = circleMembers, - onBackClick = { navController.popBackStack() }, - onMemberClick = { member, circleId -> - val memberJson = Gson().toJson(member) - val encodedMemberData = - URLEncoder.encode( - memberJson, - StandardCharsets.UTF_8.toString(), - ) - navController.navigate( - "circle_member_detail/$encodedMemberData/$circleId", + circle = circle, + circleMembers = circleMembers, + onBackClick = { navController.popBackStack() }, + onMemberClick = { member, circleId -> + val memberJson = Gson().toJson(member) + val encodedMemberData = + URLEncoder.encode( + memberJson, + StandardCharsets.UTF_8.toString(), ) - }, - onAddParticipantsClick = { circleId: String -> - navController.navigate("add_participants/$circleId") - }, - connectViewModel = connectViewModel, + navController.navigate( + "circle_member_detail/$encodedMemberData/$circleId", + ) + }, + onAddParticipantsClick = { circleId: String -> + navController.navigate("add_participants/$circleId") + }, + connectViewModel = connectViewModel, ) } else { LaunchedEffect(Unit) { navController.popBackStack() } } } composable( - "circle_member_detail/{memberData}/{circleId}", - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeIn(animationSpec = tween(250)) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 400f, - ), - ) + fadeOut(animationSpec = tween(150)) - }, + "circle_member_detail/{memberData}/{circleId}", + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeIn(animationSpec = tween(250)) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 400f, + ), + ) + fadeOut(animationSpec = tween(150)) + }, ) { backStackEntry -> val encodedMemberData = - backStackEntry.arguments?.getString("memberData") ?: "" + backStackEntry.arguments?.getString("memberData") ?: "" val circleId = backStackEntry.arguments?.getString("circleId") ?: "" val memberData = - URLDecoder.decode( - encodedMemberData, - StandardCharsets.UTF_8.toString(), - ) + URLDecoder.decode( + encodedMemberData, + StandardCharsets.UTF_8.toString(), + ) val member = - try { - Gson().fromJson(memberData, UserResponse::class.java) - } catch (e: Exception) { - null - } + try { + Gson().fromJson(memberData, UserResponse::class.java) + } catch (e: Exception) { + null + } if (member != null && circleId.isNotEmpty()) { CircleMemberDetailScreenContent( - member = member, - circleId = circleId, - onBackClick = { navController.popBackStack() }, + member = member, + circleId = circleId, + onBackClick = { navController.popBackStack() }, ) } else { LaunchedEffect(Unit) { navController.popBackStack() } @@ -812,89 +813,89 @@ fun MainComposeApp() { } AnimatedVisibility( - visible = bottomNavVisible, - enter = - slideInVertically( - initialOffsetY = { it }, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 300f, - ), - ) + - fadeIn( - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 400f, - ), - ) + - scaleIn( - initialScale = 0.6f, - animationSpec = - spring( - dampingRatio = 0.7f, - stiffness = 350f, - ), - ), - exit = - slideOutVertically( - targetOffsetY = { it }, - animationSpec = - spring( - dampingRatio = 0.9f, - stiffness = 500f, - ), - ) + - fadeOut( - animationSpec = - spring( - dampingRatio = 1.0f, - stiffness = 600f, - ), - ) + - scaleOut( - targetScale = 0.6f, - animationSpec = - spring( - dampingRatio = 0.8f, - stiffness = 500f, - ), - ), - modifier = Modifier.align(Alignment.BottomCenter), + visible = bottomNavVisible, + enter = + slideInVertically( + initialOffsetY = { it }, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 300f, + ), + ) + + fadeIn( + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 400f, + ), + ) + + scaleIn( + initialScale = 0.6f, + animationSpec = + spring( + dampingRatio = 0.7f, + stiffness = 350f, + ), + ), + exit = + slideOutVertically( + targetOffsetY = { it }, + animationSpec = + spring( + dampingRatio = 0.9f, + stiffness = 500f, + ), + ) + + fadeOut( + animationSpec = + spring( + dampingRatio = 1.0f, + stiffness = 600f, + ), + ) + + scaleOut( + targetScale = 0.6f, + animationSpec = + spring( + dampingRatio = 0.8f, + stiffness = 500f, + ), + ), + modifier = Modifier.align(Alignment.BottomCenter), ) { BottomNavigationBar( - currentRoute = currentRoute, - onDestinationClick = { route -> - navController.navigate(route) { - popUpTo(navController.graph.startDestinationId) { - saveState = true - } - launchSingleTop = true - restoreState = true + currentRoute = currentRoute, + onDestinationClick = { route -> + navController.navigate(route) { + popUpTo(navController.graph.startDestinationId) { + saveState = true } - }, - modifier = Modifier.padding(bottom = 48.dp), + launchSingleTop = true + restoreState = true + } + }, + modifier = Modifier.padding(bottom = 48.dp), ) } if (showCampusDialog) { CampusUpdateDialog( - onDismiss = { showCampusDialog = false }, + onDismiss = { showCampusDialog = false }, ) } - + // Maintenance Banner Dialog MaintenanceBannerDialog( isVisible = showMaintenanceBanner, - onDismiss = { - showMaintenanceBanner = false + onDismiss = { + showMaintenanceBanner = false }, onRetryClick = { MaintenanceChecker.checkMaintenanceStatusAsync(context) { isUnderMaintenance -> showMaintenanceBanner = isUnderMaintenance } - } + }, ) } } @@ -903,10 +904,10 @@ fun MainComposeApp() { @Composable fun AcademicsComposeScreen( - navController: NavHostController, - onOpenDrawer: () -> Unit, - academicsViewModel: AcademicsViewModel, - coursePageViewModel: CoursePageViewModel, + navController: NavHostController, + onOpenDrawer: () -> Unit, + academicsViewModel: AcademicsViewModel, + coursePageViewModel: CoursePageViewModel, ) { val context = LocalContext.current val prefs = remember { context.getSharedPreferences(Constants.USER_INFO, 0) } @@ -944,117 +945,117 @@ fun AcademicsComposeScreen( val profilePictureUrl = remember { prefs.getString(Constants.COMMUNITY_PICTURE, null) } AcademicsScreenContent( - profilePictureUrl = profilePictureUrl, - allCourses = allCourses, - onCourseClick = { course -> - val encodedTitle = - URLEncoder.encode(course.title, StandardCharsets.UTF_8.toString()) - val encodedCode = URLEncoder.encode(course.code, StandardCharsets.UTF_8.toString()) - navController.navigate("course_page/$encodedTitle/$encodedCode") - }, - onOpenDrawer = onOpenDrawer, - academicsViewModel = academicsViewModel, - coursePageViewModel = coursePageViewModel, + profilePictureUrl = profilePictureUrl, + allCourses = allCourses, + onCourseClick = { course -> + val encodedTitle = + URLEncoder.encode(course.title, StandardCharsets.UTF_8.toString()) + val encodedCode = URLEncoder.encode(course.code, StandardCharsets.UTF_8.toString()) + navController.navigate("course_page/$encodedTitle/$encodedCode") + }, + onOpenDrawer = onOpenDrawer, + academicsViewModel = academicsViewModel, + coursePageViewModel = coursePageViewModel, ) } @Composable fun ScheduleComposeScreen( - onOpenDrawer: () -> Unit, - navController: NavHostController, + onOpenDrawer: () -> Unit, + navController: NavHostController, ) { ScheduleScreenContent( - onOpenDrawer = onOpenDrawer, - onCardClick = { title: String, code: String -> - val encodedTitle = URLEncoder.encode(title, StandardCharsets.UTF_8.toString()) - val encodedCode = URLEncoder.encode(code, StandardCharsets.UTF_8.toString()) - navController.navigate("course_page/$encodedTitle/$encodedCode") - }, + onOpenDrawer = onOpenDrawer, + onCardClick = { title: String, code: String -> + val encodedTitle = URLEncoder.encode(title, StandardCharsets.UTF_8.toString()) + val encodedCode = URLEncoder.encode(code, StandardCharsets.UTF_8.toString()) + navController.navigate("course_page/$encodedTitle/$encodedCode") + }, ) } @Composable fun ConnectComposeScreen( - navController: NavHostController, - connectViewModel: ConnectViewModel, + navController: NavHostController, + connectViewModel: ConnectViewModel, ) { ConnectScreenContent( - onSearchClick = { navController.navigate("add_friend") }, - onFriendClick = { friend -> - val friendJson = Gson().toJson(friend) - val encodedFriendData = - URLEncoder.encode(friendJson, StandardCharsets.UTF_8.toString()) - navController.navigate("friend_detail/$encodedFriendData") - }, - onCircleClick = { circle, circleMembers -> - val circleJson = Gson().toJson(circle) - val encodedCircleData = - URLEncoder.encode(circleJson, StandardCharsets.UTF_8.toString()) - val encodedCircleMembers = - URLEncoder.encode( - Gson().toJson(circleMembers), - StandardCharsets.UTF_8.toString(), - ) + onSearchClick = { navController.navigate("add_friend") }, + onFriendClick = { friend -> + val friendJson = Gson().toJson(friend) + val encodedFriendData = + URLEncoder.encode(friendJson, StandardCharsets.UTF_8.toString()) + navController.navigate("friend_detail/$encodedFriendData") + }, + onCircleClick = { circle, circleMembers -> + val circleJson = Gson().toJson(circle) + val encodedCircleData = + URLEncoder.encode(circleJson, StandardCharsets.UTF_8.toString()) + val encodedCircleMembers = + URLEncoder.encode( + Gson().toJson(circleMembers), + StandardCharsets.UTF_8.toString(), + ) - navController.navigate("circle_detail/$encodedCircleData/$encodedCircleMembers") - }, - onFriendRequestsClick = { navController.navigate("friend_requests") }, - onCircleRequestsClick = { navController.navigate("circle_requests") }, - connectViewModel = connectViewModel, + navController.navigate("circle_detail/$encodedCircleData/$encodedCircleMembers") + }, + onFriendRequestsClick = { navController.navigate("friend_requests") }, + onCircleRequestsClick = { navController.navigate("circle_requests") }, + connectViewModel = connectViewModel, ) } @Composable fun BottomNavigationBar( - currentRoute: String?, - onDestinationClick: (String) -> Unit, - modifier: Modifier = Modifier, + currentRoute: String?, + onDestinationClick: (String) -> Unit, + modifier: Modifier = Modifier, ) { val cardScale by - animateFloatAsState( - targetValue = 1.0f, - animationSpec = - spring( - dampingRatio = 0.6f, - stiffness = 200f, - ), - label = "cardScale", - ) + animateFloatAsState( + targetValue = 1.0f, + animationSpec = + spring( + dampingRatio = 0.6f, + stiffness = 200f, + ), + label = "cardScale", + ) Card( - modifier = modifier.padding(horizontal = 16.dp).scale(cardScale), - shape = RoundedCornerShape(120.dp), - elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), - colors = - CardDefaults.cardColors( - containerColor = Background, - ), - border = BorderStroke(width = 1.dp, color = Secondary), + modifier = modifier.padding(horizontal = 16.dp).scale(cardScale), + shape = RoundedCornerShape(120.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + colors = + CardDefaults.cardColors( + containerColor = Background, + ), + border = BorderStroke(width = 1.dp, color = Secondary), ) { Row( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, ) { NavigationItem( - icon = R.drawable.ic_academics_outline, - text = "Academics", - isSelected = currentRoute == "academics", - onClick = { onDestinationClick("academics") }, + icon = R.drawable.ic_academics_outline, + text = "Academics", + isSelected = currentRoute == "academics", + onClick = { onDestinationClick("academics") }, ) NavigationItem( - icon = R.drawable.ic_timetable_outline, - text = "Schedule", - isSelected = currentRoute == "schedule", - onClick = { onDestinationClick("schedule") }, + icon = R.drawable.ic_timetable_outline, + text = "Schedule", + isSelected = currentRoute == "schedule", + onClick = { onDestinationClick("schedule") }, ) NavigationItem( - icon = R.drawable.ic_community_outline, - text = "Connect", - isSelected = currentRoute == "connect", - onClick = { onDestinationClick("connect") }, + icon = R.drawable.ic_community_outline, + text = "Connect", + isSelected = currentRoute == "connect", + onClick = { onDestinationClick("connect") }, ) } } @@ -1062,57 +1063,58 @@ fun BottomNavigationBar( @Composable fun NavigationItem( - icon: Int, - text: String, - isSelected: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier, + icon: Int, + text: String, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, ) { val scale by - animateFloatAsState( - targetValue = if (isSelected) 1.0f else 0.95f, - animationSpec = tween(150), - label = "scale", - ) + animateFloatAsState( + targetValue = if (isSelected) 1.0f else 0.95f, + animationSpec = tween(150), + label = "scale", + ) val backgroundColor = - if (isSelected) { - Secondary - } else { - Color.Transparent - } + if (isSelected) { + Secondary + } else { + Color.Transparent + } Row( - modifier = - modifier.scale(scale) - .clip(RoundedCornerShape(120.dp)) - .background(backgroundColor) - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() }, - ) { onClick() } - .padding(horizontal = 20.dp, vertical = 14.dp), - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, + modifier = + modifier + .scale(scale) + .clip(RoundedCornerShape(120.dp)) + .background(backgroundColor) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + ) { onClick() } + .padding(horizontal = 20.dp, vertical = 14.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically, ) { Icon( - painter = painterResource(id = icon), - contentDescription = text, - tint = TextColor, - modifier = Modifier.size(24.dp), + painter = painterResource(id = icon), + contentDescription = text, + tint = TextColor, + modifier = Modifier.size(24.dp), ) AnimatedVisibility( - visible = isSelected, - enter = fadeIn(animationSpec = tween(150)), - exit = fadeOut(animationSpec = tween(100)), + visible = isSelected, + enter = fadeIn(animationSpec = tween(150)), + exit = fadeOut(animationSpec = tween(100)), ) { Text( - text = text, - color = TextColor, - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, + text = text, + color = TextColor, + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, ) } } @@ -1120,8 +1122,8 @@ fun NavigationItem( @Composable fun DrawerContent( - navController: NavHostController, - onCloseDrawer: () -> Unit + navController: NavHostController, + onCloseDrawer: () -> Unit, ) { val context = LocalContext.current val prefs = remember { context.getSharedPreferences(Constants.USER_INFO, 0) } @@ -1133,68 +1135,69 @@ fun DrawerContent( DisposableEffect(Unit) { val listener = - SharedPreferences.OnSharedPreferenceChangeListener { _, key -> - when (key) { - Constants.COMMUNITY_CAMPUS -> { - campus = prefs.getString(Constants.COMMUNITY_CAMPUS, "") ?: "" - } + SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + when (key) { + Constants.COMMUNITY_CAMPUS -> { + campus = prefs.getString(Constants.COMMUNITY_CAMPUS, "") ?: "" } } + } prefs.registerOnSharedPreferenceChangeListener(listener) onDispose { prefs.unregisterOnSharedPreferenceChangeListener(listener) } } ModalDrawerSheet( - drawerContainerColor = Secondary, - drawerContentColor = TextColor, - modifier = Modifier.fillMaxWidth(0.8f), + drawerContainerColor = Secondary, + drawerContentColor = TextColor, + modifier = Modifier.fillMaxWidth(0.8f), ) { Column( - modifier = - Modifier.fillMaxWidth() - .fillMaxHeight() - .padding(horizontal = 26.dp, vertical = 28.dp), + modifier = + Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding(horizontal = 26.dp, vertical = 28.dp), ) { Row( - modifier = Modifier.fillMaxWidth().padding(top = 20.dp), - verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(top = 20.dp), + verticalAlignment = Alignment.CenterVertically, ) { AsyncImage( - model = profilePictureUrl, - contentDescription = "Profile Picture", - modifier = Modifier.size(52.dp).clip(CircleShape), - placeholder = painterResource(R.drawable.ic_gdscvit), - error = painterResource(R.drawable.ic_gdscvit), + model = profilePictureUrl, + contentDescription = "Profile Picture", + modifier = Modifier.size(52.dp).clip(CircleShape), + placeholder = painterResource(R.drawable.ic_gdscvit), + error = painterResource(R.drawable.ic_gdscvit), ) } Spacer(modifier = Modifier.height(4.dp)) Text( - text = name, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = - MaterialTheme.typography.labelLarge.copy( - fontWeight = FontWeight.Normal - ), + text = name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = + MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Normal, + ), ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "@$username", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = - TextStyle( - fontSize = 14.sp, - lineHeight = 20.sp, - fontWeight = FontWeight.Normal, - letterSpacing = (-.14).sp, - color = Accent, - ), + text = "@$username", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = + TextStyle( + fontSize = 14.sp, + lineHeight = 20.sp, + fontWeight = FontWeight.Normal, + letterSpacing = (-.14).sp, + color = Accent, + ), ) Spacer(modifier = Modifier.height(32.dp)) @@ -1205,66 +1208,66 @@ fun DrawerContent( if (campus.lowercase() == "vellore") { NavigationDrawerItem( - icon = { - Icon( - painter = painterResource(R.drawable.ic_empty_classroom), - contentDescription = "Find Empty Classroom", - tint = TextColor, - ) - }, - label = { - Text( - modifier = Modifier.padding(start = 24.dp), - text = "Find Empty Classroom", - color = TextColor, - style = - MaterialTheme.typography.labelLarge.copy( - fontWeight = FontWeight.Normal - ), - ) - }, - selected = false, - onClick = { - onCloseDrawer() - navController.navigate("empty_classrooms") - }, - colors = - NavigationDrawerItemDefaults.colors( - unselectedContainerColor = Color.Transparent, - selectedContainerColor = Secondary, - ), - ) - } - - NavigationDrawerItem( icon = { Icon( - painter = painterResource(R.drawable.ic_settings), - contentDescription = "Settings", - tint = TextColor, + painter = painterResource(R.drawable.ic_empty_classroom), + contentDescription = "Find Empty Classroom", + tint = TextColor, ) }, label = { Text( - modifier = Modifier.padding(start = 24.dp), - text = "Settings", - color = TextColor, - style = - MaterialTheme.typography.labelLarge.copy( - fontWeight = FontWeight.Normal - ), + modifier = Modifier.padding(start = 24.dp), + text = "Find Empty Classroom", + color = TextColor, + style = + MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Normal, + ), ) }, selected = false, onClick = { onCloseDrawer() - context.startActivity(Intent(context, SettingsActivity::class.java)) + navController.navigate("empty_classrooms") }, colors = - NavigationDrawerItemDefaults.colors( - unselectedContainerColor = Color.Transparent, - selectedContainerColor = Secondary, + NavigationDrawerItemDefaults.colors( + unselectedContainerColor = Color.Transparent, + selectedContainerColor = Secondary, + ), + ) + } + + NavigationDrawerItem( + icon = { + Icon( + painter = painterResource(R.drawable.ic_settings), + contentDescription = "Settings", + tint = TextColor, + ) + }, + label = { + Text( + modifier = Modifier.padding(start = 24.dp), + text = "Settings", + color = TextColor, + style = + MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Normal, ), + ) + }, + selected = false, + onClick = { + onCloseDrawer() + context.startActivity(Intent(context, SettingsActivity::class.java)) + }, + colors = + NavigationDrawerItemDefaults.colors( + unselectedContainerColor = Color.Transparent, + selectedContainerColor = Secondary, + ), ) Spacer(modifier = Modifier.height(24.dp)) @@ -1274,74 +1277,74 @@ fun DrawerContent( Spacer(modifier = Modifier.height(24.dp)) NavigationDrawerItem( - icon = { - Icon( - painter = painterResource(R.drawable.ic_share), - contentDescription = "Share", - tint = TextColor, - ) - }, - label = { - Text( - modifier = Modifier.padding(start = 24.dp), - text = "Share", - color = TextColor, - style = - MaterialTheme.typography.labelLarge.copy( - fontWeight = FontWeight.Normal - ), - ) - }, - selected = false, - onClick = { - onCloseDrawer() - val shareIntent = - Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra( - Intent.EXTRA_TEXT, - context.getString(R.string.share_text) - ) - } - context.startActivity(Intent.createChooser(shareIntent, null)) - }, - colors = - NavigationDrawerItemDefaults.colors( - unselectedContainerColor = Color.Transparent, - selectedContainerColor = Secondary, + icon = { + Icon( + painter = painterResource(R.drawable.ic_share), + contentDescription = "Share", + tint = TextColor, + ) + }, + label = { + Text( + modifier = Modifier.padding(start = 24.dp), + text = "Share", + color = TextColor, + style = + MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Normal, ), + ) + }, + selected = false, + onClick = { + onCloseDrawer() + val shareIntent = + Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra( + Intent.EXTRA_TEXT, + context.getString(R.string.share_text), + ) + } + context.startActivity(Intent.createChooser(shareIntent, null)) + }, + colors = + NavigationDrawerItemDefaults.colors( + unselectedContainerColor = Color.Transparent, + selectedContainerColor = Secondary, + ), ) NavigationDrawerItem( - icon = { - Icon( - painter = painterResource(R.drawable.ic_support), - contentDescription = "Support", - tint = TextColor, - ) - }, - label = { - Text( - modifier = Modifier.padding(start = 24.dp), - text = "Support", - color = TextColor, - style = - MaterialTheme.typography.labelLarge.copy( - fontWeight = FontWeight.Normal - ), - ) - }, - selected = false, - onClick = { - onCloseDrawer() - UtilFunctions.openLink(context, context.getString(R.string.telegram_link)) - }, - colors = - NavigationDrawerItemDefaults.colors( - unselectedContainerColor = Color.Transparent, - selectedContainerColor = Secondary, + icon = { + Icon( + painter = painterResource(R.drawable.ic_support), + contentDescription = "Support", + tint = TextColor, + ) + }, + label = { + Text( + modifier = Modifier.padding(start = 24.dp), + text = "Support", + color = TextColor, + style = + MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Normal, ), + ) + }, + selected = false, + onClick = { + onCloseDrawer() + UtilFunctions.openLink(context, context.getString(R.string.telegram_link)) + }, + colors = + NavigationDrawerItemDefaults.colors( + unselectedContainerColor = Color.Transparent, + selectedContainerColor = Secondary, + ), ) Spacer(modifier = Modifier.height(24.dp)) @@ -1351,95 +1354,93 @@ fun DrawerContent( Spacer(modifier = Modifier.weight(1f)) NavigationDrawerItem( - icon = { - Icon( - painter = painterResource(R.drawable.ic_logout_2), - contentDescription = "Log out", - tint = Color(0xFFFF0000), - ) - }, - label = { - Text( - text = "Log out", - color = Color(0xFFFF0000), - style = - MaterialTheme.typography.labelLarge.copy( - fontWeight = FontWeight.Normal - ), - ) - }, - selected = false, - onClick = { - onCloseDrawer() - val activity = context as? Activity - if (activity != null) { - LogoutHelper.logout(context, activity, prefs) - } - }, - colors = - NavigationDrawerItemDefaults.colors( - unselectedContainerColor = Color.Transparent, - selectedContainerColor = Secondary, + icon = { + Icon( + painter = painterResource(R.drawable.ic_logout_2), + contentDescription = "Log out", + tint = Color(0xFFFF0000), + ) + }, + label = { + Text( + text = "Log out", + color = Color(0xFFFF0000), + style = + MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Normal, ), + ) + }, + selected = false, + onClick = { + onCloseDrawer() + val activity = context as? Activity + if (activity != null) { + LogoutHelper.logout(context, activity, prefs) + } + }, + colors = + NavigationDrawerItemDefaults.colors( + unselectedContainerColor = Color.Transparent, + selectedContainerColor = Secondary, + ), ) } } } private suspend fun loadCachedCourses(prefs: SharedPreferences): List = - withContext(Dispatchers.IO) { - try { - val cachedData = - prefs.getString(Constants.CACHE_COMMUNITY_TIMETABLE, null) - ?: return@withContext emptyList() - - val userResponse = - Gson().fromJson(cachedData, UserResponse::class.java) - ?: return@withContext emptyList() - - extractCoursesFromTimetable(userResponse) - } catch (e: Exception) { - e.printStackTrace() - emptyList() - } + withContext(Dispatchers.IO) { + try { + val cachedData = + prefs.getString(Constants.CACHE_COMMUNITY_TIMETABLE, null) + ?: return@withContext emptyList() + + val userResponse = + Gson().fromJson(cachedData, UserResponse::class.java) + ?: return@withContext emptyList() + + extractCoursesFromTimetable(userResponse) + } catch (e: Exception) { + e.printStackTrace() + emptyList() } + } private fun extractCoursesFromTimetable(userResponse: UserResponse): List { val timetableData = userResponse.timetable?.data ?: return emptyList() val allLectures = - sequenceOf( - timetableData.Monday, - timetableData.Tuesday, - timetableData.Wednesday, - timetableData.Thursday, - timetableData.Friday, - timetableData.Saturday, - timetableData.Sunday, - ) - .filterNotNull() - .flatten() + sequenceOf( + timetableData.Monday, + timetableData.Tuesday, + timetableData.Wednesday, + timetableData.Thursday, + timetableData.Friday, + timetableData.Saturday, + timetableData.Sunday, + ).filterNotNull() + .flatten() val currentSemester = SemesterUtils.determineSemester() return allLectures - .groupBy { it.name } - .mapNotNull { (title, lectures) -> - if (title.isBlank()) return@mapNotNull null - - val uniqueSlots = - lectures.mapTo(LinkedHashSet()) { it.slot }.sorted().joinToString(" + ") - - val uniqueCodes = - lectures.mapTo(LinkedHashSet()) { it.code }.sorted().joinToString(" / ") - - Course( - title = title, - slot = uniqueSlots, - code = uniqueCodes, - semester = currentSemester, - isStarred = false, - ) - } - .sortedBy { it.title } + .groupBy { it.name } + .mapNotNull { (title, lectures) -> + if (title.isBlank()) return@mapNotNull null + + val uniqueSlots = + lectures.mapTo(LinkedHashSet()) { it.slot }.sorted().joinToString(" + ") + + val uniqueCodes = + lectures.mapTo(LinkedHashSet()) { it.code }.sorted().joinToString(" / ") + + Course( + title = title, + slot = uniqueSlots, + code = uniqueCodes, + semester = currentSemester, + isStarred = false, + ) + }.sortedBy { it.title } } diff --git a/build.gradle b/build.gradle index 32c055b..0369594 100755 --- a/build.gradle +++ b/build.gradle @@ -9,9 +9,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.12.0' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21' - classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.9.2' + classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.9.3' classpath 'com.google.gms:google-services:4.4.3' - classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.4' + classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files