Added course detail screens
This commit is contained in:
@@ -6,18 +6,21 @@ import androidx.room.*
|
|||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface AnnouncementDao {
|
interface AnnouncementDao {
|
||||||
@Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn ASC")
|
@Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn DESC")
|
||||||
fun getAll1(courseId: Long): DataSource.Factory<Int, Announcement>
|
fun getAll1(courseId: Long): DataSource.Factory<Int, Announcement>
|
||||||
|
|
||||||
@Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn ASC")
|
@Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn DESC")
|
||||||
fun getAll2(courseId: Long): List<Announcement>
|
fun getAll2(courseId: Long): List<Announcement>
|
||||||
|
|
||||||
@Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn ASC")
|
@Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn DESC")
|
||||||
fun getAll3(courseId: Long): LiveData<List<Announcement>>
|
fun getAll3(courseId: Long): LiveData<List<Announcement>>
|
||||||
|
|
||||||
@Query("SELECT * FROM announcement WHERE uid = :announcementId LIMIT 1")
|
@Query("SELECT * FROM announcement WHERE uid = :announcementId LIMIT 1")
|
||||||
fun getAnnouncementById(announcementId: Long): Announcement
|
fun getAnnouncementById(announcementId: Long): Announcement
|
||||||
|
|
||||||
|
@Query("SELECT * FROM announcement WHERE uid = :announcementId LIMIT 1")
|
||||||
|
fun getAnnouncementById2(announcementId: Long): LiveData<Announcement>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun insert(announcement: Announcement): Long
|
fun insert(announcement: Announcement): Long
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ data class Course (
|
|||||||
actCtx: Context,
|
actCtx: Context,
|
||||||
type: Notifications.CourseUpdateType,
|
type: Notifications.CourseUpdateType,
|
||||||
data: JsonObject
|
data: JsonObject
|
||||||
): CharSequence? = when (type) {
|
): CharSequence = when (type) {
|
||||||
Notifications.CourseUpdateType.REMOVED -> actCtx.getHtmlSpannedString(R.string.not_course_update_course_removed, data.string("title"))
|
Notifications.CourseUpdateType.REMOVED -> actCtx.getHtmlSpannedString(R.string.not_course_update_course_removed, data.string("title"))
|
||||||
Notifications.CourseUpdateType.UPDATED -> actCtx.getHtmlSpannedString(R.string.not_course_update_course_updated, data.string("title"))
|
Notifications.CourseUpdateType.UPDATED -> actCtx.getHtmlSpannedString(R.string.not_course_update_course_updated, data.string("title"))
|
||||||
Notifications.CourseUpdateType.ADDED -> actCtx.getHtmlSpannedString(R.string.not_course_update_course_added, data.string("title"))
|
Notifications.CourseUpdateType.ADDED -> actCtx.getHtmlSpannedString(R.string.not_course_update_course_added, data.string("title"))
|
||||||
@@ -72,7 +72,7 @@ data class Course (
|
|||||||
actCtx: Context,
|
actCtx: Context,
|
||||||
type: Notifications.CourseUpdateType,
|
type: Notifications.CourseUpdateType,
|
||||||
data: JsonObject
|
data: JsonObject
|
||||||
): CharSequence? = when (type) {
|
): CharSequence = when (type) {
|
||||||
Notifications.CourseUpdateType.REMOVED -> actCtx.getHtmlSpannedString(R.string.adapter_course_update_course_removed, data.string("title"))
|
Notifications.CourseUpdateType.REMOVED -> actCtx.getHtmlSpannedString(R.string.adapter_course_update_course_removed, data.string("title"))
|
||||||
Notifications.CourseUpdateType.UPDATED -> actCtx.getHtmlSpannedString(R.string.adapter_course_update_course_updated, data.string("title"))
|
Notifications.CourseUpdateType.UPDATED -> actCtx.getHtmlSpannedString(R.string.adapter_course_update_course_updated, data.string("title"))
|
||||||
Notifications.CourseUpdateType.ADDED -> actCtx.getHtmlSpannedString(R.string.adapter_course_update_course_added, data.string("title"))
|
Notifications.CourseUpdateType.ADDED -> actCtx.getHtmlSpannedString(R.string.adapter_course_update_course_added, data.string("title"))
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import de.sebse.fuplanner2.ui.courses.CoursesScreen
|
import de.sebse.fuplanner2.ui.courses.CoursesScreen
|
||||||
import de.sebse.fuplanner2.ui.details.CourseDetailsScreen
|
import de.sebse.fuplanner2.ui.details.CourseDetailsScreen
|
||||||
|
import de.sebse.fuplanner2.ui.details_announcements.CourseAnnouncementScreen
|
||||||
|
import de.sebse.fuplanner2.ui.details_description.CourseDescriptionScreen
|
||||||
|
|
||||||
sealed class MenuItem {
|
sealed class MenuItem {
|
||||||
object Courses : NavLinks(R.string.menu_courses, R.drawable.ic_menu_courses, "courses")
|
object Courses : NavLinks(R.string.menu_courses, R.drawable.ic_menu_courses, "courses")
|
||||||
@@ -42,5 +44,25 @@ fun MainActivityComposable() {
|
|||||||
val id = it.arguments!!.getLong("id")
|
val id = it.arguments!!.getLong("id")
|
||||||
CourseDetailsScreen(tools, id)
|
CourseDetailsScreen(tools, id)
|
||||||
}
|
}
|
||||||
|
composable(
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument("id") { type = NavType.LongType }
|
||||||
|
),
|
||||||
|
route = "${MenuItem.Courses.route}/{id}/description"
|
||||||
|
) {
|
||||||
|
val id = it.arguments!!.getLong("id")
|
||||||
|
CourseDescriptionScreen(tools, id)
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument("id") { type = NavType.LongType },
|
||||||
|
navArgument("announceId") { type = NavType.LongType }
|
||||||
|
),
|
||||||
|
route = "${MenuItem.Courses.route}/{id}/announcements/{announceId}"
|
||||||
|
) {
|
||||||
|
val id = it.arguments!!.getLong("id")
|
||||||
|
val announceId = it.arguments!!.getLong("announceId")
|
||||||
|
CourseAnnouncementScreen(tools, id, announceId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.sebse.fuplanner2.ui.courses
|
package de.sebse.fuplanner2.ui.courses
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -57,7 +56,6 @@ fun CoursesScreen(tools: Tools) {
|
|||||||
AppTheme {
|
AppTheme {
|
||||||
GroupedCourseList(groups = groups) { course ->
|
GroupedCourseList(groups = groups) { course ->
|
||||||
course.uid?.let {
|
course.uid?.let {
|
||||||
Log.d("WHERE TO GO", "${MenuItem.Courses.route}/$it")
|
|
||||||
tools.navTo("${MenuItem.Courses.route}/$it")
|
tools.navTo("${MenuItem.Courses.route}/$it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,9 +131,11 @@ fun CourseItem(id: Long?, title: String, lecturers: String, type: String, onclic
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(1.5.dp)
|
.height(1.5.dp)
|
||||||
.background(Brush.horizontalGradient(
|
.background(
|
||||||
colors = listOf(color, MaterialTheme.colors.surface)
|
Brush.horizontalGradient(
|
||||||
))
|
colors = listOf(color, MaterialTheme.colors.surface)
|
||||||
|
)
|
||||||
|
)
|
||||||
) {}
|
) {}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -11,17 +11,22 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.sebse.fuplanner2.MenuItem
|
||||||
import de.sebse.fuplanner2.R
|
import de.sebse.fuplanner2.R
|
||||||
import de.sebse.fuplanner2.Tools
|
import de.sebse.fuplanner2.Tools
|
||||||
import de.sebse.fuplanner2.database.Announcement
|
import de.sebse.fuplanner2.database.Announcement
|
||||||
import de.sebse.fuplanner2.database.Course
|
import de.sebse.fuplanner2.database.Course
|
||||||
|
import de.sebse.fuplanner2.database.Event
|
||||||
|
import de.sebse.fuplanner2.database.Lecturer
|
||||||
import de.sebse.fuplanner2.ui.details.components.AnnouncementItem
|
import de.sebse.fuplanner2.ui.details.components.AnnouncementItem
|
||||||
|
import de.sebse.fuplanner2.ui.details.components.EventItem
|
||||||
import de.sebse.fuplanner2.ui.details.components.LecturerItem
|
import de.sebse.fuplanner2.ui.details.components.LecturerItem
|
||||||
import de.sebse.fuplanner2.ui.details.components.QuickLinks
|
import de.sebse.fuplanner2.ui.details.components.QuickLinks
|
||||||
import de.sebse.fuplanner2.ui.shared.Heading
|
import de.sebse.fuplanner2.ui.shared.Heading
|
||||||
import de.sebse.fuplanner2.ui.theme.AppTheme
|
import de.sebse.fuplanner2.ui.theme.AppTheme
|
||||||
import de.sebse.fuplanner2.ui.tools.previews.AnnouncementPreviewProvider
|
import de.sebse.fuplanner2.ui.tools.previews.AnnouncementPreviewProvider
|
||||||
import de.sebse.fuplanner2.ui.tools.previews.CoursePreviewProvider
|
import de.sebse.fuplanner2.ui.tools.previews.CoursePreviewProvider
|
||||||
|
import de.sebse.fuplanner2.ui.tools.previews.EventPreviewProvider
|
||||||
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModel
|
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModel
|
||||||
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModelFactory
|
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModelFactory
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -31,6 +36,7 @@ fun CourseDetailsScreen(tools: Tools, id: Long) {
|
|||||||
val coursesViewModel: DetailsViewModel = viewModel(factory = DetailsViewModelFactory(id))
|
val coursesViewModel: DetailsViewModel = viewModel(factory = DetailsViewModelFactory(id))
|
||||||
val course by coursesViewModel.course.observeAsState()
|
val course by coursesViewModel.course.observeAsState()
|
||||||
val announcements by coursesViewModel.announcements.observeAsState()
|
val announcements by coursesViewModel.announcements.observeAsState()
|
||||||
|
val events by coursesViewModel.events.observeAsState()
|
||||||
val title = course?.title
|
val title = course?.title
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
LaunchedEffect(title) {
|
LaunchedEffect(title) {
|
||||||
@@ -39,25 +45,47 @@ fun CourseDetailsScreen(tools: Tools, id: Long) {
|
|||||||
LaunchedEffect(true) {
|
LaunchedEffect(true) {
|
||||||
coursesViewModel.refresh(context)
|
coursesViewModel.refresh(context)
|
||||||
}
|
}
|
||||||
CourseDetailsScreen(course, announcements, id)
|
CourseDetailsScreen(
|
||||||
|
course?.lecturers ?: emptyList(),
|
||||||
|
announcements ?: emptyList(),
|
||||||
|
events ?: emptyList(),
|
||||||
|
id,
|
||||||
|
course?.title ?: ""
|
||||||
|
) {
|
||||||
|
tools.navTo(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CourseDetailsScreen(course: Course?, announcement: List<Announcement>?, id: Long) {
|
fun CourseDetailsScreen(
|
||||||
val announcements = announcement?.subList(0, min(announcement.size, 3)) ?: listOf()
|
lecturers: List<Lecturer>,
|
||||||
|
announcements: List<Announcement>,
|
||||||
|
events: List<Event>,
|
||||||
|
id: Long,
|
||||||
|
title: String,
|
||||||
|
onClick: (String) -> Unit
|
||||||
|
) {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
item {
|
item {
|
||||||
QuickLinks(courseId = id)
|
QuickLinks(courseId = id, onClick)
|
||||||
Heading(stringResource(R.string.lecturers))
|
if (lecturers.isNotEmpty()) Heading(stringResource(R.string.lecturers))
|
||||||
}
|
}
|
||||||
items(course?.lecturers ?: listOf()) {
|
items(lecturers.sortedBy { (if (it.isResponsible) "AAAA" else "ZZZZ") + it.lastName }) {
|
||||||
LecturerItem(lecturer = it, courseTitle = course?.title ?: "")
|
LecturerItem(lecturer = it, courseTitle = title)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Heading(stringResource(R.string.announcements))
|
if (announcements.isNotEmpty()) Heading(stringResource(R.string.announcements))
|
||||||
}
|
}
|
||||||
items(items = announcements) {
|
items(items = announcements.subList(0, min(announcements.size, 3)) ?: listOf()) {
|
||||||
AnnouncementItem(it)
|
AnnouncementItem(it) {
|
||||||
|
onClick("${MenuItem.Courses.route}/$id/announcements/${it.uid}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
if (events.isNotEmpty()) Heading(stringResource(R.string.events))
|
||||||
|
}
|
||||||
|
items(items = events.subList(0, min(events.size, 3))) {
|
||||||
|
EventItem(it)
|
||||||
}
|
}
|
||||||
// TODO: Add current assignments, upcoming events
|
// TODO: Add current assignments, upcoming events
|
||||||
}
|
}
|
||||||
@@ -67,6 +95,12 @@ fun CourseDetailsScreen(course: Course?, announcement: List<Announcement>?, id:
|
|||||||
@Composable
|
@Composable
|
||||||
fun CourseDetailsScreenPreview(@PreviewParameter(CoursePreviewProvider::class, 1) course: Course) {
|
fun CourseDetailsScreenPreview(@PreviewParameter(CoursePreviewProvider::class, 1) course: Course) {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
CourseDetailsScreen(course, AnnouncementPreviewProvider().values.take(3).toList(), course.uid!!)
|
CourseDetailsScreen(
|
||||||
|
course.lecturers,
|
||||||
|
AnnouncementPreviewProvider().values.take(3).toList(),
|
||||||
|
EventPreviewProvider().values.take(3).toList(),
|
||||||
|
course.uid!!,
|
||||||
|
course.title
|
||||||
|
) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ fun AnnouncementItem(announcement: Announcement, click: () -> Unit) {
|
|||||||
.clickable(true, onClick = click)
|
.clickable(true, onClick = click)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = announcement.title ?: "Title",
|
text = announcement.title,
|
||||||
style = MaterialTheme.typography.h6
|
style = MaterialTheme.typography.h6
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package de.sebse.fuplanner2.ui.details.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import de.sebse.fuplanner2.database.Event
|
||||||
|
import de.sebse.fuplanner2.ui.shared.FuCardColumn
|
||||||
|
import de.sebse.fuplanner2.ui.theme.AppTheme
|
||||||
|
import de.sebse.fuplanner2.ui.tools.previews.EventPreviewProvider
|
||||||
|
import de.sebse.fuplanner2.utils.toDateTimeString
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EventItem(event: Event) {
|
||||||
|
EventItem(event) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EventItem(event: Event, click: () -> Unit) {
|
||||||
|
FuCardColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(true, onClick = click)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = event.title,
|
||||||
|
style = MaterialTheme.typography.h6
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = event.startDateTime.toDateTimeString(LocalContext.current) ?: "",
|
||||||
|
style = MaterialTheme.typography.subtitle1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun EventItemPreview(@PreviewParameter(EventPreviewProvider::class, 5) event: Event) {
|
||||||
|
AppTheme {
|
||||||
|
EventItem(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package de.sebse.fuplanner2.ui.details.components
|
|||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.GridCells
|
import androidx.compose.foundation.lazy.GridCells
|
||||||
@@ -24,7 +25,7 @@ data class QuickLinkProps(@StringRes val name: Int, val route: String)
|
|||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun QuickLinks(courseId: Long) {
|
fun QuickLinks(courseId: Long, onClick: (String) -> Unit) {
|
||||||
val list = listOf(
|
val list = listOf(
|
||||||
QuickLinkProps(R.string.description, "courses/$courseId/description"),
|
QuickLinkProps(R.string.description, "courses/$courseId/description"),
|
||||||
QuickLinkProps(R.string.resources, "courses/$courseId/resources"),
|
QuickLinkProps(R.string.resources, "courses/$courseId/resources"),
|
||||||
@@ -40,7 +41,8 @@ fun QuickLinks(courseId: Long) {
|
|||||||
backgroundColor = MaterialTheme.colors.secondary,
|
backgroundColor = MaterialTheme.colors.secondary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(dimensionResource(R.dimen.card_view_margin))
|
.padding(dimensionResource(R.dimen.card_view_margin))
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
|
.clickable { onClick(it.route) },
|
||||||
elevation = dimensionResource(R.dimen.card_view_elevation),
|
elevation = dimensionResource(R.dimen.card_view_elevation),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -60,6 +62,6 @@ fun QuickLinks(courseId: Long) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun QuickLinksPreview() {
|
fun QuickLinksPreview() {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
QuickLinks(3)
|
QuickLinks(3) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package de.sebse.fuplanner2.ui.details_announcements
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.sebse.fuplanner2.R
|
||||||
|
import de.sebse.fuplanner2.Tools
|
||||||
|
import de.sebse.fuplanner2.database.Announcement
|
||||||
|
import de.sebse.fuplanner2.database.Course
|
||||||
|
import de.sebse.fuplanner2.ui.shared.Heading
|
||||||
|
import de.sebse.fuplanner2.ui.shared.HtmlText
|
||||||
|
import de.sebse.fuplanner2.ui.theme.AppTheme
|
||||||
|
import de.sebse.fuplanner2.ui.tools.previews.CoursePreviewProvider
|
||||||
|
import de.sebse.fuplanner2.ui.tools.viewmodels.AnnouncementViewModel
|
||||||
|
import de.sebse.fuplanner2.ui.tools.viewmodels.AnnouncementViewModelFactory
|
||||||
|
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModel
|
||||||
|
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModelFactory
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CourseAnnouncementScreen(tools: Tools, id: Long, announcementId: Long) {
|
||||||
|
val coursesViewModel: DetailsViewModel =
|
||||||
|
viewModel(factory = DetailsViewModelFactory(id))
|
||||||
|
val announcementViewModel: AnnouncementViewModel =
|
||||||
|
viewModel(factory = AnnouncementViewModelFactory(announcementId))
|
||||||
|
val course by coursesViewModel.course.observeAsState()
|
||||||
|
val announcement by announcementViewModel.observeAsState()
|
||||||
|
val title = course?.title ?: stringResource(id = R.string.description)
|
||||||
|
LaunchedEffect(title) {
|
||||||
|
tools.setTitle(title)
|
||||||
|
}
|
||||||
|
announcement?.let { CourseAnnouncementScreen(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CourseAnnouncementScreen(announcement: Announcement) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.verticalScroll(scrollState)
|
||||||
|
) {
|
||||||
|
Heading(text = announcement.title)
|
||||||
|
HtmlText(
|
||||||
|
html = announcement.body,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun CourseAnnouncementScreenPreview(@PreviewParameter(CoursePreviewProvider::class, 1) course: Course) {
|
||||||
|
AppTheme {
|
||||||
|
/*CourseAnnouncementScreen(
|
||||||
|
"scrip<b>fsdfsfg</b><br><a href='example.com'>ti</a>on"
|
||||||
|
)*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package de.sebse.fuplanner2.ui.details_description
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.sebse.fuplanner2.R
|
||||||
|
import de.sebse.fuplanner2.Tools
|
||||||
|
import de.sebse.fuplanner2.database.Course
|
||||||
|
import de.sebse.fuplanner2.ui.shared.Heading
|
||||||
|
import de.sebse.fuplanner2.ui.shared.HtmlText
|
||||||
|
import de.sebse.fuplanner2.ui.theme.AppTheme
|
||||||
|
import de.sebse.fuplanner2.ui.tools.previews.CoursePreviewProvider
|
||||||
|
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModel
|
||||||
|
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModelFactory
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CourseDescriptionScreen(tools: Tools, id: Long) {
|
||||||
|
val coursesViewModel: DetailsViewModel = viewModel(factory = DetailsViewModelFactory(id))
|
||||||
|
val course by coursesViewModel.course.observeAsState()
|
||||||
|
val title = course?.title ?: stringResource(id = R.string.description)
|
||||||
|
LaunchedEffect(title) {
|
||||||
|
tools.setTitle(title)
|
||||||
|
}
|
||||||
|
CourseDescriptionScreen(course?.description ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CourseDescriptionScreen(description: String) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.verticalScroll(scrollState)
|
||||||
|
) {
|
||||||
|
Heading(text = stringResource(id = R.string.description))
|
||||||
|
HtmlText(
|
||||||
|
html = description,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun CourseDescriptionScreenPreview(@PreviewParameter(CoursePreviewProvider::class, 1) course: Course) {
|
||||||
|
AppTheme {
|
||||||
|
CourseDescriptionScreen(
|
||||||
|
"scrip<b>fsdfsfg</b><br><a href='example.com'>ti</a>on"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package de.sebse.fuplanner2.ui.details_description
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.webkit.WebSettings
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import androidx.navigation.fragment.navArgs
|
|
||||||
import de.sebse.fuplanner2.R
|
|
||||||
import de.sebse.fuplanner2.databinding.FragmentDescriptionBinding
|
|
||||||
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModel
|
|
||||||
import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModelFactory
|
|
||||||
|
|
||||||
|
|
||||||
class DescriptionFragment : Fragment() {
|
|
||||||
|
|
||||||
private var title: String = ""
|
|
||||||
private val args: DescriptionFragmentArgs by navArgs()
|
|
||||||
private val detailsViewModel: DetailsViewModel by viewModels { DetailsViewModelFactory(args.courseId) }
|
|
||||||
private lateinit var navController: NavController
|
|
||||||
private lateinit var binding: FragmentDescriptionBinding
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
return inflater.inflate(R.layout.fragment_description, container, false).apply {
|
|
||||||
val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
||||||
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
binding.description.settings.forceDark = WebSettings.FORCE_DARK_ON
|
|
||||||
}
|
|
||||||
detailsViewModel.course.observe(viewLifecycleOwner, Observer {
|
|
||||||
binding.description.loadDataWithBaseURL("", it.description, "text/html", "UTF-8", "")
|
|
||||||
})
|
|
||||||
navController = findNavController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,48 @@
|
|||||||
package de.sebse.fuplanner2.ui.shared
|
package de.sebse.fuplanner2.ui.shared
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
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.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import de.sebse.fuplanner2.R
|
import de.sebse.fuplanner2.R
|
||||||
import de.sebse.fuplanner2.ui.theme.AppTheme
|
import de.sebse.fuplanner2.ui.theme.AppTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Heading(text: String) {
|
fun Heading(text: String, onClick: (() -> Unit)? = null) {
|
||||||
Text(
|
Box(
|
||||||
text = text,
|
modifier = Modifier.fillMaxWidth()
|
||||||
style = MaterialTheme.typography.h5,
|
) {
|
||||||
modifier = Modifier.padding(top = dimensionResource(id = R.dimen.header_padding))
|
Text(
|
||||||
)
|
text = text,
|
||||||
|
style = MaterialTheme.typography.h5,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = dimensionResource(id = R.dimen.header_padding))
|
||||||
|
)
|
||||||
|
if (onClick != null) Text(
|
||||||
|
text = "More >>",
|
||||||
|
style = MaterialTheme.typography.subtitle2,
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
color = MaterialTheme.colors.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = dimensionResource(id = R.dimen.header_padding))
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun HeadingPreview() {
|
fun HeadingPreview() {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
Text("Super Cool")
|
Heading("Super cool") { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
app/src/main/java/de/sebse/fuplanner2/ui/shared/HtmlText.kt
Normal file
21
app/src/main/java/de/sebse/fuplanner2/ui/shared/HtmlText.kt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package de.sebse.fuplanner2.ui.shared
|
||||||
|
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HtmlText(html: String, modifier: Modifier = Modifier) {
|
||||||
|
AndroidView(
|
||||||
|
modifier = modifier,
|
||||||
|
factory = { context -> TextView(context) },
|
||||||
|
update = {
|
||||||
|
it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
|
||||||
|
it.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
it.setTextIsSelectable(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package de.sebse.fuplanner2.ui.tools.previews
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import de.sebse.fuplanner2.database.Event
|
||||||
|
import de.sebse.fuplanner2.utils.Faker
|
||||||
|
import de.sebse.fuplanner2.utils.getFaker
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class EventPreviewProvider(private val faker: Faker = getFaker()) : PreviewParameterProvider<Event> {
|
||||||
|
val title = faker.strings.title()
|
||||||
|
override val values = generateSequence(0) { it + 1 }
|
||||||
|
.map { getItem(it) }
|
||||||
|
|
||||||
|
private fun getItem(num: Int): Event {
|
||||||
|
val isExam = num == 20
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
cal.add(Calendar.DATE, num / 2 * 7 + num % 2 * 3)
|
||||||
|
cal.set(Calendar.HOUR, 14 + (num % 2) * 2)
|
||||||
|
cal.set(Calendar.MINUTE, 30)
|
||||||
|
cal.set(Calendar.SECOND, 0)
|
||||||
|
cal.set(Calendar.MILLISECOND, 0)
|
||||||
|
|
||||||
|
return Event(
|
||||||
|
faker.primitive.long(0, 100),
|
||||||
|
faker.primitive.long(0, 100),
|
||||||
|
faker.other.lastRefreshed(),
|
||||||
|
title,
|
||||||
|
90*60*1000 + (if (isExam) 1L else 0),
|
||||||
|
cal.timeInMillis,
|
||||||
|
"Hörsaal T9",
|
||||||
|
if (isExam) "Klausur" else "Vorlesung",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package de.sebse.fuplanner2.ui.tools.viewmodels
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import de.sebse.fuplanner2.database.Announcement
|
||||||
|
import de.sebse.fuplanner2.database.AppDatabase
|
||||||
|
|
||||||
|
|
||||||
|
class AnnouncementViewModelFactory(private val announcementId: Long): ViewModelProvider.NewInstanceFactory() {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T = AnnouncementViewModel(announcementId) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnnouncementViewModel(private val announcementId: Long) : ViewModel() {
|
||||||
|
private val announcement: LiveData<Announcement> =
|
||||||
|
AppDatabase.getInstance().announcementDao().getAnnouncementById2(announcementId)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeAsState(): State<Announcement?> {
|
||||||
|
return announcement.observeAsState()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import de.sebse.fuplanner2.auth.AppAccounts
|
|||||||
import de.sebse.fuplanner2.database.Announcement
|
import de.sebse.fuplanner2.database.Announcement
|
||||||
import de.sebse.fuplanner2.database.AppDatabase
|
import de.sebse.fuplanner2.database.AppDatabase
|
||||||
import de.sebse.fuplanner2.database.Course
|
import de.sebse.fuplanner2.database.Course
|
||||||
|
import de.sebse.fuplanner2.database.Event
|
||||||
import de.sebse.fuplanner2.utils.enqueueOneTimeWork
|
import de.sebse.fuplanner2.utils.enqueueOneTimeWork
|
||||||
import de.sebse.fuplanner2.worker.AbstractAccountWorker
|
import de.sebse.fuplanner2.worker.AbstractAccountWorker
|
||||||
import de.sebse.fuplanner2.worker.CourseWorker
|
import de.sebse.fuplanner2.worker.CourseWorker
|
||||||
@@ -37,4 +38,8 @@ class DetailsViewModel(private val courseId: Long) : ViewModel() {
|
|||||||
AppDatabase.getInstance().announcementDao().getAll1(courseId),
|
AppDatabase.getInstance().announcementDao().getAll1(courseId),
|
||||||
50
|
50
|
||||||
).build()
|
).build()
|
||||||
|
val events: LiveData<PagedList<Event>> = LivePagedListBuilder(
|
||||||
|
AppDatabase.getInstance().eventDao().getAll1(courseId),
|
||||||
|
50
|
||||||
|
).build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,17 +39,6 @@
|
|||||||
android:name="title"
|
android:name="title"
|
||||||
app:argType="string" />
|
app:argType="string" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
|
||||||
android:id="@+id/course_description"
|
|
||||||
android:name="de.sebse.fuplanner2.ui.details_description.DescriptionFragment"
|
|
||||||
android:label="{title}">
|
|
||||||
<argument
|
|
||||||
android:name="courseId"
|
|
||||||
app:argType="long" />
|
|
||||||
<argument
|
|
||||||
android:name="title"
|
|
||||||
app:argType="string" />
|
|
||||||
</fragment>
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/nav_notifications"
|
android:id="@+id/nav_notifications"
|
||||||
android:name="de.sebse.fuplanner2.ui.notification.NotificationFragment"
|
android:name="de.sebse.fuplanner2.ui.notification.NotificationFragment"
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
<dimen name="card_view_margin">4dp</dimen>
|
<dimen name="card_view_margin">4dp</dimen>
|
||||||
<dimen name="card_view_padding">5dp</dimen>
|
<dimen name="card_view_padding">5dp</dimen>
|
||||||
<dimen name="card_view_elevation">4dp</dimen>
|
<dimen name="card_view_elevation">4dp</dimen>
|
||||||
<dimen name="header_padding">8dp</dimen>
|
<dimen name="header_padding">16dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
<string name="course_type">Course Type</string>
|
<string name="course_type">Course Type</string>
|
||||||
<string name="description">Description</string>
|
<string name="description">Description</string>
|
||||||
<string name="resources">Resources</string>
|
<string name="resources">Resources</string>
|
||||||
|
<string name="announcement">Announcement</string>
|
||||||
<plurals name="not_course_update_text">
|
<plurals name="not_course_update_text">
|
||||||
<item quantity="one">One course message</item>
|
<item quantity="one">One course message</item>
|
||||||
<item quantity="other">%1$d course messages</item>
|
<item quantity="other">%1$d course messages</item>
|
||||||
|
|||||||
Reference in New Issue
Block a user