Added course detail screens

This commit is contained in:
Sebastian Seedorf
2021-11-20 02:18:46 +01:00
parent 348fdbf5d6
commit 46e431b277
19 changed files with 370 additions and 89 deletions

View File

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

View File

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

View File

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

View File

@@ -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(
Brush.horizontalGradient(
colors = listOf(color, MaterialTheme.colors.surface) colors = listOf(color, MaterialTheme.colors.surface)
)) )
)
) {} ) {}
Column( Column(
modifier = Modifier modifier = Modifier

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"
)*/
}
}

View File

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

View File

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

View File

@@ -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) {
Box(
modifier = Modifier.fillMaxWidth()
) {
Text( Text(
text = text, text = text,
style = MaterialTheme.typography.h5, style = MaterialTheme.typography.h5,
modifier = Modifier.padding(top = dimensionResource(id = R.dimen.header_padding)) 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") { }
} }
} }

View 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)
}
)
}

View File

@@ -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",
)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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