Compare commits

..
3 Commits
Author SHA1 Message Date
makiolaj 883e8fd101 Release v2.22.4-beta.1 (#363, #364)
Beta — Codeberg pre-release / detect (push) Successful in 5s
Beta — Codeberg pre-release / beta (push) Successful in 26m49s
2026-10-06 19:18:01 +02:00
Jean-Luc Makiolaandmakiolaj 11d1147931 Load only what the widgets show (#363) (#371)
Beta — Codeberg pre-release / detect (push) Successful in 5s
Beta — Codeberg pre-release / beta (push) Skipped
The widgets now load only what they show, so each update does much less provider work.

- Month widget: loads the displayed month ±3 months instead of ±400 days around today. `provideGlance` centres on the widget's stored month, and paging past the loaded months reloads in the live session.
- Agenda widget: loads its chosen range instead of always 365 days. Picking a wider range reloads.

This doesn't fully fix the issue. The likely cause on the reporter's Honor (EMUI) is the system holding back the WorkManager jobs Glance renders through, which the app can't change. This only shortens each update. Rendering without WorkManager is left until the reporter confirms that cause.

Closes #363

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/371
2026-10-06 19:17:38 +02:00
Jean-Luc Makiolaandmakiolaj 836817cd12 Re-query after own writes and on return to foreground (#364) (#370)
Beta — Codeberg pre-release / detect (push) Successful in 6s
Beta — Codeberg pre-release / beta (push) Skipped
Views now re-query after the app's own writes and whenever the app comes back to the foreground, instead of relying only on the provider's change notification.

On the reporter's Samsung (Android 12, "My Calendar") edits and deletes went through, but no view refreshed until a restart. Tapping today in the agenda, which starts a fresh query, showed the correct data, so the provider had the change and the notification never reached us.

- `CalendarRepositoryImpl`: every write goes through a `write { }` helper that triggers a re-query when it finishes, also when the write threw.
- New `CalendarRepository.refresh()`, called from `MainActivity.onStart()`.
- Change ticks use `DROP_OLDEST`, so a tick is never dropped while a subscriber holds the previous one.
- The instances query filters `Events.DELETED = 0`, for providers that soft-delete and keep the instances around.
- `docs/ARCHITECTURE.md`: the observer principle mentions the extra re-queries.

The issue reads like a save/delete bug, but the writes themselves work. The fix targets the refresh, which is what was broken. The agenda opening on the 1st of the month, also mentioned in the thread, is the existing focus behaviour and isn't changed here.

Closes #364

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/370
2026-10-06 19:17:24 +02:00
51 changed files with 80 additions and 365 deletions
-8
View File
@@ -7,13 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [2.22.5] — 2026-10-07
### Fixed
- **Reopening the app the next morning shows the right day.** The month view used to mark yesterday as today and keep the day you picked the evening before selected, so you had to tap today to see its events. Every view now moves on to the new day at midnight and when you come back to the app. A view you browsed to another date stays where it is ([#373]).
## [2.22.4] — 2026-10-07
### Fixed
- **Edits and deletions show up straight away on every phone.** On some phones, Samsung among them, an edited, moved or deleted event stayed where it was until the app was restarted, so a second edit could act on an event that had already changed. Calendula now refreshes its views itself after every change, and again when you come back to the app ([#364]).
- **The home-screen widgets do less work per update.** The month widget loads the months around the one it shows instead of more than two years of events, and the agenda widget only its chosen range. This should help widgets that stayed on "loading" on slower phones ([#363]).
@@ -1848,4 +1841,3 @@ automatically, with zero telemetry and no internet permission.
[#361]: https://codeberg.org/jlmakiola/calendula/issues/361
[#363]: https://codeberg.org/jlmakiola/calendula/issues/363
[#364]: https://codeberg.org/jlmakiola/calendula/issues/364
[#373]: https://codeberg.org/jlmakiola/calendula/issues/373
+2 -2
View File
@@ -32,8 +32,8 @@ android {
// (up to 2.22.3: 2.22.3 -> 22203; from 2.22.4: 2.23.0-beta.1 -> 2230001,
// 2.22.4 -> 2220499), and CI fails if the committed one doesn't match.
// See docs/RELEASING.md.
versionCode = 2220599
versionName = "2.22.5"
versionCode = 2220401
versionName = "2.22.4-beta.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -20,8 +20,6 @@ import de.jeanlucmakiola.calendula.data.contacts.ContactSpecialDatesDataSource
import de.jeanlucmakiola.calendula.data.contacts.SpecialDatesCalendarSpec
import de.jeanlucmakiola.calendula.data.reminders.ProviderReminderInstanceSource
import de.jeanlucmakiola.calendula.data.reminders.ReminderInstanceSource
import de.jeanlucmakiola.calendula.data.time.AndroidTodaySource
import de.jeanlucmakiola.calendula.data.time.TodaySource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import javax.inject.Singleton
@@ -63,12 +61,6 @@ abstract class DataBindModule {
abstract fun bindSpecialDatesCalendarSpec(
impl: AndroidSpecialDatesCalendarSpec,
): SpecialDatesCalendarSpec
@Binds
@Singleton
abstract fun bindTodaySource(
impl: AndroidTodaySource,
): TodaySource
}
@Module
@@ -1,63 +0,0 @@
package de.jeanlucmakiola.calendula.data.time
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.core.content.ContextCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.transformLatest
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import javax.inject.Inject
import kotlin.time.Clock
/** The current local date, re-emitted whenever it changes (#373). */
interface TodaySource {
val today: Flow<LocalDate>
}
/**
* Re-reads the date on every minute boundary while collected, and at once on the
* system's date, time and zone broadcasts, which can arrive late or not at all.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class AndroidTodaySource @Inject constructor(
@ApplicationContext private val context: Context,
) : TodaySource {
override val today: Flow<LocalDate> = callbackFlow {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
trySend(Unit)
}
}
val filter = IntentFilter().apply {
addAction(Intent.ACTION_DATE_CHANGED)
addAction(Intent.ACTION_TIME_CHANGED)
addAction(Intent.ACTION_TIMEZONE_CHANGED)
}
ContextCompat.registerReceiver(context, receiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
awaitClose { context.unregisterReceiver(receiver) }
}
.onStart { emit(Unit) }
.transformLatest {
while (true) {
emit(currentDate())
delay(MINUTE_MILLIS - Clock.System.now().toEpochMilliseconds() % MINUTE_MILLIS)
}
}
.distinctUntilChanged()
}
private const val MINUTE_MILLIS = 60_000L
fun currentDate(): LocalDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
@@ -150,15 +150,6 @@ fun CalendarHost(
// The date every view opens on and carries along (#184); tapping a day
// anywhere focuses it and opens the day view there.
val focus = rememberSaveable(saver = ViewFocusSaver) { ViewFocus(localToday()) }
// A focus still on yesterday follows the date over, so the next view opened
// doesn't land on the old day (#373); one moved elsewhere stays put.
val today = viewModel.today.collectAsStateWithLifecycle().value
var focusToday by rememberSaveable { mutableStateOf(today.toString()) }
LaunchedEffect(today) {
val old = LocalDate.parse(focusToday)
if (old != today && focus.date == old) focus.date = today
focusToday = today.toString()
}
val switchScope = rememberCoroutineScope()
val switchTo: ((List<CalendarView>) -> List<CalendarView>) -> Unit = { move ->
@@ -4,15 +4,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.calendula.data.time.TodaySource
import de.jeanlucmakiola.calendula.data.time.currentDate
import de.jeanlucmakiola.calendula.ui.common.CalendarView
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.datetime.LocalDate
import javax.inject.Inject
/**
@@ -24,16 +21,8 @@ import javax.inject.Inject
@HiltViewModel
class CalendarHostViewModel @Inject constructor(
prefs: SettingsPrefs,
todaySource: TodaySource,
) : ViewModel() {
/** Today, kept current across midnight so the shared focus can follow it (#373). */
val today: StateFlow<LocalDate> = todaySource.today.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
initialValue = currentDate(),
)
val defaultView: StateFlow<CalendarView?> = prefs.defaultView.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
@@ -8,8 +8,6 @@ import de.jeanlucmakiola.calendula.data.di.IoDispatcher
import de.jeanlucmakiola.calendula.data.prefs.PastEventDisplay
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.calendula.data.prefs.firstDayOfWeek
import de.jeanlucmakiola.calendula.data.time.TodaySource
import de.jeanlucmakiola.calendula.data.time.currentDate
import de.jeanlucmakiola.calendula.domain.CalendarSource
import de.jeanlucmakiola.calendula.domain.EventInstance
import de.jeanlucmakiola.calendula.domain.FailureReason
@@ -23,7 +21,6 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.DayOfWeek
@@ -33,6 +30,8 @@ import kotlinx.datetime.atStartOfDayIn
import kotlinx.datetime.atTime
import kotlinx.datetime.plus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Clock
import kotlin.time.Instant
import javax.inject.Inject
@@ -41,7 +40,6 @@ import javax.inject.Inject
class AgendaViewModel @Inject constructor(
private val repository: CalendarRepository,
settingsPrefs: SettingsPrefs,
todaySource: TodaySource,
@IoDispatcher private val io: CoroutineDispatcher,
) : ViewModel() {
@@ -86,25 +84,11 @@ class AgendaViewModel @Inject constructor(
private val zone = TimeZone.currentSystemDefault()
private val todayDate: LocalDate
get() = currentDate()
get() = Clock.System.now().toLocalDateTime(zone).date
private val _anchor = MutableStateFlow(todayDate)
val anchor: StateFlow<LocalDate> = _anchor
private var lastToday = todayDate
/**
* Today, kept current across midnight (#373). An agenda still starting on
* yesterday moves on to the new day; one jumped elsewhere stays put.
*/
private val today: StateFlow<LocalDate> = todaySource.today
.onEach { date ->
val old = lastToday
lastToday = date
if (old != date && _anchor.value == old) _anchor.value = date
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), todayDate)
// A transient, in-view override of the saved agenda range. Held in memory
// (not persisted), so it survives view switches and rotation within the
// session but resets to the saved default when the app is relaunched.
@@ -129,9 +113,8 @@ class AgendaViewModel @Inject constructor(
combine(
repository.calendars(),
repository.instances(window),
today,
) { calendars, instances, today ->
buildState(params, calendars, instances, today)
) { calendars, instances ->
buildState(params, calendars, instances)
}
}
.catch { emit(AgendaUiState.Failure(FailureReason.ProviderUnavailable)) }
@@ -173,7 +156,6 @@ class AgendaViewModel @Inject constructor(
params: AgendaParams,
calendars: List<CalendarSource>,
instances: List<EventInstance>,
today: LocalDate,
): AgendaUiState {
calendarListFailure(calendars)?.let { return AgendaUiState.Failure(it) }
val anchor = params.anchor
@@ -184,7 +166,7 @@ class AgendaViewModel @Inject constructor(
val days = groupAgendaDays(anchor, rangeEnd, instances, zone)
return AgendaUiState.Success(
anchor = anchor,
today = today,
today = todayDate,
days = days,
range = params.range,
rangeIsOverride = params.rangeIsOverride,
@@ -87,7 +87,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@@ -198,17 +197,13 @@ import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus
import kotlinx.datetime.YearMonth
import kotlinx.datetime.toJavaLocalDate
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Clock
import kotlin.time.Instant
import java.time.format.TextStyle as JavaTextStyle
import java.util.Locale
private val LocalTodayPulse = staticCompositionLocalOf { 0 }
/** Live today for the pages, whose cached states may have been built before midnight (#373). */
private val LocalMonthToday = compositionLocalOf<LocalDate?> { null }
private fun MonthUiState.Success.withToday(today: LocalDate?): MonthUiState.Success =
if (today == null || today == this.today) this else copy(today = today)
private val LocalOpenWeek = staticCompositionLocalOf<((LocalDate) -> Unit)?> { null }
/** A brief push from today's full cell into its immediate neighbours. */
@@ -324,7 +319,12 @@ fun MonthScreen(
}
val dense = viewStyle == MonthViewStyle.Dense
val today by viewModel.today.collectAsStateWithLifecycle()
// Today, from whichever state is driving; the clock only covers the first
// frame, before either has loaded.
val today = when {
scrolling -> (continuousState as? ContinuousMonthUiState.Success)?.today
else -> (state as? MonthUiState.Success)?.today
} ?: Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
// The two scrolling styles index their items differently — Continuous by
// month (which the week start can't move), Dense by week (which it can) — so
@@ -520,7 +520,6 @@ fun MonthScreen(
LocalDimCutoff provides dimCutoff,
LocalMonthDrag provides chipDrag,
LocalTodayPulse provides todayPulse,
LocalMonthToday provides today,
LocalOpenWeek provides onOpenWeek,
) {
if (scrolling) {
@@ -774,7 +773,6 @@ private fun MonthPage(
) {
val ym = yearMonthForIndex(index)
val state by remember(ym, weekStart) { monthState(ym, weekStart) }.collectAsStateWithLifecycle()
val today = LocalMonthToday.current
val chipDrag = LocalMonthDrag.current
val morph = LocalMonthMorph.current
val viewMorph = LocalViewMorph.current
@@ -784,7 +782,7 @@ private fun MonthPage(
LocalViewMorph provides viewMorph.takeIf { morphActive },
) {
when (val s = state) {
is MonthUiState.Success -> page(index, s.withToday(today))
is MonthUiState.Success -> page(index, s)
else -> loading()
}
}
@@ -1390,8 +1388,7 @@ private fun SplitMonthContent(
val ym = yearMonthForIndex(index)
val pageState by remember(ym, weekStart) { monthState(ym, weekStart) }
.collectAsStateWithLifecycle()
val page = (pageState as? MonthUiState.Success)?.withToday(LocalMonthToday.current)
?: return@HorizontalPager
val page = pageState as? MonthUiState.Success ?: return@HorizontalPager
// Only the settled page carries the drag and the morph tags: its
// neighbours repeat some of its dates and would claim them.
val active = index == pagerState.settledPage
@@ -7,8 +7,6 @@ import de.jeanlucmakiola.calendula.data.calendar.CalendarRepository
import de.jeanlucmakiola.calendula.data.di.IoDispatcher
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.calendula.data.prefs.firstDayOfWeek
import de.jeanlucmakiola.calendula.data.time.TodaySource
import de.jeanlucmakiola.calendula.data.time.currentDate
import de.jeanlucmakiola.calendula.domain.CalendarSource
import de.jeanlucmakiola.calendula.domain.EventInstance
import de.jeanlucmakiola.calendula.domain.FailureReason
@@ -47,6 +45,7 @@ import kotlinx.datetime.minus
import kotlinx.datetime.plus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Clock
import kotlin.time.Instant
import javax.inject.Inject
@@ -58,7 +57,6 @@ private const val MONTH_CACHE_SPAN = 2
class MonthViewModel @Inject constructor(
private val repository: CalendarRepository,
settingsPrefs: SettingsPrefs,
todaySource: TodaySource,
@IoDispatcher private val io: CoroutineDispatcher,
) : ViewModel() {
@@ -92,18 +90,7 @@ class MonthViewModel @Inject constructor(
)
private val todayDate: LocalDate
get() = currentDate()
private var lastToday = todayDate
/** Today, kept current across midnight so a grid left open doesn't mark yesterday (#373). */
val today: StateFlow<LocalDate> = todaySource.today
.onEach { date ->
val old = lastToday
lastToday = date
if (old != date) followToday(old, date)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), todayDate)
get() = Clock.System.now().toLocalDateTime(zone).date
private val _month = MutableStateFlow(YearMonth(todayDate.year, todayDate.month))
val month: StateFlow<YearMonth> = _month
@@ -152,9 +139,8 @@ class MonthViewModel @Inject constructor(
combine(
repository.calendars(),
repository.instances(monthGridRange(ym, ws, zone)),
today,
) { calendars, instances, today ->
buildState(ym, ws, calendars, instances, today)
) { calendars, instances ->
buildState(ym, ws, calendars, instances)
}
.catch { emit(MonthUiState.Failure(FailureReason.ProviderUnavailable)) }
.flowOn(io)
@@ -225,9 +211,8 @@ class MonthViewModel @Inject constructor(
combine(
repository.calendars(),
repository.instances(range),
today,
) { calendars, instances, today ->
buildContinuousState(window, ws, style, calendars, instances, today)
) { calendars, instances ->
buildContinuousState(window, ws, style, calendars, instances)
}
}
// A load landing is the cue to reach further out. Widening from here
@@ -272,7 +257,6 @@ class MonthViewModel @Inject constructor(
style: MonthViewStyle,
calendars: List<CalendarSource>,
instances: List<EventInstance>,
today: LocalDate,
): ContinuousMonthUiState {
calendarListFailure(calendars)?.let { return ContinuousMonthUiState.Failure(it) }
// Bucketed once for the whole window: a row then takes the handful of
@@ -299,7 +283,7 @@ class MonthViewModel @Inject constructor(
emptyMap()
}
return ContinuousMonthUiState.Success(
today = today,
today = todayDate,
monthsByIndex = months,
weeksByIndex = weeks,
weekStart = weekStart,
@@ -349,18 +333,6 @@ class MonthViewModel @Inject constructor(
_selectedDate.value = null
}
/**
* The date rolled over from [old] to [new]. A paged grid still on [old]'s
* month moves on to [new]'s and drops the day picked yesterday; one browsed
* elsewhere stays put. The scrolling styles only repaint the today marker.
*/
private fun followToday(old: LocalDate, new: LocalDate) {
if (viewStyle.value.isScrolling) return
if (_month.value != YearMonth(old.year, old.month)) return
_month.value = YearMonth(new.year, new.month)
_selectedDate.value = null
}
/**
* Track [_month] to the month a scrolling style is showing. Those styles
* navigate by scroll position and never set [_month] themselves, so it would
@@ -386,13 +358,12 @@ class MonthViewModel @Inject constructor(
weekStart: DayOfWeek,
calendars: List<CalendarSource>,
instances: List<EventInstance>,
today: LocalDate,
): MonthUiState {
calendarListFailure(calendars)?.let { return MonthUiState.Failure(it) }
val weeks = layoutMonthWeeks(ym, weekStart, instances, zone)
return MonthUiState.Success(
month = ym,
today = today,
today = todayDate,
weeks = weeks,
instancesByDay = instancesByDay(weeks.flatMap { it.days }, instances, zone),
zone = zone,
@@ -461,7 +461,6 @@ private fun PagedTimelineScreen(
CompositionLocalProvider(LocalDimCutoff provides dimCutoff) {
WeekContent(
state = state,
today = today,
pagerState = pagerState,
grid = grid,
kind = kind,
@@ -483,7 +482,6 @@ private fun PagedTimelineScreen(
@Composable
private fun WeekContent(
state: WeekUiState,
today: LocalDate,
pagerState: PagerState,
grid: PageGrid,
kind: TimelineKind,
@@ -538,7 +536,7 @@ private fun WeekContent(
is WeekUiState.Success -> if (morph != null) {
TimelineMorphFrame(
morph = morph,
today = today,
today = s.today,
scrollState = scrollState,
dragController = dragController,
onEventClick = onEventClick,
@@ -551,7 +549,7 @@ private fun WeekContent(
grid = grid,
kind = kind,
span = span,
today = today,
today = s.today,
initialStripHeight = handoffStrip ?: s.allDayStripHeight(),
scrollState = scrollState,
dragController = dragController,
@@ -620,10 +618,7 @@ private fun WeekPager(
}
ViewMorphPage(active = morphPageActive(page, pagerState.settledPage)) {
WeekPage(
// A cached page may predate midnight; today comes off the clock (#373).
state = remember(loaded, empty, today) {
loaded?.takeIf { it.today != today }?.copy(today = today) ?: loaded ?: empty
},
state = loaded ?: empty,
headerBias = kind.headerBias,
allDayHeight = allDayHeight,
scrollState = scrollState,
@@ -7,8 +7,6 @@ import de.jeanlucmakiola.calendula.data.calendar.CalendarRepository
import de.jeanlucmakiola.calendula.data.di.IoDispatcher
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.calendula.data.prefs.resolveFirstDay
import de.jeanlucmakiola.calendula.data.time.TodaySource
import de.jeanlucmakiola.calendula.data.time.currentDate
import de.jeanlucmakiola.calendula.domain.CalendarSource
import de.jeanlucmakiola.calendula.domain.EventInstance
import de.jeanlucmakiola.calendula.domain.displayPriority
@@ -29,7 +27,6 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.DayOfWeek
@@ -43,6 +40,7 @@ import kotlinx.datetime.plus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import java.util.Locale
import kotlin.time.Clock
import kotlin.time.Instant
import javax.inject.Inject
import kotlin.math.abs
@@ -75,7 +73,6 @@ fun positionOf(anchor: LocalDate, rule: (LocalDate) -> PageGrid): PagerPosition
abstract class PagedTimelineViewModel(
private val repository: CalendarRepository,
settingsPrefs: SettingsPrefs,
todaySource: TodaySource,
private val io: CoroutineDispatcher,
gridRule: Flow<(LocalDate) -> PageGrid>,
initialRule: (LocalDate) -> PageGrid,
@@ -92,7 +89,7 @@ abstract class PagedTimelineViewModel(
)
private val todayDate: LocalDate
get() = currentDate()
get() = Clock.System.now().toLocalDateTime(zone).date
// A day on the visible page; the page itself comes from the grid, so a
// settings change re-frames the same days instead of jumping.
@@ -106,17 +103,6 @@ abstract class PagedTimelineViewModel(
val rule: StateFlow<(LocalDate) -> PageGrid> =
gridRule.stateIn(viewModelScope, SharingStarted.Eagerly, initialRule)
private var lastToday = todayDate
/** Today, kept current across midnight so a page left open doesn't mark yesterday (#373). */
private val today: StateFlow<LocalDate> = todaySource.today
.onEach { date ->
val old = lastToday
lastToday = date
if (old != date) followToday(old, date)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), todayDate)
private val position: StateFlow<PagerPosition> =
combine(_anchor, rule) { anchor, rule -> positionOf(anchor, rule) }
.distinctUntilChanged()
@@ -155,9 +141,8 @@ abstract class PagedTimelineViewModel(
combine(
repository.calendars(),
repository.instances(spanRange(span.start, span.count, zone)),
today,
) { calendars, instances, today ->
buildState(span, calendars, instances, today)
) { calendars, instances ->
buildState(span, calendars, instances)
}
.catch { emit(WeekUiState.Failure(FailureReason.ProviderUnavailable)) }
.flowOn(io)
@@ -178,20 +163,12 @@ abstract class PagedTimelineViewModel(
}
fun goToToday() = jumpTo(todayDate)
/** The date rolled over; a page still showing [old] follows on to [new], one browsed elsewhere stays. */
private fun followToday(old: LocalDate, new: LocalDate) {
val anchor = _anchor.value
val grid = rule.value(anchor)
if (grid.pageFor(anchor) == grid.pageFor(old)) _anchor.value = new
}
fun goToDate(date: LocalDate) = jumpTo(date)
private fun buildState(
span: DaySpan,
calendars: List<CalendarSource>,
instances: List<EventInstance>,
today: LocalDate,
): WeekUiState {
calendarListFailure(calendars)?.let { return WeekUiState.Failure(it) }
val days = (0 until span.count).map { span.start.plus(it, DateTimeUnit.DAY) }
@@ -199,7 +176,7 @@ abstract class PagedTimelineViewModel(
val timed = instances.filterNot { it.isAllDay }
return WeekUiState.Success(
weekStart = span.start,
today = today,
today = todayDate,
days = days,
allDaySpans = layoutAllDay(allDay, days, zone),
timedByDay = days.associateWith { day -> layoutDay(timed, day, zone) },
@@ -212,12 +189,10 @@ abstract class PagedTimelineViewModel(
class WeekViewModel @Inject constructor(
repository: CalendarRepository,
settingsPrefs: SettingsPrefs,
todaySource: TodaySource,
@IoDispatcher io: CoroutineDispatcher,
) : PagedTimelineViewModel(
repository = repository,
settingsPrefs = settingsPrefs,
todaySource = todaySource,
io = io,
gridRule = settingsPrefs.weekStart.map { pref ->
val grid = WeekGrid(pref.resolveFirstDay(Locale.getDefault()))
@@ -236,12 +211,10 @@ class WeekViewModel @Inject constructor(
class MultiDayViewModel @Inject constructor(
repository: CalendarRepository,
settingsPrefs: SettingsPrefs,
todaySource: TodaySource,
@IoDispatcher io: CoroutineDispatcher,
) : PagedTimelineViewModel(
repository = repository,
settingsPrefs = settingsPrefs,
todaySource = todaySource,
io = io,
gridRule = settingsPrefs.multiDayCount.map { count ->
{ anchor: LocalDate -> DaysGrid.startingAt(anchor, count) }
@@ -254,12 +227,10 @@ class MultiDayViewModel @Inject constructor(
class DayViewModel @Inject constructor(
repository: CalendarRepository,
settingsPrefs: SettingsPrefs,
todaySource: TodaySource,
@IoDispatcher io: CoroutineDispatcher,
) : PagedTimelineViewModel(
repository = repository,
settingsPrefs = settingsPrefs,
todaySource = todaySource,
io = io,
gridRule = flowOf { _: LocalDate -> DayGrid },
initialRule = { DayGrid },
@@ -1,3 +0,0 @@
إصلاحات
• الأحداث المعدّلة أو المنقولة أو المحذوفة تظهر الآن فورًا على كل الهواتف. على بعض الهواتف، ومنها سامسونج، كان الحدث المعدّل يبقى كما هو حتى إعادة تشغيل التطبيق.
• أدوات الشاشة الرئيسية تحمّل الآن الأحداث التي تعرضها فقط. هذا يُفترض أن يساعد الأدوات التي كانت تبقى على «جارٍ التحميل» في الهواتف الأبطأ.
@@ -1,2 +0,0 @@
إصلاحات
• عند فتح التطبيق في صباح اليوم التالي يظهر اليوم الصحيح. كان عرض الشهر يعلّم الأمس على أنه اليوم ويُبقي اليوم الذي اخترته في المساء السابق محددًا. الآن تنتقل كل العروض إلى اليوم الجديد.
@@ -1,3 +0,0 @@
Выпраўлена
• Змененыя, перанесеныя і выдаленыя падзеі цяпер адразу адлюстроўваюцца на любым тэлефоне. На некаторых, у тым ліку Samsung, змененая падзея заставалася ранейшай да перазапуску праграмы.
• Віджэты на галоўным экране загружаюць толькі тыя падзеі, якія паказваюць. Гэта павінна дапамагчы віджэтам, якія на павольных тэлефонах затрымліваліся на «загрузцы».
@@ -1,2 +0,0 @@
Выпраўлена
• Калі адкрыць праграму наступнай раніцай, яна паказвае правільны дзень. Раней месяц адзначаў учорашні дзень як сённяшні і пакідаў выбраным дзень, які вы абралі ўвечары. Цяпер усе віды пераходзяць на новы дзень.
@@ -1,3 +0,0 @@
Opraveno
• Upravené, přesunuté a smazané události se teď na každém telefonu projeví hned. Na některých, mimo jiné Samsungu, zůstala změněná událost beze změny až do restartu aplikace.
• Widgety na domovské obrazovce načítají jen události, které zobrazují. To by mělo pomoct widgetům, které na pomalejších telefonech zůstávaly na „načítání“.
@@ -1,2 +0,0 @@
Opraveno
• Když aplikaci otevřete druhý den ráno, ukáže správný den. Měsíc dřív označoval včerejšek jako dnešek a nechával vybraný den, který jste zvolili večer předtím. Všechna zobrazení teď přejdou na nový den.
@@ -1,3 +0,0 @@
Behoben
• Bearbeitete, verschobene und gelöschte Termine erscheinen jetzt auf jedem Handy sofort richtig. Auf manchen Geräten, darunter Samsung, blieb ein geänderter Termin unverändert, bis die App neu gestartet wurde.
• Die Widgets auf dem Startbildschirm laden nur noch die Termine, die sie anzeigen. Das sollte Widgets helfen, die auf langsameren Handys bei „Wird geladen“ hängen blieben.
@@ -1,2 +0,0 @@
Behoben
• Wird die App am nächsten Morgen geöffnet, zeigt sie den richtigen Tag. Die Monatsansicht markierte bisher gestern als heute und ließ den am Vorabend gewählten Tag ausgewählt. Jetzt wechseln alle Ansichten auf den neuen Tag.
@@ -1,3 +0,0 @@
Fixed
• Edits, moves and deletions show up straight away on every phone. On some phones, Samsung among them, a changed event stayed put until you restarted the app.
• The home-screen widgets load only the events they show. This should help widgets stuck on "loading" on slower phones.
@@ -1,2 +0,0 @@
Fixed
• Reopening the app the next morning shows the right day. The month view used to mark yesterday as today and keep the day you picked the evening before selected. All views now move on to the new day.
@@ -1,3 +0,0 @@
Corregido
• Los eventos editados, movidos o eliminados se actualizan al momento en cualquier móvil. En algunos, como los Samsung, un evento cambiado no se actualizaba hasta reiniciar la app.
• Los widgets de la pantalla de inicio solo cargan los eventos que muestran. Esto debería ayudar a los widgets que se quedaban en «cargando» en móviles más lentos.
@@ -1,2 +0,0 @@
Corregido
• Al abrir la app a la mañana siguiente, se muestra el día correcto. La vista de mes marcaba ayer como hoy y dejaba seleccionado el día que elegiste la noche anterior. Ahora todas las vistas pasan al nuevo día.
@@ -1,3 +0,0 @@
Corrigé
• Les événements modifiés, déplacés ou supprimés s'affichent tout de suite sur tous les téléphones. Sur certains, dont les Samsung, un événement modifié ne changeait qu'après un redémarrage de l'app.
• Les widgets de l'écran d'accueil ne chargent plus que les événements qu'ils affichent, ce qui devrait aider ceux qui restaient bloqués sur « chargement » sur les téléphones lents.
@@ -1,2 +0,0 @@
Corrigé
• En ouvrant l'app le lendemain matin, vous voyez le bon jour. La vue mois marquait la veille comme aujourd'hui et gardait sélectionné le jour choisi la veille au soir. Toutes les vues passent maintenant au nouveau jour.
@@ -1,3 +0,0 @@
Javítva
• A szerkesztett, áthelyezett és törölt események most minden telefonon azonnal megjelennek. Egyes készülékeken, köztük a Samsungokon, a módosított esemény az alkalmazás újraindításáig változatlan maradt.
• A kezdőképernyő-widgetek csak a megjelenített eseményeket töltik be. Ez segíthet azokon a widgeteken, amelyek lassabb telefonokon a „betöltés” állapotban ragadtak.
@@ -1,2 +0,0 @@
Javítva
• Ha másnap reggel megnyitod az alkalmazást, a helyes napot mutatja. A havi nézet eddig a tegnapot jelölte mai napként, és kijelölve hagyta az előző este választott napot. Most minden nézet átvált az új napra.
@@ -1,3 +0,0 @@
Corretto
• Gli eventi modificati, spostati o eliminati si aggiornano subito su ogni telefono. Su alcuni, tra cui i Samsung, un evento modificato restava invariato fino al riavvio dell'app.
• I widget della schermata Home caricano solo gli eventi che mostrano. Dovrebbe aiutare i widget che restavano su «caricamento» sui telefoni più lenti.
@@ -1,2 +0,0 @@
Corretto
• Aprendo l'app la mattina dopo, viene mostrato il giorno giusto. La vista mese segnava ieri come oggi e lasciava selezionato il giorno scelto la sera prima. Ora tutte le viste passano al nuovo giorno.
@@ -1,3 +0,0 @@
修正
• 予定の編集・移動・削除が、どの端末でもすぐに反映されるようになりました。Samsung など一部の端末では、アプリを再起動するまで変更前の予定が表示されたままでした。
• ホーム画面のウィジェットは、表示する予定だけを読み込むようになりました。動作の遅い端末で「読み込み中」のまま止まるウィジェットが改善されるはずです。
@@ -1,2 +0,0 @@
修正
• 翌朝アプリを開くと、正しい日が表示されるようになりました。これまで月表示では前日が今日として表示され、前の晩に選んだ日も選択されたままでした。今はすべての表示が新しい日に切り替わります。
@@ -1,3 +0,0 @@
Opgelost
• Bewerkte, verplaatste en verwijderde afspraken zijn nu op elke telefoon meteen zichtbaar. Op sommige toestellen, waaronder Samsung, bleef een gewijzigde afspraak staan tot je de app opnieuw startte.
• De widgets op het startscherm laden alleen nog de afspraken die ze tonen. Dat zou widgets moeten helpen die op tragere telefoons op "laden" bleven hangen.
@@ -1,2 +0,0 @@
Opgelost
• Open je de app de volgende ochtend, dan zie je de juiste dag. De maandweergave markeerde gisteren als vandaag en hield de dag die je de avond ervoor koos geselecteerd. Alle weergaven gaan nu mee naar de nieuwe dag.
@@ -1,3 +0,0 @@
Poprawki
• Edytowane, przeniesione i usunięte wydarzenia od razu pokazują się poprawnie na każdym telefonie. Na niektórych, m.in. Samsungach, zmienione wydarzenie nie zmieniało się aż do ponownego uruchomienia aplikacji.
• Widżety na ekranie głównym wczytują tylko wydarzenia, które pokazują. To powinno pomóc widżetom, które na wolniejszych telefonach zostawały na „wczytywanie”.
@@ -1,2 +0,0 @@
Poprawki
• Po otwarciu aplikacji następnego ranka widać właściwy dzień. Widok miesiąca oznaczał wczoraj jako dziś i zostawiał zaznaczony dzień wybrany poprzedniego wieczoru. Teraz wszystkie widoki przechodzą na nowy dzień.
@@ -1,3 +0,0 @@
Corrigido
• Eventos editados, movidos ou excluídos aparecem atualizados na hora em qualquer celular. Em alguns aparelhos, incluindo Samsung, um evento alterado só mudava depois de reiniciar o app.
• Os widgets da tela inicial carregam apenas os eventos que mostram. Isso deve ajudar widgets que ficavam em "carregando" em celulares mais lentos.
@@ -1,2 +0,0 @@
Corrigido
• Ao abrir o app na manhã seguinte, aparece o dia certo. A visualização do mês marcava ontem como hoje e deixava selecionado o dia escolhido na noite anterior. Agora todas as visualizações passam para o novo dia.
@@ -1,3 +0,0 @@
Corrigido
• Os eventos editados, movidos ou eliminados aparecem logo atualizados em qualquer telemóvel. Em alguns, incluindo Samsung, um evento alterado só mudava depois de reiniciar a app.
• Os widgets do ecrã inicial carregam apenas os eventos que mostram. Isto deve ajudar os widgets que ficavam em «a carregar» em telemóveis mais lentos.
@@ -1,2 +0,0 @@
Corrigido
• Ao abrir a app na manhã seguinte, aparece o dia certo. A vista do mês marcava ontem como hoje e deixava selecionado o dia escolhido na noite anterior. Agora todas as vistas passam para o novo dia.
@@ -1,3 +0,0 @@
Исправлено
• Изменённые, перенесённые и удалённые события теперь сразу отображаются на любом телефоне. На некоторых, в том числе Samsung, изменённое событие оставалось прежним до перезапуска приложения.
• Виджеты на главном экране загружают только те события, которые показывают. Это должно помочь виджетам, которые на медленных телефонах зависали на «загрузке».
@@ -1,2 +0,0 @@
Исправлено
• Если открыть приложение на следующее утро, оно показывает правильный день. Раньше месяц отмечал вчерашний день как сегодняшний и не снимал выделение с дня, выбранного накануне вечером. Теперь все виды переходят на новый день.
@@ -1,3 +0,0 @@
Opravené
• Upravené, presunuté a odstránené udalosti sa teraz na každom telefóne prejavia hneď. Na niektorých, okrem iného Samsungu, zostala zmenená udalosť bez zmeny až do reštartu aplikácie.
• Widgety na domovskej obrazovke načítavajú len udalosti, ktoré zobrazujú. To by malo pomôcť widgetom, ktoré na pomalších telefónoch zostávali na „načítava sa“.
@@ -1,2 +0,0 @@
Opravené
• Keď aplikáciu otvoríte na druhý deň ráno, ukáže správny deň. Mesiac predtým označoval včerajšok ako dnešok a nechával vybraný deň, ktorý ste zvolili večer predtým. Všetky zobrazenia teraz prejdú na nový deň.
@@ -1,3 +0,0 @@
修复
• 编辑、移动或删除的日程现在在所有手机上都会立即显示。在部分手机(包括三星)上,修改后的日程要等重启应用后才会更新。
• 主屏幕小部件现在只加载其显示的日程。这应能改善在较慢手机上一直停在“加载中”的小部件。
@@ -1,2 +0,0 @@
修复
• 第二天早上打开应用时,会显示正确的日期。以前月视图会把昨天标为今天,并保留前一天晚上选中的日期。现在所有视图都会切换到新的一天。
+2 -2
View File
@@ -1,6 +1,6 @@
[versions]
agp = "9.4.1"
kotlin = "2.4.20"
agp = "9.2.1"
kotlin = "2.3.21"
ksp = "2.3.11"
hilt = "2.60.1"
coreKtx = "1.19.0"
Binary file not shown.
+2 -4
View File
@@ -1,10 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=bafd5ce9cfaea0fbccfdc8439a1ac42fbd4cd9c89dc9a988228d8a2639a58e6c
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+7 -4
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
##############################################################################
#
# gradlew start up script for POSIX generated by Gradle.
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh gradlew
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -114,6 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -171,6 +172,7 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -210,6 +212,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
Vendored
+34 -52
View File
@@ -19,39 +19,12 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem gradlew startup script for Windows
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
@rem Catch executions from older scripts and ensure they exit cleanly.
@rem This can be removed once we can be reasonably confident that few people
@rem will be migrating directly to this new wrapper.
goto afterSafetyNet
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
goto exitWithErrorLevel
:afterSafetyNet
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -72,14 +45,13 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
1>&2 echo.
1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
1>&2 echo.
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
1>&2 echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
goto exitWithErrorLevel
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -87,26 +59,36 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
1>&2 echo.
1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
1>&2 echo.
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
1>&2 echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
goto exitWithErrorLevel
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem This label must not be changed. We rely on old scripts being able to jump to this point.
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega