Add calendar widget transition
This commit is contained in:
parent
7cee2bba99
commit
dd09487729
@ -1,18 +1,17 @@
|
|||||||
package de.mm20.launcher2.ui.common
|
package de.mm20.launcher2.ui.common
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.EnterExitState
|
||||||
import androidx.compose.animation.SharedTransitionLayout
|
import androidx.compose.animation.SharedTransitionLayout
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
import androidx.compose.foundation.ScrollState
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -33,12 +32,12 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.mm20.launcher2.search.data.Tag
|
import de.mm20.launcher2.search.data.Tag
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
||||||
import de.mm20.launcher2.ui.layout.BottomReversed
|
|
||||||
import de.mm20.launcher2.ui.layout.TopReversed
|
import de.mm20.launcher2.ui.layout.TopReversed
|
||||||
import de.mm20.launcher2.ui.modifier.consumeAllScrolling
|
import de.mm20.launcher2.ui.modifier.consumeAllScrolling
|
||||||
|
|
||||||
@ -111,11 +110,16 @@ fun FavoritesTagSelector(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (canScroll) {
|
if (canScroll) {
|
||||||
|
val rot by transition.animateFloat {
|
||||||
|
if (it == EnterExitState.Visible) 0f else 180f
|
||||||
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.sharedElement(
|
modifier = Modifier
|
||||||
|
.sharedElement(
|
||||||
rememberSharedContentState("expandButton"),
|
rememberSharedContentState("expandButton"),
|
||||||
this@AnimatedContent
|
this@AnimatedContent
|
||||||
),
|
)
|
||||||
|
.rotate(rot),
|
||||||
onClick = { onExpand(true) }) {
|
onClick = { onExpand(true) }) {
|
||||||
Icon(Icons.Rounded.ExpandMore, null)
|
Icon(Icons.Rounded.ExpandMore, null)
|
||||||
}
|
}
|
||||||
@ -185,17 +189,20 @@ fun FavoritesTagSelector(
|
|||||||
modifier = Modifier.fillMaxHeight(),
|
modifier = Modifier.fillMaxHeight(),
|
||||||
verticalArrangement = if (reverse) Arrangement.TopReversed else Arrangement.Bottom,
|
verticalArrangement = if (reverse) Arrangement.TopReversed else Arrangement.Bottom,
|
||||||
) {
|
) {
|
||||||
if (expanded) {
|
val rot by transition.animateFloat {
|
||||||
|
if (it == EnterExitState.Visible) 0f else 180f
|
||||||
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.sharedElement(
|
modifier = Modifier
|
||||||
|
.sharedElement(
|
||||||
rememberSharedContentState("expandButton"),
|
rememberSharedContentState("expandButton"),
|
||||||
this@AnimatedContent
|
this@AnimatedContent
|
||||||
),
|
)
|
||||||
|
.rotate(rot),
|
||||||
onClick = { onExpand(false) }
|
onClick = { onExpand(false) }
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Rounded.ExpandLess, null)
|
Icon(Icons.Rounded.ExpandLess, null)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (editButton) {
|
if (editButton) {
|
||||||
SmallFloatingActionButton(
|
SmallFloatingActionButton(
|
||||||
|
|||||||
@ -3,22 +3,48 @@ package de.mm20.launcher2.ui.launcher.widgets.calendar
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideIn
|
||||||
|
import androidx.compose.animation.slideOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.Add
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material.icons.rounded.ArrowDropDown
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material.icons.rounded.ChevronLeft
|
||||||
|
import androidx.compose.material.icons.rounded.ChevronRight
|
||||||
|
import androidx.compose.material.icons.rounded.OpenInNew
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
@ -37,6 +63,8 @@ fun CalendarWidget(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
|
val selectedDate by viewModel.selectedDate
|
||||||
|
|
||||||
LaunchedEffect(null) {
|
LaunchedEffect(null) {
|
||||||
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
viewModel.onActive()
|
viewModel.onActive()
|
||||||
@ -59,7 +87,6 @@ fun CalendarWidget(
|
|||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
val selectedDate by viewModel.selectedDate
|
|
||||||
var showDropdown by remember { mutableStateOf(false) }
|
var showDropdown by remember { mutableStateOf(false) }
|
||||||
TextButton(onClick = { showDropdown = true }) {
|
TextButton(onClick = { showDropdown = true }) {
|
||||||
Text(
|
Text(
|
||||||
@ -98,10 +125,10 @@ fun CalendarWidget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val events by viewModel.calendarEvents
|
val events by viewModel.calendarEvents
|
||||||
|
val runningEvents by viewModel.hiddenPastEvents
|
||||||
val hasPermission by viewModel.hasPermission.collectAsState()
|
val hasPermission by viewModel.hasPermission.collectAsState()
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateContentSize()
|
|
||||||
.padding(horizontal = 12.dp)
|
.padding(horizontal = 12.dp)
|
||||||
.padding(bottom = 12.dp)
|
.padding(bottom = 12.dp)
|
||||||
) {
|
) {
|
||||||
@ -114,6 +141,27 @@ fun CalendarWidget(
|
|||||||
onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) }
|
onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
AnimatedContent(
|
||||||
|
Triple(
|
||||||
|
selectedDate,
|
||||||
|
events,
|
||||||
|
runningEvents
|
||||||
|
),
|
||||||
|
transitionSpec = {
|
||||||
|
when {
|
||||||
|
initialState.first == targetState.first -> fadeIn() togetherWith fadeOut()
|
||||||
|
initialState.first < targetState.first -> {
|
||||||
|
fadeIn() + slideIn { IntOffset((it.width * 0.25f).toInt(), 0) } togetherWith
|
||||||
|
fadeOut() + slideOut { IntOffset((it.width * -0.25f).toInt(), 0) }
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
fadeIn() + slideIn { IntOffset((it.width * -0.25f).toInt(), 0) } togetherWith
|
||||||
|
fadeOut() + slideOut { IntOffset((it.width * 0.25f).toInt(), 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { (_, events, runningEvents) ->
|
||||||
|
Column {
|
||||||
if (events.isEmpty() && hasPermission == true) {
|
if (events.isEmpty() && hasPermission == true) {
|
||||||
Info(text = stringResource(R.string.calendar_widget_no_events))
|
Info(text = stringResource(R.string.calendar_widget_no_events))
|
||||||
}
|
}
|
||||||
@ -122,7 +170,6 @@ fun CalendarWidget(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
val runningEvents by viewModel.hiddenPastEvents
|
|
||||||
if (runningEvents > 0) {
|
if (runningEvents > 0) {
|
||||||
Info(
|
Info(
|
||||||
text = pluralStringResource(
|
text = pluralStringResource(
|
||||||
@ -139,7 +186,12 @@ fun CalendarWidget(
|
|||||||
if (nextEvents.isNotEmpty()) {
|
if (nextEvents.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.calendar_widget_next_events),
|
stringResource(R.string.calendar_widget_next_events),
|
||||||
modifier = Modifier.padding(start = 4.dp, end = 4.dp, top = 8.dp, bottom = 4.dp),
|
modifier = Modifier.padding(
|
||||||
|
start = 4.dp,
|
||||||
|
end = 4.dp,
|
||||||
|
top = 8.dp,
|
||||||
|
bottom = 4.dp
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
SearchResultList(
|
SearchResultList(
|
||||||
@ -148,11 +200,19 @@ fun CalendarWidget(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val pinnedEvents by viewModel.pinnedCalendarEvents.collectAsState()
|
val pinnedEvents by viewModel.pinnedCalendarEvents.collectAsState()
|
||||||
if (pinnedEvents.isNotEmpty()) {
|
if (pinnedEvents.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.calendar_widget_pinned_events),
|
stringResource(R.string.calendar_widget_pinned_events),
|
||||||
modifier = Modifier.padding(start = 4.dp, end = 4.dp, top = 8.dp, bottom = 4.dp),
|
modifier = Modifier.padding(
|
||||||
|
start = 4.dp,
|
||||||
|
end = 4.dp,
|
||||||
|
top = 8.dp,
|
||||||
|
bottom = 4.dp
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
SearchResultList(
|
SearchResultList(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user