Add announcements to compose

This commit is contained in:
Sebastian Seedorf
2021-11-19 18:20:03 +01:00
parent 8302884fdc
commit cb905fc9a6
22 changed files with 511 additions and 57 deletions

View File

@@ -80,14 +80,14 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Compose // Compose
implementation "androidx.compose.runtime:runtime:$compose_version" implementation "androidx.compose.runtime:runtime:1.0.5"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version" implementation "androidx.compose.runtime:runtime-livedata:1.0.5"
implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui:1.0.5"
implementation "androidx.compose.ui:ui-tooling:$compose_version" implementation "androidx.compose.ui:ui-tooling:1.0.5"
implementation "androidx.compose.foundation:foundation:$compose_version" implementation "androidx.compose.foundation:foundation:1.0.5"
implementation "androidx.compose.foundation:foundation-layout:$compose_version" implementation "androidx.compose.foundation:foundation-layout:1.0.5"
implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.material:material:1.0.5"
implementation "androidx.navigation:navigation-compose:2.4.0-beta02" implementation "androidx.navigation:navigation-compose:2.4.0-beta02"
implementation "com.google.android.material:compose-theme-adapter:$compose_version" implementation "com.google.android.material:compose-theme-adapter:1.1.0"
} }

View File

@@ -9,11 +9,11 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.google.android.material.composethemeadapter.MdcTheme
import de.sebse.fuplanner2.auth.AppAccounts import de.sebse.fuplanner2.auth.AppAccounts
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.User import de.sebse.fuplanner2.database.User
import de.sebse.fuplanner2.ui.theme.AppTheme
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -25,7 +25,7 @@ class MainActivity() : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
activityViewModel = ViewModelProvider(this)[MainActivityViewModel::class.java] activityViewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]
setContent { setContent {
MdcTheme { AppTheme {
MainActivityComposable() MainActivityComposable()
} }
} }

View File

@@ -1,8 +1,8 @@
package de.sebse.fuplanner2.database package de.sebse.fuplanner2.database
import androidx.lifecycle.LiveData
import androidx.paging.DataSource import androidx.paging.DataSource
import androidx.room.* import androidx.room.*
import de.sebse.fuplanner2.utils.console
@Dao @Dao
interface AnnouncementDao { interface AnnouncementDao {
@@ -12,6 +12,9 @@ interface AnnouncementDao {
@Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn ASC") @Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn ASC")
fun getAll2(courseId: Long): List<Announcement> fun getAll2(courseId: Long): List<Announcement>
@Query("SELECT * FROM announcement WHERE courseId = :courseId ORDER BY createdOn ASC")
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
@@ -43,4 +46,4 @@ interface AnnouncementDao {
@Delete @Delete
fun delete(announcements: List<Announcement>) fun delete(announcements: List<Announcement>)
} }

View File

@@ -21,7 +21,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.google.android.material.composethemeadapter.MdcTheme import de.sebse.fuplanner2.ui.theme.AppTheme
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -140,7 +140,7 @@ fun DrawerItems(currentRoute: String?, menu: List<NavLinks>, onItemClick: (route
@Preview @Preview
@Composable @Composable
fun TopBarPreview() { fun TopBarPreview() {
MdcTheme { AppTheme {
TopBar( TopBar(
title = "A title" title = "A title"
) { } ) { }
@@ -150,7 +150,7 @@ fun TopBarPreview() {
@Preview @Preview
@Composable @Composable
fun DrawerPreview() { fun DrawerPreview() {
MdcTheme { AppTheme {
Drawer(currentRoute = "courses", menu = screens) { } Drawer(currentRoute = "courses", menu = screens) { }
} }
} }

View File

@@ -31,14 +31,14 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import com.google.android.material.composethemeadapter.MdcTheme
import de.sebse.fuplanner2.MenuItem 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.Course import de.sebse.fuplanner2.database.Course
import de.sebse.fuplanner2.ui.details.CoursePreviewProvider import de.sebse.fuplanner2.ui.theme.AppTheme
import de.sebse.fuplanner2.ui.tools.previews.CoursePreviewProvider
import de.sebse.fuplanner2.ui.tools.viewmodels.CoursesViewModel
import de.sebse.fuplanner2.utils.color.getColor import de.sebse.fuplanner2.utils.color.getColor
import de.sebse.fuplanner2.viewmodels.CoursesViewModel
@Composable @Composable
fun CoursesScreen(tools: Tools) { fun CoursesScreen(tools: Tools) {
@@ -54,7 +54,7 @@ fun CoursesScreen(tools: Tools) {
LaunchedEffect(title) { LaunchedEffect(title) {
tools.setTitle(title) tools.setTitle(title)
} }
MdcTheme { AppTheme {
GroupedCourseList(groups = groups) { course -> GroupedCourseList(groups = groups) { course ->
course.uid?.let { course.uid?.let {
Log.d("WHERE TO GO", "${MenuItem.Courses.route}/$it") Log.d("WHERE TO GO", "${MenuItem.Courses.route}/$it")
@@ -163,7 +163,7 @@ fun CourseItemHint(icon: ImageVector, @StringRes imageAltRes: Int, text: String)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable
fun CourseListPreview() { fun CourseListPreview() {
MdcTheme { AppTheme {
CourseList( CourseList(
CoursePreviewProvider().values.take(3).toList() CoursePreviewProvider().values.take(3).toList()
) { } ) { }
@@ -174,7 +174,7 @@ fun CourseListPreview() {
@Preview @Preview
@Composable @Composable
fun CourseItemPreview(@PreviewParameter(CoursePreviewProvider::class, 1) course: Course) { fun CourseItemPreview(@PreviewParameter(CoursePreviewProvider::class, 1) course: Course) {
MdcTheme { AppTheme {
CourseItem(course) {} CourseItem(course) {}
} }
} }

View File

@@ -12,27 +12,34 @@ 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 com.google.android.material.composethemeadapter.MdcTheme
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.AppDatabase
import de.sebse.fuplanner2.database.Course import de.sebse.fuplanner2.database.Course
import de.sebse.fuplanner2.ui.details.components.AnnouncementItem
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.viewmodels.DetailsViewModel import de.sebse.fuplanner2.ui.theme.AppTheme
import de.sebse.fuplanner2.viewmodels.DetailsViewModelFactory import de.sebse.fuplanner2.ui.tools.previews.AnnouncementPreviewProvider
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 @Composable
fun CourseDetailsScreen(tools: Tools, id: Long) { fun CourseDetailsScreen(tools: Tools, id: Long) {
val coursesViewModel: DetailsViewModel = viewModel(factory = DetailsViewModelFactory(id)) val coursesViewModel: DetailsViewModel = viewModel(factory = DetailsViewModelFactory(id))
val state = coursesViewModel.course.observeAsState() val state = coursesViewModel.course.observeAsState()
val announce = AppDatabase.getInstance().announcementDao().getAll3(id).observeAsState()
val title = state.value?.title val title = state.value?.title
LaunchedEffect(title) { LaunchedEffect(title) {
title?.let { tools.setTitle(it) } title?.let { tools.setTitle(it) }
} }
CourseDetailsScreen(state.value, id) CourseDetailsScreen(state.value, announce.value, id)
} }
@Composable @Composable
fun CourseDetailsScreen(course: Course?, id: Long) { fun CourseDetailsScreen(course: Course?, announcement: List<Announcement>?, id: Long) {
Column { Column {
QuickLinks(courseId = id) QuickLinks(courseId = id)
Text( Text(
@@ -44,6 +51,15 @@ fun CourseDetailsScreen(course: Course?, id: Long) {
LecturerItem(lecturer = it, courseTitle = course?.title ?: "") LecturerItem(lecturer = it, courseTitle = course?.title ?: "")
} }
} }
Text(
text = stringResource(R.string.announcements),
style = MaterialTheme.typography.h5
)
LazyColumn {
items(announcement ?: listOf()) {
AnnouncementItem(it)
}
}
// TODO: Add latest announcements, current assignments, upcoming events // TODO: Add latest announcements, current assignments, upcoming events
} }
} }
@@ -51,7 +67,7 @@ fun CourseDetailsScreen(course: Course?, id: Long) {
@Preview @Preview
@Composable @Composable
fun CourseDetailsScreenPreview(@PreviewParameter(CoursePreviewProvider::class, 1) course: Course) { fun CourseDetailsScreenPreview(@PreviewParameter(CoursePreviewProvider::class, 1) course: Course) {
MdcTheme { AppTheme {
CourseDetailsScreen(course, course.uid!!) CourseDetailsScreen(course, AnnouncementPreviewProvider().values.take(3).toList(), course.uid!!)
} }
} }

View File

@@ -0,0 +1,58 @@
package de.sebse.fuplanner2.ui.details.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Card
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.res.dimensionResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.database.Announcement
import de.sebse.fuplanner2.ui.theme.AppTheme
import de.sebse.fuplanner2.ui.tools.previews.AnnouncementPreviewProvider
import de.sebse.fuplanner2.utils.toDateTimeString
@Composable
fun AnnouncementItem(announcement: Announcement) {
AnnouncementItem(announcement) { }
}
@Composable
fun AnnouncementItem(announcement: Announcement, click: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.card_view_margin)),
elevation = dimensionResource(R.dimen.card_view_elevation)
) {
Column(
modifier = Modifier
.clickable(true, onClick = click)
.padding(dimensionResource(R.dimen.card_view_padding))
) {
Text(
text = announcement.title ?: "Title",
style = MaterialTheme.typography.h6
)
Text(
text = announcement.createdOn.toDateTimeString(LocalContext.current) ?: "",
style = MaterialTheme.typography.subtitle1
)
}
}
}
@Preview
@Composable
fun AnnouncementItemPreview(@PreviewParameter(AnnouncementPreviewProvider::class, 5) announcement: Announcement) {
AppTheme {
AnnouncementItem(announcement)
}
}

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner2.ui.details package de.sebse.fuplanner2.ui.details.components
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -21,9 +21,10 @@ import androidx.compose.ui.text.style.TextDecoration
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.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.android.material.composethemeadapter.MdcTheme
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.database.Lecturer import de.sebse.fuplanner2.database.Lecturer
import de.sebse.fuplanner2.ui.theme.AppTheme
import de.sebse.fuplanner2.ui.tools.previews.LecturerPreviewProvider
@Composable @Composable
fun LecturerItem(lecturer: Lecturer, courseTitle: String) { fun LecturerItem(lecturer: Lecturer, courseTitle: String) {
@@ -84,8 +85,8 @@ fun LecturerItem(lecturer: Lecturer, click: () -> Unit) {
@Preview @Preview
@Composable @Composable
fun LecturerItemPreview(@PreviewParameter(LecturerPreviewProvider::class, 2) lecturer: Lecturer) { fun LecturerItemPreview(@PreviewParameter(LecturerPreviewProvider::class, 3) lecturer: Lecturer) {
MdcTheme { AppTheme {
LecturerItem(lecturer, "Course Name") LecturerItem(lecturer, "Course Name")
} }
} }

View File

@@ -17,8 +17,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.android.material.composethemeadapter.MdcTheme
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.ui.theme.AppTheme
data class QuickLinkProps(@StringRes val name: Int, val route: String) data class QuickLinkProps(@StringRes val name: Int, val route: String)
@@ -64,7 +64,7 @@ fun QuickLinks(courseId: Long) {
@Preview @Preview
@Composable @Composable
fun QuickLinksPreview() { fun QuickLinksPreview() {
MdcTheme { AppTheme {
QuickLinks(3) QuickLinks(3)
} }
} }

View File

@@ -1,18 +1,34 @@
package de.sebse.fuplanner2.ui.details_announcements package de.sebse.fuplanner2.ui.details_announcements
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.paging.LivePagedListBuilder import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList import androidx.paging.PagedList
import androidx.work.workDataOf
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.utils.enqueueOneTimeWork
import de.sebse.fuplanner2.worker.AbstractAccountWorker
import de.sebse.fuplanner2.worker.AnnouncementWorker
class AnnouncementsViewModelFactory(private val courseId: Long): ViewModelProvider.NewInstanceFactory() { class AnnouncementsViewModelFactory(private val courseId: Long): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T = AnnouncementsViewModel(courseId) as T override fun <T : ViewModel> create(modelClass: Class<T>): T = AnnouncementsViewModel(courseId) as T
} }
class AnnouncementsViewModel(courseId: Long) : ViewModel() { class AnnouncementsViewModel(private val courseId: Long) : ViewModel() {
private val factory = AppDatabase.getInstance().announcementDao().getAll1(courseId) private val factory = AppDatabase.getInstance().announcementDao().getAll1(courseId)
fun refresh(ctx: Context) {
enqueueOneTimeWork<AnnouncementWorker>(ctx) {
it.setInputData(workDataOf(
AbstractAccountWorker.KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name,
AbstractAccountWorker.KEY_COURSE_ID to courseId
))
}
}
val events: LiveData<PagedList<Announcement>> = LivePagedListBuilder(factory, 50).build() val events: LiveData<PagedList<Announcement>> = LivePagedListBuilder(factory, 50).build()
} }

View File

@@ -15,8 +15,8 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.databinding.FragmentDescriptionBinding import de.sebse.fuplanner2.databinding.FragmentDescriptionBinding
import de.sebse.fuplanner2.viewmodels.DetailsViewModel import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModel
import de.sebse.fuplanner2.viewmodels.DetailsViewModelFactory import de.sebse.fuplanner2.ui.tools.viewmodels.DetailsViewModelFactory
class DescriptionFragment : Fragment() { class DescriptionFragment : Fragment() {

View File

@@ -0,0 +1,58 @@
package de.sebse.fuplanner2.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF245fa7)
val md_theme_light_onPrimary = Color(0xFFffffff)
val md_theme_light_primaryContainer = Color(0xFFd4e3ff)
val md_theme_light_onPrimaryContainer = Color(0xFF001b3d)
val md_theme_light_secondary = Color(0xFF4a6800)
val md_theme_light_onSecondary = Color(0xFFffffff)
val md_theme_light_secondaryContainer = Color(0xFFbef43c)
val md_theme_light_onSecondaryContainer = Color(0xFF131f00)
val md_theme_light_tertiary = Color(0xFF8b5000)
val md_theme_light_onTertiary = Color(0xFFffffff)
val md_theme_light_tertiaryContainer = Color(0xFFffdcba)
val md_theme_light_onTertiaryContainer = Color(0xFF2d1600)
val md_theme_light_error = Color(0xFFba1b1b)
val md_theme_light_errorContainer = Color(0xFFffdad4)
val md_theme_light_onError = Color(0xFFffffff)
val md_theme_light_onErrorContainer = Color(0xFF410001)
val md_theme_light_background = Color(0xFFfdfbff)
val md_theme_light_onBackground = Color(0xFF1b1b1d)
val md_theme_light_surface = Color(0xFFfdfbff)
val md_theme_light_onSurface = Color(0xFF1b1b1d)
val md_theme_light_surfaceVariant = Color(0xFFe0e2eb)
val md_theme_light_onSurfaceVariant = Color(0xFF44474f)
val md_theme_light_outline = Color(0xFF74777f)
val md_theme_light_inverseOnSurface = Color(0xFFf1f0f4)
val md_theme_light_inverseSurface = Color(0xFF2f3033)
val md_theme_dark_primary = Color(0xFFa6c8ff)
val md_theme_dark_onPrimary = Color(0xFF003063)
val md_theme_dark_primaryContainer = Color(0xFF00468b)
val md_theme_dark_onPrimaryContainer = Color(0xFFd4e3ff)
val md_theme_dark_secondary = Color(0xFFa3d719)
val md_theme_dark_onSecondary = Color(0xFF253600)
val md_theme_dark_secondaryContainer = Color(0xFF374e00)
val md_theme_dark_onSecondaryContainer = Color(0xFFbef43c)
val md_theme_dark_tertiary = Color(0xFFffb86b)
val md_theme_dark_onTertiary = Color(0xFF4a2800)
val md_theme_dark_tertiaryContainer = Color(0xFF6a3c00)
val md_theme_dark_onTertiaryContainer = Color(0xFFffdcba)
val md_theme_dark_error = Color(0xFFffb4a9)
val md_theme_dark_errorContainer = Color(0xFF930006)
val md_theme_dark_onError = Color(0xFF680003)
val md_theme_dark_onErrorContainer = Color(0xFFffdad4)
val md_theme_dark_background = Color(0xFF1b1b1d)
val md_theme_dark_onBackground = Color(0xFFe3e2e6)
val md_theme_dark_surface = Color(0xFF1b1b1d)
val md_theme_dark_onSurface = Color(0xFFe3e2e6)
val md_theme_dark_surfaceVariant = Color(0xFF44474f)
val md_theme_dark_onSurfaceVariant = Color(0xFFc3c6cf)
val md_theme_dark_outline = Color(0xFF8e919a)
val md_theme_dark_inverseOnSurface = Color(0xFF1b1b1d)
val md_theme_dark_inverseSurface = Color(0xFFe3e2e6)
val seed = Color(0xFF003366)
val error = Color(0xFFba1b1b)

View File

@@ -0,0 +1,102 @@
package de.sebse.fuplanner2.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
// Comments are changes in Material 3 (Material You)
//private val LightThemeColors = lightColorScheme(
private val LightThemeColors = lightColors(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
//primaryContainer = md_theme_light_primaryContainer,
//onPrimaryContainer = md_theme_light_onPrimaryContainer,
primaryVariant = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
//secondaryContainer = md_theme_light_secondaryContainer,
//onSecondaryContainer = md_theme_light_onSecondaryContainer,
secondaryVariant = md_theme_light_onSecondaryContainer,
//tertiary = md_theme_light_tertiary,
//onTertiary = md_theme_light_onTertiary,
//tertiaryContainer = md_theme_light_tertiaryContainer,
//onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
onError = md_theme_light_onError,
//errorContainer = md_theme_light_errorContainer,
//onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
//surfaceVariant = md_theme_light_surfaceVariant,
//onSurfaceVariant = md_theme_light_onSurfaceVariant,
//outline = md_theme_light_outline,
//inverseOnSurface = md_theme_light_inverseOnSurface,
//inverseSurface = md_theme_light_inverseSurface,
)
//private val DarkThemeColors = darkColorScheme(
private val DarkThemeColors = darkColors(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
//primaryContainer = md_theme_dark_primaryContainer,
//onPrimaryContainer = md_theme_dark_onPrimaryContainer,
primaryVariant = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
//secondaryContainer = md_theme_dark_secondaryContainer,
//onSecondaryContainer = md_theme_dark_onSecondaryContainer,
secondaryVariant = md_theme_dark_onSecondaryContainer,
//tertiary = md_theme_dark_tertiary,
//onTertiary = md_theme_dark_onTertiary,
//tertiaryContainer = md_theme_dark_tertiaryContainer,
//onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
onError = md_theme_dark_onError,
//errorContainer = md_theme_dark_errorContainer,
//onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
//surfaceVariant = md_theme_dark_surfaceVariant,
//onSurfaceVariant = md_theme_dark_onSurfaceVariant,
//outline = md_theme_dark_outline,
//inverseOnSurface = md_theme_dark_inverseOnSurface,
//inverseSurface = md_theme_dark_inverseSurface,
)
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val colors = if (!useDarkTheme) {
LightThemeColors
} else {
DarkThemeColors
}
MaterialTheme(
//colorScheme = colors,
colors = colors,
// typography = AppTypography,
content = content
)
}

View File

@@ -0,0 +1,117 @@
package de.sebse.fuplanner2.ui.theme
// Comments are changes in Material 3 (Material You)
// Use default instead
import androidx.compose.ui.text.font.FontFamily
//Replace with your font locations
val Roboto = FontFamily.Default
/*val AppTypography = Typography(
displayLarge = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = -0.25.sp,
),
displayMedium = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 45.sp,
lineHeight = 52.sp,
letterSpacing = 0.sp,
),
displaySmall = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 36.sp,
lineHeight = 44.sp,
letterSpacing = 0.sp,
),
headlineLarge = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 32.sp,
lineHeight = 40.sp,
letterSpacing = 0.sp,
),
headlineMedium = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 28.sp,
lineHeight = 36.sp,
letterSpacing = 0.sp,
),
headlineSmall = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 24.sp,
lineHeight = 32.sp,
letterSpacing = 0.sp,
),
titleLarge = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp,
),
titleMedium = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.Medium,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.1.sp,
),
titleSmall = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp,
),
labelLarge = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp,
),
bodyLarge = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp,
),
bodyMedium = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.25.sp,
),
bodySmall = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.W400,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.4.sp,
),
labelMedium = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp,
),
labelSmall = TextStyle(
fontFamily = Roboto,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp,
),
)*/

View File

@@ -0,0 +1,40 @@
package de.sebse.fuplanner2.ui.tools.previews
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import de.sebse.fuplanner2.database.Announcement
import de.sebse.fuplanner2.database.Attachment
import de.sebse.fuplanner2.utils.Faker
import de.sebse.fuplanner2.utils.getFaker
class AnnouncementPreviewProvider(private val faker: Faker = getFaker()) : PreviewParameterProvider<Announcement> {
override val values = generateSequence { getItem() }
private fun getItem(): Announcement {
val title = faker.strings.title()
return Announcement(
faker.primitive.long(0, 100),
faker.primitive.long(0, 100),
faker.other.lastRefreshed(),
faker.strings.uuid(title),
title,
faker.strings.lorem(100, 1000),
faker.other.date(-20, -2),
"${faker.name.firstName()} ${faker.name.lastName()}",
AttachmentPreviewProvider(faker).values
.take(faker.primitive.int(1, 4))
.toList()
)
}
}
class AttachmentPreviewProvider(private val faker: Faker = getFaker()) : PreviewParameterProvider<Attachment> {
override val values = generateSequence { getItem() }
private fun getItem(): Attachment {
return Attachment(
faker.internet.url(),
faker.strings.title(),
faker.internet.mime()
)
}
}

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner2.ui.details package de.sebse.fuplanner2.ui.tools.previews
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import de.sebse.fuplanner2.database.Course import de.sebse.fuplanner2.database.Course
@@ -7,12 +7,13 @@ import de.sebse.fuplanner2.utils.Faker
import de.sebse.fuplanner2.utils.getFaker import de.sebse.fuplanner2.utils.getFaker
class LecturerPreviewProvider(private val faker: Faker = getFaker()) : PreviewParameterProvider<Lecturer> { class LecturerPreviewProvider(private val faker: Faker = getFaker()) : PreviewParameterProvider<Lecturer> {
private val resp = faker.primitive.int(1, 2) override val values = sequence {
override val values = (0..10).map { yield(getItem(true))
getLecturer(it < resp) yield(getItem(faker.primitive.bool(.5f)))
}.asSequence() yieldAll(generateSequence { getItem(false) })
}
private fun getLecturer(isResponsible: Boolean): Lecturer { private fun getItem(isResponsible: Boolean): Lecturer {
val firstName = faker.name.firstName() val firstName = faker.name.firstName()
val lastName = faker.name.lastName() val lastName = faker.name.lastName()
return Lecturer( return Lecturer(
@@ -25,17 +26,16 @@ class LecturerPreviewProvider(private val faker: Faker = getFaker()) : PreviewPa
} }
class CoursePreviewProvider(private val faker: Faker = getFaker()) : PreviewParameterProvider<Course> { class CoursePreviewProvider(private val faker: Faker = getFaker()) : PreviewParameterProvider<Course> {
var isSummer = false private var isSummer = false
var year = 21 private var year = 21
override val values = (0..10).map { override val values = sequence {
val res = getCourse() yield(getItem())
val reduce = faker.primitive.bool(.3f) val reduce = faker.primitive.bool(.3f)
year = if (reduce && isSummer) year-1 else year year = if (reduce && isSummer) year-1 else year
isSummer = if (reduce) !isSummer else isSummer isSummer = if (reduce) !isSummer else isSummer
res }
}.asSequence()
private fun getCourse(): Course { private fun getItem(): Course {
val diff = 1000L*60*60*24*5 val diff = 1000L*60*60*24*5
val title = val title =
"${faker.strings.title()} ${if (isSummer) "S" else "W"} ${if (isSummer) year else "$year/${year + 1}"}" "${faker.strings.title()} ${if (isSummer) "S" else "W"} ${if (isSummer) year else "$year/${year + 1}"}"

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner2.viewmodels package de.sebse.fuplanner2.ui.tools.viewmodels
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner2.viewmodels package de.sebse.fuplanner2.ui.tools.viewmodels
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel

View File

@@ -21,6 +21,9 @@ class Faker(randomSeed: Int) {
val primitive: FakerPrimitive val primitive: FakerPrimitive
get() = FakerPrimitive(random) get() = FakerPrimitive(random)
val other: FakerOther
get() = FakerOther(random)
val strings: FakerStrings val strings: FakerStrings
get() = FakerStrings(random) get() = FakerStrings(random)
} }
@@ -29,6 +32,7 @@ class FakerName(private val random: Random) {
fun lastName(): String { fun lastName(): String {
return LASTNAMES.random(random) return LASTNAMES.random(random)
} }
fun firstName(): String { fun firstName(): String {
return FIRSTNAMES.random(random) return FIRSTNAMES.random(random)
} }
@@ -60,6 +64,19 @@ class FakerInternet(private val random: Random) {
.replace(Regex("[^a-z0-9.]"), "") .replace(Regex("[^a-z0-9.]"), "")
return "$user@fu-berlin.de" return "$user@fu-berlin.de"
} }
fun mime(): String {
return MIME_TYPES.random(random)
}
fun url(): String {
return "https://example.de/file"
}
companion object {
private val MIME_TYPES = listOf("application/pdf", "text/plain", "text/csv",
"application/msword")
}
} }
class FakerPrimitive(private val random: Random) { class FakerPrimitive(private val random: Random) {
@@ -76,11 +93,31 @@ class FakerPrimitive(private val random: Random) {
} }
} }
class FakerOther(private val random: Random) {
fun lastRefreshed(): Long {
return FakerPrimitive(random).long(
System.currentTimeMillis() - DIFF,
System.currentTimeMillis() + DIFF
)
}
fun date(startDay: Int, endDay: Int): Long {
return FakerPrimitive(random).long(
System.currentTimeMillis() + DAY * startDay,
System.currentTimeMillis() + DAY * endDay
)
}
companion object {
const val DAY = 1000L*60*60*24
const val DIFF = DAY*5
}
}
class FakerStrings(private val random: Random) { class FakerStrings(private val random: Random) {
fun lorem(min: Int, max: Int): String { fun lorem(min: Int, max: Int): String {
val length = (min..max).random(random) val length = (min..max).random(random)
val loremArray = LOREM.split(" ").size val loremArray = LOREM.split(" ").size
val n = ceil(length.toFloat() / loremArray).toInt() val n = ceil(length.toFloat() / loremArray).toInt() + 1
val repeatedArray = ("$LOREM ") val repeatedArray = ("$LOREM ")
.repeat(n) .repeat(n)
.trim() .trim()
@@ -109,7 +146,7 @@ class FakerStrings(private val random: Random) {
} }
companion object { companion object {
val LOREM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy "+ const val LOREM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy "+
"eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam "+ "eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam "+
"voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita "+ "voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita "+
"kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem "+ "kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem "+

View File

@@ -12,7 +12,6 @@ import de.sebse.fuplanner2.database.Course
import de.sebse.fuplanner2.database.User import de.sebse.fuplanner2.database.User
import de.sebse.fuplanner2.utils.Notifications import de.sebse.fuplanner2.utils.Notifications
import de.sebse.fuplanner2.utils.UpdateResult import de.sebse.fuplanner2.utils.UpdateResult
import de.sebse.fuplanner2.utils.console
import de.sebse.fuplanner2.whiteboard.Whiteboard import de.sebse.fuplanner2.whiteboard.Whiteboard
import de.sebse.fuplanner2.whiteboard.getCourse import de.sebse.fuplanner2.whiteboard.getCourse
import de.sebse.fuplanner2.whiteboard.getCourses import de.sebse.fuplanner2.whiteboard.getCourses
@@ -39,6 +38,7 @@ class CourseWorker(context: Context, params: WorkerParameters) : AbstractAccount
updates.added.forEach { updates.added.forEach {
if (it.isSummerSemester == latestSemester.semester && it.year == latestSemester.year) { if (it.isSummerSemester == latestSemester.semester && it.year == latestSemester.year) {
EventWorker.work(applicationContext, database, user, it) EventWorker.work(applicationContext, database, user, it)
AnnouncementWorker.work(applicationContext, database, user, it)
} }
} }
Notifications.courseUpdates(updates, database, applicationContext) Notifications.courseUpdates(updates, database, applicationContext)

View File

@@ -7,7 +7,6 @@ import de.sebse.fuplanner2.auth.AppAccounts
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.utils.Notifications import de.sebse.fuplanner2.utils.Notifications
import de.sebse.fuplanner2.utils.Updatable
import de.sebse.fuplanner2.utils.UpdateResult import de.sebse.fuplanner2.utils.UpdateResult
import de.sebse.fuplanner2.utils.mergeUpdatable import de.sebse.fuplanner2.utils.mergeUpdatable
@@ -52,6 +51,7 @@ class SyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(c
courseCreations.forEach { course -> courseCreations.forEach { course ->
if (course.isSummerSemester == latestSemester.semester && course.year == latestSemester.year) { if (course.isSummerSemester == latestSemester.semester && course.year == latestSemester.year) {
EventWorker.work(applicationContext, database, it, course) EventWorker.work(applicationContext, database, it, course)
AnnouncementWorker.work(applicationContext, database, it, course)
} }
} }
Notifications.courseUpdates(notifications, database, applicationContext) Notifications.courseUpdates(notifications, database, applicationContext)

View File

@@ -1,5 +1,6 @@
package de.sebse.fuplanner2 package de.sebse.fuplanner2
import de.sebse.fuplanner2.ui.tools.previews.AnnouncementPreviewProvider
import de.sebse.fuplanner2.utils.getFaker import de.sebse.fuplanner2.utils.getFaker
import org.junit.Test import org.junit.Test
@@ -17,4 +18,9 @@ class FakerTest {
getFaker().strings.lorem(100, 1000) getFaker().strings.lorem(100, 1000)
getFaker().strings.lorem(100, 1000) getFaker().strings.lorem(100, 1000)
} }
@Test
fun announcement() {
AnnouncementPreviewProvider().values.take(10)
}
} }