Merge branch 'development'

This commit is contained in:
brreynie 2023-05-16 12:02:55 +02:00
commit af6032e75e
73 changed files with 706 additions and 417 deletions

View file

@ -140,7 +140,7 @@ class HomeScreenTest {
fun navigationbarTest() { fun navigationbarTest() {
var hometest = false var hometest = false
var tasktest = false var tasktest = false
var sessiontest = false var friendstest = false
var profiletest = false var profiletest = false
composeTestRule.setContent { composeTestRule.setContent {
@ -150,7 +150,7 @@ class HomeScreenTest {
{false}, {false},
{hometest = true}, {hometest = true},
{tasktest = true}, {tasktest = true},
{sessiontest = true}, {friendstest = true},
{profiletest = true}, {profiletest = true},
{}, {}, {} {}, {}, {}
), ),
@ -183,7 +183,7 @@ class HomeScreenTest {
composeTestRule composeTestRule
.onNodeWithContentDescription( .onNodeWithContentDescription(
"session", "feed",
substring = true, substring = true,
ignoreCase = true ignoreCase = true
) )
@ -201,7 +201,7 @@ class HomeScreenTest {
Assert.assertTrue(hometest) Assert.assertTrue(hometest)
Assert.assertTrue(tasktest) Assert.assertTrue(tasktest)
Assert.assertTrue(sessiontest) Assert.assertTrue(friendstest)
Assert.assertTrue(profiletest) Assert.assertTrue(profiletest)
} }
} }

View file

@ -2,26 +2,14 @@ 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.clickable import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
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
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.scale
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -87,7 +75,7 @@ fun StealthButton(
) { ) {
//val clickablemodifier = if (disabled) Modifier.clickable(indication = null) else modifier //val clickablemodifier = if (disabled) Modifier.clickable(indication = null) else modifier
val borderColor = if (enabled) MaterialTheme.colors.primary val borderColor = if (enabled) MaterialTheme.colors.primary
else MaterialTheme.colors.onSurface.copy(alpha = 0.3f) else MaterialTheme.colors.onSurface.copy(alpha = 0.3f)
BasicButton( BasicButton(
text = text, text = text,
onClick = onClick, onClick = onClick,

View file

@ -29,20 +29,22 @@ fun DrawerScreenTemplate(
Scaffold( Scaffold(
scaffoldState = scaffoldState, scaffoldState = scaffoldState,
topBar = { TopAppBar( topBar = {
title = { Text(text = title) }, TopAppBar(
navigationIcon = { title = { Text(text = title) },
IconButton(onClick = { navigationIcon = {
coroutineScope.launch { scaffoldState.drawerState.open() } IconButton(onClick = {
}) { coroutineScope.launch { scaffoldState.drawerState.open() }
Icon( }) {
imageVector = Icons.Default.Menu, Icon(
contentDescription = resources().getString(AppText.menu) imageVector = Icons.Default.Menu,
) contentDescription = resources().getString(AppText.menu)
} )
}, }
actions = barAction },
)}, actions = barAction
)
},
drawerContent = { drawerContent = {
Drawer(drawerActions) Drawer(drawerActions)
@ -55,10 +57,12 @@ fun DrawerScreenTemplate(
@Preview @Preview
@Composable @Composable
fun DrawerScreenPreview() { fun DrawerScreenPreview() {
StudeezTheme { DrawerScreenTemplate( StudeezTheme {
title = "Drawer screen preview", DrawerScreenTemplate(
drawerActions =DrawerActions({}, {}, {}, {}, {}) title = "Drawer screen preview",
) { drawerActions = DrawerActions({}, {}, {}, {}, {})
Text(text = "Preview content") ) {
} } Text(text = "Preview content")
}
}
} }

View file

@ -131,15 +131,19 @@ fun ExpandedEntry(
@Preview @Preview
@Composable @Composable
fun AddButtonPreview() { fun AddButtonPreview() {
StudeezTheme { AddButton( StudeezTheme {
addButtonActions = AddButtonActions({}, {}, {}) AddButton(
)} addButtonActions = AddButtonActions({}, {}, {})
)
}
} }
@Preview @Preview
@Composable @Composable
fun ExpandedAddButtonPreview() { fun ExpandedAddButtonPreview() {
StudeezTheme { ExpandedAddButton ( StudeezTheme {
addButtonActions = AddButtonActions({}, {}, {}) ExpandedAddButton(
) } addButtonActions = AddButtonActions({}, {}, {})
)
}
} }

View file

@ -57,11 +57,15 @@ fun PrimaryScreenTemplate(
bottomBar = { NavigationBar(navigationBarActions) }, bottomBar = { NavigationBar(navigationBarActions) },
floatingActionButtonPosition = FabPosition.Center, floatingActionButtonPosition = FabPosition.Center,
isFloatingActionButtonDocked = true, isFloatingActionButtonDocked = true,
floatingActionButton = { AddButton(AddButtonActions( floatingActionButton = {
onTaskClick = navigationBarActions.onAddTaskClick, AddButton(
onFriendClick = navigationBarActions.onAddFriendClick, AddButtonActions(
onSessionClick = navigationBarActions.onAddSessionClick onTaskClick = navigationBarActions.onAddTaskClick,
)) } onFriendClick = navigationBarActions.onAddFriendClick,
onSessionClick = navigationBarActions.onAddSessionClick
)
)
}
) { ) {
content(it) content(it)
} }

View file

@ -21,18 +21,20 @@ fun SecondaryScreenTemplate(
) { ) {
Scaffold( Scaffold(
// Everything at the top of the screen // Everything at the top of the screen
topBar = { TopAppBar( topBar = {
title = { Text(text = title) }, TopAppBar(
navigationIcon = { title = { Text(text = title) },
IconButton(onClick = { popUp() }) { navigationIcon = {
Icon( IconButton(onClick = { popUp() }) {
imageVector = Icons.Default.ArrowBack, Icon(
contentDescription = resources().getString(R.string.go_back) imageVector = Icons.Default.ArrowBack,
) contentDescription = resources().getString(R.string.go_back)
} )
}, }
actions = barAction },
) }, actions = barAction
)
},
) { paddingValues -> ) { paddingValues ->
content(paddingValues) content(paddingValues)
} }
@ -41,8 +43,10 @@ fun SecondaryScreenTemplate(
@Preview @Preview
@Composable @Composable
fun SecondaryScreenToolbarPreview() { fun SecondaryScreenToolbarPreview() {
StudeezTheme { SecondaryScreenTemplate( StudeezTheme {
"Preview screen", SecondaryScreenTemplate(
{} "Preview screen",
) {} } {}
) {}
}
} }

View file

@ -11,6 +11,6 @@ fun SimpleScreenTemplate(
title: String, title: String,
content: @Composable (PaddingValues) -> Unit content: @Composable (PaddingValues) -> Unit
) { ) {
Scaffold( topBar = { TopAppBar ( title = { Text(text = title) } ) } Scaffold(topBar = { TopAppBar(title = { Text(text = title) }) }
) { paddingValues -> content(paddingValues) } ) { paddingValues -> content(paddingValues) }
} }

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.unit.sp
fun Headline( fun Headline(
text: String text: String
) { ) {
Row ( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
@ -32,7 +32,7 @@ fun Headline(
fun DateText(date: String) { fun DateText(date: String) {
Text( Text(
text = date, text = date,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Medium,
fontSize = 20.sp, fontSize = 20.sp,
modifier = Modifier.padding(horizontal = 10.dp) modifier = Modifier.padding(horizontal = 10.dp)
) )

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.common.composable package be.ugent.sel.studeez.common.composable
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.background
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.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -23,8 +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 com.google.android.material.color.MaterialColors
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
@ -105,7 +102,7 @@ fun LabeledNumberInputField(
singleLine = singleLine, singleLine = singleLine,
label = { Text(resources().getString(label)) }, label = { Text(resources().getString(label)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
onValueChange = {typedInt -> onValueChange = { typedInt ->
val isNumber = typedInt.matches(Regex("[1-9]+\\d*]")) val isNumber = typedInt.matches(Regex("[1-9]+\\d*]"))
if (isNumber) { if (isNumber) {
number = typedInt.toInt() number = typedInt.toInt()
@ -164,12 +161,11 @@ fun LabeledErrorTextField(
} }
@Preview(showBackground = true)
@Preview(showBackground = true) @Composable
@Composable fun IntInputPreview() {
fun IntInputPreview() { LabeledNumberInputField(value = 1, onNewValue = {}, label = AppText.email)
LabeledNumberInputField(value = 1, onNewValue = {}, label = AppText.email) }
}
@Composable @Composable
fun PasswordField( fun PasswordField(
@ -227,12 +223,14 @@ fun SearchField(
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
onSubmit: () -> Unit, onSubmit: () -> Unit,
@StringRes label: Int, @StringRes label: Int,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
enabled: Boolean = true
) { ) {
OutlinedTextField( OutlinedTextField(
value = value, value = value,
onValueChange = onValueChange, onValueChange = onValueChange,
modifier = modifier, modifier = modifier,
enabled = enabled,
label = { Text(text = stringResource(id = label)) }, label = { Text(text = stringResource(id = label)) },
trailingIcon = { trailingIcon = {
IconButton(onClick = onSubmit) { IconButton(onClick = onSubmit) {

View file

@ -82,7 +82,11 @@ fun TimePickerButton(
} }
} }
private fun pickDuration(context: Context, onTimeChosen: (Int) -> Unit, timeState: MutableState<Int>) { private fun pickDuration(
context: Context,
onTimeChosen: (Int) -> Unit,
timeState: MutableState<Int>
) {
val listener = OnTimeSetListener { _, hour, minute -> val listener = OnTimeSetListener { _, hour, minute ->
timeState.value = HoursMinutesSeconds(hour, minute, 0).getTotalSeconds() timeState.value = HoursMinutesSeconds(hour, minute, 0).getTotalSeconds()
onTimeChosen(timeState.value) onTimeChosen(timeState.value)

View file

@ -2,12 +2,7 @@ package be.ugent.sel.studeez.common.composable.drawer
import android.content.Context import android.content.Context
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
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.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons

View file

@ -3,8 +3,6 @@ package be.ugent.sel.studeez.common.composable.drawer
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
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.navigation.StudeezDestinations import be.ugent.sel.studeez.navigation.StudeezDestinations

View file

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

View file

@ -52,11 +52,12 @@ fun FeedEntry(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Column( Column(
verticalArrangement = Arrangement.spacedBy(0.dp) verticalArrangement = Arrangement.spacedBy(0.dp),
modifier = Modifier.weight(13f)
) { ) {
Text( Text(
text = feedEntry.subJectName, text = feedEntry.subJectName,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Medium,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,
) )
@ -66,7 +67,10 @@ fun FeedEntry(
maxLines = 1, maxLines = 1,
) )
} }
Text(text = HoursMinutesSeconds(feedEntry.totalStudyTime).toString()) Text(
text = HoursMinutesSeconds(feedEntry.totalStudyTime).toString(),
modifier = Modifier.weight(6f),
)
} }
} }
val buttonText: Int = val buttonText: Int =

View file

@ -8,13 +8,13 @@ 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.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 +99,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
) )

View file

@ -2,10 +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.SEARCH_FRIENDS_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SELECT_SUBJECT import be.ugent.sel.studeez.navigation.StudeezDestinations.SELECT_SUBJECT
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN import be.ugent.sel.studeez.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
@ -26,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) {
@ -38,8 +39,7 @@ class NavigationBarViewModel @Inject constructor(
} }
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) {

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
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
@ -65,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(studytime).toString(), text = HoursMinutesSeconds(studytime).toString(),
color = MaterialTheme.colors.onBackground.copy(alpha = 0.6f)
) )
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -82,9 +84,13 @@ 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 = "${completedTaskCount}/${taskCount}")
} }
} }
} }

View file

@ -1,20 +1,20 @@
package be.ugent.sel.studeez.common.ext package be.ugent.sel.studeez.common.ext
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
fun Modifier.textButton(): Modifier { fun Modifier.textButton(): Modifier {
return this.fillMaxWidth().padding(16.dp, 8.dp, 16.dp, 0.dp) return this
.fillMaxWidth()
.padding(16.dp, 8.dp, 16.dp, 0.dp)
} }
fun Modifier.basicButton(): Modifier { fun Modifier.basicButton(): Modifier {
return this.fillMaxWidth().padding(16.dp, 8.dp) return this
.fillMaxWidth()
.padding(16.dp, 8.dp)
} }
fun Modifier.card(): Modifier { fun Modifier.card(): Modifier {
@ -30,7 +30,9 @@ fun Modifier.dropdownSelector(): Modifier {
} }
fun Modifier.fieldModifier(): Modifier { fun Modifier.fieldModifier(): Modifier {
return this.fillMaxWidth().padding(16.dp, 4.dp) return this
.fillMaxWidth()
.padding(16.dp, 4.dp)
} }
fun Modifier.toolbarActions(): Modifier { fun Modifier.toolbarActions(): Modifier {
@ -38,9 +40,13 @@ fun Modifier.toolbarActions(): Modifier {
} }
fun Modifier.spacer(): Modifier { fun Modifier.spacer(): Modifier {
return this.fillMaxWidth().padding(12.dp) return this
.fillMaxWidth()
.padding(12.dp)
} }
fun Modifier.smallSpacer(): Modifier { fun Modifier.smallSpacer(): Modifier {
return this.fillMaxWidth().height(8.dp) return this
.fillMaxWidth()
.height(8.dp)
} }

View file

@ -48,6 +48,6 @@ class SelectedTimerInfo @Inject constructor() : SelectedState<TimerInfo>() {
@Singleton @Singleton
class SelectedUserId @Inject constructor( class SelectedUserId @Inject constructor(
userDAO: UserDAO userDAO: UserDAO
): SelectedState<String>() { ) : SelectedState<String>() {
override var value: String = userDAO.getCurrentUserId() override var value: String = userDAO.getCurrentUserId()
} }

View file

@ -2,7 +2,6 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
import be.ugent.sel.studeez.data.local.models.SessionReport import be.ugent.sel.studeez.data.local.models.SessionReport
import com.google.firebase.Timestamp import com.google.firebase.Timestamp
import com.google.firebase.firestore.DocumentReference
abstract class FunctionalTimer(initialValue: Int) { abstract class FunctionalTimer(initialValue: Int) {
var time: Time = Time(initialValue) var time: Time = Time(initialValue)

View file

@ -2,7 +2,7 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) {
constructor(sec: Int): this( constructor(sec: Int) : this(
hours = sec / (60 * 60), hours = sec / (60 * 60),
minutes = (sec / (60)) % 60, minutes = (sec / (60)) % 60,
seconds = sec % 60, seconds = sec % 60,

View file

@ -8,13 +8,13 @@ class CustomTimerInfo(
description: String, description: String,
var studyTime: Int, var studyTime: Int,
id: String = "" id: String = ""
): TimerInfo(id, name, description) { ) : TimerInfo(id, name, description) {
override fun getFunctionalTimer(): FunctionalTimer { override fun getFunctionalTimer(): FunctionalTimer {
return FunctionalCustomTimer(studyTime) return FunctionalCustomTimer(studyTime)
} }
override fun asJson() : Map<String, Any> { override fun asJson(): Map<String, Any> {
return mapOf( return mapOf(
"type" to "custom", "type" to "custom",
"name" to name, "name" to name,

View file

@ -7,14 +7,14 @@ class EndlessTimerInfo(
name: String, name: String,
description: String, description: String,
id: String = "" id: String = ""
): TimerInfo(id, name, description) { ) : TimerInfo(id, name, description) {
override fun getFunctionalTimer(): FunctionalTimer { override fun getFunctionalTimer(): FunctionalTimer {
return FunctionalEndlessTimer() return FunctionalEndlessTimer()
} }
override fun asJson() : Map<String, Any> { override fun asJson(): Map<String, Any> {
return mapOf( return mapOf(
"type" to "endless", "type" to "endless",
"name" to name, "name" to name,

View file

@ -2,7 +2,6 @@ package be.ugent.sel.studeez.data.local.models.timer_info
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
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.data.local.models.timer_functional.FunctionalTimerVisitor
class PomodoroTimerInfo( class PomodoroTimerInfo(
name: String, name: String,
@ -11,14 +10,14 @@ class PomodoroTimerInfo(
var breakTime: Int, var breakTime: Int,
var repeats: Int, var repeats: Int,
id: String = "" id: String = ""
): TimerInfo(id, name, description) { ) : TimerInfo(id, name, description) {
override fun getFunctionalTimer(): FunctionalTimer { override fun getFunctionalTimer(): FunctionalTimer {
return FunctionalPomodoroTimer(studyTime, breakTime, repeats) return FunctionalPomodoroTimer(studyTime, breakTime, repeats)
} }
override fun asJson() : Map<String, Any> { override fun asJson(): Map<String, Any> {
return mapOf( return mapOf(
"type" to "break", "type" to "break",
"name" to name, "name" to name,

View file

@ -7,4 +7,7 @@ interface FeedDAO {
fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>> 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>>>>
} }

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.domain package be.ugent.sel.studeez.domain
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.timer_info.TimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -10,11 +9,6 @@ interface SessionDAO {
fun getSessions(): Flow<List<SessionReport>> fun getSessions(): Flow<List<SessionReport>>
suspend fun getSessionsOfUser(userId: String): List<SessionReport> suspend fun getSessionsOfUser(userId: String): List<SessionReport>
/**
* Return a list of pairs, containing the username and all the studysessions of that user.
*/
fun getFriendsSessions(): Flow<List<Pair<String,List<SessionReport>>>>
fun saveSession(newSessionReport: SessionReport) fun saveSession(newSessionReport: SessionReport)
fun deleteSession(newTimer: TimerInfo) fun deleteSession(newTimer: TimerInfo)

View file

@ -20,4 +20,5 @@ interface SubjectDAO {
fun getStudyTime(subject: Subject): Flow<Int> fun getStudyTime(subject: Subject): Flow<Int>
suspend fun getSubject(subjectId: String): Subject? suspend fun getSubject(subjectId: String): Subject?
suspend fun getSubjectOfUSer(subjectId: String, userId: String): Subject
} }

View file

@ -15,4 +15,6 @@ interface TaskDAO {
fun deleteTask(oldTask: Task) fun deleteTask(oldTask: Task)
suspend fun getTask(subjectId: String, taskId: String): Task suspend fun getTask(subjectId: String, taskId: String): Task
suspend fun getTaskFromUser(subjectId: String, taskId: String, userId: String): Task
} }

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.domain package be.ugent.sel.studeez.domain
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerJson
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface TimerDAO { interface TimerDAO {

View file

@ -1,12 +1,12 @@
package be.ugent.sel.studeez.domain.implementation package be.ugent.sel.studeez.domain.implementation
import be.ugent.sel.studeez.data.local.models.timer_info.* import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerJson
import be.ugent.sel.studeez.domain.ConfigurationService import be.ugent.sel.studeez.domain.ConfigurationService
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.get import com.google.firebase.remoteconfig.ktx.get
import com.google.firebase.remoteconfig.ktx.remoteConfig import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.google.gson.Gson
import kotlinx.coroutines.tasks.await import kotlinx.coroutines.tasks.await
import javax.inject.Inject import javax.inject.Inject

View file

@ -5,19 +5,19 @@ 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.task.Subject 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.Task
import be.ugent.sel.studeez.domain.FeedDAO import be.ugent.sel.studeez.domain.*
import be.ugent.sel.studeez.domain.SessionDAO
import be.ugent.sel.studeez.domain.SubjectDAO
import be.ugent.sel.studeez.domain.TaskDAO
import com.google.firebase.Timestamp import com.google.firebase.Timestamp
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import javax.inject.Inject import javax.inject.Inject
class FirebaseFeedDAO @Inject constructor( class FirebaseFeedDAO @Inject constructor(
private val friendshipDAO: FriendshipDAO,
private val sessionDAO: SessionDAO, private val sessionDAO: SessionDAO,
private val taskDAO: TaskDAO, private val taskDAO: TaskDAO,
private val subjectDAO: SubjectDAO private val subjectDAO: SubjectDAO,
private val auth: AccountDAO,
private val userDAO: UserDAO,
) : FeedDAO { ) : FeedDAO {
/** /**
@ -37,6 +37,45 @@ class FirebaseFeedDAO @Inject constructor(
} }
} }
/**
* 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 { private fun getFormattedTime(entry: FeedEntry): String {
return DateFormat.getDateInstance().format(entry.endTime.toDate()) return DateFormat.getDateInstance().format(entry.endTime.toDate())
} }
@ -67,6 +106,14 @@ class FirebaseFeedDAO @Inject constructor(
val task: Task = taskDAO.getTask(subjectId, taskId) val task: Task = taskDAO.getTask(subjectId, taskId)
val subject: Subject = subjectDAO.getSubject(subjectId)!! val subject: Subject = subjectDAO.getSubject(subjectId)!!
return makeFeedEntry(sessionReport, subject, task)
}
private fun makeFeedEntry(
sessionReport: SessionReport,
subject: Subject,
task: Task
): FeedEntry {
return FeedEntry( return FeedEntry(
argb_color = subject.argb_color, argb_color = subject.argb_color,
subJectName = subject.name, subJectName = subject.name,
@ -78,4 +125,20 @@ class FirebaseFeedDAO @Inject constructor(
isArchived = task.archived || subject.archived 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)
}
} }

View file

@ -1,11 +1,10 @@
package be.ugent.sel.studeez.domain.implementation package be.ugent.sel.studeez.domain.implementation
import androidx.compose.runtime.collectAsState
import be.ugent.sel.studeez.common.snackbar.SnackbarManager import be.ugent.sel.studeez.common.snackbar.SnackbarManager
import be.ugent.sel.studeez.data.local.models.Friendship 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.ACCEPTED
import be.ugent.sel.studeez.data.remote.FirebaseFriendship.FRIENDSSINCE
import be.ugent.sel.studeez.data.remote.FirebaseFriendship.FRIENDID import be.ugent.sel.studeez.data.remote.FirebaseFriendship.FRIENDID
import be.ugent.sel.studeez.data.remote.FirebaseFriendship.FRIENDSSINCE
import be.ugent.sel.studeez.domain.AccountDAO import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.FriendshipDAO 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.FRIENDS_COLLECTION
@ -27,7 +26,7 @@ import be.ugent.sel.studeez.R.string as AppText
class FirebaseFriendshipDAO @Inject constructor( class FirebaseFriendshipDAO @Inject constructor(
private val firestore: FirebaseFirestore, private val firestore: FirebaseFirestore,
private val auth: AccountDAO private val auth: AccountDAO
): FriendshipDAO { ) : FriendshipDAO {
private fun currentUserDocument(): DocumentReference = firestore private fun currentUserDocument(): DocumentReference = firestore
.collection(USER_COLLECTION) .collection(USER_COLLECTION)
@ -75,24 +74,44 @@ class FirebaseFriendshipDAO @Inject constructor(
val currentUserId: String = auth.currentUserId val currentUserId: String = auth.currentUserId
val otherUserId: String = id val otherUserId: String = id
// Add entry to current user // Check if the friendship already exists for the logged in user
currentUserDocument() var allowed = false
.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) firestore.collection(USER_COLLECTION)
.document(otherUserId) .document(currentUserId)
.collection(FRIENDS_COLLECTION) .collection(FRIENDS_COLLECTION)
.add(mapOf( .whereEqualTo(FRIENDID, otherUserId)
FRIENDID to currentUserId, .get()
ACCEPTED to true, // TODO Make it not automatically accepted. .addOnSuccessListener {
FRIENDSSINCE to Timestamp.now() 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 return true
} }

View file

@ -1,24 +1,15 @@
package be.ugent.sel.studeez.domain.implementation package be.ugent.sel.studeez.domain.implementation
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.timer_info.TimerInfo 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.AccountDAO import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.FriendshipDAO
import be.ugent.sel.studeez.domain.SessionDAO import be.ugent.sel.studeez.domain.SessionDAO
import be.ugent.sel.studeez.domain.UserDAO
import be.ugent.sel.studeez.domain.implementation.FirebaseCollections.SESSION_COLLECTION import be.ugent.sel.studeez.domain.implementation.FirebaseCollections.SESSION_COLLECTION
import be.ugent.sel.studeez.domain.implementation.FirebaseCollections.USER_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.CollectionReference
import com.google.firebase.firestore.FirebaseFirestore 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.snapshots
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.tasks.await import kotlinx.coroutines.tasks.await
import javax.inject.Inject import javax.inject.Inject
@ -26,8 +17,6 @@ import javax.inject.Inject
class FirebaseSessionDAO @Inject constructor( class FirebaseSessionDAO @Inject constructor(
private val firestore: FirebaseFirestore, private val firestore: FirebaseFirestore,
private val auth: AccountDAO, private val auth: AccountDAO,
private val userDAO: UserDAO,
private val friendshipDAO: FriendshipDAO
) : SessionDAO { ) : SessionDAO {
override fun getSessions(): Flow<List<SessionReport>> { override fun getSessions(): Flow<List<SessionReport>> {
@ -37,32 +26,13 @@ class FirebaseSessionDAO @Inject constructor(
} }
override suspend fun getSessionsOfUser(userId: String): List<SessionReport> { override suspend fun getSessionsOfUser(userId: String): List<SessionReport> {
val collection = firestore.collection(USER_COLLECTION) return firestore.collection(USER_COLLECTION)
.document(userId) .document(userId)
.collection(SESSION_COLLECTION) .collection(SESSION_COLLECTION)
.get().await() .get().await()
val list: MutableList<SessionReport> = mutableListOf() .map { it.toObject(SessionReport::class.java) }
for (document in collection) {
val id = document.id
val studyTime: Int = document.getField<Int>(STUDYTIME)!!
val endTime: Timestamp = document.getField<Timestamp>(ENDTIME)!!
list.add(SessionReport(id, studyTime, endTime))
}
return list
} }
override fun getFriendsSessions(): Flow<List<Pair<String, List<SessionReport>>>> {
return friendshipDAO.getAllFriendships(auth.currentUserId)
.map { friendships ->
friendships.map { friendship ->
val userId: String = friendship.friendId
val username = userDAO.getUsername(userId)
val userSessions = getSessionsOfUser(userId)
Pair(username, userSessions)
}
}
}
override fun saveSession(newSessionReport: SessionReport) { override fun saveSession(newSessionReport: SessionReport) {
currentUserSessionsCollection().add(newSessionReport) currentUserSessionsCollection().add(newSessionReport)

View file

@ -1,6 +1,5 @@
package be.ugent.sel.studeez.domain.implementation package be.ugent.sel.studeez.domain.implementation
import android.util.Log
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.task.SubjectDocument 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.Task
@ -35,6 +34,10 @@ class FirebaseSubjectDAO @Inject constructor(
return currentUserSubjectsCollection().document(subjectId).get().await().toObject() 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) { override fun saveSubject(newSubject: Subject) {
currentUserSubjectsCollection().add(newSubject) currentUserSubjectsCollection().add(newSubject)
} }
@ -74,14 +77,17 @@ class FirebaseSubjectDAO @Inject constructor(
.map { tasks -> tasks.sumOf { it.time } } .map { tasks -> tasks.sumOf { it.time } }
} }
private fun currentUserSubjectsCollection(): CollectionReference = private fun currentUserSubjectsCollection(id: String = auth.currentUserId): CollectionReference =
firestore.collection(FirebaseCollections.USER_COLLECTION) firestore.collection(FirebaseCollections.USER_COLLECTION)
.document(auth.currentUserId) .document(id)
.collection(FirebaseCollections.SUBJECT_COLLECTION) .collection(FirebaseCollections.SUBJECT_COLLECTION)
private fun subjectTasksCollection(subject: Subject): CollectionReference = private fun subjectTasksCollection(
subject: Subject,
id: String = auth.currentUserId
): CollectionReference =
firestore.collection(FirebaseCollections.USER_COLLECTION) firestore.collection(FirebaseCollections.USER_COLLECTION)
.document(auth.currentUserId) .document(id)
.collection(FirebaseCollections.SUBJECT_COLLECTION) .collection(FirebaseCollections.SUBJECT_COLLECTION)
.document(subject.id) .document(subject.id)
.collection(FirebaseCollections.TASK_COLLECTION) .collection(FirebaseCollections.TASK_COLLECTION)

View file

@ -30,6 +30,13 @@ class FirebaseTaskDAO @Inject constructor(
return selectedSubjectTasksCollection(subjectId).document(taskId).get().await().toObject()!! 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) { override fun saveTask(newTask: Task) {
selectedSubjectTasksCollection(newTask.subjectId).add(newTask) selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
} }
@ -44,9 +51,12 @@ class FirebaseTaskDAO @Inject constructor(
selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete() selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete()
} }
private fun selectedSubjectTasksCollection(subjectId: String): CollectionReference = private fun selectedSubjectTasksCollection(
subjectId: String,
id: String = auth.currentUserId
): CollectionReference =
firestore.collection(FirebaseCollections.USER_COLLECTION) firestore.collection(FirebaseCollections.USER_COLLECTION)
.document(auth.currentUserId) .document(id)
.collection(FirebaseCollections.SUBJECT_COLLECTION) .collection(FirebaseCollections.SUBJECT_COLLECTION)
.document(subjectId) .document(subjectId)
.collection(FirebaseCollections.TASK_COLLECTION) .collection(FirebaseCollections.TASK_COLLECTION)

View file

@ -1,6 +1,7 @@
package be.ugent.sel.studeez.domain.implementation package be.ugent.sel.studeez.domain.implementation
import be.ugent.sel.studeez.data.local.models.timer_info.* import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerJson
import be.ugent.sel.studeez.domain.AccountDAO import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.TimerDAO import be.ugent.sel.studeez.domain.TimerDAO
import com.google.firebase.firestore.CollectionReference import com.google.firebase.firestore.CollectionReference
@ -29,7 +30,7 @@ class FirebaseTimerDAO @Inject constructor(
// Wrap default timers in een flow en combineer met de userTimer flow. // Wrap default timers in een flow en combineer met de userTimer flow.
val defaultTimers: List<TimerInfo> = configurationService.getDefaultTimers() val defaultTimers: List<TimerInfo> = configurationService.getDefaultTimers()
val defaultTimersFlow: Flow<List<TimerInfo>> = flowOf(defaultTimers) val defaultTimersFlow: Flow<List<TimerInfo>> = flowOf(defaultTimers)
val userTimersFlow: Flow<List<TimerInfo>> = getUserTimers() val userTimersFlow: Flow<List<TimerInfo>> = getUserTimers()
return defaultTimersFlow.combine(userTimersFlow) { defaultTimersList, userTimersList -> return defaultTimersFlow.combine(userTimersFlow) { defaultTimersList, userTimersList ->
defaultTimersList + userTimersList defaultTimersList + userTimersList
} }

View file

@ -80,10 +80,12 @@ class FirebaseUserDAO @Inject constructor(
newUsername: String, newUsername: String,
newBiography: String newBiography: String
) { ) {
currentUserDocument().set(mapOf( currentUserDocument().set(
USERNAME to newUsername, mapOf(
BIOGRAPHY to newBiography USERNAME to newUsername,
)) BIOGRAPHY to newBiography
)
)
} }
override suspend fun deleteLoggedInUserReferences() { override suspend fun deleteLoggedInUserReferences() {

View file

@ -1,6 +1,7 @@
package be.ugent.sel.studeez.domain.implementation package be.ugent.sel.studeez.domain.implementation
import be.ugent.sel.studeez.data.local.models.timer_info.* import be.ugent.sel.studeez.data.local.models.timer_info.*
import be.ugent.sel.studeez.domain.implementation.ToTimerConverter.TimerFactory
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
@ -15,32 +16,38 @@ import com.google.gson.reflect.TypeToken
class ToTimerConverter { class ToTimerConverter {
fun interface TimerFactory { fun interface TimerFactory {
fun makeTimer(map: TimerJson) : TimerInfo fun makeTimer(map: TimerJson): TimerInfo
} }
private val timerInfoMap: Map<TimerType, TimerFactory> = mapOf( private val timerInfoMap: Map<TimerType, TimerFactory> = mapOf(
TimerType.ENDLESS to TimerFactory { EndlessTimerInfo( TimerType.ENDLESS to TimerFactory {
it.name, EndlessTimerInfo(
it.description, it.name,
it.id it.description,
) }, it.id
TimerType.CUSTOM to TimerFactory { CustomTimerInfo( )
it.name, },
it.description, TimerType.CUSTOM to TimerFactory {
it.studyTime, CustomTimerInfo(
it.id it.name,
) }, it.description,
TimerType.BREAK to TimerFactory { PomodoroTimerInfo( it.studyTime,
it.name, it.id
it.description, )
it.studyTime, },
it.breakTime, TimerType.BREAK to TimerFactory {
it.repeats, PomodoroTimerInfo(
it.id it.name,
) } it.description,
it.studyTime,
it.breakTime,
it.repeats,
it.id
)
}
) )
private fun getTimer(timerJson: TimerJson): TimerInfo{ private fun getTimer(timerJson: TimerJson): TimerInfo {
val type: TimerType = TimerType.valueOf(timerJson.type.uppercase()) val type: TimerType = TimerType.valueOf(timerJson.type.uppercase())
return timerInfoMap.getValue(type).makeTimer(timerJson) return timerInfoMap.getValue(type).makeTimer(timerJson)
} }

View file

@ -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

View file

@ -16,14 +16,14 @@ 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_overview.FriendsOveriewRoute
import be.ugent.sel.studeez.screens.friends.friends_search.SearchFriendsRoute 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.edit_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.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
@ -112,7 +112,12 @@ fun StudeezNavGraph(
composable(StudeezDestinations.TASKS_SCREEN) { composable(StudeezDestinations.TASKS_SCREEN) {
TaskRoute( TaskRoute(
goBack = { openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.TASKS_SCREEN) }, goBack = {
openAndPopUp(
StudeezDestinations.SUBJECT_SCREEN,
StudeezDestinations.TASKS_SCREEN
)
},
open = open, open = open,
viewModel = hiltViewModel(), viewModel = hiltViewModel(),
) )
@ -135,10 +140,11 @@ fun StudeezNavGraph(
} }
composable(StudeezDestinations.SESSIONS_SCREEN) { composable(StudeezDestinations.FRIENDS_FEED) {
SessionsRoute( FriendsFeedRoute(
drawerActions = drawerActions, drawerActions = drawerActions,
navigationBarActions = navigationBarActions navigationBarActions = navigationBarActions,
viewModel = hiltViewModel()
) )
} }

View file

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

View file

@ -23,15 +23,17 @@ class FriendsOverviewViewModel @Inject constructor(
logService: LogService logService: LogService
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var uiState = mutableStateOf(FriendsOverviewUiState( var uiState = mutableStateOf(
userId = selectedUserIdState.value FriendsOverviewUiState(
)) userId = selectedUserIdState.value
)
)
private set private set
fun getAllFriends(): Flow<List<Pair<User, Friendship>>> { fun getAllFriends(): Flow<List<Pair<User, Friendship>>> {
return friendshipDAO.getAllFriendships( return friendshipDAO.getAllFriendships(
userId = uiState.value.userId userId = uiState.value.userId
) )
.flatMapConcat { friendships -> .flatMapConcat { friendships ->
val userFlows = friendships.map { friendship -> val userFlows = friendships.map { friendship ->
userDAO.getUserDetails(friendship.friendId) userDAO.getUserDetails(friendship.friendId)

View file

@ -1,10 +1,8 @@
package be.ugent.sel.studeez.screens.friends.friends_search package be.ugent.sel.studeez.screens.friends.friends_search
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
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.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
@ -21,7 +19,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.ProfilePicture import be.ugent.sel.studeez.common.composable.ProfilePicture
import be.ugent.sel.studeez.common.composable.SearchField
import be.ugent.sel.studeez.common.composable.drawer.DrawerEntry import be.ugent.sel.studeez.common.composable.drawer.DrawerEntry
import be.ugent.sel.studeez.data.local.models.User import be.ugent.sel.studeez.data.local.models.User
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
@ -82,14 +79,16 @@ fun SearchFriendsScreen(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
SearchField( // TODO Make search field
value = query, // SearchField(
onValueChange = { newValue -> // value = uiState.queryString,
searchFriendsActions.onQueryStringChange(newValue) // onValueChange = friendsOverviewActions.onQueryStringChange,
query = newValue // onSubmit = friendsOverviewActions.onSubmit,
}, // label = AppText.search_friends,
onSubmit = { }, // enabled = false
label = AppText.search_friends // )
Text(
text = stringResource(id = AppText.searching_friends)
) )
}, },
navigationIcon = { navigationIcon = {
@ -106,7 +105,7 @@ fun SearchFriendsScreen(
LazyColumn( LazyColumn(
modifier = Modifier.padding(paddingValues) modifier = Modifier.padding(paddingValues)
) { ) {
items (searchResults.value) { user -> items(searchResults.value) { user ->
UserEntry( UserEntry(
user = user, user = user,
goToProfile = searchFriendsActions.goToProfile goToProfile = searchFriendsActions.goToProfile
@ -124,21 +123,29 @@ fun SearchFriendsPreview() {
popUp = {}, popUp = {},
uiState = SearchFriendUiState( uiState = SearchFriendUiState(
queryString = "dit is een test", queryString = "dit is een test",
searchResults = flowOf(listOf(User( searchResults = flowOf(
id = "someid", listOf(
username = "Eerste user", User(
biography = "blah blah blah" id = "someid",
))) username = "Eerste user",
biography = "blah blah blah"
)
)
)
), ),
searchFriendsActions = SearchFriendsActions( searchFriendsActions = SearchFriendsActions(
onQueryStringChange = {}, onQueryStringChange = {},
getUsersWithUsername = {}, getUsersWithUsername = {},
getAllUsers = { getAllUsers = {
flowOf(listOf(User( flowOf(
id = "someid", listOf(
username = "Eerste user", User(
biography = "blah blah blah" id = "someid",
))) username = "Eerste user",
biography = "blah blah blah"
)
)
)
}, },
goToProfile = { } goToProfile = { }
) )
@ -164,11 +171,11 @@ fun UserEntry(
ProfilePicture() ProfilePicture()
} }
Box ( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Column ( Column(
modifier = Modifier modifier = Modifier
.padding(vertical = 4.dp) .padding(vertical = 4.dp)
) { ) {
@ -179,7 +186,11 @@ fun UserEntry(
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Text( Text(
text = "${resources().getString(AppText.app_name)} ${resources().getString(AppText.friend)}", text = "${resources().getString(AppText.app_name)} ${
resources().getString(
AppText.friend
)
}",
fontSize = 14.sp, fontSize = 14.sp,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis

View file

@ -10,7 +10,7 @@ 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -18,7 +18,7 @@ class SearchFriendsViewModel @Inject constructor(
private val userDAO: UserDAO, private val userDAO: UserDAO,
private val selectedProfileState: SelectedUserId, private val selectedProfileState: SelectedUserId,
logService: LogService logService: LogService
): StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var uiState = mutableStateOf(SearchFriendUiState()) var uiState = mutableStateOf(SearchFriendUiState())
private set private set
@ -49,8 +49,8 @@ class SearchFriendsViewModel @Inject constructor(
*/ */
fun getAllUsers(): Flow<List<User>> { fun getAllUsers(): Flow<List<User>> {
return userDAO.getAllUsers() return userDAO.getAllUsers()
.filter { users -> .map { users ->
users.any { user -> users.filter { user ->
user.id != userDAO.getCurrentUserId() user.id != userDAO.getCurrentUserId()
} }
} }

View file

@ -0,0 +1,133 @@
package be.ugent.sel.studeez.screens.friends_feed
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.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.unit.dp
import be.ugent.sel.studeez.common.composable.DateText
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
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.data.local.models.FeedEntry
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText
@Composable
fun FriendsFeedRoute(
viewModel: FriendsFeedViewModel,
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions
) {
val uiState by viewModel.uiState.collectAsState()
FriendsFeedScreen(
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
uiState = uiState,
)
}
@Composable
fun FriendsFeedScreen(
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions,
uiState: FriendsFeedUiState,
) {
PrimaryScreenTemplate(
title = resources().getString(AppText.friends_feed),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
) {
when (uiState) {
FriendsFeedUiState.Loading -> LoadingFeed()
is FriendsFeedUiState.Succes -> {
val friendsSessions = uiState.friendSessions
LazyColumn {
// Default Timers, cannot be edited
items(friendsSessions) {
val (day, feedEntries) = it
DateText(date = day)
feedEntries.forEach { (name, feedEntry) ->
FriendsFeedEntry(name = name, feedEntry = feedEntry)
}
Spacer(modifier = Modifier.height(10.dp))
}
}
}
}
}
}
@Composable
fun FriendsFeedEntry(
name: String, feedEntry: FeedEntry
) {
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),
modifier = Modifier.weight(10f),
) {
Text(
text = "$name studied for ${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(),
modifier = Modifier
.weight(3f)
.padding(start = 5.dp),
)
}
}
}
}
}

View file

@ -0,0 +1,10 @@
package be.ugent.sel.studeez.screens.friends_feed
import be.ugent.sel.studeez.data.local.models.FeedEntry
sealed interface FriendsFeedUiState {
object Loading : FriendsFeedUiState
data class Succes(
val friendSessions: List<Pair<String, List<Pair<String, FeedEntry>>>>,
) : FriendsFeedUiState
}

View file

@ -0,0 +1,30 @@
package be.ugent.sel.studeez.screens.friends_feed
import androidx.lifecycle.viewModelScope
import be.ugent.sel.studeez.domain.FeedDAO
import be.ugent.sel.studeez.domain.LogService
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 javax.inject.Inject
@HiltViewModel
class FriendsFeedViewModel @Inject constructor(
feedDAO: FeedDAO,
logService: LogService
) : StudeezViewModel(logService) {
val uiState: StateFlow<FriendsFeedUiState> =
feedDAO.getFriendsSessions()
.map { it.toList() }
.map { FriendsFeedUiState.Succes(it) }
.stateIn(
scope = viewModelScope,
initialValue = FriendsFeedUiState.Loading,
started = SharingStarted.Eagerly,
)
}

View file

@ -1,4 +1,5 @@
package be.ugent.sel.studeez.screens.home package be.ugent.sel.studeez.screens.home
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

View file

@ -92,7 +92,8 @@ fun ProfileScreen(
item { item {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(5.dp), horizontalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier.fillMaxWidth() modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(align = Alignment.CenterHorizontally) .wrapContentWidth(align = Alignment.CenterHorizontally)
) { ) {
AmountOfFriendsButton( AmountOfFriendsButton(
@ -140,7 +141,7 @@ fun ProfileScreenPreview() {
fun AmountOfFriendsButton( fun AmountOfFriendsButton(
amountOfFriends: Int, amountOfFriends: Int,
onClick: () -> Unit onClick: () -> Unit
){ ) {
Button( Button(
onClick = onClick, onClick = onClick,
shape = defaultButtonShape() shape = defaultButtonShape()

View file

@ -5,7 +5,6 @@ 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 com.google.firebase.auth.FirebaseAuth
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject

View file

@ -82,7 +82,7 @@ fun EditProfileScreen(
) )
} }
item { item {
BasicTextButton( BasicTextButton(
text = AppText.delete_profile, text = AppText.delete_profile,
Modifier.textButton(), Modifier.textButton(),
action = editProfileActions.onDeleteClick action = editProfileActions.onDeleteClick

View file

@ -1,6 +1,6 @@
package be.ugent.sel.studeez.screens.profile.edit_profile package be.ugent.sel.studeez.screens.profile.edit_profile
data class ProfileEditUiState ( data class ProfileEditUiState(
val username: String = "", val username: String = "",
val biography: String = "" val biography: String = ""
) )

View file

@ -30,7 +30,7 @@ data class PublicProfileActions(
val getUserDetails: () -> Flow<User>, val getUserDetails: () -> Flow<User>,
val getAmountOfFriends: () -> Flow<Int>, val getAmountOfFriends: () -> Flow<Int>,
val onViewFriendsClick: () -> Unit, val onViewFriendsClick: () -> Unit,
val sendFriendRequest: () -> Boolean val sendFriendRequest: () -> Unit
) )
fun getPublicProfileActions( fun getPublicProfileActions(
@ -39,13 +39,17 @@ fun getPublicProfileActions(
): PublicProfileActions { ): PublicProfileActions {
return PublicProfileActions( return PublicProfileActions(
getUserDetails = { viewModel.getUserDetails(viewModel.uiState.value.userId) }, getUserDetails = { viewModel.getUserDetails(viewModel.uiState.value.userId) },
getAmountOfFriends = { viewModel.getAmountOfFriends( getAmountOfFriends = {
userId = viewModel.uiState.value.userId viewModel.getAmountOfFriends(
) }, userId = viewModel.uiState.value.userId
)
},
onViewFriendsClick = { viewModel.onViewFriendsClick(open) }, onViewFriendsClick = { viewModel.onViewFriendsClick(open) },
sendFriendRequest = { viewModel.sendFriendRequest( sendFriendRequest = {
userId = viewModel.uiState.value.userId viewModel.sendFriendRequest(
) } userId = viewModel.uiState.value.userId
)
}
) )
} }
@ -121,15 +125,17 @@ fun PublicProfilePreview() {
PublicProfileScreen( PublicProfileScreen(
publicProfileActions = PublicProfileActions( publicProfileActions = PublicProfileActions(
getUserDetails = { getUserDetails = {
flowOf(User( flowOf(
id = "someid", User(
username = "Maxime De Poorter", id = "someid",
biography = "I am a different student and this is my public profile" username = "Maxime De Poorter",
)) biography = "I am a different student and this is my public profile"
)
)
}, },
getAmountOfFriends = { flowOf(113) }, getAmountOfFriends = { flowOf(113) },
onViewFriendsClick = {}, onViewFriendsClick = {},
sendFriendRequest = { true } sendFriendRequest = {}
), ),
popUp = {} popUp = {}
) )
@ -138,7 +144,7 @@ fun PublicProfilePreview() {
@Composable @Composable
fun PublicProfileEllipsis( fun PublicProfileEllipsis(
sendFriendRequest: () -> Boolean sendFriendRequest: () -> Unit
) { ) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
@ -147,8 +153,7 @@ fun PublicProfileEllipsis(
) { ) {
Icon( Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_more_horizontal), imageVector = ImageVector.vectorResource(id = R.drawable.ic_more_horizontal),
contentDescription = resources().getString(AppText.view_more), contentDescription = resources().getString(AppText.view_more)
modifier = Modifier.fillMaxSize()
) )
} }
@ -172,7 +177,7 @@ fun PublicProfileEllipsis(
fun PublicProfileEllipsisPreview() { fun PublicProfileEllipsisPreview() {
StudeezTheme { StudeezTheme {
PublicProfileEllipsis( PublicProfileEllipsis(
sendFriendRequest = { true } sendFriendRequest = {}
) )
} }
} }

View file

@ -18,7 +18,7 @@ class PublicProfileViewModel @Inject constructor(
private val friendshipDAO: FriendshipDAO, private val friendshipDAO: FriendshipDAO,
selectedUserIdState: SelectedUserId, selectedUserIdState: SelectedUserId,
logService: LogService logService: LogService
): StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
val uiState = mutableStateOf( val uiState = mutableStateOf(
PublicProfileUiState( PublicProfileUiState(
@ -53,8 +53,8 @@ class PublicProfileViewModel @Inject constructor(
fun sendFriendRequest( fun sendFriendRequest(
userId: String userId: String
): Boolean { ) {
return friendshipDAO.sendFriendshipRequest(userId) friendshipDAO.sendFriendshipRequest(userId)
} }
} }

View file

@ -39,7 +39,8 @@ fun SessionRoute(
val soundPlayer = SoundPlayer(LocalContext.current) val soundPlayer = SoundPlayer(LocalContext.current)
val sessionActions = getSessionActions(viewModel, openAndPopUp) val sessionActions = getSessionActions(viewModel, openAndPopUp)
val sessionScreen = viewModel.getTimer().accept(GetSessionScreenComposable(soundPlayer, open, sessionActions)) val sessionScreen =
viewModel.getTimer().accept(GetSessionScreenComposable(soundPlayer, open, sessionActions))
sessionScreen() sessionScreen()
} }

View file

@ -21,7 +21,7 @@ class SoundPlayer(private val context: Context) {
private fun initPlayer(): MediaPlayer { private fun initPlayer(): MediaPlayer {
return MediaPlayer.create( return MediaPlayer.create(
context, context,
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
) )

View file

@ -27,7 +27,7 @@ fun BreakSessionScreenComposable(
sessionActions = sessionActions, sessionActions = sessionActions,
midSection = { Dots(pomodoroTimer = pomodoroTimer) }, midSection = { Dots(pomodoroTimer = pomodoroTimer) },
callMediaPlayer = { soundPlayer.playOn(pomodoroTimer.hasCurrentCountdownEnded()) }, callMediaPlayer = { soundPlayer.playOn(pomodoroTimer.hasCurrentCountdownEnded()) },
motivationString = { motivationString (pomodoroTimer = pomodoroTimer) } motivationString = { motivationString(pomodoroTimer = pomodoroTimer) }
) )
} }
@ -57,11 +57,13 @@ private fun Dots(pomodoroTimer: FunctionalPomodoroTimer): Int {
@Composable @Composable
private fun Dot(color: Color) { private fun Dot(color: Color) {
Box(modifier = Modifier Box(
.padding(5.dp) modifier = Modifier
.size(10.dp) .padding(5.dp)
.clip(CircleShape) .size(10.dp)
.background(color)) .clip(CircleShape)
.background(color)
)
} }

View file

@ -12,11 +12,12 @@ class GetSessionScreenComposable(
private val soundPlayer: SoundPlayer, private val soundPlayer: SoundPlayer,
private val open: (String) -> Unit, private val open: (String) -> Unit,
private val sessionActions: SessionActions private val sessionActions: SessionActions
) : ) :
FunctionalTimerVisitor<@Composable () -> Unit> { FunctionalTimerVisitor<@Composable () -> Unit> {
override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): @Composable () -> Unit { override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): @Composable () -> Unit {
return { CustomTimerSessionScreenComposable( return {
CustomTimerSessionScreenComposable(
open = open, open = open,
sessionActions = sessionActions, sessionActions = sessionActions,
soundPlayer = soundPlayer, soundPlayer = soundPlayer,

View file

@ -23,10 +23,10 @@ fun SessionScreen(
open: (String) -> Unit, open: (String) -> Unit,
sessionActions: SessionActions, sessionActions: SessionActions,
callMediaPlayer: () -> Unit = {}, callMediaPlayer: () -> Unit = {},
midSection: @Composable () -> Int = {0}, midSection: @Composable () -> Int = { 0 },
motivationString: @Composable () -> String, motivationString: @Composable () -> String,
) { ) {
Column( Column(
modifier = Modifier.padding(10.dp) modifier = Modifier.padding(10.dp)
) { ) {

View file

@ -135,9 +135,11 @@ fun SessionRecapScreenPreview() {
SessionRecapScreen( SessionRecapScreen(
modifier = Modifier, modifier = Modifier,
sessionRecapActions = SessionRecapActions( sessionRecapActions = SessionRecapActions(
{ SessionReport( {
studyTime = 100, SessionReport(
) }, studyTime = 100,
)
},
{}, {},
{}, {},
) )

View file

@ -1,42 +0,0 @@
package be.ugent.sel.studeez.screens.sessions
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText
@Composable
fun SessionsRoute(
// viewModel: SessionsViewModel,
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions
) {
SessionsScreen(
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
)
}
@Composable
fun SessionsScreen(
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions
) {
PrimaryScreenTemplate(
title = resources().getString(AppText.upcoming_sessions),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
) {
Text(
text = resources().getString(AppText.sessions_temp_description),
modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center
)
}
}

View file

@ -21,7 +21,7 @@ class SignUpViewModel @Inject constructor(
private val accountDAO: AccountDAO, private val accountDAO: AccountDAO,
private val userDAO: UserDAO, private val userDAO: UserDAO,
logService: LogService logService: LogService
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var uiState = mutableStateOf(SignUpUiState()) var uiState = mutableStateOf(SignUpUiState())
private set private set
@ -35,6 +35,7 @@ class SignUpViewModel @Inject constructor(
fun onUsernameChange(newValue: String) { fun onUsernameChange(newValue: String) {
uiState.value = uiState.value.copy(username = newValue) uiState.value = uiState.value.copy(username = newValue)
} }
fun onEmailChange(newValue: String) { fun onEmailChange(newValue: String) {
uiState.value = uiState.value.copy(email = newValue) uiState.value = uiState.value.copy(email = newValue)
} }

View file

@ -26,7 +26,7 @@ class SplashViewModel @Inject constructor(
showError.value = false showError.value = false
if (accountDAO.hasUser) { if (accountDAO.hasUser) {
openAndPopUp(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SPLASH_SCREEN) openAndPopUp(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SPLASH_SCREEN)
} else{ } else {
openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.SPLASH_SCREEN) openAndPopUp(StudeezDestinations.SIGN_UP_SCREEN, StudeezDestinations.SPLASH_SCREEN)
} }
} }

View file

@ -9,7 +9,7 @@ import be.ugent.sel.studeez.screens.timer_form.form_screens.BreakTimerFormScreen
import be.ugent.sel.studeez.screens.timer_form.form_screens.CustomTimerFormScreen import be.ugent.sel.studeez.screens.timer_form.form_screens.CustomTimerFormScreen
import be.ugent.sel.studeez.screens.timer_form.form_screens.EndlessTimerFormScreen import be.ugent.sel.studeez.screens.timer_form.form_screens.EndlessTimerFormScreen
class GetTimerFormScreen: TimerInfoVisitor<AbstractTimerFormScreen> { class GetTimerFormScreen : TimerInfoVisitor<AbstractTimerFormScreen> {
override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): AbstractTimerFormScreen { override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): AbstractTimerFormScreen {
return CustomTimerFormScreen(customTimerInfo) return CustomTimerFormScreen(customTimerInfo)

View file

@ -18,10 +18,10 @@ fun TimerAddRoute(
TimerFormScreen( TimerFormScreen(
popUp = popUp, popUp = popUp,
getTimerInfo = viewModel::getTimerInfo, getTimerInfo = viewModel::getTimerInfo,
extraButton= { }, extraButton = { },
AppText.add_timer AppText.add_timer
) { ) {
viewModel.saveTimer(it, goBack = {popUp(); popUp()}) viewModel.saveTimer(it, goBack = { popUp(); popUp() })
} }
} }
@ -42,7 +42,7 @@ fun TimerEditRoute(
TimerFormScreen( TimerFormScreen(
popUp = popUp, popUp = popUp,
getTimerInfo = viewModel::getTimerInfo, getTimerInfo = viewModel::getTimerInfo,
extraButton= { deleteButton() }, extraButton = { deleteButton() },
AppText.edit_timer AppText.edit_timer
) { ) {
viewModel.editTimer(it, goBack = popUp) viewModel.editTimer(it, goBack = popUp)

View file

@ -1,7 +1,8 @@
package be.ugent.sel.studeez.screens.timer_form.form_screens package be.ugent.sel.studeez.screens.timer_form.form_screens
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R

View file

@ -1,6 +1,8 @@
package be.ugent.sel.studeez.screens.timer_form.form_screens package be.ugent.sel.studeez.screens.timer_form.form_screens
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
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
@ -13,8 +15,7 @@ import be.ugent.sel.studeez.R.string as AppText
class BreakTimerFormScreen( class BreakTimerFormScreen(
private val breakTimerInfo: PomodoroTimerInfo private val breakTimerInfo: PomodoroTimerInfo
): AbstractTimerFormScreen(breakTimerInfo) { ) : AbstractTimerFormScreen(breakTimerInfo) {
@Composable @Composable
@ -28,7 +29,7 @@ class BreakTimerFormScreen(
breakTimerInfo.breakTime = newTime breakTimerInfo.breakTime = newTime
} }
valids["repeats"] = remember {mutableStateOf(true)} valids["repeats"] = remember { mutableStateOf(true) }
firsts["repeats"] = remember { mutableStateOf(true) } firsts["repeats"] = remember { mutableStateOf(true) }
LabeledErrorTextField( LabeledErrorTextField(

View file

@ -9,7 +9,7 @@ import be.ugent.sel.studeez.R.string as AppText
class CustomTimerFormScreen( class CustomTimerFormScreen(
private val customTimerInfo: CustomTimerInfo private val customTimerInfo: CustomTimerInfo
): AbstractTimerFormScreen(customTimerInfo) { ) : AbstractTimerFormScreen(customTimerInfo) {
@Composable @Composable
override fun ExtraFields() { override fun ExtraFields() {

View file

@ -7,7 +7,7 @@ import be.ugent.sel.studeez.ui.theme.StudeezTheme
class EndlessTimerFormScreen( class EndlessTimerFormScreen(
endlessTimerInfo: EndlessTimerInfo endlessTimerInfo: EndlessTimerInfo
): AbstractTimerFormScreen(endlessTimerInfo) { ) : AbstractTimerFormScreen(endlessTimerInfo) {
} }
@Preview @Preview

View file

@ -1,6 +1,8 @@
package be.ugent.sel.studeez.screens.timer_form.timer_type_select package be.ugent.sel.studeez.screens.timer_form.timer_type_select
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -12,10 +14,8 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.data.local.models.timer_info.* import be.ugent.sel.studeez.data.local.models.timer_info.*
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.*
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.CUSTOM
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.BREAK
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.ENDLESS
val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf( val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf(
CUSTOM to CustomTimerInfo("", "", 0), CUSTOM to CustomTimerInfo("", "", 0),
@ -40,7 +40,9 @@ fun TimerTypeSelectScreen(
val default: TimerInfo = defaultTimerInfo.getValue(timerType) val default: TimerInfo = defaultTimerInfo.getValue(timerType)
Button( Button(
onClick = { viewModel.onTimerTypeChosen(default, open) }, onClick = { viewModel.onTimerTypeChosen(default, open) },
modifier = Modifier.fillMaxWidth().padding(5.dp) modifier = Modifier
.fillMaxWidth()
.padding(5.dp)
) { ) {
Text(text = timerType.name) Text(text = timerType.name)
} }

View file

@ -68,11 +68,13 @@ fun TimerOverviewScreen(
LazyColumn { LazyColumn {
// Custom timer, select new duration each time // Custom timer, select new duration each time
item { item {
TimerEntry(timerInfo = CustomTimerInfo( TimerEntry(
name = resources().getString(R.string.custom_name), timerInfo = CustomTimerInfo(
description = resources().getString(R.string.custom_name), name = resources().getString(R.string.custom_name),
studyTime = 0 description = resources().getString(R.string.custom_name),
)) studyTime = 0
)
)
} }
// Default Timers, cannot be edited // Default Timers, cannot be edited
items(timerOverviewActions.getDefaultTimers()) { items(timerOverviewActions.getDefaultTimers()) {

View file

@ -2,6 +2,6 @@ package be.ugent.sel.studeez.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val Blue100 = Color( 30, 100, 200, 255) val Blue100 = Color(30, 100, 200, 255)
val Blue120 = Color( 27, 90, 180, 255) val Blue120 = Color(27, 90, 180, 255)
val Yellow100 = Color(255, 210, 0, 255) val Yellow100 = Color(255, 210, 0, 255)

View file

@ -36,6 +36,9 @@
<string name="your_feed">This is your feed</string> <string name="your_feed">This is your feed</string>
<string name="empty_feed_help_text">Click here to create you first subject and tasks to get started</string> <string name="empty_feed_help_text">Click here to create you first subject and tasks to get started</string>
<!-- Friends Feed -->
<string name="friends_feed">Feed</string>
<!-- Tasks --> <!-- Tasks -->
<string name="tasks">Tasks</string> <string name="tasks">Tasks</string>
<string name="task">Task</string> <string name="task">Task</string>
@ -134,6 +137,9 @@
<string name="send_friend_request">Send friend request</string> <string name="send_friend_request">Send friend request</string>
<string name="remove_friend">Remove as friend</string> <string name="remove_friend">Remove as friend</string>
<string name="show_profile">Show profile</string> <string name="show_profile">Show profile</string>
<string name="click_search_friends">Click to search friends</string>
<string name="searching_friends">Searching friends</string>
<string name="already_friend">You are already befriended with that person.</string>
<!-- ========== Create & edit screens ========== --> <!-- ========== Create & edit screens ========== -->