nordiccalendar

A simple Material 3 native calendar app fitting my personal requirements
git clone git://archive.git.mtrnord.blog/MTRNord/nordiccalendar.git
Log | Files | Refs | README | LICENSE

commit b9362b0f97961c9a97766d2690b6086717819f0e
parent 50fc855b1003d110569e23b40d4475391594483d
Author: MTRNord <MTRNord@users.noreply.github.com>
Date:   Mon, 28 Jul 2025 19:50:17 +0200

Introduce the SidebarDrawerViewModel

Diffstat:
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt | 1-
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt | 3---
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt | 59+++++++++++++++++++++++++++++------------------------------
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/data/CalendarRepository.kt | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt | 5++++-
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt | 136++++++++++++++++++++++++++++++-------------------------------------------------
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/EventDetailsViewModel.kt | 4++--
Aapp/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/SidebarDrawerViewModel.kt | 30++++++++++++++++++++++++++++++
8 files changed, 186 insertions(+), 133 deletions(-)

diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt @@ -245,7 +245,6 @@ fun CalendarView(backStackEntry: NavBackStackEntry, navController: NavHostContro title = stringResource(R.string.app_name), selectedDestination = "calendar", navController = navController, - calendarViewModel = calendarViewModel, floatingActionButton = { FloatingActionButton(onClick = { /* Event hinzufügen (später) */ }) { Icon( diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.res.stringResource import androidx.navigation.NavController import kotlinx.coroutines.launch import space.midnightthoughts.nordiccalendar.R -import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -31,7 +30,6 @@ fun AppScaffold( title: String? = null, selectedDestination: String, navController: NavController, - calendarViewModel: CalendarViewModel? = null, floatingActionButton: (@Composable () -> Unit)? = null, content: @Composable (Modifier) -> Unit ) { @@ -49,7 +47,6 @@ fun AppScaffold( drawerContent = { SidebarDrawer( selectedDestination = selectedDestination, - calendarViewModel = calendarViewModel, navController = navController, drawerState = drawerState, ) diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt @@ -25,31 +25,33 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.NavGraph.Companion.findStartDestination import kotlinx.coroutines.launch import space.midnightthoughts.nordiccalendar.Destinations import space.midnightthoughts.nordiccalendar.R -import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel +import space.midnightthoughts.nordiccalendar.viewmodels.SidebarDrawerViewModel @Composable fun SidebarDrawer( - calendarViewModel: CalendarViewModel?, navController: NavController, selectedDestination: String, drawerState: DrawerState ) { - val calendars = remember(calendarViewModel) { - calendarViewModel?.calendars - }?.collectAsState(initial = emptyList()) + val viewModel: SidebarDrawerViewModel = viewModel() + + val calendars = remember(viewModel) { + viewModel.calendars + }.collectAsState(initial = emptyList()) val selectedCalendars = remember(calendars) { derivedStateOf { - calendars?.value?.filter { it.selected } + calendars.value.filter { it.selected } } } Log.d( "SidebarDrawer", - "Selected Calendars: ${selectedCalendars.value}, Calendars: ${calendars?.value}" + "Selected Calendars: ${selectedCalendars.value}, Calendars: ${calendars.value}" ) val scope = rememberCoroutineScope() @@ -126,31 +128,28 @@ fun SidebarDrawer( modifier = Modifier.fillMaxWidth(), colors = NavigationDrawerItemDefaults.colors() ) - if (selectedCalendars.value !== null && calendars?.value?.isEmpty() == false) { - Spacer(Modifier.height(16.dp)) - HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color) - Spacer(Modifier.height(8.dp)) - Text( - stringResource(R.string.calendar_selection), - style = MaterialTheme.typography.titleMedium, - modifier = - Modifier.padding(start = 8.dp, bottom = 8.dp, end = 8.dp, top = 8.dp) - ) - Spacer(Modifier.height(8.dp)) - calendars?.value?.forEach { calendar -> - Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { - Checkbox( - checked = selectedCalendars.value?.contains(calendar) == true, - onCheckedChange = { - scope.launch { - calendarViewModel?.toggleCalendar(calendar) - } + Spacer(Modifier.height(16.dp)) + HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color) + Spacer(Modifier.height(8.dp)) + Text( + stringResource(R.string.calendar_selection), + style = MaterialTheme.typography.titleMedium, + modifier = + Modifier.padding(start = 8.dp, bottom = 8.dp, end = 8.dp, top = 8.dp) + ) + Spacer(Modifier.height(8.dp)) + calendars.value.forEach { calendar -> + Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + Checkbox( + checked = selectedCalendars.value.contains(calendar), + onCheckedChange = { + scope.launch { + viewModel.toggleCalendar(calendar) } - ) - Text(calendar.name, modifier = Modifier.padding(start = 8.dp)) - } + } + ) + Text(calendar.name, modifier = Modifier.padding(start = 8.dp)) } - } } } diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/data/CalendarRepository.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/data/CalendarRepository.kt @@ -1,21 +1,57 @@ package space.midnightthoughts.nordiccalendar.data -import android.content.ContentResolver import android.content.Context -import android.content.SharedPreferences import androidx.core.content.edit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch import space.midnightthoughts.nordiccalendar.util.Calendar import space.midnightthoughts.nordiccalendar.util.CalendarData import space.midnightthoughts.nordiccalendar.util.Event -class CalendarRepository(private val context: Context) { +class CalendarRepository private constructor() { private val calendarData = CalendarData() - private val prefs: SharedPreferences by lazy { - context.getSharedPreferences("calendar_prefs", Context.MODE_PRIVATE) + + // StateFlow für Kalenderliste + private val _calendarsFlow = MutableStateFlow<List<Calendar>>(emptyList()) + val calendarsFlow: StateFlow<List<Calendar>> = _calendarsFlow.asStateFlow() + private val repoScope = CoroutineScope(Dispatchers.IO) + + // StateFlow für Zeitbereich und Events + private val _startMillis = MutableStateFlow(System.currentTimeMillis()) + val startMillis: StateFlow<Long> = _startMillis.asStateFlow() + private val _endMillis = MutableStateFlow(System.currentTimeMillis()) + val endMillis: StateFlow<Long> = _endMillis.asStateFlow() + private val _eventsFlow = MutableStateFlow<List<Event>>(emptyList()) + val eventsFlow: StateFlow<List<Event>> = _eventsFlow.asStateFlow() + + // Context muss jetzt bei jedem Aufruf übergeben werden + fun initialize(context: Context) { + loadCalendarsToFlow(context) + repoScope.launch { + combine(_calendarsFlow, _startMillis, _endMillis) { calendars, start, end -> + val selectedIds = calendars.filter { it.selected }.map { it.id } + getEventsForCalendars(context, selectedIds, start, end) + }.collect { events -> + _eventsFlow.value = events + } + } + } + + private fun loadCalendarsToFlow(context: Context) { + repoScope.launch { + val calendars = getCalendars(context) + _calendarsFlow.value = calendars + } } - private val contentResolver: ContentResolver get() = context.contentResolver - fun getCalendars(): List<Calendar> { + fun getCalendars(context: Context): List<Calendar> { + val contentResolver = context.contentResolver + val prefs = context.getSharedPreferences("calendar_prefs", Context.MODE_PRIVATE) val calendars = calendarData.getCalendars(contentResolver) val selectedIds = prefs.getStringSet("selected_calendar_ids", null) return if (selectedIds != null) { @@ -25,7 +61,8 @@ class CalendarRepository(private val context: Context) { } } - fun setCalendarSelected(calendarId: Long, selected: Boolean) { + fun setCalendarSelected(context: Context, calendarId: Long, selected: Boolean) { + val prefs = context.getSharedPreferences("calendar_prefs", Context.MODE_PRIVATE) val selectedIds = prefs.getStringSet("selected_calendar_ids", null)?.toMutableSet() ?: mutableSetOf() if (selected) { @@ -34,16 +71,38 @@ class CalendarRepository(private val context: Context) { selectedIds.remove(calendarId.toString()) } prefs.edit { putStringSet("selected_calendar_ids", selectedIds) } + loadCalendarsToFlow(context) + } + + fun setTimeRange(startMillis: Long, endMillis: Long) { + _startMillis.value = startMillis + _endMillis.value = endMillis } fun getEventsForCalendars( + context: Context, calendarIds: List<Long>, startMillis: Long, endMillis: Long ): List<Event> = - calendarData.getEventsForCalendars(contentResolver, calendarIds, startMillis, endMillis) + calendarData.getEventsForCalendars( + context.contentResolver, + calendarIds, + startMillis, + endMillis + ) - fun getEventById(eventId: Long): Event? { - return calendarData.getEventById(contentResolver, eventId) + fun getEventById(context: Context, eventId: Long): Event? { + return calendarData.getEventById(context.contentResolver, eventId) + } + + companion object { + @Volatile + private var INSTANCE: CalendarRepository? = null + fun getInstance(): CalendarRepository { + return INSTANCE ?: synchronized(this) { + INSTANCE ?: CalendarRepository().also { INSTANCE = it } + } + } } } diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt @@ -90,7 +90,10 @@ fun CalendarScreen( PullToRefreshBox( state = pullToRefreshState, modifier = modifier, - onRefresh = { calendarViewModel.updateEvents() }, + onRefresh = { + // updateEvents gibt es nicht mehr im ViewModel, stattdessen Tab neu setzen, um Zeitraum zu triggern + calendarViewModel.setTab(selectedTab) + }, isRefreshing = isRefreshing.value, indicator = { Indicator( diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt @@ -4,57 +4,38 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.stateIn import space.midnightthoughts.nordiccalendar.data.CalendarRepository -import space.midnightthoughts.nordiccalendar.util.Calendar -import space.midnightthoughts.nordiccalendar.util.Event class CalendarViewModel(app: Application) : AndroidViewModel(app) { - val isRefreshing = MutableStateFlow(false) - private val repository = CalendarRepository(app) - - private val _calendars = MutableStateFlow<List<Calendar>>(emptyList()) - val calendars: StateFlow<List<Calendar>> = _calendars.asStateFlow() + private val repository = CalendarRepository.getInstance() - private val _events = MutableStateFlow<List<Event>>(emptyList()) - val events: StateFlow<List<Event>> = _events.asStateFlow() + val events = repository.eventsFlow.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5000), + emptyList() + ) + val startMillis = repository.startMillis + val endMillis = repository.endMillis + val isRefreshing = MutableStateFlow(false) private val _selectedTab = MutableStateFlow(0) val selectedTab: StateFlow<Int> = _selectedTab.asStateFlow() - private val _startMillis = MutableStateFlow(getDefaultStartMillis(0)) - val startMillis: StateFlow<Long> = _startMillis.asStateFlow() - private val _endMillis = MutableStateFlow(getDefaultEndMillis(0)) - val endMillis: StateFlow<Long> = _endMillis.asStateFlow() - init { - loadCalendars() - } - - private fun loadCalendars() { - isRefreshing.value = true - viewModelScope.launch { - val allCalendars = repository.getCalendars() - _calendars.value = allCalendars - updateEvents() - isRefreshing.value = false - } - } - - fun toggleCalendar(calendar: Calendar) { - viewModelScope.launch { - repository.setCalendarSelected(calendar.id, !calendar.selected) - loadCalendars() - } + // Repository initialisieren (Context benötigt) + repository.initialize(app) + setTab(0) } fun setTab(tab: Int) { _selectedTab.value = tab - _startMillis.value = getDefaultStartMillis(tab) - _endMillis.value = getDefaultEndMillis(tab) - updateEvents() + val start = getDefaultStartMillis(tab) + val end = getDefaultEndMillis(tab) + repository.setTimeRange(start, end) } private fun getDefaultStartMillis(tab: Int): Long { @@ -120,78 +101,63 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) { } } - fun updateEvents() { - viewModelScope.launch { - isRefreshing.value = true - try { - val selectedIds = _calendars.value.filter { it.selected }.map { it.id } - _events.value = repository.getEventsForCalendars( - selectedIds, - _startMillis.value, - _endMillis.value - ) - } finally { - isRefreshing.value = false - } - } - } - + // Die folgenden Methoden ändern nur noch den Zeitraum im Repository fun nextDay() { if (_selectedTab.value != 2) return - val cal = java.util.Calendar.getInstance().apply { timeInMillis = _startMillis.value } + val cal = java.util.Calendar.getInstance().apply { timeInMillis = startMillis.value } cal.add(java.util.Calendar.DAY_OF_MONTH, 1) - _startMillis.value = cal.timeInMillis + val start = cal.timeInMillis cal.set(java.util.Calendar.HOUR_OF_DAY, 23) cal.set(java.util.Calendar.MINUTE, 59) cal.set(java.util.Calendar.SECOND, 59) - _endMillis.value = cal.timeInMillis - updateEvents() + val end = cal.timeInMillis + repository.setTimeRange(start, end) } fun prevDay() { if (_selectedTab.value != 2) return - val cal = java.util.Calendar.getInstance().apply { timeInMillis = _startMillis.value } + val cal = java.util.Calendar.getInstance().apply { timeInMillis = startMillis.value } cal.add(java.util.Calendar.DAY_OF_MONTH, -1) - _startMillis.value = cal.timeInMillis + val start = cal.timeInMillis cal.set(java.util.Calendar.HOUR_OF_DAY, 23) cal.set(java.util.Calendar.MINUTE, 59) cal.set(java.util.Calendar.SECOND, 59) - _endMillis.value = cal.timeInMillis - updateEvents() + val end = cal.timeInMillis + repository.setTimeRange(start, end) } fun nextWeek() { if (_selectedTab.value != 1) return - val cal = java.util.Calendar.getInstance().apply { timeInMillis = _startMillis.value } + val cal = java.util.Calendar.getInstance().apply { timeInMillis = startMillis.value } cal.add(java.util.Calendar.WEEK_OF_YEAR, 1) - _startMillis.value = cal.timeInMillis + val start = cal.timeInMillis cal.add(java.util.Calendar.DAY_OF_WEEK, 6) cal.set(java.util.Calendar.HOUR_OF_DAY, 23) cal.set(java.util.Calendar.MINUTE, 59) cal.set(java.util.Calendar.SECOND, 59) - _endMillis.value = cal.timeInMillis - updateEvents() + val end = cal.timeInMillis + repository.setTimeRange(start, end) } fun prevWeek() { if (_selectedTab.value != 1) return - val cal = java.util.Calendar.getInstance().apply { timeInMillis = _startMillis.value } + val cal = java.util.Calendar.getInstance().apply { timeInMillis = startMillis.value } cal.add(java.util.Calendar.WEEK_OF_YEAR, -1) - _startMillis.value = cal.timeInMillis + val start = cal.timeInMillis cal.add(java.util.Calendar.DAY_OF_WEEK, 6) cal.set(java.util.Calendar.HOUR_OF_DAY, 23) cal.set(java.util.Calendar.MINUTE, 59) cal.set(java.util.Calendar.SECOND, 59) - _endMillis.value = cal.timeInMillis - updateEvents() + val end = cal.timeInMillis + repository.setTimeRange(start, end) } fun nextMonth() { if (_selectedTab.value != 0) return - val cal = java.util.Calendar.getInstance().apply { timeInMillis = _startMillis.value } + val cal = java.util.Calendar.getInstance().apply { timeInMillis = startMillis.value } cal.add(java.util.Calendar.MONTH, 1) cal.set(java.util.Calendar.DAY_OF_MONTH, 1) - _startMillis.value = cal.timeInMillis + val start = cal.timeInMillis cal.set( java.util.Calendar.DAY_OF_MONTH, cal.getActualMaximum(java.util.Calendar.DAY_OF_MONTH) @@ -199,16 +165,16 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) { cal.set(java.util.Calendar.HOUR_OF_DAY, 23) cal.set(java.util.Calendar.MINUTE, 59) cal.set(java.util.Calendar.SECOND, 59) - _endMillis.value = cal.timeInMillis - updateEvents() + val end = cal.timeInMillis + repository.setTimeRange(start, end) } fun prevMonth() { if (_selectedTab.value != 0) return - val cal = java.util.Calendar.getInstance().apply { timeInMillis = _startMillis.value } + val cal = java.util.Calendar.getInstance().apply { timeInMillis = startMillis.value } cal.add(java.util.Calendar.MONTH, -1) cal.set(java.util.Calendar.DAY_OF_MONTH, 1) - _startMillis.value = cal.timeInMillis + val start = cal.timeInMillis cal.set( java.util.Calendar.DAY_OF_MONTH, cal.getActualMaximum(java.util.Calendar.DAY_OF_MONTH) @@ -216,38 +182,38 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) { cal.set(java.util.Calendar.HOUR_OF_DAY, 23) cal.set(java.util.Calendar.MINUTE, 59) cal.set(java.util.Calendar.SECOND, 59) - _endMillis.value = cal.timeInMillis - updateEvents() + val end = cal.timeInMillis + repository.setTimeRange(start, end) } fun setTodayWeek() { if (_selectedTab.value != 1) return val cal = java.util.Calendar.getInstance() - _startMillis.value = cal.apply { + val start = cal.apply { set(java.util.Calendar.DAY_OF_WEEK, firstDayOfWeek) set(java.util.Calendar.HOUR_OF_DAY, 0) set(java.util.Calendar.MINUTE, 0) set(java.util.Calendar.SECOND, 0) }.timeInMillis - _endMillis.value = cal.apply { + val end = cal.apply { add(java.util.Calendar.DAY_OF_WEEK, 6) set(java.util.Calendar.HOUR_OF_DAY, 23) set(java.util.Calendar.MINUTE, 59) set(java.util.Calendar.SECOND, 59) }.timeInMillis - updateEvents() + repository.setTimeRange(start, end) } fun setTodayMonth() { if (_selectedTab.value != 0) return val cal = java.util.Calendar.getInstance() - _startMillis.value = cal.apply { + val start = cal.apply { set(java.util.Calendar.DAY_OF_MONTH, 1) set(java.util.Calendar.HOUR_OF_DAY, 0) set(java.util.Calendar.MINUTE, 0) set(java.util.Calendar.SECOND, 0) }.timeInMillis - _endMillis.value = cal.apply { + val end = cal.apply { set( java.util.Calendar.DAY_OF_MONTH, getActualMaximum(java.util.Calendar.DAY_OF_MONTH) @@ -256,22 +222,22 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) { set(java.util.Calendar.MINUTE, 59) set(java.util.Calendar.SECOND, 59) }.timeInMillis - updateEvents() + repository.setTimeRange(start, end) } fun setTodayDay() { if (_selectedTab.value != 2) return val cal = java.util.Calendar.getInstance() - _startMillis.value = cal.apply { + val start = cal.apply { set(java.util.Calendar.HOUR_OF_DAY, 0) set(java.util.Calendar.MINUTE, 0) set(java.util.Calendar.SECOND, 0) }.timeInMillis - _endMillis.value = cal.apply { + val end = cal.apply { set(java.util.Calendar.HOUR_OF_DAY, 23) set(java.util.Calendar.MINUTE, 59) set(java.util.Calendar.SECOND, 59) }.timeInMillis - updateEvents() + repository.setTimeRange(start, end) } } diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/EventDetailsViewModel.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/EventDetailsViewModel.kt @@ -15,7 +15,7 @@ class EventDetailsViewModel( app: Application, savedStateHandle: SavedStateHandle ) : AndroidViewModel(app) { - private val repository = CalendarRepository(app) + private val repository = CalendarRepository.getInstance() private val _event = MutableStateFlow<Event?>(null) val event: StateFlow<Event?> = _event.asStateFlow() @@ -28,7 +28,7 @@ class EventDetailsViewModel( private fun loadEvent(eventId: Long) { viewModelScope.launch { - val event = repository.getEventById(eventId) + val event = repository.getEventById(getApplication<Application>(), eventId) _event.value = event } } diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/SidebarDrawerViewModel.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/SidebarDrawerViewModel.kt @@ -0,0 +1,30 @@ +package space.midnightthoughts.nordiccalendar.viewmodels + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import space.midnightthoughts.nordiccalendar.data.CalendarRepository +import space.midnightthoughts.nordiccalendar.util.Calendar + +class SidebarDrawerViewModel(app: Application) : AndroidViewModel(app) { + private val repository = CalendarRepository.getInstance() + + val calendars = repository.calendarsFlow.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5000), + emptyList() + ) + + fun toggleCalendar(calendar: Calendar) { + viewModelScope.launch { + repository.setCalendarSelected( + getApplication<Application>(), + calendar.id, + !calendar.selected + ) + } + } +}