First Compose layout
This commit is contained in:
@@ -32,10 +32,12 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
compose true
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled = true
|
||||
composeOptions {
|
||||
kotlinCompilerVersion kotlin_version
|
||||
kotlinCompilerExtensionVersion compose_version
|
||||
}
|
||||
|
||||
// To inline the bytecode built with JVM target 1.8 into
|
||||
@@ -54,7 +56,6 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
@@ -77,4 +78,14 @@ dependencies {
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
// Compose
|
||||
implementation "androidx.compose.runtime:runtime:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
||||
implementation "androidx.compose.ui:ui:$compose_version"
|
||||
implementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
|
||||
implementation "androidx.compose.material:material:$compose_version"
|
||||
implementation "com.google.android.material:compose-theme-adapter:$compose_version"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.beust.klaxon.KlaxonException
|
||||
|
||||
|
||||
data class Lecturer (
|
||||
val fistName: String,
|
||||
val firstName: String,
|
||||
val lastName: String,
|
||||
val email: String,
|
||||
val isResponsible: Boolean
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package de.sebse.fuplanner2.ui.courses
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import de.sebse.fuplanner2.R
|
||||
import de.sebse.fuplanner2.database.Course
|
||||
import de.sebse.fuplanner2.ui.CaptionHolder
|
||||
import de.sebse.fuplanner2.ui.CustomHolder
|
||||
import de.sebse.fuplanner2.ui.ListItemHolder
|
||||
import de.sebse.fuplanner2.ui.ViewHolderGenerator
|
||||
import de.sebse.fuplanner2.utils.cast
|
||||
import java.util.*
|
||||
|
||||
class CoursesAdapter(private val onclick: (Course) -> Unit) : RecyclerView.Adapter<CustomHolder>() {
|
||||
|
||||
private val positionalData: ArrayList<Any> = arrayListOf()
|
||||
var dataset: List<Course> = listOf()
|
||||
set(value) {
|
||||
field = value
|
||||
positionalData.clear()
|
||||
var last: Course? = null
|
||||
dataset.forEach {
|
||||
if (last?.let { last -> it < last } != false)
|
||||
positionalData.add(Pair(it.isSummerSemester, it.year))
|
||||
positionalData.add(it)
|
||||
last = it
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomHolder {
|
||||
return ViewHolderGenerator.getHolderByType(parent, viewType)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: CustomHolder, position: Int) {
|
||||
// val viewType = getItemViewType(position)
|
||||
val res = holder.itemView.resources
|
||||
when (holder) {
|
||||
is CaptionHolder -> cast<Pair<Boolean, Int?>>(positionalData[position])?.let { (isSummer, year) ->
|
||||
holder.string.text = when {
|
||||
year == null -> res.getString(R.string.projects)
|
||||
isSummer -> res.getString(R.string.summer_semester, year)
|
||||
else -> res.getString(R.string.winter_semester, year, year+1)
|
||||
}
|
||||
}
|
||||
is ListItemHolder -> cast<Course>(positionalData[position])?.let {
|
||||
holder.title.text = it.title
|
||||
holder.subLeft.text = it.lecturers.filter { lecturer -> lecturer.isResponsible }.joinToString { lecturer ->
|
||||
res.getString(R.string.full_name, lecturer.fistName.substring(0, 1)+".", lecturer.lastName)
|
||||
}
|
||||
holder.subRight.text = it.type
|
||||
holder.itemView.setOnClickListener { _ -> this.onclick(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (positionalData[position]) {
|
||||
is Course -> ViewHolderGenerator.HolderType.ITEM.ordinal
|
||||
else -> ViewHolderGenerator.HolderType.HEADER.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
// Return the size of your dataset (invoked by the layout manager)
|
||||
override fun getItemCount() = positionalData.size
|
||||
}
|
||||
@@ -1,25 +1,20 @@
|
||||
package de.sebse.fuplanner2.ui.courses
|
||||
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings.Global.getString
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import de.sebse.fuplanner2.R
|
||||
import de.sebse.fuplanner2.auth.AppAccounts
|
||||
import de.sebse.fuplanner2.database.Course
|
||||
import de.sebse.fuplanner2.databinding.ActivityMainBinding
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import de.sebse.fuplanner2.databinding.FragmentRefreshRecyclerBinding
|
||||
import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME
|
||||
import de.sebse.fuplanner2.worker.CourseWorker
|
||||
|
||||
class CoursesFragment : Fragment() {
|
||||
|
||||
@@ -32,42 +27,23 @@ class CoursesFragment : Fragment() {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val viewManager = LinearLayoutManager(context)
|
||||
val viewAdapter = CoursesAdapter(this::onClick)
|
||||
): View {
|
||||
navController = findNavController()
|
||||
coursesViewModel = ViewModelProvider(this).get(CoursesViewModel::class.java)
|
||||
binding = FragmentRefreshRecyclerBinding.inflate(inflater, container, false)
|
||||
return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply {
|
||||
binding.recyclerView.apply {
|
||||
//setHasFixedSize(true)
|
||||
layoutManager = viewManager
|
||||
adapter = viewAdapter
|
||||
}
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
val work = OneTimeWorkRequestBuilder<CourseWorker>()
|
||||
.setInputData(workDataOf(
|
||||
KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name
|
||||
))
|
||||
.build()
|
||||
WorkManager.getInstance(context.applicationContext)
|
||||
.enqueue(work)
|
||||
WorkManager.getInstance(context.applicationContext)
|
||||
.getWorkInfoByIdLiveData(work.id)
|
||||
.observe(viewLifecycleOwner, {
|
||||
if (it.state.isFinished)
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
})
|
||||
}
|
||||
coursesViewModel.text.observe(viewLifecycleOwner, Observer {
|
||||
viewAdapter.dataset = it
|
||||
})
|
||||
navController = findNavController()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onClick(course: Course) {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
MdcTheme {
|
||||
val courses = coursesViewModel.text.observeAsState(listOf())
|
||||
LazyColumn {
|
||||
items(courses.value) { course -> CourseItem(course) {
|
||||
course.uid?.let {
|
||||
navController.navigate(CoursesFragmentDirections.actionNavHomeToCourseDetails(it, course.title))
|
||||
}
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
154
app/src/main/java/de/sebse/fuplanner2/ui/courses/components.kt
Normal file
154
app/src/main/java/de/sebse/fuplanner2/ui/courses/components.kt
Normal file
@@ -0,0 +1,154 @@
|
||||
package de.sebse.fuplanner2.ui.courses
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.Person
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import de.sebse.fuplanner2.R
|
||||
import de.sebse.fuplanner2.database.Course
|
||||
import de.sebse.fuplanner2.database.Lecturer
|
||||
import de.sebse.fuplanner2.utils.color.getColor
|
||||
|
||||
@Composable
|
||||
fun CourseList(courses: List<Course>, onclick: () -> Unit) {
|
||||
LazyColumn {
|
||||
items(courses) { course -> CourseItem(course, onclick) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CourseItem(course: Course, onclick: () -> Unit) {
|
||||
@Suppress("SimplifiableCallChain")
|
||||
CourseItem(
|
||||
id = course.uid,
|
||||
title = course.title,
|
||||
lecturers = course.lecturers
|
||||
.filter { lecturer -> lecturer.isResponsible }
|
||||
.map { lecturer -> stringResource(R.string.full_name, lecturer.firstName.substring(0, 1)+".", lecturer.lastName) }
|
||||
.joinToString(),
|
||||
type = course.type,
|
||||
onclick = onclick
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CourseItem(id: Long?, title: String, lecturers: String, type: String, onclick: () -> Unit) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onclick)
|
||||
.padding(dimensionResource(R.dimen.card_view_margin))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(dimensionResource(R.dimen.card_view_padding))
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
color = Color(getColor(LocalContext.current, id ?: 0, highContrast = true)),
|
||||
style = MaterialTheme.typography.h6
|
||||
)
|
||||
CourseItemHint(
|
||||
icon = Icons.Outlined.Person,
|
||||
imageAltRes = R.string.lecturers,
|
||||
text = lecturers
|
||||
)
|
||||
CourseItemHint(
|
||||
icon = Icons.Outlined.Info,
|
||||
imageAltRes = R.string.course_type,
|
||||
text = type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CourseItemHint(icon: ImageVector, @StringRes imageAltRes: Int, text: String) {
|
||||
Row {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = stringResource(imageAltRes),
|
||||
modifier = Modifier.padding(
|
||||
start = dimensionResource(R.dimen.card_view_padding)
|
||||
)
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
modifier = Modifier.padding(
|
||||
start = dimensionResource(R.dimen.card_view_padding)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
fun CourseListPreview() {
|
||||
val course = Course(
|
||||
userId = 1,
|
||||
lastRefreshed = 1,
|
||||
lvNumber = hashSetOf("1000"),
|
||||
title = "Kurs mit tollem Namen",
|
||||
description = "Beschreibung",
|
||||
internalId = "165464635",
|
||||
isSummerSemester = false,
|
||||
year = 2020,
|
||||
lecturers = listOf(
|
||||
Lecturer(
|
||||
firstName = "Max",
|
||||
lastName = "Maurer",
|
||||
email = "some@mail.com",
|
||||
isResponsible = false
|
||||
),
|
||||
Lecturer(
|
||||
firstName = "Peter",
|
||||
lastName = "Engelbert",
|
||||
email = "coolio@example.com",
|
||||
isResponsible = false
|
||||
)
|
||||
),
|
||||
moduleType = 1236,
|
||||
type = "Type"
|
||||
)
|
||||
MdcTheme {
|
||||
CourseList(listOf(
|
||||
course, course, course
|
||||
)) { }
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview
|
||||
@Composable
|
||||
fun CourseItemPreview() {
|
||||
MdcTheme {
|
||||
CourseItem(
|
||||
12411111111,
|
||||
"Höhere Algorithmik",
|
||||
"M. Berta, P. Parker",
|
||||
"Vorlesung"
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class DetailsAdapter(private val onQuickLink: (ButtonTypes) -> Unit, private val
|
||||
holder.btnResources.setOnClickListener { this.onQuickLink(ButtonTypes.RESOURCES) }
|
||||
}
|
||||
is MailHolder -> cast<Lecturer>(positionalData[position])?.let { type ->
|
||||
holder.title.text = res.getString(R.string.full_name, type.fistName, type.lastName)
|
||||
holder.title.text = res.getString(R.string.full_name, type.firstName, type.lastName)
|
||||
holder.subLeft.text = type.email
|
||||
holder.itemView.setOnClickListener { this.onMailTo(type) }
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ class DetailsFragment : Fragment() {
|
||||
intent.type = "text/html"
|
||||
intent.data = Uri.fromParts("mailto", lecturer.email, null)
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, this.title)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.email_preview, lecturer.fistName, lecturer.lastName))
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.send_email, lecturer.fistName, lecturer.lastName)))
|
||||
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.email_preview, lecturer.firstName, lecturer.lastName))
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.send_email, lecturer.firstName, lecturer.lastName)))
|
||||
}
|
||||
|
||||
private fun launchFragment(btnType: DetailsAdapter.ButtonTypes) {
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
package de.sebse.fuplanner2.ui.schedule
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.*
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.alamkanak.weekview.WeekView
|
||||
import com.alamkanak.weekview.WeekViewDisplayable
|
||||
import com.alamkanak.weekview.WeekViewEntity
|
||||
import com.alamkanak.weekview.WeekViewEvent
|
||||
import de.sebse.fuplanner2.R
|
||||
import de.sebse.fuplanner2.database.AppDatabase
|
||||
import de.sebse.fuplanner2.database.Course
|
||||
import de.sebse.fuplanner2.database.Event
|
||||
import de.sebse.fuplanner2.ui.schedule.MyCustomPagingAdapter.LoadMoreHandler
|
||||
import de.sebse.fuplanner2.utils.color.getColor
|
||||
import de.sebse.fuplanner2.utils.getHtmlSpannedString
|
||||
import de.sebse.fuplanner2.utils.toTimeString
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -26,8 +23,6 @@ import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
|
||||
class ScheduleFragment : Fragment() {
|
||||
|
||||
@@ -164,56 +159,6 @@ data class ContextEvent(val actCtx: Context, val event: Event) {
|
||||
.setStyle(style)
|
||||
.build()
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun getColor(actCtx: Context, seed: Long): Int {
|
||||
var h = Random(seed).nextInt(0xFFFF)
|
||||
h = h * 360 / 0xffff
|
||||
//int s = 0xff & encodedHash[2];
|
||||
//s = s * 100 / 0xffff;
|
||||
//int v = 0xff & encodedHash[3];
|
||||
//v = v * 100 / 0xffff;
|
||||
|
||||
// range for more beautiful colors
|
||||
h = h / 30 * 30
|
||||
val (s, v) =
|
||||
if (actCtx.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES)
|
||||
Pair(100, 20)//Pair(100, 80)
|
||||
else
|
||||
Pair(60, 100)
|
||||
val (r, g, b) = hsvToRgb(h / 360.0, s / 100.0, v / 100.0)
|
||||
return rgbToColorInt(r, g, b)
|
||||
}
|
||||
|
||||
private fun hsvToRgb(
|
||||
hue: Double,
|
||||
saturation: Double,
|
||||
value: Double
|
||||
): Triple<Double, Double, Double> {
|
||||
val h = (hue * 6).toInt()
|
||||
val f = hue * 6 - h
|
||||
val p = value * (1 - saturation)
|
||||
val q = value * (1 - f * saturation)
|
||||
val t = value * (1 - (1 - f) * saturation)
|
||||
return when (h) {
|
||||
0 -> Triple(value, t, p)
|
||||
1 -> Triple(q, value, p)
|
||||
2 -> Triple(p, value, t)
|
||||
3 -> Triple(p, q, value)
|
||||
4 -> Triple(t, p, value)
|
||||
5 -> Triple(value, p, q)
|
||||
else -> Triple(0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
private fun rgbToColorInt(
|
||||
r: Double,
|
||||
g: Double,
|
||||
b: Double
|
||||
): Int {
|
||||
return 0xFF000000.toInt() + (r*0xFF).roundToInt() * 0x10000 + (g*0xFF).roundToInt() * 0x100 + (b*0xFF).roundToInt()
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomPagingAdapter(
|
||||
|
||||
@@ -5,11 +5,13 @@ package de.sebse.fuplanner2.utils
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.text.Spanned
|
||||
import android.text.format.DateFormat
|
||||
import android.util.Log
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.LiveData
|
||||
@@ -20,6 +22,8 @@ import kotlinx.coroutines.runBlocking
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
object console {
|
||||
@@ -60,6 +64,60 @@ object xml {
|
||||
}
|
||||
}
|
||||
|
||||
object color {
|
||||
@ColorInt
|
||||
fun getColor(actCtx: Context, seed: Long, highContrast: Boolean = false): Int {
|
||||
var h = Random(seed).nextInt(0xFFFF)
|
||||
h = h * 360 / 0xffff
|
||||
//int s = 0xff & encodedHash[2];
|
||||
//s = s * 100 / 0xffff;
|
||||
//int v = 0xff & encodedHash[3];
|
||||
//v = v * 100 / 0xffff;
|
||||
|
||||
// range for more beautiful colors
|
||||
h = h / 30 * 30
|
||||
val isNightMode = actCtx.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
val doDarkColors = if (highContrast) !isNightMode else isNightMode
|
||||
val (s, v) =
|
||||
if (doDarkColors)
|
||||
Pair(100, 50)//Pair(100, 80)
|
||||
else
|
||||
Pair(100, 70)
|
||||
val (r, g, b) = hsvToRgb(h / 360.0, s / 100.0, v / 100.0)
|
||||
return rgbToColorInt(r, g, b)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
private fun rgbToColorInt(
|
||||
r: Double,
|
||||
g: Double,
|
||||
b: Double
|
||||
): Int {
|
||||
return 0xFF000000.toInt() + (r*0xFF).roundToInt() * 0x10000 + (g*0xFF).roundToInt() * 0x100 + (b*0xFF).roundToInt()
|
||||
}
|
||||
|
||||
private fun hsvToRgb(
|
||||
hue: Double,
|
||||
saturation: Double,
|
||||
value: Double
|
||||
): Triple<Double, Double, Double> {
|
||||
val h = (hue * 6).toInt()
|
||||
val f = hue * 6 - h
|
||||
val p = value * (1 - saturation)
|
||||
val q = value * (1 - f * saturation)
|
||||
val t = value * (1 - (1 - f) * saturation)
|
||||
return when (h) {
|
||||
0 -> Triple(value, t, p)
|
||||
1 -> Triple(q, value, p)
|
||||
2 -> Triple(p, value, t)
|
||||
3 -> Triple(p, q, value)
|
||||
4 -> Triple(t, p, value)
|
||||
5 -> Triple(value, p, q)
|
||||
else -> Triple(0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T: ListenableWorker> enqueueOneTimeWork(appCtx: Context, workBuilder: (OneTimeWorkRequest.Builder) -> OneTimeWorkRequest.Builder): LiveData<WorkInfo> {
|
||||
val work = workBuilder(OneTimeWorkRequestBuilder<T>()).build()
|
||||
val workManager = WorkManager.getInstance(appCtx)
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<string name="dialog_location_br"><![CDATA[<b>Location:</b><br>%1$s<br>]]></string>
|
||||
<string name="dialog_time"><![CDATA[<b>Time:</b><br>%1$s - %2$s]]></string>
|
||||
<string name="dialog_course_br"><![CDATA[<b>Course:</b><br>%1$s<br>]]></string>
|
||||
<string name="course_type">Course Type</string>
|
||||
<plurals name="not_course_update_text">
|
||||
<item quantity="one">One course message</item>
|
||||
<item quantity="other">%1$d course messages</item>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="FUTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
|
||||
<style name="FUTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.31'
|
||||
ext.compose_version = '1.0.5'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
||||
Reference in New Issue
Block a user