Merge pull request #103 from SELab1/feed

Feed
This commit is contained in:
brreynie 2023-05-09 19:33:53 +02:00 committed by GitHub Enterprise
commit d9d83569db
26 changed files with 549 additions and 51 deletions

View file

@ -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
@ -23,4 +26,14 @@ fun Headline(
fontSize = 34.sp fontSize = 34.sp
) )
} }
}
@Composable
fun DateText(date: String) {
Text(
text = date,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
modifier = Modifier.padding(horizontal = 10.dp)
)
} }

View file

@ -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.Bold
)
}
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 {} }, {}
)
}

View file

@ -0,0 +1,96 @@
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.R
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
@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.Bold,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
Text(
text = feedEntry.taskName,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
Text(text = HoursMinutesSeconds(feedEntry.totalStudyTime).toString())
}
}
StealthButton(
text = R.string.continue_task,
modifier = Modifier
.padding(start = 10.dp, end = 5.dp)
.weight(6f)
) {
continueWithTask()
}
}
}
}
@Preview
@Composable
fun FeedEntryPreview() {
FeedEntry(
continueWithTask = {},
feedEntry = FeedEntry(
argb_color = 0xFFFFD200,
subJectName = "Test Subject",
taskName = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkk",
totalStudyTime = 20,
)
)
}

View file

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

View file

@ -0,0 +1,43 @@
package be.ugent.sel.studeez.common.composable.feed
import androidx.lifecycle.viewModelScope
import be.ugent.sel.studeez.data.SelectedTask
import be.ugent.sel.studeez.data.local.models.FeedEntry
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.*
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)
}
}

View file

@ -32,6 +32,7 @@ fun TaskEntry(
task: Task, task: Task,
onCheckTask: (Boolean) -> Unit, onCheckTask: (Boolean) -> Unit,
onDeleteTask: () -> Unit, onDeleteTask: () -> Unit,
onStartTask: () -> Unit
) { ) {
Card( Card(
modifier = Modifier modifier = Modifier
@ -95,6 +96,7 @@ fun TaskEntry(
modifier = Modifier modifier = Modifier
.padding(end = 5.dp), .padding(end = 5.dp),
) { ) {
onStartTask()
} }
} }
} }
@ -110,7 +112,7 @@ fun TaskEntryPreview() {
name = "Test Task", name = "Test Task",
completed = false, completed = false,
), ),
{}, {}, {}, {}, {}
) )
} }
@ -122,7 +124,7 @@ fun CompletedTaskEntryPreview() {
name = "Test Task", name = "Test Task",
completed = true, completed = true,
), ),
{}, {}, {}, {}, {},
) )
} }
@ -134,6 +136,6 @@ fun OverflowTaskEntryPreview() {
name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk", name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk",
completed = false, completed = false,
), ),
{}, {}, {}, {}, {}
) )
} }

View file

@ -0,0 +1,13 @@
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)
)

View file

@ -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 = ""
) )

View file

@ -2,6 +2,7 @@ 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)
@ -17,10 +18,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
) )
} }

View file

@ -33,4 +33,7 @@ abstract class DatabaseModule {
@Binds @Binds
abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO
@Binds
abstract fun provideFeedDAO(impl: FirebaseFeedDAO): FeedDAO
} }

View file

@ -0,0 +1,10 @@
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>>>
}

View file

@ -12,4 +12,5 @@ interface SubjectDAO {
fun deleteSubject(oldSubject: Subject) fun deleteSubject(oldSubject: Subject)
fun updateSubject(newSubject: Subject) fun updateSubject(newSubject: Subject)
suspend fun getSubject(subjectId: String): Subject?
} }

View file

@ -15,4 +15,6 @@ interface TaskDAO {
fun deleteTask(oldTask: Task) fun deleteTask(oldTask: Task)
fun toggleTaskCompleted(task: Task, completed: Boolean) fun toggleTaskCompleted(task: Task, completed: Boolean)
suspend fun getTask(subjectId: String, taskId: String): Task
} }

View file

@ -1,13 +1,16 @@
package be.ugent.sel.studeez.domain.implementation 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.Subject
import be.ugent.sel.studeez.data.local.models.task.Task
import be.ugent.sel.studeez.domain.AccountDAO import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.SubjectDAO import be.ugent.sel.studeez.domain.SubjectDAO
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.snapshots import com.google.firebase.firestore.ktx.snapshots
import com.google.firebase.firestore.ktx.toObject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.tasks.await
import javax.inject.Inject import javax.inject.Inject
class FireBaseSubjectDAO @Inject constructor( class FireBaseSubjectDAO @Inject constructor(
@ -20,6 +23,10 @@ class FireBaseSubjectDAO @Inject constructor(
.map { it.toObjects(Subject::class.java) } .map { it.toObjects(Subject::class.java) }
} }
override suspend fun getSubject(subjectId: String): Subject? {
return currentUserSubjectsCollection().document(subjectId).get().await().toObject()
}
override fun saveSubject(newSubject: Subject) { override fun saveSubject(newSubject: Subject) {
currentUserSubjectsCollection().add(newSubject) currentUserSubjectsCollection().add(newSubject)
} }

View file

@ -8,8 +8,11 @@ import be.ugent.sel.studeez.domain.TaskDAO
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.snapshots import com.google.firebase.firestore.ktx.snapshots
import com.google.firebase.firestore.ktx.toObject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.tasks.await
import javax.inject.Inject import javax.inject.Inject
class FireBaseTaskDAO @Inject constructor( class FireBaseTaskDAO @Inject constructor(
@ -22,6 +25,10 @@ class FireBaseTaskDAO @Inject constructor(
.map { it.toObjects(Task::class.java) } .map { it.toObjects(Task::class.java) }
} }
override suspend fun getTask(subjectId: String, taskId: String): Task {
return selectedSubjectTasksCollection(subjectId).document(taskId).get().await().toObject()!!
}
override fun saveTask(newTask: Task) { override fun saveTask(newTask: Task) {
selectedSubjectTasksCollection(newTask.subjectId).add(newTask) selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
} }

View file

@ -0,0 +1,78 @@
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.FeedDAO
import be.ugent.sel.studeez.domain.SessionDAO
import be.ugent.sel.studeez.domain.TaskDAO
import com.google.firebase.Timestamp
import kotlinx.coroutines.flow.*
import javax.inject.Inject
class FirebaseFeedDAO @Inject constructor(
private val sessionDAO: SessionDAO,
private val taskDAO: TaskDAO,
private val subjectDAO: FireBaseSubjectDAO
) : 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()) }
}
}
}
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 FeedEntry(
argb_color = subject.argb_color,
subJectName = subject.name,
taskName = task.name,
taskId = task.id,
subjectId = subject.id,
totalStudyTime = sessionReport.studyTime,
endTime = sessionReport.endTime
)
}
}

View file

@ -65,9 +65,9 @@ fun StudeezNavGraph(
composable(StudeezDestinations.HOME_SCREEN) { composable(StudeezDestinations.HOME_SCREEN) {
HomeRoute( HomeRoute(
open, open,
viewModel = hiltViewModel(),
drawerActions = drawerActions, drawerActions = drawerActions,
navigationBarActions = navigationBarActions navigationBarActions = navigationBarActions,
feedViewModel = hiltViewModel(),
) )
} }

View file

@ -5,35 +5,45 @@ 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
fun HomeRoute( fun HomeRoute(
open: (String) -> Unit, open: (String) -> Unit,
viewModel: HomeViewModel,
drawerActions: DrawerActions, drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions, navigationBarActions: NavigationBarActions,
feedViewModel: FeedViewModel,
) { ) {
val feedUiState by feedViewModel.uiState.collectAsState()
HomeScreen( HomeScreen(
onStartSessionClick = { viewModel.onStartSessionClick(open) },
drawerActions = drawerActions, drawerActions = drawerActions,
open = open,
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, open: (String) -> 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),
@ -41,9 +51,7 @@ fun HomeScreen(
navigationBarActions = navigationBarActions, navigationBarActions = navigationBarActions,
// TODO barAction = { FriendsAction() } // TODO barAction = { FriendsAction() }
) { ) {
BasicButton(R.string.start_session, Modifier.basicButton()) { Feed(feedUiState, continueTask, onEmptyFeedHelp)
onStartSessionClick()
}
} }
} }
@ -61,8 +69,40 @@ fun FriendsAction() {
@Composable @Composable
fun HomeScreenPreview() { fun HomeScreenPreview() {
HomeScreen( HomeScreen(
onStartSessionClick = {},
drawerActions = DrawerActions({}, {}, {}, {}, {}), drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
open = {},
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 = {}
) )
} }

View file

@ -1,19 +0,0 @@
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.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val accountDAO: AccountDAO,
logService: LogService
) : StudeezViewModel(logService) {
fun onStartSessionClick(open: (String) -> Unit) {
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
}
}

View file

@ -1,7 +1,9 @@
package be.ugent.sel.studeez.screens.session package be.ugent.sel.studeez.screens.session
import be.ugent.sel.studeez.data.SelectedTask
import be.ugent.sel.studeez.data.SelectedTimerState import be.ugent.sel.studeez.data.SelectedTimerState
import be.ugent.sel.studeez.data.SessionReportState import be.ugent.sel.studeez.data.SessionReportState
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_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
@ -13,21 +15,22 @@ import javax.inject.Inject
class SessionViewModel @Inject constructor( class SessionViewModel @Inject constructor(
private val selectedTimerState: SelectedTimerState, private val selectedTimerState: SelectedTimerState,
private val sessionReportState: SessionReportState, private val sessionReportState: SessionReportState,
private val selectedTask: SelectedTask,
logService: LogService logService: LogService
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
private val task : String = "No task selected" // placeholder for tasks implementation private val task : Task = selectedTask()
fun getTimer() : FunctionalTimer { fun getTimer() : FunctionalTimer {
return selectedTimerState.selectedTimer!! return selectedTimerState.selectedTimer!!
} }
fun getTask(): String { fun getTask(): String {
return task return task.name
} }
fun endSession(openAndPopUp: (String, String) -> Unit) { fun endSession(openAndPopUp: (String, String) -> Unit) {
sessionReportState.sessionReport = getTimer().getSessionReport() sessionReportState.sessionReport = getTimer().getSessionReport(task.subjectId, task.id)
openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN) openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN)
} }
} }

View file

@ -30,6 +30,7 @@ data class TaskActions(
val deleteTask: (Task) -> Unit, val deleteTask: (Task) -> Unit,
val onCheckTask: (Task, Boolean) -> Unit, val onCheckTask: (Task, Boolean) -> Unit,
val editSubject: () -> Unit, val editSubject: () -> Unit,
val startTask: (Task) -> Unit
) )
fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions { fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions {
@ -39,7 +40,8 @@ fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskAction
getSubject = viewModel::getSelectedSubject, getSubject = viewModel::getSelectedSubject,
deleteTask = viewModel::deleteTask, deleteTask = viewModel::deleteTask,
onCheckTask = { task, isChecked -> viewModel.toggleTaskCompleted(task, isChecked) }, onCheckTask = { task, isChecked -> viewModel.toggleTaskCompleted(task, isChecked) },
editSubject = { viewModel.editSubject(open) } editSubject = { viewModel.editSubject(open) },
startTask = { task -> viewModel.startTask(task, open) }
) )
} }
@ -75,6 +77,7 @@ fun TaskScreen(
task = it, task = it,
onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) }, onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) },
onDeleteTask = { taskActions.deleteTask(it) }, onDeleteTask = { taskActions.deleteTask(it) },
onStartTask = { taskActions.startTask(it) }
) )
} }
} }
@ -108,6 +111,7 @@ fun TaskScreenPreview() {
{}, {},
{ _, _ -> run {} }, { _, _ -> run {} },
{}, {},
{}
) )
) )
} }

View file

@ -1,6 +1,7 @@
package be.ugent.sel.studeez.screens.tasks package be.ugent.sel.studeez.screens.tasks
import be.ugent.sel.studeez.data.SelectedSubject import be.ugent.sel.studeez.data.SelectedSubject
import be.ugent.sel.studeez.data.SelectedTask
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.LogService import be.ugent.sel.studeez.domain.LogService
@ -17,6 +18,7 @@ class TaskViewModel @Inject constructor(
private val taskDAO: TaskDAO, private val taskDAO: TaskDAO,
private val subjectDAO: SubjectDAO, private val subjectDAO: SubjectDAO,
private val selectedSubject: SelectedSubject, private val selectedSubject: SelectedSubject,
private val selectedTask: SelectedTask,
logService: LogService, logService: LogService,
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
fun addTask(open: (String) -> Unit) { fun addTask(open: (String) -> Unit) {
@ -47,4 +49,9 @@ class TaskViewModel @Inject constructor(
fun editSubject(open: (String) -> Unit) { fun editSubject(open: (String) -> Unit) {
open(StudeezDestinations.EDIT_SUBJECT_FORM) open(StudeezDestinations.EDIT_SUBJECT_FORM)
} }
fun startTask(task: Task, open: (String) -> Unit) {
selectedTask.set(task)
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
}
} }

View file

@ -82,12 +82,13 @@ fun TimerOverviewScreen(
items(timers.value) { timerInfo -> items(timers.value) { timerInfo ->
TimerEntry( TimerEntry(
timerInfo = timerInfo, timerInfo = timerInfo,
) { rightButton = {
StealthButton( StealthButton(
text = R.string.edit, text = R.string.edit,
onClick = { timerOverviewActions.onEditClick(timerInfo) } onClick = { timerOverviewActions.onEditClick(timerInfo) }
) )
} }
)
} }

View file

@ -1,10 +1,13 @@
package be.ugent.sel.studeez.screens.timer_selection package be.ugent.sel.studeez.screens.timer_selection
import androidx.compose.foundation.layout.padding
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.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
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.SecondaryScreenTemplate import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.StealthButton import be.ugent.sel.studeez.common.composable.StealthButton
@ -99,7 +102,10 @@ fun CustomTimerEntry(
) )
}, },
rightButton = { rightButton = {
TimePickerButton(initialSeconds = hms.getTotalSeconds()) { chosenTime -> TimePickerButton(
initialSeconds = hms.getTotalSeconds(),
modifier = Modifier.padding(horizontal = 5.dp)
) { chosenTime ->
timerInfo.studyTime = chosenTime timerInfo.studyTime = chosenTime
} }
} }

View file

@ -5,6 +5,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import be.ugent.sel.studeez.data.SelectedTimerState import be.ugent.sel.studeez.data.SelectedTimerState
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
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.domain.LogService import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.TimerDAO import be.ugent.sel.studeez.domain.TimerDAO
@ -21,7 +22,9 @@ class TimerSelectionViewModel @Inject constructor(
logService: LogService logService: LogService
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var customTimerStudyTime: MutableState<Int> = mutableStateOf(0) var customTimerStudyTime: MutableState<Int> = mutableStateOf(
HoursMinutesSeconds(1, 0, 0).getTotalSeconds()
)
fun getAllTimers() : Flow<List<TimerInfo>> { fun getAllTimers() : Flow<List<TimerInfo>> {
return timerDAO.getAllTimers() return timerDAO.getAllTimers()

View file

@ -29,6 +29,11 @@
<string name="home">Home</string> <string name="home">Home</string>
<string name="start_session">Start session</string> <string name="start_session">Start session</string>
<!-- Feed-->
<string name="continue_task">Continue</string>
<string name="your_feed">This is your feed</string>
<string name="empty_feed_help_text">Create you first subject and tasks to get started</string>
<!-- Tasks --> <!-- Tasks -->
<string name="tasks">Tasks</string> <string name="tasks">Tasks</string>
<string name="task">Task</string> <string name="task">Task</string>