Merge branch 'development' into feed_friends

This commit is contained in:
brreynie 2023-05-16 11:10:13 +02:00 committed by GitHub Enterprise
commit 754cad20ed
47 changed files with 1441 additions and 174 deletions

2
.idea/misc.xml generated
View file

@ -1,6 +1,6 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -147,4 +147,4 @@ protobuf {
}
}
}
}
}

View file

@ -0,0 +1,74 @@
package be.ugent.sel.studeez
import androidx.compose.material.FloatingActionButton
import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.common.composable.AddButtonActions
import be.ugent.sel.studeez.common.composable.ExpandedAddButton
import org.junit.Rule
import org.junit.Test
class FabTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun expandFabTest() {
var expand = false
composeTestRule.setContent {
FloatingActionButton(
onClick = {expand = true}
) {}
}
composeTestRule.waitForIdle()
composeTestRule
.onNode(hasClickAction())
.assertExists()
.performClick()
assert(expand)
}
@Test
fun fabTest() {
var task = false
var session = false
var friend = false
composeTestRule.setContent {
ExpandedAddButton(
addButtonActions = AddButtonActions(
{task = true},
{friend = true},
{session = true}
)
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithContentDescription("Session")
.assertExists()
.performClick()
composeTestRule
.onNodeWithContentDescription("Task")
.assertExists()
.performClick()
composeTestRule
.onNodeWithContentDescription("Friend")
.assertExists()
.performClick()
assert(task)
assert(session)
assert(friend)
}
}

View file

@ -0,0 +1,207 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.feed.FeedUiState
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.data.local.models.FeedEntry
import be.ugent.sel.studeez.screens.home.HomeScreen
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class HomeScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun homeScreenTest() {
var continueTask = false
composeTestRule.setContent {
HomeScreen(
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({false}, {}, {}, {}, {}, {}, {}, {}),
feedUiState = FeedUiState.Succes(mapOf(
"08 May 2023" to listOf(
FeedEntry(
argb_color = 0xFFABD200,
subJectName = "Test Subject",
taskName = "Test Task",
totalStudyTime = 600,
)
)
)),
continueTask = {_, _ -> continueTask = true },
onEmptyFeedHelp = {},
onViewFriendsClick = {},
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
"continue",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
Assert.assertTrue(continueTask)
}
@Test
fun drawerTest() {
var homebuttontest = false
var timersbuttontest = false
var settingsbuttontest = false
var logoutbuttontest = false
var aboutbuttontest = false
composeTestRule.setContent {
HomeScreen(
drawerActions = DrawerActions(
{homebuttontest = true},
{timersbuttontest = true},
{settingsbuttontest = true},
{logoutbuttontest = true},
{aboutbuttontest = true}
),
navigationBarActions = NavigationBarActions({false}, {}, {}, {}, {}, {}, {}, {}),
feedUiState = FeedUiState.Succes(mapOf()),
continueTask = {_, _ -> },
onEmptyFeedHelp = {},
onViewFriendsClick = {},
)
}
composeTestRule.waitForIdle()
composeTestRule
.onAllNodesWithText(
"home",
substring = true,
ignoreCase = true
)[2] // Third node has the button
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
"timer",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
"settings",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
"log out",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
"about",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
Assert.assertTrue(homebuttontest)
Assert.assertTrue(timersbuttontest)
Assert.assertTrue(settingsbuttontest)
Assert.assertTrue(logoutbuttontest)
Assert.assertTrue(aboutbuttontest)
}
@Test
fun navigationbarTest() {
var hometest = false
var tasktest = false
var sessiontest = false
var profiletest = false
composeTestRule.setContent {
HomeScreen(
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions(
{false},
{hometest = true},
{tasktest = true},
{sessiontest = true},
{profiletest = true},
{}, {}, {}
),
feedUiState = FeedUiState.Succes(mapOf()),
continueTask = {_, _ -> },
onEmptyFeedHelp = {},
onViewFriendsClick = {},
)
}
composeTestRule.waitForIdle()
composeTestRule
.onAllNodesWithContentDescription(
"Home",
substring = true,
ignoreCase = true
)[0] // Third node has the button
.assertExists()
.performClick()
composeTestRule
.onNodeWithContentDescription(
"tasks",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithContentDescription(
"session",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithContentDescription(
"profile",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
Assert.assertTrue(hometest)
Assert.assertTrue(tasktest)
Assert.assertTrue(sessiontest)
Assert.assertTrue(profiletest)
}
}

View file

@ -14,7 +14,7 @@ import org.junit.Assert.*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
class InstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.

View file

@ -0,0 +1,68 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.screens.log_in.LoginScreen
import be.ugent.sel.studeez.screens.log_in.LoginScreenActions
import be.ugent.sel.studeez.screens.log_in.LoginUiState
import org.junit.Rule
import org.junit.Test
class LoginScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun loginScreenTest() {
var login = false
var signup = false
var forgot_password = false
composeTestRule.setContent {
LoginScreen(
uiState = LoginUiState(),
loginScreenActions = LoginScreenActions(
{}, {},
{signup = true},
{login = true},
{forgot_password = true}
)
)
}
composeTestRule.waitForIdle()
composeTestRule
.onAllNodesWithText(
text = "Sign in",
substring = true,
ignoreCase = true
)[0] // The first object is the button
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "Forgot",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "Sign up",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(signup)
assert(login)
assert(forgot_password)
}
}

View file

@ -0,0 +1,70 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.screens.profile.edit_profile.EditProfileActions
import be.ugent.sel.studeez.screens.profile.edit_profile.EditProfileScreen
import be.ugent.sel.studeez.screens.profile.edit_profile.ProfileEditUiState
import org.junit.Rule
import org.junit.Test
class ProfileEditScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun profileEditScreenTest() {
var edit_save = false
var goback = false
var delete_click = false
composeTestRule.setContent {
EditProfileScreen(
goBack = {goback = true},
uiState = ProfileEditUiState(),
editProfileActions = EditProfileActions(
onUserNameChange = {},
onBiographyChange = {},
onSaveClick = {edit_save = true},
onDeleteClick = { delete_click = true },
),
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "save",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "delete",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithContentDescription(
label = "go back",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(edit_save)
assert(goback)
assert(delete_click)
}
}

View file

@ -0,0 +1,61 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.screens.profile.ProfileActions
import be.ugent.sel.studeez.screens.profile.ProfileScreen
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
class ProfileScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun profileScreenTest() {
var edit = false
var view_friends = false
composeTestRule.setContent {
ProfileScreen(
profileActions = ProfileActions(
getUsername = {null},
onEditProfileClick = {edit = true},
getBiography = {null},
getAmountOfFriends = { flowOf(0) },
onViewFriendsClick = {view_friends = true}
),
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {})
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithContentDescription(
label = "edit profile",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "friends",
substring = true,
ignoreCase = true,
)
.assertExists()
.performClick()
assert(edit)
assert(view_friends)
}
}

View file

@ -0,0 +1,75 @@
package be.ugent.sel.studeez
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.data.local.models.SessionReport
import be.ugent.sel.studeez.screens.session_recap.SessionRecapActions
import be.ugent.sel.studeez.screens.session_recap.SessionRecapScreen
import com.google.firebase.Timestamp
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class SessionRecapScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun sessionRecapTest() {
var saveCalled = false
var discardCalled = false
composeTestRule.setContent {
SessionRecapScreen(
Modifier,
SessionRecapActions(
{
SessionReport(
"",
0,
Timestamp(0, 0),
"")
},
{ saveCalled = true },
{ discardCalled = true }
)
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
"You studied",
substring = true,
ignoreCase = true
)
.assertExists()
composeTestRule
.onNodeWithText(
"save",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
"discard",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
Assert.assertTrue(saveCalled)
Assert.assertTrue(discardCalled)
}
}

View file

@ -0,0 +1,52 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.screens.sign_up.SignUpActions
import be.ugent.sel.studeez.screens.sign_up.SignUpScreen
import be.ugent.sel.studeez.screens.sign_up.SignUpUiState
import org.junit.Rule
import org.junit.Test
class SignUpScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun signupScreenTest() {
var create = false
var login = false
composeTestRule.setContent {
SignUpScreen(
uiState = SignUpUiState(),
signUpActions = SignUpActions({}, {}, {}, {}, {create = true}, {login = true})
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "log in",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onAllNodesWithText(
text = "Create account",
substring = true,
ignoreCase = true
)[0] // First node has the button
.assertExists()
.performClick()
assert(login)
assert(create)
}
}

View file

@ -0,0 +1,40 @@
package be.ugent.sel.studeez
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.screens.splash.SplashScreen
import org.junit.Rule
import org.junit.Test
class SplashScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun splashScreenTest() {
var tryAgain = false
composeTestRule.setContent {
SplashScreen(
Modifier,
{tryAgain = true},
true
)
}
composeTestRule.waitForIdle()
composeTestRule
.onAllNodesWithText(
text = "try again",
substring = true,
ignoreCase = true
)[1] // Second node is the button
.assertExists()
.performClick()
assert(tryAgain)
}
}

View file

@ -0,0 +1,158 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.common.composable.DeleteButton
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.screens.subjects.SubjectScreen
import be.ugent.sel.studeez.screens.subjects.SubjectUiState
import be.ugent.sel.studeez.screens.subjects.form.SubjectForm
import be.ugent.sel.studeez.screens.subjects.form.SubjectFormUiState
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
class SubjectScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun addSubjectScreenTest() {
var confirm = false
var goback = false
composeTestRule.setContent {
SubjectForm(
title = R.string.new_subject,
goBack = {goback = true},
uiState = SubjectFormUiState(),
onConfirm = {confirm = true},
onNameChange = {},
onColorChange = {},
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "confirm",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithContentDescription(
label = "go back",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(confirm)
assert(goback)
}
@Test
fun editSubjectScreenTest() {
var confirm = false
var delete = false
composeTestRule.setContent {
SubjectForm(
title = R.string.edit_subject,
goBack = {},
uiState = SubjectFormUiState(
name = "Test Subject",
),
onConfirm = {confirm = true},
onNameChange = {},
onColorChange = {},
)
DeleteButton(text = R.string.delete_subject) {
delete = true
}
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "confirm",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "delete",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(confirm)
assert(delete)
}
@Test
fun subjectScreenTest() {
var view = false
var add = false
composeTestRule.setContent {
SubjectScreen(
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({false}, {}, {}, {}, {}, {}, {}, {}),
onAddSubject = { add = true },
onViewSubject = { view = true },
getStudyTime = { flowOf() },
getCompletedTaskCount = { flowOf() },
getTaskCount = { flowOf() },
uiState = SubjectUiState.Succes(
listOf(
Subject(
id = "",
name = "Test Subject",
argb_color = 0xFFFFD200,
archived = false
)
)
)
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "view",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "new subject",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(add)
assert(view)
}
}

View file

@ -0,0 +1,160 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.common.composable.DeleteButton
import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.data.local.models.task.Task
import be.ugent.sel.studeez.screens.tasks.TaskActions
import be.ugent.sel.studeez.screens.tasks.TaskScreen
import be.ugent.sel.studeez.screens.tasks.form.TaskForm
import be.ugent.sel.studeez.screens.tasks.form.TaskFormUiState
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
class TaskScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun addTaskScreenTest() {
var confirm = false
var goback = false
composeTestRule.setContent {
TaskForm(
title = R.string.new_task,
goBack = {goback = true},
uiState = TaskFormUiState(),
onConfirm = {confirm = true},
onNameChange = {},
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "confirm",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithContentDescription(
label = "go back",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(confirm)
assert(goback)
}
@Test
fun editTaskScreenTest() {
var confirm = false
var delete = false
composeTestRule.setContent {
TaskForm(
title = R.string.edit_task,
goBack = {},
uiState = TaskFormUiState(
name = "Test Task",
),
onConfirm = {confirm = true},
onNameChange = {},
) {
DeleteButton(text = R.string.delete_task) {
delete = true
}
}
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "confirm",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "delete",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(confirm)
assert(delete)
}
@Test
fun taskScreenTest() {
var add = false
var edit = false
var start = false
composeTestRule.setContent {
TaskScreen(
goBack = {},
taskActions = TaskActions(
{add = true},
{ Subject(name = "Test Subject") },
{ flowOf(listOf(Task())) },
{ _, _ -> run {} },
{edit = true},
{start = true},
{},
)
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithContentDescription(
label = "edit",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "new",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "start",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(add)
assert(edit)
assert(start)
}
}

View file

@ -0,0 +1,58 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewActions
import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewScreen
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
class TimerOverviewScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun timerOverviewScreenTest() {
var add = false
var edit = false
composeTestRule.setContent {
TimerOverviewScreen(
timerOverviewActions = TimerOverviewActions(
{ flowOf(listOf(EndlessTimerInfo("", ""))) },
{ listOf() },
{edit = true},
{add = true}
),
drawerActions = DrawerActions({}, {}, {}, {}, {})
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "add",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
composeTestRule
.onNodeWithText(
text = "edit",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(add)
assert(edit)
}
}

View file

@ -0,0 +1,40 @@
package be.ugent.sel.studeez
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionActions
import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionScreen
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
class TimerSelectionScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun timerOverviewScreenTest() {
var start = false
composeTestRule.setContent {
TimerSelectionScreen(
timerSelectionActions = TimerSelectionActions({ flowOf()}, {start = true}, 0),
popUp = {}
)
}
composeTestRule.waitForIdle()
composeTestRule
.onNodeWithText(
text = "start",
substring = true,
ignoreCase = true
)
.assertExists()
.performClick()
assert(start)
}
}

View file

@ -227,12 +227,14 @@ fun SearchField(
onValueChange: (String) -> Unit,
onSubmit: () -> Unit,
@StringRes label: Int,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
enabled: Boolean = true
) {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
label = { Text(text = stringResource(id = label)) },
trailingIcon = {
IconButton(onClick = onSubmit) {

View file

@ -81,7 +81,7 @@ fun FeedWithElements(
Text(
text = "${HoursMinutesSeconds(totalDayStudyTime)}",
fontSize = 15.sp,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Medium
)
}
feedEntries.forEach { feedEntry ->

View file

@ -56,7 +56,7 @@ fun FeedEntry(
) {
Text(
text = feedEntry.subJectName,
fontWeight = FontWeight.Bold,
fontWeight = FontWeight.Medium,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)

View file

@ -5,6 +5,9 @@ import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.navigation.StudeezDestinations.FRIENDS_FEED
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SEARCH_FRIENDS_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SELECT_SUBJECT
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
@ -33,13 +36,11 @@ class NavigationBarViewModel @Inject constructor(
}
fun onAddTaskClick(open: (String) -> Unit) {
// TODO open(CREATE_TASK_SCREEN)
SnackbarManager.showMessage(AppText.create_task_not_possible_yet) // TODO Remove
open(SELECT_SUBJECT)
}
fun onAddFriendClick(open: (String) -> Unit) {
// TODO open(SEARCH_FRIENDS_SCREEN)
SnackbarManager.showMessage(AppText.add_friend_not_possible_yet) // TODO Remove
open(SEARCH_FRIENDS_SCREEN)
}
fun onAddSessionClick(open: (String) -> Unit) {

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.List
@ -30,10 +31,10 @@ import be.ugent.sel.studeez.R.string as AppText
@Composable
fun SubjectEntry(
subject: Subject,
onViewSubject: () -> Unit,
getTaskCount: () -> Flow<Int>,
getCompletedTaskCount: () -> Flow<Int>,
getStudyTime: () -> Flow<Int>,
selectButton: @Composable (RowScope) -> Unit,
) {
val studytime by getStudyTime().collectAsState(initial = 0)
val taskCount by getTaskCount().collectAsState(initial = 0)
@ -65,16 +66,17 @@ fun SubjectEntry(
) {
Text(
text = subject.name,
fontWeight = FontWeight.Bold,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
fontWeight = FontWeight.Medium
)
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = HoursMinutesSeconds(studytime).toString(),
color = MaterialTheme.colors.onBackground.copy(alpha = 0.6f)
)
Row(
verticalAlignment = Alignment.CenterVertically,
@ -82,21 +84,18 @@ fun SubjectEntry(
) {
Icon(
imageVector = Icons.Default.List,
contentDescription = stringResource(id = AppText.tasks)
contentDescription = stringResource(id = AppText.tasks),
tint = MaterialTheme.colors.onBackground.copy(alpha = 0.6f)
)
Text(
text = "${completedTaskCount}/${taskCount}",
color = MaterialTheme.colors.onBackground.copy(alpha = 0.6f)
)
Text(text = "${completedTaskCount}/${taskCount}")
}
}
}
}
StealthButton(
text = AppText.view_tasks,
modifier = Modifier
.padding(start = 10.dp, end = 5.dp)
.weight(1f)
) {
onViewSubject()
}
selectButton(this)
}
}
}
@ -109,11 +108,16 @@ fun SubjectEntryPreview() {
name = "Test Subject",
argb_color = 0xFFFFD200,
),
onViewSubject = {},
getTaskCount = { flowOf() },
getCompletedTaskCount = { flowOf() },
getStudyTime = { flowOf() },
)
) {
StealthButton(
text = AppText.view_tasks,
modifier = Modifier
.padding(start = 10.dp, end = 5.dp)
) {}
}
}
@Preview
@ -124,9 +128,8 @@ fun OverflowSubjectEntryPreview() {
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
argb_color = 0xFFFFD200,
),
onViewSubject = {},
getTaskCount = { flowOf() },
getCompletedTaskCount = { flowOf() },
getStudyTime = { flowOf() },
)
) {}
}

View file

@ -5,6 +5,7 @@ import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.data.local.models.task.Task
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.domain.UserDAO
import javax.inject.Inject
import javax.inject.Singleton
@ -42,4 +43,11 @@ class SelectedSubject @Inject constructor() : SelectedState<Subject>() {
@Singleton
class SelectedTimerInfo @Inject constructor() : SelectedState<TimerInfo>() {
override lateinit var value: TimerInfo
}
@Singleton
class SelectedUserId @Inject constructor(
userDAO: UserDAO
): SelectedState<String>() {
override var value: String = userDAO.getCurrentUserId()
}

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.data.local.models.task
import com.google.firebase.firestore.DocumentId
import com.google.firebase.firestore.Exclude
data class Subject(
@DocumentId val id: String = "",

View file

@ -1,11 +1,10 @@
package be.ugent.sel.studeez.domain.implementation
import androidx.compose.runtime.collectAsState
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
import be.ugent.sel.studeez.data.local.models.Friendship
import be.ugent.sel.studeez.data.remote.FirebaseFriendship.ACCEPTED
import be.ugent.sel.studeez.data.remote.FirebaseFriendship.FRIENDSSINCE
import be.ugent.sel.studeez.data.remote.FirebaseFriendship.FRIENDID
import be.ugent.sel.studeez.data.remote.FirebaseFriendship.FRIENDSSINCE
import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.FriendshipDAO
import be.ugent.sel.studeez.domain.implementation.FirebaseCollections.FRIENDS_COLLECTION
@ -18,6 +17,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@ -75,24 +75,40 @@ class FirebaseFriendshipDAO @Inject constructor(
val currentUserId: String = auth.currentUserId
val otherUserId: String = id
// Add entry to current user
currentUserDocument()
.collection(FRIENDS_COLLECTION)
.add(mapOf(
FRIENDID to otherUserId,
ACCEPTED to true, // TODO Make it not automatically accepted.
FRIENDSSINCE to Timestamp.now()
))
// Add entry to other user
// Check if the friendship already exists for the logged in user
var allowed = false
firestore.collection(USER_COLLECTION)
.document(otherUserId)
.document(currentUserId)
.collection(FRIENDS_COLLECTION)
.add(mapOf(
FRIENDID to currentUserId,
ACCEPTED to true, // TODO Make it not automatically accepted.
FRIENDSSINCE to Timestamp.now()
))
.whereEqualTo(FRIENDID, otherUserId)
.get()
.addOnSuccessListener {
allowed = it.documents.isEmpty()
if (allowed) {
// Add entry to current user
currentUserDocument()
.collection(FRIENDS_COLLECTION)
.add(mapOf(
FRIENDID to otherUserId,
ACCEPTED to true, // TODO Make it not automatically accepted.
FRIENDSSINCE to Timestamp.now()
))
// Add entry to other user
firestore.collection(USER_COLLECTION)
.document(otherUserId)
.collection(FRIENDS_COLLECTION)
.add(mapOf(
FRIENDID to currentUserId,
ACCEPTED to true, // TODO Make it not automatically accepted.
FRIENDSSINCE to Timestamp.now()
))
}
}.addOnSuccessListener {
val message = if (allowed) AppText.success else AppText.already_friend
SnackbarManager.showMessage(message)
}
return true
}

View file

@ -94,4 +94,4 @@ class FirebaseSubjectDAO @Inject constructor(
fun Query.subjectNotArchived(): Query =
this.whereEqualTo(SubjectDocument.archived, false)
}
}

View file

@ -27,6 +27,7 @@ object StudeezDestinations {
const val EDIT_SUBJECT_FORM = "edit_subject"
const val TASKS_SCREEN = "tasks"
const val ADD_TASK_FORM = "add_task"
const val SELECT_SUBJECT = "select_subject"
const val EDIT_TASK_FORM = "edit_task"
// Friends flow

View file

@ -31,6 +31,7 @@ import be.ugent.sel.studeez.screens.splash.SplashRoute
import be.ugent.sel.studeez.screens.subjects.SubjectRoute
import be.ugent.sel.studeez.screens.subjects.form.SubjectCreateRoute
import be.ugent.sel.studeez.screens.subjects.form.SubjectEditRoute
import be.ugent.sel.studeez.screens.subjects.select.SubjectSelectionRoute
import be.ugent.sel.studeez.screens.tasks.TaskRoute
import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute
import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute
@ -69,7 +70,7 @@ fun StudeezNavGraph(
// NavBar
composable(StudeezDestinations.HOME_SCREEN) {
HomeRoute(
open,
open = open,
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
feedViewModel = hiltViewModel(),
@ -86,6 +87,14 @@ fun StudeezNavGraph(
)
}
composable(StudeezDestinations.SELECT_SUBJECT) {
SubjectSelectionRoute(
open = { openAndPopUp(it, StudeezDestinations.SELECT_SUBJECT) },
goBack = goBack,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.ADD_SUBJECT_FORM) {
SubjectCreateRoute(
goBack = goBack,
@ -104,7 +113,7 @@ fun StudeezNavGraph(
composable(StudeezDestinations.TASKS_SCREEN) {
TaskRoute(
goBack = goBack,
goBack = { openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.TASKS_SCREEN) },
open = open,
viewModel = hiltViewModel(),
)

View file

@ -1,15 +1,14 @@
package be.ugent.sel.studeez.screens.friends.friends_overview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -23,7 +22,6 @@ import androidx.compose.ui.unit.sp
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.ProfilePicture
import be.ugent.sel.studeez.common.composable.SearchField
import be.ugent.sel.studeez.common.composable.drawer.DrawerEntry
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.data.local.models.Friendship
@ -89,13 +87,32 @@ fun FriendsOverviewScreen(
topBar = {
TopAppBar(
title = {
// TODO Link to each other
SearchField(
value = uiState.queryString,
onValueChange = friendsOverviewActions.onQueryStringChange,
onSubmit = friendsOverviewActions.onSubmit,
label = AppText.search_friends
)
// TODO Make search field
// SearchField(
// value = uiState.queryString,
// onValueChange = friendsOverviewActions.onQueryStringChange,
// onSubmit = friendsOverviewActions.onSubmit,
// label = AppText.search_friends,
// enabled = false
// )
IconButton(
onClick = friendsOverviewActions.onSubmit,
// modifier = Modifier.background(
// color = MaterialTheme.colors.background
// ),
) {
Row {
Text(
text = stringResource(id = AppText.click_search_friends),
color = MaterialTheme.colors.onPrimary
)
Icon(
imageVector = Icons.Default.Search,
contentDescription = stringResource(AppText.search_friends),
tint = MaterialTheme.colors.onPrimary
)
}
}
},
navigationIcon = {
IconButton(onClick = popUp) {
@ -162,49 +179,52 @@ fun FriendsEntry(
viewProfile: (String) -> Unit,
removeFriend: (Friendship) -> Unit
) {
Row (
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 15.dp, vertical = 7.dp),
) {
Box(
modifier = Modifier
.padding(vertical = 4.dp)
) {
ProfilePicture()
}
Box (
Card {
Row (
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 15.dp, vertical = 7.dp),
horizontalArrangement = Arrangement.spacedBy(15.dp)
) {
Column (
Box(
modifier = Modifier
.padding(vertical = 4.dp)
) {
Text(
text = user.username,
fontSize = 16.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = "${resources().getString(AppText.app_name)} ${resources().getString(AppText.friend)}",
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
ProfilePicture()
}
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterEnd
Box (
modifier = Modifier
.fillMaxWidth()
) {
FriendsOverviewDropDown(
friendship = friendship,
viewProfile = viewProfile,
removeFriend = removeFriend
)
Column (
modifier = Modifier
.padding(vertical = 4.dp)
) {
Text(
text = user.username,
fontSize = 16.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = "${resources().getString(AppText.app_name)} ${resources().getString(AppText.friend)}",
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterEnd
) {
FriendsOverviewDropDown(
friendship = friendship,
viewProfile = viewProfile,
removeFriend = removeFriend
)
}
}
}
}

View file

@ -1,6 +1,7 @@
package be.ugent.sel.studeez.screens.friends.friends_overview
import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.data.SelectedUserId
import be.ugent.sel.studeez.data.local.models.Friendship
import be.ugent.sel.studeez.data.local.models.User
import be.ugent.sel.studeez.domain.FriendshipDAO
@ -8,7 +9,6 @@ import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.UserDAO
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel
import be.ugent.sel.studeez.screens.profile.public_profile.SelectedProfileState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@ -19,12 +19,12 @@ import javax.inject.Inject
class FriendsOverviewViewModel @Inject constructor(
private val userDAO: UserDAO,
private val friendshipDAO: FriendshipDAO,
private val selectedProfileState: SelectedProfileState,
private val selectedUserIdState: SelectedUserId,
logService: LogService
) : StudeezViewModel(logService) {
var uiState = mutableStateOf(FriendsOverviewUiState(
userId = selectedProfileState.selectedUserId
userId = selectedUserIdState.value
))
private set
@ -63,7 +63,7 @@ class FriendsOverviewViewModel @Inject constructor(
userId: String,
open: (String) -> Unit
) {
selectedProfileState.selectedUserId = userId
selectedUserIdState.value = userId
open(StudeezDestinations.PUBLIC_PROFILE_SCREEN)
}

View file

@ -9,6 +9,7 @@ import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -82,14 +83,16 @@ fun SearchFriendsScreen(
topBar = {
TopAppBar(
title = {
SearchField(
value = query,
onValueChange = { newValue ->
searchFriendsActions.onQueryStringChange(newValue)
query = newValue
},
onSubmit = { },
label = AppText.search_friends
// TODO Make search field
// SearchField(
// value = uiState.queryString,
// onValueChange = friendsOverviewActions.onQueryStringChange,
// onSubmit = friendsOverviewActions.onSubmit,
// label = AppText.search_friends,
// enabled = false
// )
Text(
text = stringResource(id = AppText.searching_friends)
)
},
navigationIcon = {

View file

@ -1,22 +1,22 @@
package be.ugent.sel.studeez.screens.friends.friends_search
import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.data.SelectedUserId
import be.ugent.sel.studeez.data.local.models.User
import be.ugent.sel.studeez.data.remote.FirebaseUser
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.UserDAO
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel
import be.ugent.sel.studeez.screens.profile.public_profile.SelectedProfileState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import javax.inject.Inject
@HiltViewModel
class SearchFriendsViewModel @Inject constructor(
private val userDAO: UserDAO,
private val selectedProfileState: SelectedProfileState,
private val selectedProfileState: SelectedUserId,
logService: LogService
): StudeezViewModel(logService) {
@ -49,8 +49,8 @@ class SearchFriendsViewModel @Inject constructor(
*/
fun getAllUsers(): Flow<List<User>> {
return userDAO.getAllUsers()
.filter { users ->
users.any { user ->
.map { users ->
users.filter { user ->
user.id != userDAO.getCurrentUserId()
}
}
@ -60,7 +60,7 @@ class SearchFriendsViewModel @Inject constructor(
userId: String,
open: (String) -> Unit
) {
selectedProfileState.selectedUserId = userId
selectedProfileState.value = userId
open(StudeezDestinations.PUBLIC_PROFILE_SCREEN)
}
}

View file

@ -18,6 +18,7 @@ import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.Headline
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.drawer.DrawerEntry
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
import be.ugent.sel.studeez.data.local.models.User
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.screens.profile.AmountOfFriendsButton
@ -30,7 +31,7 @@ data class PublicProfileActions(
val getUserDetails: () -> Flow<User>,
val getAmountOfFriends: () -> Flow<Int>,
val onViewFriendsClick: () -> Unit,
val sendFriendRequest: () -> Boolean
val sendFriendRequest: () -> Unit
)
fun getPublicProfileActions(
@ -43,9 +44,11 @@ fun getPublicProfileActions(
userId = viewModel.uiState.value.userId
) },
onViewFriendsClick = { viewModel.onViewFriendsClick(open) },
sendFriendRequest = { viewModel.sendFriendRequest(
userId = viewModel.uiState.value.userId
) }
sendFriendRequest = {
viewModel.sendFriendRequest(
userId = viewModel.uiState.value.userId
)
}
)
}
@ -129,7 +132,7 @@ fun PublicProfilePreview() {
},
getAmountOfFriends = { flowOf(113) },
onViewFriendsClick = {},
sendFriendRequest = { true }
sendFriendRequest = {}
),
popUp = {}
)
@ -138,7 +141,7 @@ fun PublicProfilePreview() {
@Composable
fun PublicProfileEllipsis(
sendFriendRequest: () -> Boolean
sendFriendRequest: () -> Unit
) {
var expanded by remember { mutableStateOf(false) }
@ -147,8 +150,7 @@ fun PublicProfileEllipsis(
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_more_horizontal),
contentDescription = resources().getString(AppText.view_more),
modifier = Modifier.fillMaxSize()
contentDescription = resources().getString(AppText.view_more)
)
}
@ -172,7 +174,7 @@ fun PublicProfileEllipsis(
fun PublicProfileEllipsisPreview() {
StudeezTheme {
PublicProfileEllipsis(
sendFriendRequest = { true }
sendFriendRequest = {}
)
}
}

View file

@ -1,6 +1,8 @@
package be.ugent.sel.studeez.screens.profile.public_profile
import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
import be.ugent.sel.studeez.data.SelectedUserId
import be.ugent.sel.studeez.data.local.models.User
import be.ugent.sel.studeez.domain.FriendshipDAO
import be.ugent.sel.studeez.domain.LogService
@ -9,20 +11,20 @@ import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.selects.select
import javax.inject.Inject
import be.ugent.sel.studeez.R.string as AppText
@HiltViewModel
class PublicProfileViewModel @Inject constructor(
private val userDAO: UserDAO,
private val friendshipDAO: FriendshipDAO,
selectedProfileState: SelectedProfileState,
selectedUserIdState: SelectedUserId,
logService: LogService
): StudeezViewModel(logService) {
val uiState = mutableStateOf(
PublicProfileUiState(
userId = selectedProfileState.selectedUserId
userId = selectedUserIdState.value
)
)
@ -53,8 +55,8 @@ class PublicProfileViewModel @Inject constructor(
fun sendFriendRequest(
userId: String
): Boolean {
return friendshipDAO.sendFriendshipRequest(userId)
) {
friendshipDAO.sendFriendshipRequest(userId)
}
}

View file

@ -1,12 +0,0 @@
package be.ugent.sel.studeez.screens.profile.public_profile
import be.ugent.sel.studeez.domain.UserDAO
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SelectedProfileState @Inject constructor(
userDAO: UserDAO
) {
var selectedUserId: String = userDAO.getCurrentUserId()
}

View file

@ -1,10 +1,6 @@
package be.ugent.sel.studeez.screens.session
import android.annotation.SuppressLint
import android.content.Context
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.net.Uri
import kotlinx.coroutines.delay
import javax.inject.Singleton
import kotlin.time.Duration.Companion.seconds
@ -14,10 +10,8 @@ object InvisibleSessionManager {
private var viewModel: SessionViewModel? = null
private lateinit var mediaPlayer: MediaPlayer
fun setParameters(viewModel: SessionViewModel, context: Context) {
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
this.mediaPlayer = MediaPlayer.create(context, uri)
this.mediaPlayer.isLooping = false
fun setParameters(viewModel: SessionViewModel, mediaPlayer: MediaPlayer) {
this.mediaPlayer = mediaPlayer
this.viewModel = viewModel
}

View file

@ -1,5 +1,8 @@
package be.ugent.sel.studeez.screens.session
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
@ -28,8 +31,11 @@ fun SessionRoute(
openAndPopUp: (String, String) -> Unit,
viewModel: SessionViewModel,
) {
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val mediaPlayer = MediaPlayer.create(LocalContext.current, uri)
mediaPlayer.isLooping = false
InvisibleSessionManager.setParameters(viewModel = viewModel, context = LocalContext.current)
InvisibleSessionManager.setParameters(viewModel = viewModel, mediaPlayer = mediaPlayer)
val soundPlayer = SoundPlayer(LocalContext.current)
val sessionActions = getSessionActions(viewModel, openAndPopUp)

View file

@ -15,10 +15,12 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.common.composable.StealthButton
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry
import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.navigation.StudeezDestinations
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import be.ugent.sel.studeez.R.string as AppText
@ -35,11 +37,11 @@ fun SubjectRoute(
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
onAddSubject = { viewModel.onAddSubject(open) },
onViewSubject = { viewModel.onViewSubject(it, open) },
onViewSubject = { viewModel.onSelectSubject(it) { open(StudeezDestinations.TASKS_SCREEN) } },
getTaskCount = viewModel::getTaskCount,
getCompletedTaskCount = viewModel::getCompletedTaskCount,
getStudyTime = viewModel::getStudyTime,
uiState,
uiState = uiState,
)
}
@ -76,14 +78,22 @@ fun SubjectScreen(
) {
NewTaskSubjectButton(onClick = onAddSubject, AppText.new_subject)
LazyColumn {
items(uiState.subjects) {
items(uiState.subjects) { subject ->
SubjectEntry(
subject = it,
onViewSubject = { onViewSubject(it) },
getTaskCount = { getTaskCount(it) },
getCompletedTaskCount = { getCompletedTaskCount(it) },
getStudyTime = { getStudyTime(it) },
)
subject = subject,
getTaskCount = { getTaskCount(subject) },
getCompletedTaskCount = { getCompletedTaskCount(subject) },
getStudyTime = { getStudyTime(subject) },
) {
StealthButton(
text = AppText.view_tasks,
modifier = Modifier
.padding(start = 10.dp, end = 5.dp)
.weight(1f)
) {
onViewSubject(subject)
}
}
}
}
}

View file

@ -42,8 +42,8 @@ class SubjectViewModel @Inject constructor(
return subjectDAO.getStudyTime(subject)
}
fun onViewSubject(subject: Subject, open: (String) -> Unit) {
fun onSelectSubject(subject: Subject, open: () -> Unit) {
selectedSubject.set(subject)
open(StudeezDestinations.TASKS_SCREEN)
open()
}
}

View file

@ -18,7 +18,6 @@ import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.DeleteButton
import be.ugent.sel.studeez.common.composable.FormComposable
import be.ugent.sel.studeez.common.composable.LabelledInputField
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.fieldModifier
import be.ugent.sel.studeez.common.ext.generateRandomArgb

View file

@ -2,8 +2,6 @@ package be.ugent.sel.studeez.screens.subjects.form
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.graphics.Color
import be.ugent.sel.studeez.common.ext.generateRandomArgb
import be.ugent.sel.studeez.data.SelectedSubject
import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.domain.LogService

View file

@ -0,0 +1,128 @@
package be.ugent.sel.studeez.screens.subjects.select
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.StealthButton
import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry
import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.subjects.SubjectUiState
import be.ugent.sel.studeez.screens.subjects.SubjectViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
@Composable
fun SubjectSelectionRoute(
open: (String) -> Unit,
goBack: () -> Unit,
viewModel: SubjectViewModel,
) {
val uiState by viewModel.uiState.collectAsState()
SubjectSelectionScreen(
onViewSubject = { viewModel.onSelectSubject(it) { open(StudeezDestinations.ADD_TASK_FORM) } },
getTaskCount = viewModel::getTaskCount,
getCompletedTaskCount = viewModel::getCompletedTaskCount,
getStudyTime = viewModel::getStudyTime,
goBack = goBack,
uiState = uiState,
)
}
@Composable
fun SubjectSelectionScreen(
goBack: () -> Unit,
onViewSubject: (Subject) -> Unit,
getTaskCount: (Subject) -> Flow<Int>,
getCompletedTaskCount: (Subject) -> Flow<Int>,
getStudyTime: (Subject) -> Flow<Int>,
uiState: SubjectUiState,
) {
SecondaryScreenTemplate(
title = stringResource(R.string.select_subject_title),
barAction = {},
popUp = goBack,
) {
when (uiState) {
SubjectUiState.Loading -> Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(color = MaterialTheme.colors.onBackground)
}
is SubjectUiState.Succes -> {
Column(
modifier = Modifier.padding(top = 5.dp)
) {
LazyColumn {
items(uiState.subjects) { subject ->
SubjectEntry(
subject = subject,
getTaskCount = { getTaskCount(subject) },
getCompletedTaskCount = { getCompletedTaskCount(subject) },
getStudyTime = { getStudyTime(subject) },
) {
StealthButton(
text = R.string.select_subject,
modifier = Modifier
.padding(start = 4.dp, end = 4.dp)
.weight(1f)
) {
onViewSubject(subject)
}
}
}
}
}
}
}
}
}
@Preview
@Composable
fun SubjectScreenPreview() {
SubjectSelectionScreen(
goBack = {},
onViewSubject = {},
getTaskCount = { flowOf() },
getCompletedTaskCount = { flowOf() },
getStudyTime = { flowOf() },
uiState = SubjectUiState.Succes(
listOf(
Subject(
name = "Test Subject",
argb_color = 0xFFFFD200,
)
)
)
)
}
@Preview
@Composable
fun SubjectScreenLoadingPreview() {
SubjectSelectionScreen(
goBack = {},
onViewSubject = {},
getTaskCount = { flowOf() },
getCompletedTaskCount = { flowOf() },
getStudyTime = { flowOf() },
uiState = SubjectUiState.Loading,
)
}

View file

@ -4,7 +4,6 @@ import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import be.ugent.sel.studeez.common.composable.DeleteButton
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.FormComposable
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.R.string as AppText

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
@ -46,4 +47,13 @@ fun TimerTypeSelectScreen(
}
}
}
}
@Preview
@Composable
fun TimerTypeSelectScreenPreview() {
TimerTypeSelectScreen(
open = {},
popUp = {}
)
}

View file

@ -43,6 +43,7 @@
<string name="tasks">Tasks</string>
<string name="task">Task</string>
<string name="my_subjects">My Subjects</string>
<string name="select_subject_title">Select Subject</string>
<string name="new_subject">New Subject</string>
<string name="new_task">New Task</string>
<string name="edit_subject">Edit Subject</string>
@ -50,6 +51,7 @@
<string name="delete_subject">Delete Subject</string>
<string name="delete_task">Delete Task</string>
<string name="view_tasks">View</string>
<string name="select_subject">Select</string>
<string name="regenerate_color">Regenerate Color</string>
<!-- Sessions -->
@ -135,6 +137,9 @@
<string name="send_friend_request">Send friend request</string>
<string name="remove_friend">Remove as friend</string>
<string name="show_profile">Show profile</string>
<string name="click_search_friends">Click to search friends</string>
<string name="searching_friends">Searching friends</string>
<string name="already_friend">You are already befriended with that person.</string>
<!-- ========== Create & edit screens ========== -->

View file

@ -7,13 +7,14 @@ import org.junit.Test
class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() {
private val breakTime = 10
private val breaks = 2
private val repeats = 3 // = breaks + 1
override val hours = 0
override val minutes = 0
override val seconds = 10
private lateinit var pomodoroTimer: FunctionalPomodoroTimer
override fun setTimer() {
pomodoroTimer = FunctionalPomodoroTimer(time, breakTime, breaks)
pomodoroTimer = FunctionalPomodoroTimer(time, breakTime, repeats)
}
@Test

View file

@ -1,12 +1,11 @@
package be.ugent.sel.studeez.timer_functional
import android.media.MediaPlayer
import be.ugent.sel.studeez.data.SelectedSessionReport
import be.ugent.sel.studeez.data.SelectedTask
import be.ugent.sel.studeez.data.SelectedTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.implementation.LogServiceImpl
import be.ugent.sel.studeez.screens.session.InvisibleSessionManager
import be.ugent.sel.studeez.screens.session.SessionViewModel
@ -22,13 +21,13 @@ import org.mockito.kotlin.mock
class InvisibleSessionManagerTest {
private var selectedTimer: SelectedTimer = SelectedTimer()
private lateinit var viewModel: SessionViewModel
private var mediaPlayer: MediaPlayer = mock()
@Test
fun InvisibleEndlessTimerTest() = runTest {
selectedTimer.set(FunctionalEndlessTimer())
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl())
InvisibleSessionManager.setParameters(viewModel, mock())
val test = launch {
InvisibleSessionManager.updateTimer()
@ -47,10 +46,10 @@ class InvisibleSessionManagerTest {
fun InvisiblePomodoroTimerTest() = runTest {
val studyTime = 10
val breakTime = 5
val repeats = 1
val repeats = 2
selectedTimer.set(FunctionalPomodoroTimer(studyTime, breakTime, repeats))
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl())
InvisibleSessionManager.setParameters(viewModel, mock())
val test = launch {
InvisibleSessionManager.updateTimer()
@ -82,8 +81,8 @@ class InvisibleSessionManagerTest {
@Test
fun InvisibleCustomTimerTest() = runTest {
selectedTimer.set(FunctionalCustomTimer(5))
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl())
InvisibleSessionManager.setParameters(viewModel, mock())
val test = launch {
InvisibleSessionManager.updateTimer()

View file

@ -21,4 +21,3 @@ plugins {
// Hilt
id 'com.google.dagger.hilt.android' version '2.44' apply false
}

View file

@ -22,4 +22,6 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false