Merge branch 'development' into refactor
This commit is contained in:
commit
6542d2dbf2
141 changed files with 5058 additions and 1136 deletions
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<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" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|
|
@ -123,9 +123,6 @@ dependencies {
|
||||||
implementation 'com.google.firebase:firebase-firestore-ktx'
|
implementation 'com.google.firebase:firebase-firestore-ktx'
|
||||||
implementation 'com.google.firebase:firebase-perf-ktx'
|
implementation 'com.google.firebase:firebase-perf-ktx'
|
||||||
implementation 'com.google.firebase:firebase-config-ktx'
|
implementation 'com.google.firebase:firebase-config-ktx'
|
||||||
|
|
||||||
// Colorpicker
|
|
||||||
implementation 'com.github.skydoves:colorpicker-compose:1.0.2'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow references to generate code
|
// Allow references to generate code
|
||||||
|
|
74
app/src/androidTest/java/be/ugent/sel/studeez/FabTest.kt
Normal file
74
app/src/androidTest/java/be/ugent/sel/studeez/FabTest.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
207
app/src/androidTest/java/be/ugent/sel/studeez/HomeScreenTest.kt
Normal file
207
app/src/androidTest/java/be/ugent/sel/studeez/HomeScreenTest.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ import org.junit.Assert.*
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ExampleInstrumentedTest {
|
class InstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun useAppContext() {
|
fun useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
160
app/src/androidTest/java/be/ugent/sel/studeez/TaskScreenTest.kt
Normal file
160
app/src/androidTest/java/be/ugent/sel/studeez/TaskScreenTest.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,19 +2,9 @@ package be.ugent.sel.studeez.common.composable
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Button
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.ButtonColors
|
|
||||||
import androidx.compose.material.ButtonDefaults
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TextButton
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -31,7 +21,11 @@ import be.ugent.sel.studeez.common.ext.defaultButtonShape
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) {
|
fun BasicTextButton(
|
||||||
|
@StringRes text: Int,
|
||||||
|
modifier: Modifier,
|
||||||
|
action: () -> Unit
|
||||||
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = action,
|
onClick = action,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
@ -48,6 +42,7 @@ fun BasicButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||||
border: BorderStroke? = null,
|
border: BorderStroke? = null,
|
||||||
|
enabled: Boolean = true,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
|
@ -56,6 +51,7 @@ fun BasicButton(
|
||||||
shape = defaultButtonShape(),
|
shape = defaultButtonShape(),
|
||||||
colors = colors,
|
colors = colors,
|
||||||
border = border,
|
border = border,
|
||||||
|
enabled = enabled,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(text),
|
text = stringResource(text),
|
||||||
|
@ -74,17 +70,22 @@ fun BasicButtonPreview() {
|
||||||
fun StealthButton(
|
fun StealthButton(
|
||||||
@StringRes text: Int,
|
@StringRes text: Int,
|
||||||
modifier: Modifier = Modifier.card(),
|
modifier: Modifier = Modifier.card(),
|
||||||
|
enabled: Boolean = true,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
//val clickablemodifier = if (disabled) Modifier.clickable(indication = null) else modifier
|
||||||
|
val borderColor = if (enabled) MaterialTheme.colors.primary
|
||||||
|
else MaterialTheme.colors.onSurface.copy(alpha = 0.3f)
|
||||||
BasicButton(
|
BasicButton(
|
||||||
text = text,
|
text = text,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
backgroundColor = MaterialTheme.colors.surface,
|
backgroundColor = MaterialTheme.colors.surface,
|
||||||
contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.4f)
|
contentColor = borderColor
|
||||||
),
|
),
|
||||||
border = BorderStroke(3.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.4f))
|
border = BorderStroke(2.dp, borderColor)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package be.ugent.sel.studeez.common.composable
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateFloat
|
import androidx.compose.animation.core.animateFloat
|
||||||
import androidx.compose.animation.core.updateTransition
|
import androidx.compose.animation.core.updateTransition
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.FloatingActionButton
|
import androidx.compose.material.FloatingActionButton
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FormComposable(
|
||||||
|
title: String,
|
||||||
|
popUp: () -> Unit,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
SecondaryScreenTemplate(title = title, popUp = popUp) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImageBackgroundButton(
|
||||||
|
paint: Painter,
|
||||||
|
str: String,
|
||||||
|
background2: Color,
|
||||||
|
setBackground1: (Color) -> Unit,
|
||||||
|
setBackground2: (Color) -> Unit
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = paint,
|
||||||
|
str,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
if (background2 == Color.Transparent) {
|
||||||
|
setBackground1(Color.LightGray)
|
||||||
|
setBackground2(Color.Transparent)
|
||||||
|
} else {
|
||||||
|
setBackground2(Color.Transparent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.border(
|
||||||
|
width = 2.dp,
|
||||||
|
color = background2,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
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 be.ugent.sel.studeez.R
|
||||||
|
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProfilePicture() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.background(MaterialTheme.colors.primary, CircleShape)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Person,
|
||||||
|
contentDescription = stringResource(id = R.string.username),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(30.dp)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
tint = MaterialTheme.colors.onPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ProfilePicturePreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
ProfilePicture()
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,13 @@ package be.ugent.sel.studeez.common.composable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -24,3 +27,13 @@ fun Headline(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DateText(date: String) {
|
||||||
|
Text(
|
||||||
|
text = date,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
modifier = Modifier.padding(horizontal = 10.dp)
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,13 +3,13 @@ package be.ugent.sel.studeez.common.composable
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Email
|
import androidx.compose.material.icons.filled.Email
|
||||||
import androidx.compose.material.icons.filled.Lock
|
import androidx.compose.material.icons.filled.Lock
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
@ -22,7 +22,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import be.ugent.sel.studeez.common.ext.fieldModifier
|
import be.ugent.sel.studeez.common.ext.fieldModifier
|
||||||
import be.ugent.sel.studeez.resources
|
import be.ugent.sel.studeez.resources
|
||||||
import kotlin.math.sin
|
|
||||||
import be.ugent.sel.studeez.R.drawable as AppIcon
|
import be.ugent.sel.studeez.R.drawable as AppIcon
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
@ -47,7 +46,7 @@ fun LabelledInputField(
|
||||||
value: String,
|
value: String,
|
||||||
onNewValue: (String) -> Unit,
|
onNewValue: (String) -> Unit,
|
||||||
@StringRes label: Int,
|
@StringRes label: Int,
|
||||||
singleLine: Boolean = false
|
singleLine: Boolean = true
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = value,
|
value = value,
|
||||||
|
@ -119,7 +118,9 @@ fun LabeledErrorTextField(
|
||||||
initialValue: String,
|
initialValue: String,
|
||||||
@StringRes label: Int,
|
@StringRes label: Int,
|
||||||
singleLine: Boolean = false,
|
singleLine: Boolean = false,
|
||||||
errorText: Int,
|
isValid: MutableState<Boolean> = remember { mutableStateOf(true) },
|
||||||
|
isFirst: MutableState<Boolean> = remember { mutableStateOf(false) },
|
||||||
|
@StringRes errorText: Int,
|
||||||
keyboardType: KeyboardType,
|
keyboardType: KeyboardType,
|
||||||
predicate: (String) -> Boolean,
|
predicate: (String) -> Boolean,
|
||||||
onNewCorrectValue: (String) -> Unit
|
onNewCorrectValue: (String) -> Unit
|
||||||
|
@ -128,31 +129,28 @@ fun LabeledErrorTextField(
|
||||||
mutableStateOf(initialValue)
|
mutableStateOf(initialValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isValid by remember {
|
|
||||||
mutableStateOf(predicate(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = modifier.fieldModifier(),
|
modifier = modifier.fieldModifier(),
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = { newText ->
|
onValueChange = { newText ->
|
||||||
|
isFirst.value = false
|
||||||
value = newText
|
value = newText
|
||||||
isValid = predicate(value)
|
isValid.value = predicate(value)
|
||||||
if (isValid) {
|
if (isValid.value) {
|
||||||
onNewCorrectValue(newText)
|
onNewCorrectValue(newText)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
singleLine = singleLine,
|
singleLine = singleLine,
|
||||||
label = { Text(text = stringResource(id = label)) },
|
label = { Text(text = stringResource(id = label)) },
|
||||||
isError = !isValid,
|
isError = !isValid.value && !isFirst.value,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
keyboardType = keyboardType,
|
keyboardType = keyboardType,
|
||||||
imeAction = ImeAction.Done
|
imeAction = ImeAction.Done
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid.value && !isFirst.value) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(start = 16.dp),
|
modifier = Modifier.padding(start = 16.dp),
|
||||||
text = stringResource(id = errorText),
|
text = stringResource(id = errorText),
|
||||||
|
@ -219,3 +217,35 @@ private fun PasswordField(
|
||||||
visualTransformation = visualTransformation
|
visualTransformation = visualTransformation
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchField(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
onSubmit: () -> Unit,
|
||||||
|
@StringRes label: Int,
|
||||||
|
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) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Search,
|
||||||
|
contentDescription = stringResource(label),
|
||||||
|
tint = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
|
textColor = MaterialTheme.colors.onBackground,
|
||||||
|
backgroundColor = MaterialTheme.colors.background
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
|
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.material.Text
|
||||||
|
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.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import be.ugent.sel.studeez.common.composable.BasicTextButton
|
||||||
|
import be.ugent.sel.studeez.common.composable.DateText
|
||||||
|
import be.ugent.sel.studeez.common.composable.Headline
|
||||||
|
import be.ugent.sel.studeez.common.ext.textButton
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Feed(
|
||||||
|
uiState: FeedUiState,
|
||||||
|
continueTask: (String, String) -> Unit,
|
||||||
|
onEmptyFeedHelp: () -> Unit
|
||||||
|
) {
|
||||||
|
when (uiState) {
|
||||||
|
FeedUiState.Loading -> LoadingFeed()
|
||||||
|
is FeedUiState.Succes -> LoadedFeed(
|
||||||
|
uiState = uiState,
|
||||||
|
continueTask = continueTask,
|
||||||
|
onEmptyFeedHelp = onEmptyFeedHelp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadedFeed(
|
||||||
|
uiState: FeedUiState.Succes,
|
||||||
|
continueTask: (String, String) -> Unit,
|
||||||
|
onEmptyFeedHelp: () -> Unit,
|
||||||
|
) {
|
||||||
|
if (uiState.feedEntries.isEmpty()) EmptyFeed(onEmptyFeedHelp)
|
||||||
|
else FeedWithElements(uiState = uiState, continueTask = continueTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadingFeed() {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(color = MaterialTheme.colors.onBackground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FeedWithElements(
|
||||||
|
uiState: FeedUiState.Succes,
|
||||||
|
continueTask: (String, String) -> Unit,
|
||||||
|
) {
|
||||||
|
val feedEntries = uiState.feedEntries
|
||||||
|
LazyColumn {
|
||||||
|
items(feedEntries.toList()) { (date, feedEntries) ->
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
val totalDayStudyTime: Int = feedEntries.sumOf { it.totalStudyTime }
|
||||||
|
DateText(date = date)
|
||||||
|
Text(
|
||||||
|
text = "${HoursMinutesSeconds(totalDayStudyTime)}",
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
feedEntries.forEach { feedEntry ->
|
||||||
|
FeedEntry(feedEntry = feedEntry) {
|
||||||
|
continueTask(feedEntry.subjectId, feedEntry.taskId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EmptyFeed(onEmptyFeedHelp: () -> Unit) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Headline(text = stringResource(id = AppText.your_feed))
|
||||||
|
|
||||||
|
BasicTextButton(
|
||||||
|
AppText.empty_feed_help_text,
|
||||||
|
Modifier.textButton(),
|
||||||
|
action = onEmptyFeedHelp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FeedLoadingPreview() {
|
||||||
|
Feed(
|
||||||
|
uiState = FeedUiState.Loading,
|
||||||
|
continueTask = { _, _ -> run {} }, {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FeedPreview() {
|
||||||
|
Feed(
|
||||||
|
uiState = FeedUiState.Succes(
|
||||||
|
mapOf(
|
||||||
|
"08 May 2023" to listOf(
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 600,
|
||||||
|
),
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"09 May 2023" to listOf(
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFD1200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
),
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
continueTask = { _, _ -> run {} }, {}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import be.ugent.sel.studeez.common.composable.StealthButton
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FeedEntry(
|
||||||
|
feedEntry: FeedEntry,
|
||||||
|
continueWithTask: () -> Unit,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.weight(11f)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color(feedEntry.argb_color)),
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(0.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = feedEntry.subJectName,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = feedEntry.taskName,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(text = HoursMinutesSeconds(feedEntry.totalStudyTime).toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val buttonText: Int =
|
||||||
|
if (feedEntry.isArchived) AppText.deleted else AppText.continue_task
|
||||||
|
StealthButton(
|
||||||
|
text = buttonText,
|
||||||
|
enabled = !feedEntry.isArchived,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp, end = 5.dp)
|
||||||
|
.weight(6f)
|
||||||
|
) {
|
||||||
|
if (!feedEntry.isArchived) {
|
||||||
|
continueWithTask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FeedEntryPreview() {
|
||||||
|
FeedEntry(
|
||||||
|
continueWithTask = {},
|
||||||
|
feedEntry = FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 20,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FeedEntryOverflowPreview() {
|
||||||
|
FeedEntry(
|
||||||
|
continueWithTask = {},
|
||||||
|
feedEntry = FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkk",
|
||||||
|
totalStudyTime = 20,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
|
||||||
|
sealed interface FeedUiState {
|
||||||
|
object Loading : FeedUiState
|
||||||
|
data class Succes(val feedEntries: Map<String, List<FeedEntry>>) : FeedUiState
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import be.ugent.sel.studeez.data.SelectedTask
|
||||||
|
import be.ugent.sel.studeez.domain.FeedDAO
|
||||||
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class FeedViewModel @Inject constructor(
|
||||||
|
feedDAO: FeedDAO,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
|
private val selectedTask: SelectedTask,
|
||||||
|
logService: LogService
|
||||||
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
|
val uiState: StateFlow<FeedUiState> = feedDAO.getFeedEntries()
|
||||||
|
.map { FeedUiState.Succes(it) }
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
initialValue = FeedUiState.Loading,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun continueTask(open: (String) -> Unit, subjectId: String, taskId: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val task = taskDAO.getTask(subjectId, taskId)
|
||||||
|
selectedTask.set(task)
|
||||||
|
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onEmptyFeedHelp(open: (String) -> Unit) {
|
||||||
|
open(StudeezDestinations.ADD_SUBJECT_FORM)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,13 +8,15 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.List
|
import androidx.compose.material.icons.filled.List
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.material.icons.outlined.Check
|
||||||
import androidx.compose.material.icons.outlined.DateRange
|
import androidx.compose.material.icons.outlined.DateRange
|
||||||
|
import androidx.compose.material.icons.outlined.Face
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
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.HOME_SCREEN
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
|
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN
|
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN
|
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN
|
||||||
import be.ugent.sel.studeez.resources
|
import be.ugent.sel.studeez.resources
|
||||||
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||||
|
@ -99,11 +101,11 @@ fun NavigationBar(
|
||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.DateRange, resources().getString(AppText.sessions)
|
imageVector = Icons.Outlined.Face, resources().getString(AppText.friends_feed)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
label = { Text(text = resources().getString(AppText.sessions)) },
|
label = { Text(text = resources().getString(AppText.friends_feed)) },
|
||||||
selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN),
|
selected = navigationBarActions.isSelectedTab(FRIENDS_FEED),
|
||||||
onClick = navigationBarActions.onSessionsClick
|
onClick = navigationBarActions.onSessionsClick
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,11 @@ package be.ugent.sel.studeez.common.composable.navbar
|
||||||
|
|
||||||
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
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.HOME_SCREEN
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
|
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_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.SUBJECT_SCREEN
|
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -25,7 +27,7 @@ class NavigationBarViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSessionsClick(open: (String) -> Unit) {
|
fun onSessionsClick(open: (String) -> Unit) {
|
||||||
open(SESSIONS_SCREEN)
|
open(FRIENDS_FEED)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onProfileClick(open: (String) -> Unit) {
|
fun onProfileClick(open: (String) -> Unit) {
|
||||||
|
@ -33,13 +35,11 @@ class NavigationBarViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAddTaskClick(open: (String) -> Unit) {
|
fun onAddTaskClick(open: (String) -> Unit) {
|
||||||
// TODO open(CREATE_TASK_SCREEN)
|
open(SELECT_SUBJECT)
|
||||||
SnackbarManager.showMessage(AppText.create_task_not_possible_yet) // TODO Remove
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAddFriendClick(open: (String) -> Unit) {
|
fun onAddFriendClick(open: (String) -> Unit) {
|
||||||
// TODO open(SEARCH_FRIENDS_SCREEN)
|
open(SEARCH_FRIENDS_SCREEN)
|
||||||
SnackbarManager.showMessage(AppText.add_friend_not_possible_yet) // TODO Remove
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAddSessionClick(open: (String) -> Unit) {
|
fun onAddSessionClick(open: (String) -> Unit) {
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
package be.ugent.sel.studeez.common.composable.tasks
|
package be.ugent.sel.studeez.common.composable.tasks
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.List
|
import androidx.compose.material.icons.filled.List
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
@ -24,16 +21,24 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
|
||||||
import be.ugent.sel.studeez.common.composable.StealthButton
|
import be.ugent.sel.studeez.common.composable.StealthButton
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SubjectEntry(
|
fun SubjectEntry(
|
||||||
subject: Subject,
|
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)
|
||||||
|
val completedTaskCount by getCompletedTaskCount().collectAsState(initial = 0)
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
@ -61,16 +66,17 @@ fun SubjectEntry(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = subject.name,
|
text = subject.name,
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = HoursMinutesSeconds(subject.time).toString(),
|
text = HoursMinutesSeconds(studytime).toString(),
|
||||||
|
color = MaterialTheme.colors.onBackground.copy(alpha = 0.6f)
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
@ -78,21 +84,18 @@ fun SubjectEntry(
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.List,
|
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 = "0/0") // TODO
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StealthButton(
|
selectButton(this)
|
||||||
text = AppText.view_tasks,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 10.dp, end = 5.dp)
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
onViewSubject()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,9 +107,17 @@ fun SubjectEntryPreview() {
|
||||||
subject = Subject(
|
subject = Subject(
|
||||||
name = "Test Subject",
|
name = "Test Subject",
|
||||||
argb_color = 0xFFFFD200,
|
argb_color = 0xFFFFD200,
|
||||||
time = 60
|
|
||||||
),
|
),
|
||||||
|
getTaskCount = { flowOf() },
|
||||||
|
getCompletedTaskCount = { flowOf() },
|
||||||
|
getStudyTime = { flowOf() },
|
||||||
|
) {
|
||||||
|
StealthButton(
|
||||||
|
text = AppText.view_tasks,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp, end = 5.dp)
|
||||||
) {}
|
) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
|
@ -116,7 +127,9 @@ fun OverflowSubjectEntryPreview() {
|
||||||
subject = Subject(
|
subject = Subject(
|
||||||
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
|
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
|
||||||
argb_color = 0xFFFFD200,
|
argb_color = 0xFFFFD200,
|
||||||
time = 60
|
|
||||||
),
|
),
|
||||||
|
getTaskCount = { flowOf() },
|
||||||
|
getCompletedTaskCount = { flowOf() },
|
||||||
|
getStudyTime = { flowOf() },
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
|
@ -1,17 +1,7 @@
|
||||||
package be.ugent.sel.studeez.common.composable.tasks
|
package be.ugent.sel.studeez.common.composable.tasks
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.material.*
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.Card
|
|
||||||
import androidx.compose.material.Checkbox
|
|
||||||
import androidx.compose.material.CheckboxDefaults
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.IconButton
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -31,7 +21,8 @@ import be.ugent.sel.studeez.resources
|
||||||
fun TaskEntry(
|
fun TaskEntry(
|
||||||
task: Task,
|
task: Task,
|
||||||
onCheckTask: (Boolean) -> Unit,
|
onCheckTask: (Boolean) -> Unit,
|
||||||
onDeleteTask: () -> Unit,
|
onArchiveTask: () -> Unit,
|
||||||
|
onStartTask: () -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -80,7 +71,7 @@ fun TaskEntry(
|
||||||
Box(modifier = Modifier.weight(7f)) {
|
Box(modifier = Modifier.weight(7f)) {
|
||||||
if (task.completed) {
|
if (task.completed) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onDeleteTask,
|
onClick = onArchiveTask,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 20.dp)
|
.padding(start = 20.dp)
|
||||||
) {
|
) {
|
||||||
|
@ -95,6 +86,7 @@ fun TaskEntry(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 5.dp),
|
.padding(end = 5.dp),
|
||||||
) {
|
) {
|
||||||
|
onStartTask()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +102,7 @@ fun TaskEntryPreview() {
|
||||||
name = "Test Task",
|
name = "Test Task",
|
||||||
completed = false,
|
completed = false,
|
||||||
),
|
),
|
||||||
{}, {},
|
{}, {}, {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +114,7 @@ fun CompletedTaskEntryPreview() {
|
||||||
name = "Test Task",
|
name = "Test Task",
|
||||||
completed = true,
|
completed = true,
|
||||||
),
|
),
|
||||||
{}, {},
|
{}, {}, {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +126,6 @@ fun OverflowTaskEntryPreview() {
|
||||||
name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk",
|
name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk",
|
||||||
completed = false,
|
completed = false,
|
||||||
),
|
),
|
||||||
{}, {},
|
{}, {}, {}
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package be.ugent.sel.studeez.common.ext
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
fun Color.Companion.generateRandomArgb(): Long {
|
||||||
|
val random = Random
|
||||||
|
val mask: Long = (0x000000FFL shl random.nextInt(0, 3)).inv()
|
||||||
|
return random.nextLong(0xFF000000L, 0xFFFFFFFFL) and mask
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class EditTimerState @Inject constructor(){
|
|
||||||
lateinit var timerInfo: TimerInfo
|
|
||||||
}
|
|
53
app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt
Normal file
53
app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package be.ugent.sel.studeez.data
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to cummunicate between viewmodels.
|
||||||
|
*/
|
||||||
|
abstract class SelectedState<T> {
|
||||||
|
abstract var value: T
|
||||||
|
operator fun invoke() = value
|
||||||
|
fun set(newValue: T) {
|
||||||
|
this.value = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedSessionReport @Inject constructor() : SelectedState<SessionReport>() {
|
||||||
|
override lateinit var value: SessionReport
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedTask @Inject constructor() : SelectedState<Task>() {
|
||||||
|
override lateinit var value: Task
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedTimer @Inject constructor() : SelectedState<FunctionalTimer>() {
|
||||||
|
override lateinit var value: FunctionalTimer
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedSubject @Inject constructor() : SelectedState<Subject>() {
|
||||||
|
override lateinit var value: 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()
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to communicate the selected subject from the subject overview other screens.
|
|
||||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
class SelectedSubject @Inject constructor() {
|
|
||||||
private lateinit var subject: Subject
|
|
||||||
operator fun invoke() = subject
|
|
||||||
fun set(subject: Subject) {
|
|
||||||
this.subject = subject
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSet() = this::subject.isInitialized
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to communicate the selected task from the task overview other screens.
|
|
||||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
class SelectedTask @Inject constructor() {
|
|
||||||
private lateinit var task: Task
|
|
||||||
|
|
||||||
operator fun invoke() = task
|
|
||||||
fun set(task: Task) {
|
|
||||||
this.task = task
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSet() = this::task.isInitialized
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to communicate the SelectedTimer from the selection screen to the session screen.
|
|
||||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
class SelectedTimerState @Inject constructor(){
|
|
||||||
var selectedTimer: FunctionalTimer? = null
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to communicate the SelectedTimer from the selection screen to the session screen.
|
|
||||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
class SessionReportState @Inject constructor(){
|
|
||||||
var sessionReport: SessionReport? = null
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package be.ugent.sel.studeez.data.local.models
|
||||||
|
|
||||||
|
import com.google.firebase.Timestamp
|
||||||
|
|
||||||
|
data class FeedEntry(
|
||||||
|
val argb_color: Long = 0,
|
||||||
|
val subJectName: String = "",
|
||||||
|
val taskName: String = "",
|
||||||
|
val taskId: String = "", // Name of task is not unique
|
||||||
|
val subjectId: String = "",
|
||||||
|
val totalStudyTime: Int = 0,
|
||||||
|
val endTime: Timestamp = Timestamp(0, 0),
|
||||||
|
val isArchived: Boolean = false
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
package be.ugent.sel.studeez.data.local.models
|
||||||
|
|
||||||
|
import com.google.firebase.Timestamp
|
||||||
|
import com.google.firebase.firestore.DocumentId
|
||||||
|
|
||||||
|
data class Friendship(
|
||||||
|
@DocumentId val id: String = "",
|
||||||
|
val friendId: String = "",
|
||||||
|
val friendsSince: Timestamp = Timestamp.now(),
|
||||||
|
val accepted: Boolean = false
|
||||||
|
)
|
|
@ -6,5 +6,7 @@ import com.google.firebase.firestore.DocumentId
|
||||||
data class SessionReport(
|
data class SessionReport(
|
||||||
@DocumentId val id: String = "",
|
@DocumentId val id: String = "",
|
||||||
val studyTime: Int = 0,
|
val studyTime: Int = 0,
|
||||||
val endTime: Timestamp = Timestamp(0, 0)
|
val endTime: Timestamp = Timestamp(0, 0),
|
||||||
|
val taskId: String = "",
|
||||||
|
val subjectId: String = ""
|
||||||
)
|
)
|
|
@ -1,3 +1,9 @@
|
||||||
package be.ugent.sel.studeez.data.local.models
|
package be.ugent.sel.studeez.data.local.models
|
||||||
|
|
||||||
data class User(val id: String = "")
|
import com.google.firebase.firestore.DocumentId
|
||||||
|
|
||||||
|
data class User(
|
||||||
|
@DocumentId val id: String = "",
|
||||||
|
val username: String = "",
|
||||||
|
val biography: String = ""
|
||||||
|
)
|
||||||
|
|
|
@ -5,6 +5,13 @@ import com.google.firebase.firestore.DocumentId
|
||||||
data class Subject(
|
data class Subject(
|
||||||
@DocumentId val id: String = "",
|
@DocumentId val id: String = "",
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val time: Int = 0,
|
|
||||||
val argb_color: Long = 0,
|
val argb_color: Long = 0,
|
||||||
|
var archived: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
object SubjectDocument {
|
||||||
|
const val id = "id"
|
||||||
|
const val name = "name"
|
||||||
|
const val archived = "archived"
|
||||||
|
const val argb_color = "argb_color"
|
||||||
|
}
|
|
@ -5,9 +5,10 @@ import com.google.firebase.firestore.DocumentId
|
||||||
data class Task(
|
data class Task(
|
||||||
@DocumentId val id: String = "",
|
@DocumentId val id: String = "",
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val completed: Boolean = false,
|
var completed: Boolean = false,
|
||||||
val time: Int = 0,
|
val time: Int = 0,
|
||||||
val subjectId: String = "",
|
val subjectId: String = "",
|
||||||
|
var archived: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
object TaskDocument {
|
object TaskDocument {
|
||||||
|
@ -16,4 +17,5 @@ object TaskDocument {
|
||||||
const val completed = "completed"
|
const val completed = "completed"
|
||||||
const val time = "time"
|
const val time = "time"
|
||||||
const val subjectId = "subjectId"
|
const val subjectId = "subjectId"
|
||||||
|
const val archived = "archived"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,17 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
|
||||||
|
|
||||||
class FunctionalPomodoroTimer(
|
class FunctionalPomodoroTimer(
|
||||||
private var studyTime: Int,
|
private var studyTime: Int,
|
||||||
private var breakTime: Int, repeats: Int
|
private var breakTime: Int,
|
||||||
|
val repeats: Int
|
||||||
) : FunctionalTimer(studyTime) {
|
) : FunctionalTimer(studyTime) {
|
||||||
|
|
||||||
var breaksRemaining = repeats
|
var breaksRemaining = repeats - 1
|
||||||
var isInBreak = false
|
var isInBreak = false
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick() {
|
||||||
if (hasEnded()) {
|
if (hasEnded()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCurrentCountdownEnded()) {
|
if (hasCurrentCountdownEnded()) {
|
||||||
if (isInBreak) {
|
if (isInBreak) {
|
||||||
breaksRemaining--
|
breaksRemaining--
|
||||||
|
|
|
@ -17,10 +17,12 @@ abstract class FunctionalTimer(initialValue: Int) {
|
||||||
|
|
||||||
abstract fun hasCurrentCountdownEnded(): Boolean
|
abstract fun hasCurrentCountdownEnded(): Boolean
|
||||||
|
|
||||||
fun getSessionReport(): SessionReport {
|
fun getSessionReport(subjectId: String, taskId: String): SessionReport {
|
||||||
return SessionReport(
|
return SessionReport(
|
||||||
studyTime = totalStudyTime,
|
studyTime = totalStudyTime,
|
||||||
endTime = Timestamp.now()
|
endTime = Timestamp.now(),
|
||||||
|
taskId = taskId,
|
||||||
|
subjectId = subjectId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package be.ugent.sel.studeez.data.remote
|
||||||
|
|
||||||
|
object FirebaseFriendship {
|
||||||
|
const val FRIENDID: String = "friendId"
|
||||||
|
const val ACCEPTED: String = "accepted"
|
||||||
|
const val FRIENDSSINCE: String = "friendsSince"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package be.ugent.sel.studeez.data.remote
|
||||||
|
|
||||||
|
object FirebaseSessionReport {
|
||||||
|
const val STUDYTIME: String = "studyTime"
|
||||||
|
const val ENDTIME: String = "endTime"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package be.ugent.sel.studeez.data.remote
|
||||||
|
|
||||||
|
object FirebaseUser {
|
||||||
|
const val USERNAME: String = "username"
|
||||||
|
const val BIOGRAPHY: String = "biography"
|
||||||
|
}
|
|
@ -16,6 +16,9 @@ abstract class DatabaseModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun provideUserDAO(impl: FirebaseUserDAO): UserDAO
|
abstract fun provideUserDAO(impl: FirebaseUserDAO): UserDAO
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun provideFriendshipDAO(impl: FirebaseFriendshipDAO): FriendshipDAO
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun provideTimerDAO(impl: FirebaseTimerDAO): TimerDAO
|
abstract fun provideTimerDAO(impl: FirebaseTimerDAO): TimerDAO
|
||||||
|
|
||||||
|
@ -26,11 +29,14 @@ abstract class DatabaseModule {
|
||||||
abstract fun provideConfigurationService(impl: FirebaseConfigurationService): ConfigurationService
|
abstract fun provideConfigurationService(impl: FirebaseConfigurationService): ConfigurationService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun provideSessionDAO(impl: FireBaseSessionDAO): SessionDAO
|
abstract fun provideSessionDAO(impl: FirebaseSessionDAO): SessionDAO
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun provideSubjectDAO(impl: FireBaseSubjectDAO): SubjectDAO
|
abstract fun provideSubjectDAO(impl: FirebaseSubjectDAO): SubjectDAO
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO
|
abstract fun provideTaskDAO(impl: FirebaseTaskDAO): TaskDAO
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun provideFeedDAO(impl: FirebaseFeedDAO): FeedDAO
|
||||||
}
|
}
|
13
app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt
Normal file
13
app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package be.ugent.sel.studeez.domain
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface FeedDAO {
|
||||||
|
|
||||||
|
fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>>
|
||||||
|
|
||||||
|
suspend fun getFeedEntriesFromUser(id: String): Map<String, List<FeedEntry>>
|
||||||
|
|
||||||
|
fun getFriendsSessions(): Flow<Map<String, List<Pair<String, FeedEntry>>>>
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package be.ugent.sel.studeez.domain
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.Friendship
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be used for interactions between friends.
|
||||||
|
*/
|
||||||
|
interface FriendshipDAO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all friendships of a chosen user.
|
||||||
|
*/
|
||||||
|
fun getAllFriendships(
|
||||||
|
userId: String
|
||||||
|
): Flow<List<Friendship>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the amount of friends of a chosen user.
|
||||||
|
* This method should be faster than just counting the length of getAllFriends()
|
||||||
|
*/
|
||||||
|
fun getFriendshipCount(
|
||||||
|
userId: String
|
||||||
|
): Flow<Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id the id of the friendship that you want details of
|
||||||
|
* @return the details of a Friendship
|
||||||
|
*/
|
||||||
|
fun getFriendshipDetails(id: String): Friendship
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a friend request to a user.
|
||||||
|
* @param id of the user that you want to add as a friend
|
||||||
|
* @return Success/faillure of transaction
|
||||||
|
*/
|
||||||
|
fun sendFriendshipRequest(id: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept a friend request that has already been sent.
|
||||||
|
* @param id of the friendship that you want to update
|
||||||
|
* @return: Success/faillure of transaction
|
||||||
|
*/
|
||||||
|
fun acceptFriendship(id: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a friend or decline a friendrequest.
|
||||||
|
* @param friendship the one you want to remove
|
||||||
|
* @return: Success/faillure of transaction
|
||||||
|
*/
|
||||||
|
fun removeFriendship(
|
||||||
|
friendship: Friendship
|
||||||
|
): Boolean
|
||||||
|
}
|
|
@ -1,12 +1,16 @@
|
||||||
package be.ugent.sel.studeez.domain
|
package be.ugent.sel.studeez.domain
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface SessionDAO {
|
interface SessionDAO {
|
||||||
|
|
||||||
fun getSessions(): Flow<List<SessionReport>>
|
fun getSessions(): Flow<List<SessionReport>>
|
||||||
|
suspend fun getSessionsOfUser(userId: String): List<SessionReport>
|
||||||
|
|
||||||
fun saveSession(newSessionReport: SessionReport)
|
fun saveSession(newSessionReport: SessionReport)
|
||||||
|
|
||||||
|
|
|
@ -12,4 +12,13 @@ interface SubjectDAO {
|
||||||
fun deleteSubject(oldSubject: Subject)
|
fun deleteSubject(oldSubject: Subject)
|
||||||
|
|
||||||
fun updateSubject(newSubject: Subject)
|
fun updateSubject(newSubject: Subject)
|
||||||
|
|
||||||
|
suspend fun archiveSubject(subject: Subject)
|
||||||
|
|
||||||
|
fun getTaskCount(subject: Subject): Flow<Int>
|
||||||
|
fun getCompletedTaskCount(subject: Subject): Flow<Int>
|
||||||
|
fun getStudyTime(subject: Subject): Flow<Int>
|
||||||
|
|
||||||
|
suspend fun getSubject(subjectId: String): Subject?
|
||||||
|
suspend fun getSubjectOfUSer(subjectId: String, userId: String): Subject
|
||||||
}
|
}
|
|
@ -14,5 +14,7 @@ interface TaskDAO {
|
||||||
|
|
||||||
fun deleteTask(oldTask: Task)
|
fun deleteTask(oldTask: Task)
|
||||||
|
|
||||||
fun toggleTaskCompleted(task: Task, completed: Boolean)
|
suspend fun getTask(subjectId: String, taskId: String): Task
|
||||||
|
|
||||||
|
suspend fun getTaskFromUser(subjectId: String, taskId: String, userId: String): Task
|
||||||
}
|
}
|
|
@ -1,13 +1,52 @@
|
||||||
package be.ugent.sel.studeez.domain
|
package be.ugent.sel.studeez.domain
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface UserDAO {
|
interface UserDAO {
|
||||||
|
|
||||||
suspend fun getUsername(): String?
|
fun getCurrentUserId(): String
|
||||||
suspend fun save(newUsername: String)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all references to this user in the database. Similar to the deleteCascade in
|
* @return all users
|
||||||
|
*/
|
||||||
|
fun getAllUsers(): Flow<List<User>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all users based on a query, a trimmed down version of getAllUsers()
|
||||||
|
*/
|
||||||
|
fun getUsersWithQuery(
|
||||||
|
fieldName: String,
|
||||||
|
value: String
|
||||||
|
): Flow<List<User>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request information about a user
|
||||||
|
*/
|
||||||
|
fun getUserDetails(
|
||||||
|
userId: String
|
||||||
|
): Flow<User>
|
||||||
|
|
||||||
|
suspend fun getUsername(
|
||||||
|
userId: String
|
||||||
|
): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return information on the currently logged in user.
|
||||||
|
*/
|
||||||
|
suspend fun getLoggedInUser(): User
|
||||||
|
// TODO Should be refactored to fun getLoggedInUser(): Flow<User>, without suspend.
|
||||||
|
|
||||||
|
suspend fun saveLoggedInUser(
|
||||||
|
newUsername: String,
|
||||||
|
newBiography: String = ""
|
||||||
|
)
|
||||||
|
// TODO Should be refactored to fun saveLoggedInUser(...): Boolean, without suspend.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all references to the logged in user in the database. Similar to the deleteCascade in
|
||||||
* relational databases.
|
* relational databases.
|
||||||
*/
|
*/
|
||||||
suspend fun deleteUserReferences()
|
suspend fun deleteLoggedInUserReferences()
|
||||||
|
// TODO Should be refactored to fun deleteLoggedInUserReferences(): Boolean, without suspend.
|
||||||
}
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
package be.ugent.sel.studeez.domain.implementation
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
|
||||||
import be.ugent.sel.studeez.domain.SessionDAO
|
|
||||||
import com.google.firebase.firestore.CollectionReference
|
|
||||||
import com.google.firebase.firestore.FirebaseFirestore
|
|
||||||
import com.google.firebase.firestore.ktx.snapshots
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class FireBaseSessionDAO @Inject constructor(
|
|
||||||
private val firestore: FirebaseFirestore,
|
|
||||||
private val auth: AccountDAO
|
|
||||||
) : SessionDAO {
|
|
||||||
|
|
||||||
override fun getSessions(): Flow<List<SessionReport>> {
|
|
||||||
return currentUserSessionsCollection()
|
|
||||||
.snapshots()
|
|
||||||
.map { it.toObjects(SessionReport::class.java) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun saveSession(newSessionReport: SessionReport) {
|
|
||||||
currentUserSessionsCollection().add(newSessionReport)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deleteSession(newTimer: TimerInfo) {
|
|
||||||
currentUserSessionsCollection().document(newTimer.id).delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun currentUserSessionsCollection(): CollectionReference =
|
|
||||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
|
||||||
.document(auth.currentUserId)
|
|
||||||
.collection(FireBaseCollections.SESSION_COLLECTION)
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package be.ugent.sel.studeez.domain.implementation
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
|
||||||
import be.ugent.sel.studeez.domain.SubjectDAO
|
|
||||||
import com.google.firebase.firestore.CollectionReference
|
|
||||||
import com.google.firebase.firestore.FirebaseFirestore
|
|
||||||
import com.google.firebase.firestore.ktx.snapshots
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class FireBaseSubjectDAO @Inject constructor(
|
|
||||||
private val firestore: FirebaseFirestore,
|
|
||||||
private val auth: AccountDAO,
|
|
||||||
) : SubjectDAO {
|
|
||||||
override fun getSubjects(): Flow<List<Subject>> {
|
|
||||||
return currentUserSubjectsCollection()
|
|
||||||
.snapshots()
|
|
||||||
.map { it.toObjects(Subject::class.java) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun saveSubject(newSubject: Subject) {
|
|
||||||
currentUserSubjectsCollection().add(newSubject)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deleteSubject(oldSubject: Subject) {
|
|
||||||
currentUserSubjectsCollection().document(oldSubject.id).delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateSubject(newSubject: Subject) {
|
|
||||||
currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun currentUserSubjectsCollection(): CollectionReference =
|
|
||||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
|
||||||
.document(auth.currentUserId)
|
|
||||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
package be.ugent.sel.studeez.domain.implementation
|
|
||||||
|
|
||||||
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.task.TaskDocument
|
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
|
||||||
import be.ugent.sel.studeez.domain.TaskDAO
|
|
||||||
import com.google.firebase.firestore.CollectionReference
|
|
||||||
import com.google.firebase.firestore.FirebaseFirestore
|
|
||||||
import com.google.firebase.firestore.ktx.snapshots
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class FireBaseTaskDAO @Inject constructor(
|
|
||||||
private val firestore: FirebaseFirestore,
|
|
||||||
private val auth: AccountDAO,
|
|
||||||
) : TaskDAO {
|
|
||||||
override fun getTasks(subject: Subject): Flow<List<Task>> {
|
|
||||||
return selectedSubjectTasksCollection(subject.id)
|
|
||||||
.snapshots()
|
|
||||||
.map { it.toObjects(Task::class.java) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun saveTask(newTask: Task) {
|
|
||||||
selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateTask(newTask: Task) {
|
|
||||||
selectedSubjectTasksCollection(newTask.id).document(newTask.id).set(newTask)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deleteTask(oldTask: Task) {
|
|
||||||
selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toggleTaskCompleted(task: Task, completed: Boolean) {
|
|
||||||
selectedSubjectTasksCollection(task.subjectId)
|
|
||||||
.document(task.id)
|
|
||||||
.update(TaskDocument.completed, completed)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun selectedSubjectTasksCollection(subjectId: String): CollectionReference =
|
|
||||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
|
||||||
.document(auth.currentUserId)
|
|
||||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
|
||||||
.document(subjectId)
|
|
||||||
.collection(FireBaseCollections.TASK_COLLECTION)
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
package be.ugent.sel.studeez.domain.implementation
|
package be.ugent.sel.studeez.domain.implementation
|
||||||
|
|
||||||
object FireBaseCollections {
|
object FirebaseCollections {
|
||||||
const val SESSION_COLLECTION = "sessions"
|
const val SESSION_COLLECTION = "sessions"
|
||||||
const val USER_COLLECTION = "users"
|
const val USER_COLLECTION = "users"
|
||||||
|
const val FRIENDS_COLLECTION = "friends"
|
||||||
const val TIMER_COLLECTION = "timers"
|
const val TIMER_COLLECTION = "timers"
|
||||||
const val SUBJECT_COLLECTION = "subjects"
|
const val SUBJECT_COLLECTION = "subjects"
|
||||||
const val TASK_COLLECTION = "tasks"
|
const val TASK_COLLECTION = "tasks"
|
|
@ -0,0 +1,137 @@
|
||||||
|
package be.ugent.sel.studeez.domain.implementation
|
||||||
|
|
||||||
|
import android.icu.text.DateFormat
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||||
|
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.domain.*
|
||||||
|
import com.google.firebase.Timestamp
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FirebaseFeedDAO @Inject constructor(
|
||||||
|
private val friendshipDAO: FriendshipDAO,
|
||||||
|
private val sessionDAO: SessionDAO,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
|
private val subjectDAO: SubjectDAO,
|
||||||
|
private val auth: AccountDAO,
|
||||||
|
private val userDAO: UserDAO,
|
||||||
|
) : FeedDAO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a map as with key the day and value a list of feedentries for that day.
|
||||||
|
*/
|
||||||
|
override fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>> {
|
||||||
|
return sessionDAO.getSessions().map { sessionReports ->
|
||||||
|
sessionReports
|
||||||
|
.map { sessionReport -> sessionToFeedEntry(sessionReport) }
|
||||||
|
.sortedByDescending { it.endTime }
|
||||||
|
.groupBy { getFormattedTime(it) }
|
||||||
|
.mapValues { (_, entries) ->
|
||||||
|
entries
|
||||||
|
.groupBy { it.taskId }
|
||||||
|
.map { fuseFeedEntries(it.component2()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a map as with key the day and value a list of feedentries for that day.
|
||||||
|
*/
|
||||||
|
override suspend fun getFeedEntriesFromUser(id: String): Map<String, List<FeedEntry>> {
|
||||||
|
return sessionDAO.getSessionsOfUser(id)
|
||||||
|
.map { sessionReport -> sessionToFeedEntryFromUser(sessionReport, id) }
|
||||||
|
.sortedByDescending { it.endTime }
|
||||||
|
.groupBy { getFormattedTime(it) }
|
||||||
|
.mapValues { (_, entries) ->
|
||||||
|
entries
|
||||||
|
.groupBy { it.taskId }
|
||||||
|
.map { fuseFeedEntries(it.component2()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFriendsSessions(): Flow<Map<String, List<Pair<String, FeedEntry>>>> {
|
||||||
|
return friendshipDAO.getAllFriendships(auth.currentUserId)
|
||||||
|
.map { friendships ->
|
||||||
|
friendships.map { friendship ->
|
||||||
|
val userId: String = friendship.friendId
|
||||||
|
val username = userDAO.getUsername(userId)
|
||||||
|
val friendFeed = getFeedEntriesFromUser(userId)
|
||||||
|
Pair(username, friendFeed)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
mergeNameAndEntries(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mergeNameAndEntries(l: List<Pair<String, Map<String, List<FeedEntry>>>>): Map<String, List<Pair<String, FeedEntry>>> {
|
||||||
|
val new: MutableMap<String, List<Pair<String, FeedEntry>>> = mutableMapOf()
|
||||||
|
for ((name, map) in l) {
|
||||||
|
for ((day, feedEntries: List<FeedEntry>) in map) {
|
||||||
|
new[day] = new.getOrDefault(day, listOf()) + feedEntries.map { Pair(name, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFormattedTime(entry: FeedEntry): String {
|
||||||
|
return DateFormat.getDateInstance().format(entry.endTime.toDate())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Givin a list of entries referencing the same task, in the same day, fuse them into one
|
||||||
|
* feed-entry by adding the studytime and keeping the most recent end-timestamp
|
||||||
|
*/
|
||||||
|
private fun fuseFeedEntries(entries: List<FeedEntry>): FeedEntry =
|
||||||
|
entries.drop(1).fold(entries[0]) { accEntry, newEntry ->
|
||||||
|
accEntry.copy(
|
||||||
|
totalStudyTime = accEntry.totalStudyTime + newEntry.totalStudyTime,
|
||||||
|
endTime = getMostRecent(accEntry.endTime, newEntry.endTime)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMostRecent(t1: Timestamp, t2: Timestamp): Timestamp {
|
||||||
|
return if (t1 < t2) t2 else t1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a sessionReport to a feedEntry. Fetch Task and Subject to get names
|
||||||
|
*/
|
||||||
|
private suspend fun sessionToFeedEntry(sessionReport: SessionReport): FeedEntry {
|
||||||
|
val subjectId: String = sessionReport.subjectId
|
||||||
|
val taskId: String = sessionReport.taskId
|
||||||
|
|
||||||
|
val task: Task = taskDAO.getTask(subjectId, taskId)
|
||||||
|
val subject: Subject = subjectDAO.getSubject(subjectId)!!
|
||||||
|
|
||||||
|
return makeFeedEntry(sessionReport, subject, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeFeedEntry(sessionReport: SessionReport, subject: Subject, task: Task): FeedEntry {
|
||||||
|
return FeedEntry(
|
||||||
|
argb_color = subject.argb_color,
|
||||||
|
subJectName = subject.name,
|
||||||
|
taskName = task.name,
|
||||||
|
taskId = task.id,
|
||||||
|
subjectId = subject.id,
|
||||||
|
totalStudyTime = sessionReport.studyTime,
|
||||||
|
endTime = sessionReport.endTime,
|
||||||
|
isArchived = task.archived || subject.archived
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a sessionReport to a feedEntry. Fetch Task and Subject to get names
|
||||||
|
*/
|
||||||
|
private suspend fun sessionToFeedEntryFromUser(sessionReport: SessionReport, id: String): FeedEntry {
|
||||||
|
val subjectId: String = sessionReport.subjectId
|
||||||
|
val taskId: String = sessionReport.taskId
|
||||||
|
|
||||||
|
val task: Task = taskDAO.getTaskFromUser(subjectId, taskId, id)
|
||||||
|
val subject: Subject = subjectDAO.getSubjectOfUSer(subjectId, id)
|
||||||
|
|
||||||
|
return makeFeedEntry(sessionReport, subject, task)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
package be.ugent.sel.studeez.domain.implementation
|
||||||
|
|
||||||
|
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.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
|
||||||
|
import be.ugent.sel.studeez.domain.implementation.FirebaseCollections.USER_COLLECTION
|
||||||
|
import com.google.firebase.Timestamp
|
||||||
|
import com.google.firebase.firestore.DocumentReference
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore
|
||||||
|
import com.google.firebase.firestore.ktx.snapshots
|
||||||
|
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
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
class FirebaseFriendshipDAO @Inject constructor(
|
||||||
|
private val firestore: FirebaseFirestore,
|
||||||
|
private val auth: AccountDAO
|
||||||
|
): FriendshipDAO {
|
||||||
|
|
||||||
|
private fun currentUserDocument(): DocumentReference = firestore
|
||||||
|
.collection(USER_COLLECTION)
|
||||||
|
.document(auth.currentUserId)
|
||||||
|
|
||||||
|
override fun getAllFriendships(
|
||||||
|
userId: String
|
||||||
|
): Flow<List<Friendship>> {
|
||||||
|
return firestore
|
||||||
|
.collection(USER_COLLECTION)
|
||||||
|
.document(userId)
|
||||||
|
.collection(FRIENDS_COLLECTION)
|
||||||
|
.snapshots()
|
||||||
|
.map { it.toObjects(Friendship::class.java) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFriendshipCount(
|
||||||
|
userId: String
|
||||||
|
): Flow<Int> {
|
||||||
|
return flow {
|
||||||
|
val friendshipCount = suspendCoroutine { continuation ->
|
||||||
|
firestore
|
||||||
|
.collection(USER_COLLECTION)
|
||||||
|
.document(userId)
|
||||||
|
.collection(FRIENDS_COLLECTION)
|
||||||
|
.get()
|
||||||
|
.addOnSuccessListener { querySnapshot ->
|
||||||
|
continuation.resume(querySnapshot.size())
|
||||||
|
}
|
||||||
|
.addOnFailureListener { exception ->
|
||||||
|
continuation.resumeWithException(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(friendshipCount)
|
||||||
|
}.catch {
|
||||||
|
SnackbarManager.showMessage(AppText.generic_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFriendshipDetails(id: String): Friendship {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendFriendshipRequest(id: String): Boolean {
|
||||||
|
val currentUserId: String = auth.currentUserId
|
||||||
|
val otherUserId: String = id
|
||||||
|
|
||||||
|
// Check if the friendship already exists for the logged in user
|
||||||
|
var allowed = false
|
||||||
|
firestore.collection(USER_COLLECTION)
|
||||||
|
.document(currentUserId)
|
||||||
|
.collection(FRIENDS_COLLECTION)
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun acceptFriendship(id: String): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeFriendship(
|
||||||
|
friendship: Friendship
|
||||||
|
): Boolean {
|
||||||
|
val currentUserId: String = auth.currentUserId
|
||||||
|
val otherUserId: String = friendship.friendId
|
||||||
|
|
||||||
|
// Remove at logged in user
|
||||||
|
firestore.collection(USER_COLLECTION)
|
||||||
|
.document(currentUserId)
|
||||||
|
.collection(FRIENDS_COLLECTION)
|
||||||
|
.document(friendship.id)
|
||||||
|
.delete()
|
||||||
|
|
||||||
|
// Remove at other user
|
||||||
|
firestore.collection(USER_COLLECTION)
|
||||||
|
.document(otherUserId)
|
||||||
|
.collection(FRIENDS_COLLECTION)
|
||||||
|
.whereEqualTo(FRIENDID, currentUserId)
|
||||||
|
.get()
|
||||||
|
.addOnSuccessListener {
|
||||||
|
for (document in it) {
|
||||||
|
document.reference.delete()
|
||||||
|
}
|
||||||
|
}.addOnFailureListener {
|
||||||
|
SnackbarManager.showMessage(AppText.generic_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package be.ugent.sel.studeez.domain.implementation
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
||||||
|
import be.ugent.sel.studeez.data.remote.FirebaseSessionReport
|
||||||
|
import be.ugent.sel.studeez.data.remote.FirebaseSessionReport.ENDTIME
|
||||||
|
import be.ugent.sel.studeez.data.remote.FirebaseSessionReport.STUDYTIME
|
||||||
|
import be.ugent.sel.studeez.domain.*
|
||||||
|
import be.ugent.sel.studeez.domain.implementation.FirebaseCollections.SESSION_COLLECTION
|
||||||
|
import be.ugent.sel.studeez.domain.implementation.FirebaseCollections.USER_COLLECTION
|
||||||
|
import com.google.firebase.Timestamp
|
||||||
|
import com.google.firebase.firestore.CollectionReference
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore
|
||||||
|
import com.google.firebase.firestore.ktx.getField
|
||||||
|
import com.google.firebase.firestore.ktx.snapshots
|
||||||
|
import com.google.firebase.firestore.ktx.toObject
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.tasks.await
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FirebaseSessionDAO @Inject constructor(
|
||||||
|
private val firestore: FirebaseFirestore,
|
||||||
|
private val auth: AccountDAO,
|
||||||
|
) : SessionDAO {
|
||||||
|
|
||||||
|
override fun getSessions(): Flow<List<SessionReport>> {
|
||||||
|
return currentUserSessionsCollection()
|
||||||
|
.snapshots()
|
||||||
|
.map { it.toObjects(SessionReport::class.java) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getSessionsOfUser(userId: String): List<SessionReport> {
|
||||||
|
return firestore.collection(USER_COLLECTION)
|
||||||
|
.document(userId)
|
||||||
|
.collection(SESSION_COLLECTION)
|
||||||
|
.get().await()
|
||||||
|
.map { it.toObject(SessionReport::class.java) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun saveSession(newSessionReport: SessionReport) {
|
||||||
|
currentUserSessionsCollection().add(newSessionReport)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteSession(newTimer: TimerInfo) {
|
||||||
|
currentUserSessionsCollection().document(newTimer.id).delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun currentUserSessionsCollection(): CollectionReference =
|
||||||
|
firestore.collection(USER_COLLECTION)
|
||||||
|
.document(auth.currentUserId)
|
||||||
|
.collection(SESSION_COLLECTION)
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package be.ugent.sel.studeez.domain.implementation
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.SubjectDocument
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.TaskDocument
|
||||||
|
import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
|
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||||
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
|
import com.google.firebase.firestore.CollectionReference
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore
|
||||||
|
import com.google.firebase.firestore.Query
|
||||||
|
import com.google.firebase.firestore.ktx.snapshots
|
||||||
|
import com.google.firebase.firestore.ktx.toObject
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.tasks.await
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.collections.count
|
||||||
|
|
||||||
|
class FirebaseSubjectDAO @Inject constructor(
|
||||||
|
private val firestore: FirebaseFirestore,
|
||||||
|
private val auth: AccountDAO,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
|
) : SubjectDAO {
|
||||||
|
override fun getSubjects(): Flow<List<Subject>> {
|
||||||
|
return currentUserSubjectsCollection()
|
||||||
|
.subjectNotArchived()
|
||||||
|
.snapshots()
|
||||||
|
.map { it.toObjects(Subject::class.java) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getSubject(subjectId: String): Subject? {
|
||||||
|
return currentUserSubjectsCollection().document(subjectId).get().await().toObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getSubjectOfUSer(subjectId: String, userId: String): Subject {
|
||||||
|
return currentUserSubjectsCollection(userId).document(subjectId).get().await().toObject()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveSubject(newSubject: Subject) {
|
||||||
|
currentUserSubjectsCollection().add(newSubject)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteSubject(oldSubject: Subject) {
|
||||||
|
currentUserSubjectsCollection().document(oldSubject.id).delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateSubject(newSubject: Subject) {
|
||||||
|
currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun archiveSubject(subject: Subject) {
|
||||||
|
currentUserSubjectsCollection().document(subject.id).update(SubjectDocument.archived, true)
|
||||||
|
currentUserSubjectsCollection().document(subject.id)
|
||||||
|
.collection(FirebaseCollections.TASK_COLLECTION)
|
||||||
|
.taskNotArchived()
|
||||||
|
.get().await()
|
||||||
|
.documents
|
||||||
|
.forEach {
|
||||||
|
it.reference.update(TaskDocument.archived, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTaskCount(subject: Subject): Flow<Int> {
|
||||||
|
return taskDAO.getTasks(subject)
|
||||||
|
.map(List<Task>::count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCompletedTaskCount(subject: Subject): Flow<Int> {
|
||||||
|
return taskDAO.getTasks(subject)
|
||||||
|
.map { tasks -> tasks.count { it.completed && !it.archived } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStudyTime(subject: Subject): Flow<Int> {
|
||||||
|
return taskDAO.getTasks(subject)
|
||||||
|
.map { tasks -> tasks.sumOf { it.time } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun currentUserSubjectsCollection(id: String = auth.currentUserId): CollectionReference =
|
||||||
|
firestore.collection(FirebaseCollections.USER_COLLECTION)
|
||||||
|
.document(id)
|
||||||
|
.collection(FirebaseCollections.SUBJECT_COLLECTION)
|
||||||
|
|
||||||
|
private fun subjectTasksCollection(subject: Subject, id: String = auth.currentUserId): CollectionReference =
|
||||||
|
firestore.collection(FirebaseCollections.USER_COLLECTION)
|
||||||
|
.document(id)
|
||||||
|
.collection(FirebaseCollections.SUBJECT_COLLECTION)
|
||||||
|
.document(subject.id)
|
||||||
|
.collection(FirebaseCollections.TASK_COLLECTION)
|
||||||
|
|
||||||
|
fun CollectionReference.subjectNotArchived(): Query =
|
||||||
|
this.whereEqualTo(SubjectDocument.archived, false)
|
||||||
|
|
||||||
|
fun Query.subjectNotArchived(): Query =
|
||||||
|
this.whereEqualTo(SubjectDocument.archived, false)
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package be.ugent.sel.studeez.domain.implementation
|
||||||
|
|
||||||
|
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.task.TaskDocument
|
||||||
|
import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
|
import com.google.firebase.firestore.CollectionReference
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore
|
||||||
|
import com.google.firebase.firestore.Query
|
||||||
|
import com.google.firebase.firestore.ktx.snapshots
|
||||||
|
import com.google.firebase.firestore.ktx.toObject
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.tasks.await
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FirebaseTaskDAO @Inject constructor(
|
||||||
|
private val firestore: FirebaseFirestore,
|
||||||
|
private val auth: AccountDAO,
|
||||||
|
) : TaskDAO {
|
||||||
|
override fun getTasks(subject: Subject): Flow<List<Task>> {
|
||||||
|
return selectedSubjectTasksCollection(subject.id)
|
||||||
|
.taskNotArchived()
|
||||||
|
.snapshots()
|
||||||
|
.map { it.toObjects(Task::class.java) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getTask(subjectId: String, taskId: String): Task {
|
||||||
|
return selectedSubjectTasksCollection(subjectId).document(taskId).get().await().toObject()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getTaskFromUser(subjectId: String, taskId: String, userId: String): Task {
|
||||||
|
return selectedSubjectTasksCollection(subjectId, userId)
|
||||||
|
.document(taskId)
|
||||||
|
.get()
|
||||||
|
.await().toObject(Task::class.java)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveTask(newTask: Task) {
|
||||||
|
selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateTask(newTask: Task) {
|
||||||
|
selectedSubjectTasksCollection(newTask.subjectId)
|
||||||
|
.document(newTask.id)
|
||||||
|
.set(newTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteTask(oldTask: Task) {
|
||||||
|
selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectedSubjectTasksCollection(subjectId: String, id: String = auth.currentUserId): CollectionReference =
|
||||||
|
firestore.collection(FirebaseCollections.USER_COLLECTION)
|
||||||
|
.document(id)
|
||||||
|
.collection(FirebaseCollections.SUBJECT_COLLECTION)
|
||||||
|
.document(subjectId)
|
||||||
|
.collection(FirebaseCollections.TASK_COLLECTION)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend CollectionReference and Query with some filters
|
||||||
|
|
||||||
|
fun CollectionReference.taskNotArchived(): Query =
|
||||||
|
this.whereEqualTo(TaskDocument.archived, false)
|
||||||
|
|
||||||
|
fun Query.taskNotArchived(): Query =
|
||||||
|
this.whereEqualTo(TaskDocument.archived, false)
|
||||||
|
|
||||||
|
fun CollectionReference.taskNotCompleted(): Query =
|
||||||
|
this.whereEqualTo(TaskDocument.completed, true)
|
||||||
|
|
||||||
|
fun Query.taskNotCompleted(): Query =
|
||||||
|
this.whereEqualTo(TaskDocument.completed, true)
|
|
@ -48,8 +48,8 @@ class FirebaseTimerDAO @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun currentUserTimersCollection(): CollectionReference =
|
private fun currentUserTimersCollection(): CollectionReference =
|
||||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
firestore.collection(FirebaseCollections.USER_COLLECTION)
|
||||||
.document(auth.currentUserId)
|
.document(auth.currentUserId)
|
||||||
.collection(FireBaseCollections.TIMER_COLLECTION)
|
.collection(FirebaseCollections.TIMER_COLLECTION)
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,34 +2,91 @@ package be.ugent.sel.studeez.domain.implementation
|
||||||
|
|
||||||
import be.ugent.sel.studeez.R
|
import be.ugent.sel.studeez.R
|
||||||
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
|
import be.ugent.sel.studeez.data.remote.FirebaseUser.BIOGRAPHY
|
||||||
|
import be.ugent.sel.studeez.data.remote.FirebaseUser.USERNAME
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
import be.ugent.sel.studeez.domain.UserDAO
|
import be.ugent.sel.studeez.domain.UserDAO
|
||||||
|
import be.ugent.sel.studeez.domain.implementation.FirebaseCollections.USER_COLLECTION
|
||||||
import com.google.firebase.firestore.DocumentReference
|
import com.google.firebase.firestore.DocumentReference
|
||||||
import com.google.firebase.firestore.FirebaseFirestore
|
import com.google.firebase.firestore.FirebaseFirestore
|
||||||
|
import com.google.firebase.firestore.ktx.snapshots
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.tasks.await
|
import kotlinx.coroutines.tasks.await
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class FirebaseUserDAO @Inject constructor(
|
class FirebaseUserDAO @Inject constructor(
|
||||||
private val firestore: FirebaseFirestore,
|
private val firestore: FirebaseFirestore,
|
||||||
private val auth: AccountDAO
|
private val auth: AccountDAO
|
||||||
) : UserDAO {
|
) : UserDAO {
|
||||||
|
|
||||||
override suspend fun getUsername(): String? {
|
override fun getCurrentUserId(): String {
|
||||||
return currentUserDocument().get().await().getString("username")
|
return auth.currentUserId
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun save(newUsername: String) {
|
|
||||||
currentUserDocument().set(mapOf("username" to newUsername))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun currentUserDocument(): DocumentReference =
|
private fun currentUserDocument(): DocumentReference =
|
||||||
firestore.collection(USER_COLLECTION).document(auth.currentUserId)
|
firestore
|
||||||
|
.collection(USER_COLLECTION)
|
||||||
|
.document(auth.currentUserId)
|
||||||
|
|
||||||
companion object {
|
override fun getAllUsers(): Flow<List<User>> {
|
||||||
private const val USER_COLLECTION = "users"
|
return firestore
|
||||||
|
.collection(USER_COLLECTION)
|
||||||
|
.snapshots()
|
||||||
|
.map { it.toObjects(User::class.java) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteUserReferences() {
|
override fun getUsersWithQuery(
|
||||||
|
fieldName: String,
|
||||||
|
value: String
|
||||||
|
): Flow<List<User>> {
|
||||||
|
return firestore
|
||||||
|
.collection(USER_COLLECTION)
|
||||||
|
.whereEqualTo(fieldName, value)
|
||||||
|
.snapshots()
|
||||||
|
.map { it.toObjects(User::class.java) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserDetails(userId: String): Flow<User> {
|
||||||
|
return flow {
|
||||||
|
val snapshot = firestore
|
||||||
|
.collection(USER_COLLECTION)
|
||||||
|
.document(userId)
|
||||||
|
.get()
|
||||||
|
.await()
|
||||||
|
val user = snapshot.toObject(User::class.java)!!
|
||||||
|
emit(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUsername(userId: String): String {
|
||||||
|
val user = firestore.collection(USER_COLLECTION)
|
||||||
|
.document(userId)
|
||||||
|
.get().await()
|
||||||
|
return user.getString(USERNAME)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getLoggedInUser(): User {
|
||||||
|
val userDocument = currentUserDocument().get().await()
|
||||||
|
return User(
|
||||||
|
username = userDocument.getString(USERNAME) ?: "",
|
||||||
|
biography = userDocument.getString(BIOGRAPHY) ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun saveLoggedInUser(
|
||||||
|
newUsername: String,
|
||||||
|
newBiography: String
|
||||||
|
) {
|
||||||
|
currentUserDocument().set(mapOf(
|
||||||
|
USERNAME to newUsername,
|
||||||
|
BIOGRAPHY to newBiography
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteLoggedInUserReferences() {
|
||||||
currentUserDocument().delete()
|
currentUserDocument().delete()
|
||||||
.addOnSuccessListener { SnackbarManager.showMessage(R.string.success) }
|
.addOnSuccessListener { SnackbarManager.showMessage(R.string.success) }
|
||||||
.addOnFailureListener { SnackbarManager.showMessage(R.string.generic_error) }
|
.addOnFailureListener { SnackbarManager.showMessage(R.string.generic_error) }
|
||||||
|
|
|
@ -4,7 +4,7 @@ object StudeezDestinations {
|
||||||
// NavBar
|
// NavBar
|
||||||
const val HOME_SCREEN = "home"
|
const val HOME_SCREEN = "home"
|
||||||
const val SUBJECT_SCREEN = "subjects"
|
const val SUBJECT_SCREEN = "subjects"
|
||||||
const val SESSIONS_SCREEN = "sessions"
|
const val FRIENDS_FEED = "friends_feed"
|
||||||
const val PROFILE_SCREEN = "profile"
|
const val PROFILE_SCREEN = "profile"
|
||||||
|
|
||||||
// Drawer
|
// Drawer
|
||||||
|
@ -27,10 +27,13 @@ object StudeezDestinations {
|
||||||
const val EDIT_SUBJECT_FORM = "edit_subject"
|
const val EDIT_SUBJECT_FORM = "edit_subject"
|
||||||
const val TASKS_SCREEN = "tasks"
|
const val TASKS_SCREEN = "tasks"
|
||||||
const val ADD_TASK_FORM = "add_task"
|
const val ADD_TASK_FORM = "add_task"
|
||||||
|
const val SELECT_SUBJECT = "select_subject"
|
||||||
const val EDIT_TASK_FORM = "edit_task"
|
const val EDIT_TASK_FORM = "edit_task"
|
||||||
|
|
||||||
// Friends flow
|
// Friends flow
|
||||||
|
const val FRIENDS_OVERVIEW_SCREEN = "friends_overview"
|
||||||
const val SEARCH_FRIENDS_SCREEN = "search_friends"
|
const val SEARCH_FRIENDS_SCREEN = "search_friends"
|
||||||
|
const val PUBLIC_PROFILE_SCREEN = "public_profile"
|
||||||
|
|
||||||
// Create & edit screens
|
// Create & edit screens
|
||||||
const val CREATE_TASK_SCREEN = "create_task"
|
const val CREATE_TASK_SCREEN = "create_task"
|
||||||
|
|
|
@ -14,22 +14,26 @@ import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions
|
||||||
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
||||||
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel
|
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel
|
||||||
import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions
|
import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions
|
||||||
|
import be.ugent.sel.studeez.screens.friends.friends_overview.FriendsOveriewRoute
|
||||||
|
import be.ugent.sel.studeez.screens.friends.friends_search.SearchFriendsRoute
|
||||||
|
import be.ugent.sel.studeez.screens.friends_feed.FriendsFeedRoute
|
||||||
import be.ugent.sel.studeez.screens.home.HomeRoute
|
import be.ugent.sel.studeez.screens.home.HomeRoute
|
||||||
import be.ugent.sel.studeez.screens.log_in.LoginRoute
|
import be.ugent.sel.studeez.screens.log_in.LoginRoute
|
||||||
import be.ugent.sel.studeez.screens.profile.EditProfileRoute
|
|
||||||
import be.ugent.sel.studeez.screens.profile.ProfileRoute
|
import be.ugent.sel.studeez.screens.profile.ProfileRoute
|
||||||
|
import be.ugent.sel.studeez.screens.profile.edit_profile.EditProfileRoute
|
||||||
|
import be.ugent.sel.studeez.screens.profile.public_profile.PublicProfileRoute
|
||||||
import be.ugent.sel.studeez.screens.session.SessionRoute
|
import be.ugent.sel.studeez.screens.session.SessionRoute
|
||||||
import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute
|
import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute
|
||||||
import be.ugent.sel.studeez.screens.sessions.SessionsRoute
|
|
||||||
import be.ugent.sel.studeez.screens.settings.SettingsRoute
|
import be.ugent.sel.studeez.screens.settings.SettingsRoute
|
||||||
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
|
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
|
||||||
import be.ugent.sel.studeez.screens.splash.SplashRoute
|
import be.ugent.sel.studeez.screens.splash.SplashRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.SubjectRoute
|
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.TaskRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute
|
import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute
|
import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute
|
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute
|
|
||||||
import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute
|
import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute
|
||||||
import be.ugent.sel.studeez.screens.timer_form.TimerEditRoute
|
import be.ugent.sel.studeez.screens.timer_form.TimerEditRoute
|
||||||
import be.ugent.sel.studeez.screens.timer_form.timer_type_select.TimerTypeSelectScreen
|
import be.ugent.sel.studeez.screens.timer_form.timer_type_select.TimerTypeSelectScreen
|
||||||
|
@ -51,6 +55,7 @@ fun StudeezNavGraph(
|
||||||
val open: (String) -> Unit = { appState.navigate(it) }
|
val open: (String) -> Unit = { appState.navigate(it) }
|
||||||
val openAndPopUp: (String, String) -> Unit =
|
val openAndPopUp: (String, String) -> Unit =
|
||||||
{ route, popUp -> appState.navigateAndPopUp(route, popUp) }
|
{ route, popUp -> appState.navigateAndPopUp(route, popUp) }
|
||||||
|
val clearAndNavigate: (route: String) -> Unit = { route -> appState.clearAndNavigate(route) }
|
||||||
|
|
||||||
val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp)
|
val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp)
|
||||||
val navigationBarActions: NavigationBarActions =
|
val navigationBarActions: NavigationBarActions =
|
||||||
|
@ -64,10 +69,11 @@ fun StudeezNavGraph(
|
||||||
// NavBar
|
// NavBar
|
||||||
composable(StudeezDestinations.HOME_SCREEN) {
|
composable(StudeezDestinations.HOME_SCREEN) {
|
||||||
HomeRoute(
|
HomeRoute(
|
||||||
open,
|
open = open,
|
||||||
viewModel = hiltViewModel(),
|
|
||||||
drawerActions = drawerActions,
|
drawerActions = drawerActions,
|
||||||
navigationBarActions = navigationBarActions
|
navigationBarActions = navigationBarActions,
|
||||||
|
feedViewModel = hiltViewModel(),
|
||||||
|
viewModel = hiltViewModel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,8 +86,16 @@ fun StudeezNavGraph(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(StudeezDestinations.SELECT_SUBJECT) {
|
||||||
|
SubjectSelectionRoute(
|
||||||
|
open = { openAndPopUp(it, StudeezDestinations.SELECT_SUBJECT) },
|
||||||
|
goBack = goBack,
|
||||||
|
viewModel = hiltViewModel(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
composable(StudeezDestinations.ADD_SUBJECT_FORM) {
|
composable(StudeezDestinations.ADD_SUBJECT_FORM) {
|
||||||
SubjectAddRoute(
|
SubjectCreateRoute(
|
||||||
goBack = goBack,
|
goBack = goBack,
|
||||||
openAndPopUp = openAndPopUp,
|
openAndPopUp = openAndPopUp,
|
||||||
viewModel = hiltViewModel(),
|
viewModel = hiltViewModel(),
|
||||||
|
@ -98,14 +112,14 @@ fun StudeezNavGraph(
|
||||||
|
|
||||||
composable(StudeezDestinations.TASKS_SCREEN) {
|
composable(StudeezDestinations.TASKS_SCREEN) {
|
||||||
TaskRoute(
|
TaskRoute(
|
||||||
goBack = goBack,
|
goBack = { openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.TASKS_SCREEN) },
|
||||||
open = open,
|
open = open,
|
||||||
viewModel = hiltViewModel(),
|
viewModel = hiltViewModel(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(StudeezDestinations.ADD_TASK_FORM) {
|
composable(StudeezDestinations.ADD_TASK_FORM) {
|
||||||
TaskAddRoute(
|
TaskCreateRoute(
|
||||||
goBack = goBack,
|
goBack = goBack,
|
||||||
openAndPopUp = openAndPopUp,
|
openAndPopUp = openAndPopUp,
|
||||||
viewModel = hiltViewModel(),
|
viewModel = hiltViewModel(),
|
||||||
|
@ -121,10 +135,11 @@ fun StudeezNavGraph(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
composable(StudeezDestinations.SESSIONS_SCREEN) {
|
composable(StudeezDestinations.FRIENDS_FEED) {
|
||||||
SessionsRoute(
|
FriendsFeedRoute(
|
||||||
drawerActions = drawerActions,
|
drawerActions = drawerActions,
|
||||||
navigationBarActions = navigationBarActions
|
navigationBarActions = navigationBarActions,
|
||||||
|
viewModel = hiltViewModel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +215,7 @@ fun StudeezNavGraph(
|
||||||
|
|
||||||
composable(StudeezDestinations.SESSION_RECAP) {
|
composable(StudeezDestinations.SESSION_RECAP) {
|
||||||
SessionRecapRoute(
|
SessionRecapRoute(
|
||||||
openAndPopUp = openAndPopUp,
|
clearAndNavigate = clearAndNavigate,
|
||||||
viewModel = hiltViewModel()
|
viewModel = hiltViewModel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -220,8 +235,28 @@ fun StudeezNavGraph(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Friends flow
|
// Friends flow
|
||||||
|
composable(StudeezDestinations.FRIENDS_OVERVIEW_SCREEN) {
|
||||||
|
FriendsOveriewRoute(
|
||||||
|
open = open,
|
||||||
|
popUp = goBack,
|
||||||
|
viewModel = hiltViewModel()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) {
|
composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) {
|
||||||
// TODO
|
SearchFriendsRoute(
|
||||||
|
popUp = goBack,
|
||||||
|
open = open,
|
||||||
|
viewModel = hiltViewModel()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(StudeezDestinations.PUBLIC_PROFILE_SCREEN) {
|
||||||
|
PublicProfileRoute(
|
||||||
|
popUp = goBack,
|
||||||
|
open = open,
|
||||||
|
viewModel = hiltViewModel()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create & edit screens
|
// Create & edit screens
|
||||||
|
|
|
@ -0,0 +1,308 @@
|
||||||
|
package be.ugent.sel.studeez.screens.friends.friends_overview
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
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
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
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.drawer.DrawerEntry
|
||||||
|
import be.ugent.sel.studeez.common.ext.basicButton
|
||||||
|
import be.ugent.sel.studeez.data.local.models.Friendship
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||||
|
import com.google.firebase.Timestamp
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
data class FriendsOverviewActions(
|
||||||
|
val getFriendsFlow: () -> Flow<List<Pair<User, Friendship>>>,
|
||||||
|
val searchFriends: () -> Unit,
|
||||||
|
val onQueryStringChange: (String) -> Unit,
|
||||||
|
val onSubmit: () -> Unit,
|
||||||
|
val viewProfile: (String) -> Unit,
|
||||||
|
val removeFriend: (Friendship) -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFriendsOverviewActions(
|
||||||
|
viewModel: FriendsOverviewViewModel,
|
||||||
|
open: (String) -> Unit
|
||||||
|
): FriendsOverviewActions {
|
||||||
|
return FriendsOverviewActions(
|
||||||
|
getFriendsFlow = viewModel::getAllFriends,
|
||||||
|
searchFriends = { viewModel.searchFriends(open) },
|
||||||
|
onQueryStringChange = viewModel::onQueryStringChange,
|
||||||
|
onSubmit = { viewModel.onSubmit(open) },
|
||||||
|
viewProfile = { userId ->
|
||||||
|
viewModel.viewProfile(userId, open)
|
||||||
|
},
|
||||||
|
removeFriend = viewModel::removeFriend
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FriendsOveriewRoute(
|
||||||
|
open: (String) -> Unit,
|
||||||
|
popUp: () -> Unit,
|
||||||
|
viewModel: FriendsOverviewViewModel
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState
|
||||||
|
FriendsOverviewScreen(
|
||||||
|
popUp = popUp,
|
||||||
|
uiState = uiState,
|
||||||
|
friendsOverviewActions = getFriendsOverviewActions(
|
||||||
|
viewModel = viewModel,
|
||||||
|
open = open
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FriendsOverviewScreen(
|
||||||
|
popUp: () -> Unit,
|
||||||
|
uiState: FriendsOverviewUiState,
|
||||||
|
friendsOverviewActions: FriendsOverviewActions
|
||||||
|
) {
|
||||||
|
val friends = friendsOverviewActions.getFriendsFlow().collectAsState(initial = emptyList())
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
// 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) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = resources().getString(R.string.go_back)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO Add inbox action
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn (
|
||||||
|
modifier = Modifier.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
if (friends.value.isEmpty()) {
|
||||||
|
// Show a quick button to search friends when the user does not have any friends yet.
|
||||||
|
item {
|
||||||
|
BasicButton(
|
||||||
|
text = AppText.no_friends,
|
||||||
|
modifier = Modifier.basicButton()
|
||||||
|
) {
|
||||||
|
friendsOverviewActions.searchFriends()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(friends.value) { friend ->
|
||||||
|
FriendsEntry(
|
||||||
|
user = friend.first,
|
||||||
|
friendship = friend.second,
|
||||||
|
viewProfile = { userId -> friendsOverviewActions.viewProfile(userId) },
|
||||||
|
removeFriend = friendsOverviewActions.removeFriend
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FriendsOverviewPreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
FriendsOverviewScreen(
|
||||||
|
popUp = {},
|
||||||
|
uiState = FriendsOverviewUiState(""),
|
||||||
|
friendsOverviewActions = FriendsOverviewActions(
|
||||||
|
getFriendsFlow = { emptyFlow() },
|
||||||
|
searchFriends = {},
|
||||||
|
onQueryStringChange = {},
|
||||||
|
onSubmit = {},
|
||||||
|
viewProfile = {},
|
||||||
|
removeFriend = {}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FriendsEntry(
|
||||||
|
user: User,
|
||||||
|
friendship: Friendship,
|
||||||
|
viewProfile: (String) -> Unit,
|
||||||
|
removeFriend: (Friendship) -> Unit
|
||||||
|
) {
|
||||||
|
Card {
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 15.dp, vertical = 7.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(15.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
ProfilePicture()
|
||||||
|
}
|
||||||
|
|
||||||
|
Box (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FriendsEntryPreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
FriendsEntry(
|
||||||
|
user = User(
|
||||||
|
id = "",
|
||||||
|
username = "Tibo De Peuter",
|
||||||
|
biography = "short bio"
|
||||||
|
),
|
||||||
|
friendship = Friendship(
|
||||||
|
id = "",
|
||||||
|
friendId = "someId",
|
||||||
|
friendsSince = Timestamp.now(),
|
||||||
|
accepted = true
|
||||||
|
),
|
||||||
|
viewProfile = {},
|
||||||
|
removeFriend = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FriendsOverviewDropDown(
|
||||||
|
friendship: Friendship,
|
||||||
|
viewProfile: (String) -> Unit,
|
||||||
|
removeFriend: (Friendship) -> Unit
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = { expanded = true }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = R.drawable.ic_more_horizontal),
|
||||||
|
contentDescription = resources().getString(AppText.view_more),
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false }
|
||||||
|
) {
|
||||||
|
DrawerEntry(
|
||||||
|
icon = Icons.Default.Person,
|
||||||
|
text = stringResource(id = AppText.show_profile)
|
||||||
|
) {
|
||||||
|
viewProfile(friendship.friendId)
|
||||||
|
}
|
||||||
|
DrawerEntry(
|
||||||
|
icon = Icons.Default.Delete,
|
||||||
|
text = stringResource(id = AppText.remove_friend)
|
||||||
|
) {
|
||||||
|
removeFriend(friendship)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FriendsOverviewDropDownPreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
FriendsOverviewDropDown(
|
||||||
|
friendship = Friendship(
|
||||||
|
id = "",
|
||||||
|
friendId = "someId",
|
||||||
|
friendsSince = Timestamp.now(),
|
||||||
|
accepted = true
|
||||||
|
),
|
||||||
|
viewProfile = {},
|
||||||
|
removeFriend = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package be.ugent.sel.studeez.screens.friends.friends_overview
|
||||||
|
|
||||||
|
data class FriendsOverviewUiState(
|
||||||
|
val userId: String,
|
||||||
|
val queryString: String = ""
|
||||||
|
)
|
|
@ -0,0 +1,78 @@
|
||||||
|
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
|
||||||
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flatMapConcat
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class FriendsOverviewViewModel @Inject constructor(
|
||||||
|
private val userDAO: UserDAO,
|
||||||
|
private val friendshipDAO: FriendshipDAO,
|
||||||
|
private val selectedUserIdState: SelectedUserId,
|
||||||
|
logService: LogService
|
||||||
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
|
var uiState = mutableStateOf(FriendsOverviewUiState(
|
||||||
|
userId = selectedUserIdState.value
|
||||||
|
))
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun getAllFriends(): Flow<List<Pair<User, Friendship>>> {
|
||||||
|
return friendshipDAO.getAllFriendships(
|
||||||
|
userId = uiState.value.userId
|
||||||
|
)
|
||||||
|
.flatMapConcat { friendships ->
|
||||||
|
val userFlows = friendships.map { friendship ->
|
||||||
|
userDAO.getUserDetails(friendship.friendId)
|
||||||
|
}
|
||||||
|
combine(userFlows) { users ->
|
||||||
|
friendships.zip(users) { friendship, user ->
|
||||||
|
Pair(user, friendship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchFriends(open: (String) -> Unit) {
|
||||||
|
open(StudeezDestinations.SEARCH_FRIENDS_SCREEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onQueryStringChange(newValue: String) {
|
||||||
|
uiState.value = uiState.value.copy(
|
||||||
|
queryString = newValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSubmit(open: (String) -> Unit) {
|
||||||
|
val query = uiState.value.queryString // TODO Pass as argument
|
||||||
|
open(StudeezDestinations.SEARCH_FRIENDS_SCREEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun viewProfile(
|
||||||
|
userId: String,
|
||||||
|
open: (String) -> Unit
|
||||||
|
) {
|
||||||
|
selectedUserIdState.value = userId
|
||||||
|
open(StudeezDestinations.PUBLIC_PROFILE_SCREEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFriend(
|
||||||
|
friendship: Friendship
|
||||||
|
) {
|
||||||
|
friendshipDAO.removeFriendship(
|
||||||
|
friendship = friendship
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package be.ugent.sel.studeez.screens.friends.friends_search
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
|
||||||
|
data class SearchFriendUiState(
|
||||||
|
val queryString: String = "",
|
||||||
|
val searchResults: Flow<List<User>> = emptyFlow()
|
||||||
|
)
|
|
@ -0,0 +1,264 @@
|
||||||
|
package be.ugent.sel.studeez.screens.friends.friends_search
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
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.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import be.ugent.sel.studeez.R
|
||||||
|
import be.ugent.sel.studeez.common.composable.ProfilePicture
|
||||||
|
import be.ugent.sel.studeez.common.composable.drawer.DrawerEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
data class SearchFriendsActions(
|
||||||
|
val onQueryStringChange: (String) -> Unit,
|
||||||
|
val getUsersWithUsername: (String) -> Unit,
|
||||||
|
val getAllUsers: () -> Flow<List<User>>,
|
||||||
|
val goToProfile: (String) -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getSearchFriendsActions(
|
||||||
|
viewModel: SearchFriendsViewModel,
|
||||||
|
open: (String) -> Unit
|
||||||
|
): SearchFriendsActions {
|
||||||
|
return SearchFriendsActions(
|
||||||
|
onQueryStringChange = viewModel::onQueryStringChange,
|
||||||
|
getUsersWithUsername = viewModel::getUsersWithUsername,
|
||||||
|
getAllUsers = { viewModel.getAllUsers() },
|
||||||
|
goToProfile = { userId -> viewModel.goToProfile(userId, open) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchFriendsRoute(
|
||||||
|
popUp: () -> Unit,
|
||||||
|
open: (String) -> Unit,
|
||||||
|
viewModel: SearchFriendsViewModel
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState
|
||||||
|
|
||||||
|
SearchFriendsScreen(
|
||||||
|
popUp = popUp,
|
||||||
|
uiState = uiState,
|
||||||
|
searchFriendsActions = getSearchFriendsActions(
|
||||||
|
viewModel = viewModel,
|
||||||
|
open = open
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchFriendsScreen(
|
||||||
|
popUp: () -> Unit,
|
||||||
|
uiState: SearchFriendUiState,
|
||||||
|
searchFriendsActions: SearchFriendsActions
|
||||||
|
) {
|
||||||
|
var query by remember { mutableStateOf(uiState.queryString) }
|
||||||
|
val searchResults = searchFriendsActions.getAllUsers().collectAsState(
|
||||||
|
initial = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
// 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 = {
|
||||||
|
IconButton(onClick = popUp) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = resources().getString(R.string.go_back)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
items (searchResults.value) { user ->
|
||||||
|
UserEntry(
|
||||||
|
user = user,
|
||||||
|
goToProfile = searchFriendsActions.goToProfile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SearchFriendsPreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
SearchFriendsScreen(
|
||||||
|
popUp = {},
|
||||||
|
uiState = SearchFriendUiState(
|
||||||
|
queryString = "dit is een test",
|
||||||
|
searchResults = flowOf(listOf(User(
|
||||||
|
id = "someid",
|
||||||
|
username = "Eerste user",
|
||||||
|
biography = "blah blah blah"
|
||||||
|
)))
|
||||||
|
),
|
||||||
|
searchFriendsActions = SearchFriendsActions(
|
||||||
|
onQueryStringChange = {},
|
||||||
|
getUsersWithUsername = {},
|
||||||
|
getAllUsers = {
|
||||||
|
flowOf(listOf(User(
|
||||||
|
id = "someid",
|
||||||
|
username = "Eerste user",
|
||||||
|
biography = "blah blah blah"
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
goToProfile = { }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UserEntry(
|
||||||
|
user: User,
|
||||||
|
goToProfile: (String) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 15.dp, vertical = 7.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(15.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
ProfilePicture()
|
||||||
|
}
|
||||||
|
|
||||||
|
Box (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
SearchFriendsDropDown(
|
||||||
|
user = user,
|
||||||
|
goToProfile = goToProfile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun UserEntryPreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
UserEntry(
|
||||||
|
user = User(
|
||||||
|
id = "someid",
|
||||||
|
username = "Eerste user",
|
||||||
|
biography = "blah blah blah"
|
||||||
|
),
|
||||||
|
goToProfile = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Three dots that open a dropdown menu that allow to go the users profile.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun SearchFriendsDropDown(
|
||||||
|
user: User,
|
||||||
|
goToProfile: (String) -> Unit
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = { expanded = true }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = R.drawable.ic_more_horizontal),
|
||||||
|
contentDescription = stringResource(AppText.view_more),
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(onClick = { expanded = false }) {
|
||||||
|
DrawerEntry(
|
||||||
|
icon = Icons.Default.Person,
|
||||||
|
text = stringResource(id = AppText.show_profile)
|
||||||
|
) {
|
||||||
|
goToProfile(user.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SearchFriendsDropDownPreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
SearchFriendsDropDown(
|
||||||
|
user = User(
|
||||||
|
id = "someid",
|
||||||
|
username = "Eerste user",
|
||||||
|
biography = "blah blah blah"
|
||||||
|
),
|
||||||
|
goToProfile = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SearchFriendsViewModel @Inject constructor(
|
||||||
|
private val userDAO: UserDAO,
|
||||||
|
private val selectedProfileState: SelectedUserId,
|
||||||
|
logService: LogService
|
||||||
|
): StudeezViewModel(logService) {
|
||||||
|
|
||||||
|
var uiState = mutableStateOf(SearchFriendUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun onQueryStringChange(newValue: String) {
|
||||||
|
uiState.value = uiState.value.copy(
|
||||||
|
queryString = newValue
|
||||||
|
)
|
||||||
|
uiState.value = uiState.value.copy(
|
||||||
|
searchResults = userDAO.getUsersWithQuery(
|
||||||
|
fieldName = FirebaseUser.USERNAME,
|
||||||
|
value = uiState.value.queryString
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUsersWithUsername(
|
||||||
|
value: String
|
||||||
|
): Flow<List<User>> {
|
||||||
|
return userDAO.getUsersWithQuery(
|
||||||
|
fieldName = FirebaseUser.USERNAME,
|
||||||
|
value = value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all users, except for the current user.
|
||||||
|
*/
|
||||||
|
fun getAllUsers(): Flow<List<User>> {
|
||||||
|
return userDAO.getAllUsers()
|
||||||
|
.map { users ->
|
||||||
|
users.filter { user ->
|
||||||
|
user.id != userDAO.getCurrentUserId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun goToProfile(
|
||||||
|
userId: String,
|
||||||
|
open: (String) -> Unit
|
||||||
|
) {
|
||||||
|
selectedProfileState.value = userId
|
||||||
|
open(StudeezDestinations.PUBLIC_PROFILE_SCREEN)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,6 @@ import androidx.compose.material.Card
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
@ -20,7 +19,6 @@ import androidx.compose.ui.unit.dp
|
||||||
import be.ugent.sel.studeez.common.composable.DateText
|
import be.ugent.sel.studeez.common.composable.DateText
|
||||||
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
||||||
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
||||||
import be.ugent.sel.studeez.common.composable.feed.LoadingFeed
|
|
||||||
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
||||||
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
|
@ -33,11 +31,10 @@ fun FriendsFeedRoute(
|
||||||
drawerActions: DrawerActions,
|
drawerActions: DrawerActions,
|
||||||
navigationBarActions: NavigationBarActions
|
navigationBarActions: NavigationBarActions
|
||||||
) {
|
) {
|
||||||
val friendsFeedUiState by viewModel.uiState.collectAsState()
|
|
||||||
FriendsFeedScreen(
|
FriendsFeedScreen(
|
||||||
drawerActions = drawerActions,
|
drawerActions = drawerActions,
|
||||||
navigationBarActions = navigationBarActions,
|
navigationBarActions = navigationBarActions,
|
||||||
uiState = friendsFeedUiState,
|
viewModel = viewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,20 +42,22 @@ fun FriendsFeedRoute(
|
||||||
fun FriendsFeedScreen(
|
fun FriendsFeedScreen(
|
||||||
drawerActions: DrawerActions,
|
drawerActions: DrawerActions,
|
||||||
navigationBarActions: NavigationBarActions,
|
navigationBarActions: NavigationBarActions,
|
||||||
uiState: FriendsFeedUiState,
|
viewModel: FriendsFeedViewModel
|
||||||
) {
|
) {
|
||||||
PrimaryScreenTemplate(
|
PrimaryScreenTemplate(
|
||||||
title = resources().getString(AppText.friends_feed),
|
title = resources().getString(AppText.friends_feed),
|
||||||
drawerActions = drawerActions,
|
drawerActions = drawerActions,
|
||||||
navigationBarActions = navigationBarActions
|
navigationBarActions = navigationBarActions
|
||||||
) {
|
) {
|
||||||
when (uiState) {
|
|
||||||
FriendsFeedUiState.Loading -> LoadingFeed()
|
val friendsSessions = viewModel.getFriendsSessions().collectAsState(initial = emptyList())
|
||||||
is FriendsFeedUiState.Succes -> {
|
|
||||||
val friendsSessions = uiState.friendSessions
|
|
||||||
|
|
||||||
|
|
||||||
LazyColumn() {
|
LazyColumn() {
|
||||||
// Default Timers, cannot be edited
|
// Default Timers, cannot be edited
|
||||||
items(friendsSessions) {
|
items(friendsSessions.value) {
|
||||||
val (day, feedEntries) = it
|
val (day, feedEntries) = it
|
||||||
DateText(date = day)
|
DateText(date = day)
|
||||||
feedEntries.forEach { (name, feedEntry) ->
|
feedEntries.forEach { (name, feedEntry) ->
|
||||||
|
@ -67,8 +66,7 @@ fun FriendsFeedScreen(
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package be.ugent.sel.studeez.screens.friends_feed
|
package be.ugent.sel.studeez.screens.friends_feed
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||||
import be.ugent.sel.studeez.domain.FeedDAO
|
import be.ugent.sel.studeez.domain.FeedDAO
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
|
import be.ugent.sel.studeez.domain.SessionDAO
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.toList
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
|
@ -17,13 +18,10 @@ class FriendsFeedViewModel @Inject constructor(
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
val uiState: StateFlow<FriendsFeedUiState> = feedDAO.getFriendsSessions()
|
fun getFriendsSessions(): Flow<List<Pair<String, List<Pair<String, FeedEntry>>>>> {
|
||||||
.map { it.toList() }
|
return feedDAO.getFriendsSessions().map { it.toList() }
|
||||||
.map { FriendsFeedUiState.Succes(it) }
|
}
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
initialValue = FriendsFeedUiState.Loading,
|
|
||||||
started = SharingStarted.Eagerly,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,17 @@ import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import be.ugent.sel.studeez.R
|
import be.ugent.sel.studeez.R
|
||||||
import be.ugent.sel.studeez.common.composable.BasicButton
|
|
||||||
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
||||||
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
||||||
|
import be.ugent.sel.studeez.common.composable.feed.Feed
|
||||||
|
import be.ugent.sel.studeez.common.composable.feed.FeedUiState
|
||||||
|
import be.ugent.sel.studeez.common.composable.feed.FeedViewModel
|
||||||
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
||||||
import be.ugent.sel.studeez.common.ext.basicButton
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
import be.ugent.sel.studeez.resources
|
import be.ugent.sel.studeez.resources
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -21,35 +24,43 @@ fun HomeRoute(
|
||||||
viewModel: HomeViewModel,
|
viewModel: HomeViewModel,
|
||||||
drawerActions: DrawerActions,
|
drawerActions: DrawerActions,
|
||||||
navigationBarActions: NavigationBarActions,
|
navigationBarActions: NavigationBarActions,
|
||||||
|
feedViewModel: FeedViewModel,
|
||||||
) {
|
) {
|
||||||
|
val feedUiState by feedViewModel.uiState.collectAsState()
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
onStartSessionClick = { viewModel.onStartSessionClick(open) },
|
onViewFriendsClick = { viewModel.onViewFriendsClick(open) },
|
||||||
drawerActions = drawerActions,
|
drawerActions = drawerActions,
|
||||||
navigationBarActions = navigationBarActions,
|
navigationBarActions = navigationBarActions,
|
||||||
|
feedUiState = feedUiState,
|
||||||
|
continueTask = { subjectId, taskId -> feedViewModel.continueTask(open, subjectId, taskId) },
|
||||||
|
onEmptyFeedHelp = { feedViewModel.onEmptyFeedHelp(open) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(
|
fun HomeScreen(
|
||||||
onStartSessionClick: () -> Unit,
|
onViewFriendsClick: () -> Unit,
|
||||||
drawerActions: DrawerActions,
|
drawerActions: DrawerActions,
|
||||||
navigationBarActions: NavigationBarActions
|
navigationBarActions: NavigationBarActions,
|
||||||
|
feedUiState: FeedUiState,
|
||||||
|
continueTask: (String, String) -> Unit,
|
||||||
|
onEmptyFeedHelp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
PrimaryScreenTemplate(
|
PrimaryScreenTemplate(
|
||||||
title = resources().getString(R.string.home),
|
title = resources().getString(R.string.home),
|
||||||
drawerActions = drawerActions,
|
drawerActions = drawerActions,
|
||||||
navigationBarActions = navigationBarActions,
|
navigationBarActions = navigationBarActions,
|
||||||
// TODO barAction = { FriendsAction() }
|
barAction = { FriendsAction(onViewFriendsClick) }
|
||||||
) {
|
) {
|
||||||
BasicButton(R.string.start_session, Modifier.basicButton()) {
|
Feed(feedUiState, continueTask, onEmptyFeedHelp)
|
||||||
onStartSessionClick()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FriendsAction() {
|
fun FriendsAction(
|
||||||
IconButton(onClick = { /*TODO*/ }) {
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
IconButton(onClick = onClick) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Person,
|
imageVector = Icons.Default.Person,
|
||||||
contentDescription = resources().getString(R.string.friends)
|
contentDescription = resources().getString(R.string.friends)
|
||||||
|
@ -61,8 +72,40 @@ fun FriendsAction() {
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreenPreview() {
|
fun HomeScreenPreview() {
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
onStartSessionClick = {},
|
onViewFriendsClick = {},
|
||||||
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||||
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {})
|
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||||
|
feedUiState = FeedUiState.Succes(
|
||||||
|
mapOf(
|
||||||
|
"08 May 2023" to listOf(
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFABD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 600,
|
||||||
|
),
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"09 May 2023" to listOf(
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFD1200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
),
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFF5C89,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
continueTask = { _, _ -> run {} },
|
||||||
|
onEmptyFeedHelp = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
package be.ugent.sel.studeez.screens.home
|
package be.ugent.sel.studeez.screens.home
|
||||||
|
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
|
@ -9,11 +7,11 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class HomeViewModel @Inject constructor(
|
class HomeViewModel @Inject constructor(
|
||||||
private val accountDAO: AccountDAO,
|
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
fun onStartSessionClick(open: (String) -> Unit) {
|
|
||||||
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
|
fun onViewFriendsClick(open: (String) -> Unit) {
|
||||||
|
open(StudeezDestinations.FRIENDS_OVERVIEW_SCREEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.profile
|
|
||||||
|
|
||||||
data class ProfileEditUiState (
|
|
||||||
val username: String = ""
|
|
||||||
)
|
|
|
@ -1,37 +1,50 @@
|
||||||
package be.ugent.sel.studeez.screens.profile
|
package be.ugent.sel.studeez.screens.profile
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Edit
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import be.ugent.sel.studeez.R
|
import be.ugent.sel.studeez.R
|
||||||
import be.ugent.sel.studeez.common.composable.Headline
|
import be.ugent.sel.studeez.common.composable.Headline
|
||||||
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
||||||
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
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.navbar.NavigationBarActions
|
||||||
|
import be.ugent.sel.studeez.common.ext.defaultButtonShape
|
||||||
import be.ugent.sel.studeez.resources
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
data class ProfileActions(
|
data class ProfileActions(
|
||||||
val getUsername: suspend CoroutineScope.() -> String?,
|
val getUsername: suspend CoroutineScope.() -> String?,
|
||||||
|
val getBiography: suspend CoroutineScope.() -> String?,
|
||||||
|
val getAmountOfFriends: () -> Flow<Int>,
|
||||||
val onEditProfileClick: () -> Unit,
|
val onEditProfileClick: () -> Unit,
|
||||||
|
val onViewFriendsClick: () -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getProfileActions(
|
fun getProfileActions(
|
||||||
viewModel: ProfileViewModel,
|
viewModel: ProfileViewModel,
|
||||||
open: (String) -> Unit,
|
open: (String) -> Unit
|
||||||
): ProfileActions {
|
): ProfileActions {
|
||||||
return ProfileActions(
|
return ProfileActions(
|
||||||
getUsername = { viewModel.getUsername() },
|
getUsername = { viewModel.getUsername() },
|
||||||
|
getBiography = { viewModel.getBiography() },
|
||||||
|
getAmountOfFriends = { viewModel.getAmountOfFriends() },
|
||||||
onEditProfileClick = { viewModel.onEditProfileClick(open) },
|
onEditProfileClick = { viewModel.onEditProfileClick(open) },
|
||||||
|
onViewFriendsClick = { viewModel.onViewFriendsClick(open) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +69,12 @@ fun ProfileScreen(
|
||||||
navigationBarActions: NavigationBarActions,
|
navigationBarActions: NavigationBarActions,
|
||||||
) {
|
) {
|
||||||
var username: String? by remember { mutableStateOf("") }
|
var username: String? by remember { mutableStateOf("") }
|
||||||
|
var biography: String? by remember { mutableStateOf("") }
|
||||||
|
val amountOfFriends = profileActions.getAmountOfFriends().collectAsState(initial = 0)
|
||||||
|
|
||||||
LaunchedEffect(key1 = Unit) {
|
LaunchedEffect(key1 = Unit) {
|
||||||
username = profileActions.getUsername(this)
|
username = profileActions.getUsername(this)
|
||||||
|
biography = profileActions.getBiography(this)
|
||||||
}
|
}
|
||||||
PrimaryScreenTemplate(
|
PrimaryScreenTemplate(
|
||||||
title = resources().getString(AppText.profile),
|
title = resources().getString(AppText.profile),
|
||||||
|
@ -65,7 +82,35 @@ fun ProfileScreen(
|
||||||
navigationBarActions = navigationBarActions,
|
navigationBarActions = navigationBarActions,
|
||||||
barAction = { EditAction(onClick = profileActions.onEditProfileClick) }
|
barAction = { EditAction(onClick = profileActions.onEditProfileClick) }
|
||||||
) {
|
) {
|
||||||
Headline(text = (username ?: resources().getString(R.string.no_username)))
|
LazyColumn(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(15.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Headline(text = username ?: resources().getString(AppText.no_username))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.wrapContentWidth(align = Alignment.CenterHorizontally)
|
||||||
|
) {
|
||||||
|
AmountOfFriendsButton(
|
||||||
|
amountOfFriends = amountOfFriends.value
|
||||||
|
) {
|
||||||
|
profileActions.onViewFriendsClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = biography ?: "",
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(48.dp, 0.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +123,6 @@ fun EditAction(
|
||||||
imageVector = Icons.Default.Edit,
|
imageVector = Icons.Default.Edit,
|
||||||
contentDescription = resources().getString(AppText.edit_profile)
|
contentDescription = resources().getString(AppText.edit_profile)
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +130,38 @@ fun EditAction(
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileScreenPreview() {
|
fun ProfileScreenPreview() {
|
||||||
ProfileScreen(
|
ProfileScreen(
|
||||||
profileActions = ProfileActions({ null }, {}),
|
profileActions = ProfileActions({ null }, { null }, { emptyFlow() }, {}, {}),
|
||||||
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||||
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {})
|
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AmountOfFriendsButton(
|
||||||
|
amountOfFriends: Int,
|
||||||
|
onClick: () -> Unit
|
||||||
|
){
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
shape = defaultButtonShape()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = resources().getQuantityString(
|
||||||
|
/* id = */ R.plurals.friends_amount,
|
||||||
|
/* quantity = */ amountOfFriends,
|
||||||
|
/* ...formatArgs = */ amountOfFriends
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AmountOfFriendsButtonPreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
Column {
|
||||||
|
AmountOfFriendsButton(amountOfFriends = 1) { }
|
||||||
|
AmountOfFriendsButton(amountOfFriends = 100) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,39 @@
|
||||||
package be.ugent.sel.studeez.screens.profile
|
package be.ugent.sel.studeez.screens.profile
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.domain.FriendshipDAO
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.domain.UserDAO
|
import be.ugent.sel.studeez.domain.UserDAO
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ProfileViewModel @Inject constructor(
|
class ProfileViewModel @Inject constructor(
|
||||||
private val userDAO: UserDAO,
|
private val userDAO: UserDAO,
|
||||||
|
private val friendshipDAO: FriendshipDAO,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
suspend fun getUsername(): String? {
|
suspend fun getUsername(): String {
|
||||||
return userDAO.getUsername()
|
return userDAO.getLoggedInUser().username
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getBiography(): String {
|
||||||
|
return userDAO.getLoggedInUser().biography
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAmountOfFriends(): Flow<Int> {
|
||||||
|
return friendshipDAO.getFriendshipCount(userDAO.getCurrentUserId())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEditProfileClick(open: (String) -> Unit) {
|
fun onEditProfileClick(open: (String) -> Unit) {
|
||||||
open(StudeezDestinations.EDIT_PROFILE_SCREEN)
|
open(StudeezDestinations.EDIT_PROFILE_SCREEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onViewFriendsClick(open: (String) -> Unit) {
|
||||||
|
open(StudeezDestinations.FRIENDS_OVERVIEW_SCREEN)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,20 +1,21 @@
|
||||||
package be.ugent.sel.studeez.screens.profile
|
package be.ugent.sel.studeez.screens.profile.edit_profile
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import be.ugent.sel.studeez.R
|
|
||||||
import be.ugent.sel.studeez.common.composable.BasicTextButton
|
import be.ugent.sel.studeez.common.composable.BasicTextButton
|
||||||
import be.ugent.sel.studeez.common.composable.LabelledInputField
|
import be.ugent.sel.studeez.common.composable.LabelledInputField
|
||||||
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
||||||
import be.ugent.sel.studeez.common.ext.textButton
|
import be.ugent.sel.studeez.common.ext.textButton
|
||||||
import be.ugent.sel.studeez.resources
|
import be.ugent.sel.studeez.resources
|
||||||
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
data class EditProfileActions(
|
data class EditProfileActions(
|
||||||
val onUserNameChange: (String) -> Unit,
|
val onUserNameChange: (String) -> Unit,
|
||||||
|
val onBiographyChange: (String) -> Unit,
|
||||||
val onSaveClick: () -> Unit,
|
val onSaveClick: () -> Unit,
|
||||||
val onDeleteClick: () -> Unit
|
val onDeleteClick: () -> Unit
|
||||||
)
|
)
|
||||||
|
@ -25,6 +26,7 @@ fun getEditProfileActions(
|
||||||
): EditProfileActions {
|
): EditProfileActions {
|
||||||
return EditProfileActions(
|
return EditProfileActions(
|
||||||
onUserNameChange = { viewModel.onUsernameChange(it) },
|
onUserNameChange = { viewModel.onUsernameChange(it) },
|
||||||
|
onBiographyChange = { viewModel.onBiographyChange(it) },
|
||||||
onSaveClick = { viewModel.onSaveClick() },
|
onSaveClick = { viewModel.onSaveClick() },
|
||||||
onDeleteClick = { viewModel.onDeleteClick(openAndPopUp) },
|
onDeleteClick = { viewModel.onDeleteClick(openAndPopUp) },
|
||||||
)
|
)
|
||||||
|
@ -51,36 +53,49 @@ fun EditProfileScreen(
|
||||||
editProfileActions: EditProfileActions,
|
editProfileActions: EditProfileActions,
|
||||||
) {
|
) {
|
||||||
SecondaryScreenTemplate(
|
SecondaryScreenTemplate(
|
||||||
title = resources().getString(R.string.editing_profile),
|
title = resources().getString(AppText.editing_profile),
|
||||||
popUp = goBack
|
popUp = goBack
|
||||||
) {
|
) {
|
||||||
Column {
|
LazyColumn {
|
||||||
|
item {
|
||||||
LabelledInputField(
|
LabelledInputField(
|
||||||
value = uiState.username,
|
value = uiState.username,
|
||||||
onNewValue = editProfileActions.onUserNameChange,
|
onNewValue = editProfileActions.onUserNameChange,
|
||||||
label = R.string.username
|
label = AppText.username
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
LabelledInputField(
|
||||||
|
value = uiState.biography,
|
||||||
|
onNewValue = editProfileActions.onBiographyChange,
|
||||||
|
label = AppText.biography
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
BasicTextButton(
|
BasicTextButton(
|
||||||
text = R.string.save,
|
text = AppText.save,
|
||||||
Modifier.textButton(),
|
Modifier.textButton(),
|
||||||
action = {
|
action = {
|
||||||
editProfileActions.onSaveClick()
|
editProfileActions.onSaveClick()
|
||||||
goBack()
|
goBack()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
BasicTextButton(
|
BasicTextButton(
|
||||||
text = R.string.delete_profile,
|
text = AppText.delete_profile,
|
||||||
Modifier.textButton(),
|
Modifier.textButton(),
|
||||||
action = editProfileActions.onDeleteClick
|
action = editProfileActions.onDeleteClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun EditProfileScreenComposable() {
|
fun EditProfileScreenComposable() {
|
||||||
StudeezTheme {
|
StudeezTheme {
|
||||||
EditProfileScreen({}, ProfileEditUiState(), EditProfileActions({}, {}, {}))
|
EditProfileScreen({}, ProfileEditUiState(), EditProfileActions({}, {}, {}, {}))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package be.ugent.sel.studeez.screens.profile.edit_profile
|
||||||
|
|
||||||
|
data class ProfileEditUiState (
|
||||||
|
val username: String = "",
|
||||||
|
val biography: String = ""
|
||||||
|
)
|
|
@ -1,8 +1,9 @@
|
||||||
package be.ugent.sel.studeez.screens.profile
|
package be.ugent.sel.studeez.screens.profile.edit_profile
|
||||||
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import be.ugent.sel.studeez.R
|
import be.ugent.sel.studeez.R
|
||||||
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.domain.UserDAO
|
import be.ugent.sel.studeez.domain.UserDAO
|
||||||
|
@ -23,7 +24,11 @@ class ProfileEditViewModel @Inject constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
launchCatching {
|
launchCatching {
|
||||||
uiState.value = uiState.value.copy(username = userDAO.getUsername()!!)
|
val user: User = userDAO.getLoggedInUser()
|
||||||
|
uiState.value = uiState.value.copy(
|
||||||
|
username = user.username,
|
||||||
|
biography = user.biography
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,16 +36,23 @@ class ProfileEditViewModel @Inject constructor(
|
||||||
uiState.value = uiState.value.copy(username = newValue)
|
uiState.value = uiState.value.copy(username = newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onBiographyChange(newValue: String) {
|
||||||
|
uiState.value = uiState.value.copy(biography = newValue)
|
||||||
|
}
|
||||||
|
|
||||||
fun onSaveClick() {
|
fun onSaveClick() {
|
||||||
launchCatching {
|
launchCatching {
|
||||||
userDAO.save(uiState.value.username)
|
userDAO.saveLoggedInUser(
|
||||||
|
newUsername = uiState.value.username,
|
||||||
|
newBiography = uiState.value.biography
|
||||||
|
)
|
||||||
SnackbarManager.showMessage(R.string.success)
|
SnackbarManager.showMessage(R.string.success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDeleteClick(openAndPopUp: (String, String) -> Unit) {
|
fun onDeleteClick(openAndPopUp: (String, String) -> Unit) {
|
||||||
launchCatching {
|
launchCatching {
|
||||||
userDAO.deleteUserReferences() // Delete references
|
userDAO.deleteLoggedInUserReferences() // Delete references
|
||||||
accountDAO.deleteAccount() // Delete authentication
|
accountDAO.deleteAccount() // Delete authentication
|
||||||
}
|
}
|
||||||
openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.EDIT_PROFILE_SCREEN)
|
openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.EDIT_PROFILE_SCREEN)
|
|
@ -0,0 +1,179 @@
|
||||||
|
package be.ugent.sel.studeez.screens.profile.public_profile
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.MailOutline
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
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.Headline
|
||||||
|
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
||||||
|
import be.ugent.sel.studeez.common.composable.drawer.DrawerEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.User
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.screens.profile.AmountOfFriendsButton
|
||||||
|
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
data class PublicProfileActions(
|
||||||
|
val getUserDetails: () -> Flow<User>,
|
||||||
|
val getAmountOfFriends: () -> Flow<Int>,
|
||||||
|
val onViewFriendsClick: () -> Unit,
|
||||||
|
val sendFriendRequest: () -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getPublicProfileActions(
|
||||||
|
viewModel: PublicProfileViewModel,
|
||||||
|
open: (String) -> Unit
|
||||||
|
): PublicProfileActions {
|
||||||
|
return PublicProfileActions(
|
||||||
|
getUserDetails = { viewModel.getUserDetails(viewModel.uiState.value.userId) },
|
||||||
|
getAmountOfFriends = { viewModel.getAmountOfFriends(
|
||||||
|
userId = viewModel.uiState.value.userId
|
||||||
|
) },
|
||||||
|
onViewFriendsClick = { viewModel.onViewFriendsClick(open) },
|
||||||
|
sendFriendRequest = {
|
||||||
|
viewModel.sendFriendRequest(
|
||||||
|
userId = viewModel.uiState.value.userId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PublicProfileRoute(
|
||||||
|
popUp: () -> Unit,
|
||||||
|
open: (String) -> Unit,
|
||||||
|
viewModel: PublicProfileViewModel
|
||||||
|
) {
|
||||||
|
PublicProfileScreen(
|
||||||
|
publicProfileActions = getPublicProfileActions(
|
||||||
|
viewModel = viewModel,
|
||||||
|
open = open
|
||||||
|
),
|
||||||
|
popUp = popUp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PublicProfileScreen(
|
||||||
|
publicProfileActions: PublicProfileActions,
|
||||||
|
popUp: () -> Unit
|
||||||
|
) {
|
||||||
|
val user = publicProfileActions.getUserDetails().collectAsState(initial = User())
|
||||||
|
val amountOfFriends = publicProfileActions.getAmountOfFriends().collectAsState(initial = 0)
|
||||||
|
|
||||||
|
SecondaryScreenTemplate(
|
||||||
|
title = stringResource(id = AppText.profile),
|
||||||
|
popUp = popUp,
|
||||||
|
barAction = {
|
||||||
|
PublicProfileEllipsis(
|
||||||
|
sendFriendRequest = publicProfileActions.sendFriendRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(15.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Headline(text = user.value.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentWidth(align = Alignment.CenterHorizontally)
|
||||||
|
) {
|
||||||
|
AmountOfFriendsButton(
|
||||||
|
amountOfFriends = amountOfFriends.value
|
||||||
|
) {
|
||||||
|
publicProfileActions.onViewFriendsClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = user.value.biography,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(48.dp, 0.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PublicProfilePreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
PublicProfileScreen(
|
||||||
|
publicProfileActions = PublicProfileActions(
|
||||||
|
getUserDetails = {
|
||||||
|
flowOf(User(
|
||||||
|
id = "someid",
|
||||||
|
username = "Maxime De Poorter",
|
||||||
|
biography = "I am a different student and this is my public profile"
|
||||||
|
))
|
||||||
|
},
|
||||||
|
getAmountOfFriends = { flowOf(113) },
|
||||||
|
onViewFriendsClick = {},
|
||||||
|
sendFriendRequest = {}
|
||||||
|
),
|
||||||
|
popUp = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PublicProfileEllipsis(
|
||||||
|
sendFriendRequest: () -> Unit
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = { expanded = true }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = R.drawable.ic_more_horizontal),
|
||||||
|
contentDescription = resources().getString(AppText.view_more)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(onClick = { expanded = false }) {
|
||||||
|
DrawerEntry(
|
||||||
|
icon = Icons.Default.MailOutline,
|
||||||
|
text = stringResource(id = AppText.send_friend_request)
|
||||||
|
) {
|
||||||
|
sendFriendRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PublicProfileEllipsisPreview() {
|
||||||
|
StudeezTheme {
|
||||||
|
PublicProfileEllipsis(
|
||||||
|
sendFriendRequest = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package be.ugent.sel.studeez.screens.profile.public_profile
|
||||||
|
|
||||||
|
data class PublicProfileUiState(
|
||||||
|
var userId: String = ""
|
||||||
|
)
|
|
@ -0,0 +1,60 @@
|
||||||
|
package be.ugent.sel.studeez.screens.profile.public_profile
|
||||||
|
|
||||||
|
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.domain.FriendshipDAO
|
||||||
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class PublicProfileViewModel @Inject constructor(
|
||||||
|
private val userDAO: UserDAO,
|
||||||
|
private val friendshipDAO: FriendshipDAO,
|
||||||
|
selectedUserIdState: SelectedUserId,
|
||||||
|
logService: LogService
|
||||||
|
): StudeezViewModel(logService) {
|
||||||
|
|
||||||
|
val uiState = mutableStateOf(
|
||||||
|
PublicProfileUiState(
|
||||||
|
userId = selectedUserIdState.value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getUserDetails(
|
||||||
|
userId: String
|
||||||
|
): Flow<User> {
|
||||||
|
uiState.value = uiState.value.copy(
|
||||||
|
userId = userId
|
||||||
|
)
|
||||||
|
return userDAO.getUserDetails(
|
||||||
|
userId = uiState.value.userId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAmountOfFriends(
|
||||||
|
userId: String
|
||||||
|
): Flow<Int> {
|
||||||
|
return friendshipDAO.getFriendshipCount(
|
||||||
|
userId = userId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onViewFriendsClick(
|
||||||
|
open: (String) -> Unit
|
||||||
|
) {
|
||||||
|
open(StudeezDestinations.FRIENDS_OVERVIEW_SCREEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendFriendRequest(
|
||||||
|
userId: String
|
||||||
|
) {
|
||||||
|
friendshipDAO.sendFriendshipRequest(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,9 +10,9 @@ object InvisibleSessionManager {
|
||||||
private var viewModel: SessionViewModel? = null
|
private var viewModel: SessionViewModel? = null
|
||||||
private lateinit var mediaPlayer: MediaPlayer
|
private lateinit var mediaPlayer: MediaPlayer
|
||||||
|
|
||||||
fun setParameters(viewModel: SessionViewModel, mediaplayer: MediaPlayer) {
|
fun setParameters(viewModel: SessionViewModel, mediaPlayer: MediaPlayer) {
|
||||||
|
this.mediaPlayer = mediaPlayer
|
||||||
this.viewModel = viewModel
|
this.viewModel = viewModel
|
||||||
this.mediaPlayer = mediaplayer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateTimer() {
|
suspend fun updateTimer() {
|
||||||
|
|
|
@ -6,28 +6,22 @@ import android.net.Uri
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
||||||
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
|
import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreenComposable
|
||||||
import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreen
|
|
||||||
|
|
||||||
data class SessionActions(
|
data class SessionActions(
|
||||||
val getTimer: () -> FunctionalTimer,
|
val getTimer: () -> FunctionalTimer,
|
||||||
val getTask: () -> String,
|
val getTask: () -> String,
|
||||||
val startMediaPlayer: () -> Unit,
|
|
||||||
val releaseMediaPlayer: () -> Unit,
|
|
||||||
val endSession: () -> Unit
|
val endSession: () -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getSessionActions(
|
private fun getSessionActions(
|
||||||
viewModel: SessionViewModel,
|
viewModel: SessionViewModel,
|
||||||
openAndPopUp: (String, String) -> Unit,
|
openAndPopUp: (String, String) -> Unit,
|
||||||
mediaplayer: MediaPlayer,
|
|
||||||
): SessionActions {
|
): SessionActions {
|
||||||
return SessionActions(
|
return SessionActions(
|
||||||
getTimer = viewModel::getTimer,
|
getTimer = viewModel::getTimer,
|
||||||
getTask = viewModel::getTask,
|
getTask = viewModel::getTask,
|
||||||
endSession = { viewModel.endSession(openAndPopUp) },
|
endSession = { viewModel.endSession(openAndPopUp) },
|
||||||
startMediaPlayer = mediaplayer::start,
|
|
||||||
releaseMediaPlayer = mediaplayer::release,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,20 +31,15 @@ fun SessionRoute(
|
||||||
openAndPopUp: (String, String) -> Unit,
|
openAndPopUp: (String, String) -> Unit,
|
||||||
viewModel: SessionViewModel,
|
viewModel: SessionViewModel,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
val mediaplayer = MediaPlayer.create(context, uri)
|
val mediaPlayer = MediaPlayer.create(LocalContext.current, uri)
|
||||||
mediaplayer.isLooping = false
|
mediaPlayer.isLooping = false
|
||||||
|
|
||||||
InvisibleSessionManager.setParameters(
|
InvisibleSessionManager.setParameters(viewModel = viewModel, mediaPlayer = mediaPlayer)
|
||||||
viewModel = viewModel,
|
|
||||||
mediaplayer = mediaplayer
|
|
||||||
)
|
|
||||||
|
|
||||||
val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen(mediaplayer))
|
val soundPlayer = SoundPlayer(LocalContext.current)
|
||||||
|
val sessionActions = getSessionActions(viewModel, openAndPopUp)
|
||||||
|
val sessionScreen = viewModel.getTimer().accept(GetSessionScreenComposable(soundPlayer, open, sessionActions))
|
||||||
|
|
||||||
sessionScreen(
|
sessionScreen()
|
||||||
open = open,
|
|
||||||
sessionActions = getSessionActions(viewModel, openAndPopUp, mediaplayer)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package be.ugent.sel.studeez.screens.session
|
package be.ugent.sel.studeez.screens.session
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.SelectedTimerState
|
import be.ugent.sel.studeez.data.SelectedSessionReport
|
||||||
import be.ugent.sel.studeez.data.SessionReportState
|
import be.ugent.sel.studeez.data.SelectedTask
|
||||||
|
import be.ugent.sel.studeez.data.SelectedTimer
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
|
@ -11,23 +12,21 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SessionViewModel @Inject constructor(
|
class SessionViewModel @Inject constructor(
|
||||||
private val selectedTimerState: SelectedTimerState,
|
private val selectedTimer: SelectedTimer,
|
||||||
private val sessionReportState: SessionReportState,
|
private val sessionReport: SelectedSessionReport,
|
||||||
|
private val selectedTask: SelectedTask,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
fun getTimer(): FunctionalTimer {
|
||||||
private val task : String = "No task selected" // placeholder for tasks implementation
|
return selectedTimer()
|
||||||
|
|
||||||
fun getTimer() : FunctionalTimer {
|
|
||||||
return selectedTimerState.selectedTimer!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTask(): String {
|
fun getTask(): String {
|
||||||
return task
|
return selectedTask().name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endSession(openAndPopUp: (String, String) -> Unit) {
|
fun endSession(openAndPopUp: (String, String) -> Unit) {
|
||||||
sessionReportState.sessionReport = getTimer().getSessionReport()
|
sessionReport.set(getTimer().getSessionReport(selectedTask().subjectId, selectedTask().id))
|
||||||
openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN)
|
openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.MediaPlayer
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
|
||||||
|
class SoundPlayer(private val context: Context) {
|
||||||
|
|
||||||
|
var oldValue: Boolean = false
|
||||||
|
var mediaPlayer: MediaPlayer = initPlayer()
|
||||||
|
|
||||||
|
fun playOn(newValue: Boolean) {
|
||||||
|
if (oldValue != newValue) {
|
||||||
|
mediaPlayer.start()
|
||||||
|
mediaPlayer.setOnCompletionListener {
|
||||||
|
mediaPlayer = initPlayer()
|
||||||
|
}
|
||||||
|
oldValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun initPlayer(): MediaPlayer {
|
||||||
|
return MediaPlayer.create(
|
||||||
|
context,
|
||||||
|
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,143 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
|
|
||||||
import be.ugent.sel.studeez.screens.session.SessionActions
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
abstract class AbstractSessionScreen {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
operator fun invoke(
|
|
||||||
open: (String) -> Unit,
|
|
||||||
sessionActions: SessionActions,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(10.dp)
|
|
||||||
) {
|
|
||||||
Timer(
|
|
||||||
sessionActions = sessionActions,
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center, modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(50.dp)
|
|
||||||
) {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
sessionActions.releaseMediaPlayer
|
|
||||||
sessionActions.endSession()
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 20.dp)
|
|
||||||
.border(1.dp, Color.Red, RoundedCornerShape(32.dp))
|
|
||||||
.background(Color.Transparent)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "End session",
|
|
||||||
color = Color.Red,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
modifier = Modifier.padding(1.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Timer(
|
|
||||||
sessionActions: SessionActions,
|
|
||||||
) {
|
|
||||||
var tikker by remember { mutableStateOf(false) }
|
|
||||||
LaunchedEffect(tikker) {
|
|
||||||
delay(1.seconds)
|
|
||||||
sessionActions.getTimer().tick()
|
|
||||||
callMediaPlayer()
|
|
||||||
tikker = !tikker
|
|
||||||
}
|
|
||||||
|
|
||||||
val hms = sessionActions.getTimer().getHoursMinutesSeconds()
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = hms.toString(),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(50.dp),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 40.sp,
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = motivationString(),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontWeight = FontWeight.Light,
|
|
||||||
fontSize = 30.sp
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center, modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(50.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.background(Color.Blue, RoundedCornerShape(32.dp))
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = sessionActions.getTask(),
|
|
||||||
color = Color.White,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
abstract fun motivationString(): String
|
|
||||||
|
|
||||||
abstract fun callMediaPlayer()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun TimerPreview() {
|
|
||||||
val sessionScreen = object : AbstractSessionScreen() {
|
|
||||||
@Composable
|
|
||||||
override fun motivationString(): String = "Test"
|
|
||||||
override fun callMediaPlayer() {}
|
|
||||||
|
|
||||||
}
|
|
||||||
sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}, {}))
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import be.ugent.sel.studeez.R
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
|
|
||||||
import be.ugent.sel.studeez.resources
|
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
|
||||||
|
|
||||||
class BreakSessionScreen(
|
|
||||||
private val funPomoDoroTimer: FunctionalPomodoroTimer,
|
|
||||||
private var mediaplayer: MediaPlayer?
|
|
||||||
): AbstractSessionScreen() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun motivationString(): String {
|
|
||||||
if (funPomoDoroTimer.isInBreak) {
|
|
||||||
return resources().getString(AppText.state_take_a_break)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (funPomoDoroTimer.hasEnded()) {
|
|
||||||
return resources().getString(AppText.state_done)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resources().getQuantityString(
|
|
||||||
R.plurals.state_focus_remaining,
|
|
||||||
funPomoDoroTimer.breaksRemaining,
|
|
||||||
funPomoDoroTimer.breaksRemaining
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun callMediaPlayer() {
|
|
||||||
if (funPomoDoroTimer.hasEnded()) {
|
|
||||||
mediaplayer?.let { it: MediaPlayer ->
|
|
||||||
it.setOnCompletionListener {
|
|
||||||
it.release()
|
|
||||||
mediaplayer = null
|
|
||||||
}
|
|
||||||
it.start()
|
|
||||||
}
|
|
||||||
} else if (funPomoDoroTimer.hasCurrentCountdownEnded()) {
|
|
||||||
mediaplayer?.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import be.ugent.sel.studeez.R
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
import be.ugent.sel.studeez.screens.session.SoundPlayer
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BreakSessionScreenComposable(
|
||||||
|
open: (String) -> Unit,
|
||||||
|
sessionActions: SessionActions,
|
||||||
|
pomodoroTimer: FunctionalPomodoroTimer,
|
||||||
|
soundPlayer: SoundPlayer,
|
||||||
|
) {
|
||||||
|
SessionScreen(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
midSection = { Dots(pomodoroTimer = pomodoroTimer) },
|
||||||
|
callMediaPlayer = { soundPlayer.playOn(pomodoroTimer.hasCurrentCountdownEnded()) },
|
||||||
|
motivationString = { motivationString (pomodoroTimer = pomodoroTimer) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Dots(pomodoroTimer: FunctionalPomodoroTimer): Int {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
if (pomodoroTimer.hasEnded()) {
|
||||||
|
repeat(pomodoroTimer.repeats) {
|
||||||
|
Dot(Color.Green)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat(pomodoroTimer.repeats - pomodoroTimer.breaksRemaining - 1) {
|
||||||
|
Dot(color = Color.DarkGray)
|
||||||
|
}
|
||||||
|
if (!pomodoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray)
|
||||||
|
repeat(pomodoroTimer.breaksRemaining) {
|
||||||
|
Dot(color = Color.Gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pomodoroTimer.breaksRemaining
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Dot(color: Color) {
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.padding(5.dp)
|
||||||
|
.size(10.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(color))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun motivationString(pomodoroTimer: FunctionalPomodoroTimer): String {
|
||||||
|
if (pomodoroTimer.isInBreak) {
|
||||||
|
return resources().getString(R.string.state_take_a_break)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pomodoroTimer.hasEnded()) {
|
||||||
|
return resources().getString(R.string.state_done)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources().getString(R.string.state_focus)
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
|
|
||||||
import be.ugent.sel.studeez.resources
|
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSessionScreen(
|
|
||||||
private val functionalTimer: FunctionalCustomTimer,
|
|
||||||
private var mediaplayer: MediaPlayer?
|
|
||||||
): AbstractSessionScreen() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun motivationString(): String {
|
|
||||||
if (functionalTimer.hasEnded()) {
|
|
||||||
return resources().getString(AppText.state_done)
|
|
||||||
}
|
|
||||||
return resources().getString(AppText.state_focus)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun callMediaPlayer() {
|
|
||||||
if (functionalTimer.hasEnded()) {
|
|
||||||
mediaplayer?.let { it: MediaPlayer ->
|
|
||||||
it.setOnCompletionListener {
|
|
||||||
it.release()
|
|
||||||
mediaplayer = null
|
|
||||||
}
|
|
||||||
it.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import be.ugent.sel.studeez.R
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
import be.ugent.sel.studeez.screens.session.SoundPlayer
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CustomTimerSessionScreenComposable(
|
||||||
|
open: (String) -> Unit,
|
||||||
|
sessionActions: SessionActions,
|
||||||
|
customTimer: FunctionalCustomTimer,
|
||||||
|
soundPlayer: SoundPlayer
|
||||||
|
) {
|
||||||
|
SessionScreen(
|
||||||
|
open = open,
|
||||||
|
callMediaPlayer = { soundPlayer.playOn(customTimer.hasEnded()) },
|
||||||
|
sessionActions = sessionActions
|
||||||
|
) {
|
||||||
|
motivationString(customTimer = customTimer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun motivationString(customTimer: FunctionalCustomTimer): String {
|
||||||
|
if (customTimer.hasEnded()) {
|
||||||
|
return resources().getString(R.string.state_done)
|
||||||
|
}
|
||||||
|
return resources().getString(R.string.state_focus)
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import be.ugent.sel.studeez.resources
|
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
|
||||||
|
|
||||||
|
|
||||||
class EndlessSessionScreen : AbstractSessionScreen() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun motivationString(): String {
|
|
||||||
return resources().getString(AppText.state_focus)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun callMediaPlayer() {}
|
|
||||||
}
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import be.ugent.sel.studeez.R
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EndlessTimerSessionScreenComposable(
|
||||||
|
open: (String) -> Unit,
|
||||||
|
sessionActions: SessionActions,
|
||||||
|
) {
|
||||||
|
SessionScreen(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions
|
||||||
|
) {
|
||||||
|
motivationString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun motivationString(): String {
|
||||||
|
return resources().getString(R.string.state_focus)
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
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.data.local.models.timer_functional.FunctionalTimerVisitor
|
|
||||||
|
|
||||||
class GetSessionScreen(private val mediaplayer: MediaPlayer?) : FunctionalTimerVisitor<AbstractSessionScreen> {
|
|
||||||
override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen =
|
|
||||||
CustomSessionScreen(functionalCustomTimer, mediaplayer)
|
|
||||||
|
|
||||||
override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen =
|
|
||||||
EndlessSessionScreen()
|
|
||||||
|
|
||||||
override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen =
|
|
||||||
BreakSessionScreen(functionalPomodoroTimer, mediaplayer)
|
|
||||||
}
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.data.local.models.timer_functional.FunctionalTimerVisitor
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
import be.ugent.sel.studeez.screens.session.SoundPlayer
|
||||||
|
|
||||||
|
class GetSessionScreenComposable(
|
||||||
|
private val soundPlayer: SoundPlayer,
|
||||||
|
private val open: (String) -> Unit,
|
||||||
|
private val sessionActions: SessionActions
|
||||||
|
) :
|
||||||
|
FunctionalTimerVisitor<@Composable () -> Unit> {
|
||||||
|
|
||||||
|
override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): @Composable () -> Unit {
|
||||||
|
return { CustomTimerSessionScreenComposable(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
soundPlayer = soundPlayer,
|
||||||
|
customTimer = functionalCustomTimer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): @Composable () -> Unit {
|
||||||
|
return {
|
||||||
|
EndlessTimerSessionScreenComposable(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): @Composable () -> Unit {
|
||||||
|
return {
|
||||||
|
BreakSessionScreenComposable(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
soundPlayer = soundPlayer,
|
||||||
|
pomodoroTimer = functionalPomodoroTimer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue