diff --git a/android/src/main/java/co/touchlab/droidcon/android/MainApp.kt b/android/src/main/java/co/touchlab/droidcon/android/MainApp.kt index b96e87dc..a2acb4b6 100644 --- a/android/src/main/java/co/touchlab/droidcon/android/MainApp.kt +++ b/android/src/main/java/co/touchlab/droidcon/android/MainApp.kt @@ -31,7 +31,10 @@ class MainApp : Application() { single { this@MainApp } single> { MainActivity::class.java } single { - get().getSharedPreferences("DROIDCON_SETTINGS_2023", Context.MODE_PRIVATE) + get().getSharedPreferences( + "DROIDCON_SETTINGS_2023", + Context.MODE_PRIVATE + ) } single { SharedPreferencesSettings(delegate = get()) } diff --git a/android/src/main/java/co/touchlab/droidcon/android/service/impl/DefaultFirebaseMessagingService.kt b/android/src/main/java/co/touchlab/droidcon/android/service/impl/DefaultFirebaseMessagingService.kt index 2fce49a7..277ab102 100644 --- a/android/src/main/java/co/touchlab/droidcon/android/service/impl/DefaultFirebaseMessagingService.kt +++ b/android/src/main/java/co/touchlab/droidcon/android/service/impl/DefaultFirebaseMessagingService.kt @@ -22,7 +22,10 @@ class DefaultFirebaseMessagingService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { super.onMessageReceived(message) - if (message.data.isNotEmpty() && message.data[Notification.Keys.notificationType] == Notification.Values.refreshDataType) { + if ( + message.data.isNotEmpty() && + message.data[Notification.Keys.notificationType] == Notification.Values.refreshDataType + ) { MainScope().launch { notificationService.handleNotification( Notification.Remote.RefreshData @@ -32,7 +35,10 @@ class DefaultFirebaseMessagingService : FirebaseMessagingService() { // If we have notification, we're running in foreground and should show it ourselves. val originalNotification = message.notification ?: return - val notification = NotificationCompat.Builder(this, message.notification?.channelId ?: "") + val notification = NotificationCompat.Builder( + this, + message.notification?.channelId ?: "" + ) .setContentTitle(originalNotification.title) .setContentText(originalNotification.body) .apply { diff --git a/android/src/main/java/co/touchlab/droidcon/android/util/NotificationLocalizedStringFactory.kt b/android/src/main/java/co/touchlab/droidcon/android/util/NotificationLocalizedStringFactory.kt index 96f07e38..15b9286a 100644 --- a/android/src/main/java/co/touchlab/droidcon/android/util/NotificationLocalizedStringFactory.kt +++ b/android/src/main/java/co/touchlab/droidcon/android/util/NotificationLocalizedStringFactory.kt @@ -9,7 +9,9 @@ class NotificationLocalizedStringFactory( ) : NotificationSchedulingService.LocalizedStringFactory { override fun reminderTitle(roomName: String?): String { - val ending = roomName?.let { context.getString(R.string.notification_reminder_title_in_room, it) } ?: "" + val ending = roomName?.let { + context.getString(R.string.notification_reminder_title_in_room, it) + } ?: "" return context.getString(R.string.notification_reminder_title_base, ending) } diff --git a/ios/src/iosMain/kotlin/co/touchlab/droidcon/ios/util/NotificationLocalizedStringFactory.kt b/ios/src/iosMain/kotlin/co/touchlab/droidcon/ios/util/NotificationLocalizedStringFactory.kt index 783791d1..83d0cf36 100644 --- a/ios/src/iosMain/kotlin/co/touchlab/droidcon/ios/util/NotificationLocalizedStringFactory.kt +++ b/ios/src/iosMain/kotlin/co/touchlab/droidcon/ios/util/NotificationLocalizedStringFactory.kt @@ -12,12 +12,32 @@ class NotificationLocalizedStringFactory( ) : NotificationSchedulingService.LocalizedStringFactory { override fun reminderTitle(roomName: String?): String { - val ending = roomName?.let { NSString.stringWithFormat(bundle.localizedStringForKey("Notification.Reminder.Title.InRoom", null, null).convertParametersForPrintf(), it.cstr) } ?: "" - return NSString.stringWithFormat(bundle.localizedStringForKey("Notification.Reminder.Title.Base", null, null).convertParametersForPrintf(), ending.cstr) + val ending = roomName?.let { + NSString.stringWithFormat( + bundle.localizedStringForKey("Notification.Reminder.Title.InRoom", null, null) + .convertParametersForPrintf(), + it.cstr + ) + } ?: "" + return NSString.stringWithFormat( + bundle.localizedStringForKey( + "Notification.Reminder.Title.Base", + null, + null + ).convertParametersForPrintf(), + ending.cstr + ) } override fun reminderBody(sessionTitle: String): String { - return NSString.stringWithFormat(bundle.localizedStringForKey("Notification.Reminder.Body", null, null).convertParametersForPrintf(), sessionTitle.cstr) + return NSString.stringWithFormat( + bundle.localizedStringForKey( + "Notification.Reminder.Body", + null, + null + ).convertParametersForPrintf(), + sessionTitle.cstr + ) } override fun feedbackTitle(): String { diff --git a/ios/src/iosMain/kotlin/co/touchlab/droidcon/ios/util/formatter/IOSDateFormatter.kt b/ios/src/iosMain/kotlin/co/touchlab/droidcon/ios/util/formatter/IOSDateFormatter.kt index 0e1385c7..6b732bad 100644 --- a/ios/src/iosMain/kotlin/co/touchlab/droidcon/ios/util/formatter/IOSDateFormatter.kt +++ b/ios/src/iosMain/kotlin/co/touchlab/droidcon/ios/util/formatter/IOSDateFormatter.kt @@ -16,7 +16,11 @@ class IOSDateFormatter : DateFormatter { private val monthWithDay: NSDateFormatter by lazy { NSDateFormatter().also { val dateTemplate = "MMM d" - it.dateFormat = NSDateFormatter.dateFormatFromTemplate(dateTemplate, 0.toULong(), NSLocale.currentLocale)!! + it.dateFormat = NSDateFormatter.dateFormatFromTemplate( + dateTemplate, + 0.toULong(), + NSLocale.currentLocale + )!! } } @@ -30,7 +34,11 @@ class IOSDateFormatter : DateFormatter { private val timeOnlyNoPeriod: NSDateFormatter by lazy { NSDateFormatter().also { val dateTemplate = "hh:mm" - it.dateFormat = NSDateFormatter.dateFormatFromTemplate(dateTemplate, 0.toULong(), NSLocale.currentLocale)!! + it.dateFormat = NSDateFormatter.dateFormatFromTemplate( + dateTemplate, + 0.toULong(), + NSLocale.currentLocale + )!! } } @@ -40,15 +48,18 @@ class IOSDateFormatter : DateFormatter { override fun timeOnly(dateTime: LocalDateTime) = dateTime.date()?.let { timeOnly.stringFromDate(it) } - override fun timeOnlyInterval(fromDateTime: LocalDateTime, toDateTime: LocalDateTime) = interval( - fromDateTime.date()?.let { timeOnlyNoPeriod.stringFromDate(it) }, - toDateTime.date()?.let { timeOnly.stringFromDate(it) } - ) + override fun timeOnlyInterval(fromDateTime: LocalDateTime, toDateTime: LocalDateTime) = + interval( + fromDateTime.date()?.let { timeOnlyNoPeriod.stringFromDate(it) }, + toDateTime.date()?.let { timeOnly.stringFromDate(it) } + ) - private fun LocalDate.date() = NSCalendar.currentCalendar.dateFromComponents(toNSDateComponents()) + private fun LocalDate.date() = + NSCalendar.currentCalendar.dateFromComponents(toNSDateComponents()) private fun LocalDateTime.date() = - NSCalendar.currentCalendar.dateFromComponents(toNSDateComponents()) // TODOKPG - Pretty sure this is device time zone, might be OK. Just for local formating + // TODOKPG - Pretty sure this is device time zone, might be OK. Just for local formating + NSCalendar.currentCalendar.dateFromComponents(toNSDateComponents()) private fun interval(from: String?, to: String?) = listOfNotNull(from, to).joinToString(" – ") } diff --git a/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/settings/PlatformSpecificSettings.kt b/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/settings/PlatformSpecificSettings.kt index 629b1226..2bc66830 100644 --- a/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/settings/PlatformSpecificSettings.kt +++ b/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/settings/PlatformSpecificSettings.kt @@ -19,7 +19,10 @@ internal actual fun PlatformSwitchApp() { val context = LocalContext.current Button( onClick = { - val intent = context.packageManager.getLaunchIntentForPackage(Constants.SisterApp.androidPackageName) + val intent = + context + .packageManager + .getLaunchIntentForPackage(Constants.SisterApp.androidPackageName) if (intent != null) { context.startActivity(intent) } else { diff --git a/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/util/Dialog.kt b/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/util/Dialog.kt index 1a040985..538f05ea 100644 --- a/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/util/Dialog.kt +++ b/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/util/Dialog.kt @@ -2,15 +2,19 @@ package co.touchlab.droidcon.ui.util import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.Dialog as AndroidXComposeDialog +import androidx.compose.ui.window.DialogProperties @OptIn(ExperimentalComposeUiApi::class) @Composable internal actual fun Dialog(dismiss: () -> Unit, content: @Composable () -> Unit) { AndroidXComposeDialog( onDismissRequest = dismiss, - properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false, usePlatformDefaultWidth = false), + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false, + usePlatformDefaultWidth = false + ), ) { content() } diff --git a/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/util/LocalImage.jvm.kt b/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/util/LocalImage.jvm.kt index 7ab27810..4acc58b1 100644 --- a/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/util/LocalImage.jvm.kt +++ b/shared-ui/src/androidMain/kotlin/co/touchlab/droidcon/ui/util/LocalImage.jvm.kt @@ -24,9 +24,15 @@ import co.touchlab.droidcon.ui.theme.Dimensions // platforms and on each platform we need to get the drawable according to provided name. @SuppressLint("ComposableNaming", "DiscouragedApi") @Composable -internal actual fun __LocalImage(imageResourceName: String, modifier: Modifier, contentDescription: String?) { +internal actual fun __LocalImage( + imageResourceName: String, + modifier: Modifier, + contentDescription: String? +) { val context = LocalContext.current - val imageRes = context.resources.getIdentifier(imageResourceName, "drawable", context.packageName).takeIf { it != 0 } + val imageRes = + context.resources.getIdentifier(imageResourceName, "drawable", context.packageName) + .takeIf { it != 0 } if (imageRes != null) { androidx.compose.foundation.Image( modifier = modifier, @@ -36,7 +42,10 @@ internal actual fun __LocalImage(imageResourceName: String, modifier: Modifier, ) } else { Row( - modifier = modifier.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(Dimensions.Padding.half)), + modifier = modifier.background( + MaterialTheme.colorScheme.primary, + RoundedCornerShape(Dimensions.Padding.half) + ), verticalAlignment = Alignment.CenterVertically ) { Spacer(modifier = Modifier.weight(1f)) @@ -46,7 +55,11 @@ internal actual fun __LocalImage(imageResourceName: String, modifier: Modifier, modifier = Modifier.padding(Dimensions.Padding.half), tint = Color.White ) - Text("Image not supported", modifier = Modifier.padding(Dimensions.Padding.default), color = Color.White) + Text( + "Image not supported", + modifier = Modifier.padding(Dimensions.Padding.default), + color = Color.White + ) Spacer(modifier = Modifier.weight(1f)) } } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/BottomNavigationView.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/BottomNavigationView.kt index ed98270f..fc77d37c 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/BottomNavigationView.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/BottomNavigationView.kt @@ -35,11 +35,13 @@ internal fun BottomNavigationView(viewModel: ApplicationViewModel, modifier: Mod NavigationBar { viewModel.tabs.forEach { tab -> val (title, icon) = when (tab) { - ApplicationViewModel.Tab.Schedule -> "Schedule" to Icons.Filled.CalendarMonth + ApplicationViewModel.Tab.Schedule -> + "Schedule" to Icons.Filled.CalendarMonth // FIXME: Was originally "My agenda" but then it doesn't seem to fit. ApplicationViewModel.Tab.MyAgenda -> "Agenda" to Icons.Filled.Schedule ApplicationViewModel.Tab.Venue -> "Venue" to Icons.Filled.Map - ApplicationViewModel.Tab.Sponsors -> "Sponsors" to Icons.Filled.LocalFireDepartment + ApplicationViewModel.Tab.Sponsors -> + "Sponsors" to Icons.Filled.LocalFireDepartment ApplicationViewModel.Tab.Settings -> "Settings" to Icons.Filled.Settings } NavigationBarItem( @@ -66,11 +68,13 @@ internal fun BottomNavigationView(viewModel: ApplicationViewModel, modifier: Mod title = "Droidcon NYC 2024", emptyText = "Sessions could not be loaded.", ) + ApplicationViewModel.Tab.MyAgenda -> SessionListView( viewModel = viewModel.agenda, title = "Agenda", emptyText = "Add sessions to your agenda from session detail in schedule.", ) + ApplicationViewModel.Tab.Venue -> VenueView() ApplicationViewModel.Tab.Sponsors -> SponsorsView(viewModel.sponsors) ApplicationViewModel.Tab.Settings -> SettingsView(viewModel.settings) diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/settings/SettingsView.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/settings/SettingsView.kt index dab5b8bb..c161768d 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/settings/SettingsView.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/settings/SettingsView.kt @@ -79,7 +79,11 @@ internal fun SettingsView(viewModel: SettingsViewModel) { } @Composable -internal fun IconTextSwitchRow(text: String, image: ImageVector, checked: MutableObservableProperty) { +internal fun IconTextSwitchRow( + text: String, + image: ImageVector, + checked: MutableObservableProperty +) { val isChecked by checked.observeAsState() Row( modifier = Modifier diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/util/LocalImage.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/util/LocalImage.kt index b5680f41..93656900 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/util/LocalImage.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/ui/util/LocalImage.kt @@ -4,9 +4,17 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @Composable -internal expect fun __LocalImage(imageResourceName: String, modifier: Modifier, contentDescription: String?) +internal expect fun __LocalImage( + imageResourceName: String, + modifier: Modifier, + contentDescription: String? +) @Composable -internal fun LocalImage(imageResourceName: String, modifier: Modifier = Modifier, contentDescription: String? = null) { +internal fun LocalImage( + imageResourceName: String, + modifier: Modifier = Modifier, + contentDescription: String? = null +) { __LocalImage(imageResourceName, modifier, contentDescription) } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/util/NavigationController.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/util/NavigationController.kt index 76f94f92..957f4a21 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/util/NavigationController.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/util/NavigationController.kt @@ -36,7 +36,10 @@ private val LocalNavigationViewDimensions = staticCompositionLocalOf() - private var stackTracking: MutableList by published(stack, equalityPolicy = neverEqualPolicy()) + private var stackTracking: MutableList by published( + stack, + equalityPolicy = neverEqualPolicy() + ) private val observeStack by observe(::stackTracking) private var activeChild: NavigationController? = null @@ -46,17 +49,23 @@ class NavigationController : BaseViewModel() { } internal sealed class NavigationStackItem { - class BackPressHandler(val onBackPressed: BackPressHandlerScope.() -> Unit) : NavigationStackItem() { + class BackPressHandler(val onBackPressed: BackPressHandlerScope.() -> Unit) : + NavigationStackItem() { override fun toString(): String { return "BackPress@${hashCode().toUInt().toString(16)}" } } - class Push(val item: MutableObservableProperty, val content: @Composable (T) -> Unit) : NavigationStackItem() { + class Push( + val item: MutableObservableProperty, + val content: @Composable (T) -> Unit + ) : NavigationStackItem() { override fun toString(): String { - return "Push(${item.value}@${item.hashCode().toUInt().toString(16)})@${hashCode().toUInt().toString(16)}" + return "Push(${item.value}@${ + item.hashCode().toUInt().toString(16) + })@${hashCode().toUInt().toString(16)}" } } } @@ -130,7 +139,10 @@ class NavigationController : BaseViewModel() { } @Composable - private fun PushedStackItem(item: NavigationStackItem.Push, itemModifier: Modifier) { + private fun PushedStackItem( + item: NavigationStackItem.Push, + itemModifier: Modifier + ) { println("$item") val itemValue by item.item.observeAsState() @@ -142,7 +154,10 @@ class NavigationController : BaseViewModel() { } @Composable - internal fun Pushed(item: MutableObservableProperty, content: @Composable (T) -> Unit) { + internal fun Pushed( + item: MutableObservableProperty, + content: @Composable (T) -> Unit + ) { remember { val stackItem = NavigationStackItem.Push(item, content).also { notifyingStackChange { @@ -206,14 +221,19 @@ private class ReferenceTracking(private val onDispose: () -> Unit) : RememberObs } @Composable -internal fun BackPressHandler(onBackPressed: NavigationController.BackPressHandlerScope.() -> Unit) { +internal fun BackPressHandler( + onBackPressed: NavigationController.BackPressHandlerScope.() -> Unit +) { val navigationController = LocalNavigationController.current navigationController.HandleBackPressEffect(onBackPressed) } internal interface NavigationStackScope { - fun NavigationLink(item: MutableObservableProperty, content: @Composable (T) -> Unit) + fun NavigationLink( + item: MutableObservableProperty, + content: @Composable (T) -> Unit + ) } internal class NavigationLinkWrapper( @@ -236,7 +256,8 @@ internal class NavigationLinkWrapper( } override fun equals(other: Any?): Boolean { - return (other as? NavigationLinkWrapper<*>)?.let { it.index == index && it.value == value } ?: false + return (other as? NavigationLinkWrapper<*>)?.let { it.index == index && it.value == value } + ?: false } override fun hashCode(): Int { @@ -246,7 +267,11 @@ internal class NavigationLinkWrapper( @OptIn(ExperimentalAnimationApi::class) @Composable -internal fun NavigationStack(key: Any?, links: NavigationStackScope.() -> Unit, content: @Composable () -> Unit) { +internal fun NavigationStack( + key: Any?, + links: NavigationStackScope.() -> Unit, + content: @Composable () -> Unit +) { val activeLinkComposables by remember(key) { val constructedLinks = mutableListOf>>() val scope = object : NavigationStackScope { @@ -256,7 +281,12 @@ internal fun NavigationStack(key: Any?, links: NavigationStackScope.() -> Unit, ) { constructedLinks.add( item.map { - NavigationLinkWrapper(index = constructedLinks.size, value = it, reset = { item.value = null }, content) + NavigationLinkWrapper( + index = constructedLinks.size, + value = it, + reset = { item.value = null }, + content + ) } ) } @@ -269,10 +299,15 @@ internal fun NavigationStack(key: Any?, links: NavigationStackScope.() -> Unit, AnimatedContent( targetState = activeLinkComposables, transitionSpec = { - if (initialState.indexOfLast { it.body != null } < targetState.indexOfLast { it.body != null }) { - slideInHorizontally(initialOffsetX = { it }) with slideOutHorizontally(targetOffsetX = { -it }) + if ( + initialState.indexOfLast { it.body != null } < + targetState.indexOfLast { it.body != null } + ) { + slideInHorizontally(initialOffsetX = { it }) with + slideOutHorizontally(targetOffsetX = { -it }) } else { - slideInHorizontally(initialOffsetX = { -it }) with slideOutHorizontally(targetOffsetX = { it }) + slideInHorizontally(initialOffsetX = { -it }) with + slideOutHorizontally(targetOffsetX = { it }) } }, contentAlignment = Alignment.BottomCenter diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/AgendaViewModel.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/AgendaViewModel.kt index 7c2d89a3..a77c9aab 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/AgendaViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/AgendaViewModel.kt @@ -26,6 +26,12 @@ class AgendaViewModel( ) { fun create() = - AgendaViewModel(sessionGateway, sessionDayFactory, sessionDetailFactory, sessionDetailScrollStateStorage, dateTimeService) + AgendaViewModel( + sessionGateway, + sessionDayFactory, + sessionDetailFactory, + sessionDetailScrollStateStorage, + dateTimeService + ) } } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/BaseSessionListViewModel.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/BaseSessionListViewModel.kt index b1bfef29..987dd2ad 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/BaseSessionListViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/BaseSessionListViewModel.kt @@ -18,7 +18,11 @@ abstract class BaseSessionListViewModel( private set val observeDays by observe(::days) - var selectedDay: SessionDayViewModel? by managed(days?.firstOrNull { it.date == sessionDetailScrollStateStorage.selectedDay }) + var selectedDay: SessionDayViewModel? by managed( + days?.firstOrNull { + it.date == sessionDetailScrollStateStorage.selectedDay + } + ) val observeSelectedDay by observe(::selectedDay) var presentedSessionDetail: SessionDetailViewModel? by managed(null) @@ -45,7 +49,8 @@ abstract class BaseSessionListViewModel( } .also { newDays -> days = newDays - selectedDay = newDays.firstOrNull { it.day == selectedDay?.day } ?: newDays.firstOrNull() + selectedDay = newDays.firstOrNull { it.day == selectedDay?.day } + ?: newDays.firstOrNull() } } } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/ScheduleViewModel.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/ScheduleViewModel.kt index ed46896a..90dc4639 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/ScheduleViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/ScheduleViewModel.kt @@ -35,6 +35,12 @@ class ScheduleViewModel( ) { fun create() = - ScheduleViewModel(sessionGateway, sessionDayFactory, sessionDetailFactory, sessionDetailScrollStateStorage, dateTimeService) + ScheduleViewModel( + sessionGateway, + sessionDayFactory, + sessionDetailFactory, + sessionDetailScrollStateStorage, + dateTimeService + ) } } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionBlockViewModel.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionBlockViewModel.kt index f97da9c5..47ba21b1 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionBlockViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionBlockViewModel.kt @@ -32,6 +32,12 @@ class SessionBlockViewModel( startsAt: LocalDateTime, items: List, onScheduleItemSelected: (ScheduleItem) -> Unit, - ) = SessionBlockViewModel(sessionListItemFactory, dateFormatter, startsAt, items, onScheduleItemSelected) + ) = SessionBlockViewModel( + sessionListItemFactory, + dateFormatter, + startsAt, + items, + onScheduleItemSelected + ) } } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionDetailScrollStateStorage.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionDetailScrollStateStorage.kt index ba1ce1ca..a7da1ec4 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionDetailScrollStateStorage.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionDetailScrollStateStorage.kt @@ -13,9 +13,16 @@ class SessionDetailScrollStateStorage { agendaScrollStates[day] } else { scrollStates[day] - } ?: SessionDayViewModel.ScrollState(firstVisibleItemIndex = 0, firstVisibleItemScrollOffset = 0) + } ?: SessionDayViewModel.ScrollState( + firstVisibleItemIndex = 0, + firstVisibleItemScrollOffset = 0 + ) - fun setScrollState(day: LocalDate, agenda: Boolean, scrollState: SessionDayViewModel.ScrollState) { + fun setScrollState( + day: LocalDate, + agenda: Boolean, + scrollState: SessionDayViewModel.ScrollState + ) { if (agenda) { agendaScrollStates[day] = scrollState } else { diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionDetailViewModel.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionDetailViewModel.kt index 6d954abb..0e091769 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionDetailViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/session/SessionDetailViewModel.kt @@ -36,7 +36,11 @@ class SessionDetailViewModel( initialItem: ScheduleItem, ) : BaseViewModel() { - private val item by collected(initialItem, sessionGateway.observeScheduleItem(initialItem.session.id), identityEqualityPolicy()) + private val item by collected( + initialItem, + sessionGateway.observeScheduleItem(initialItem.session.id), + identityEqualityPolicy() + ) private val observeItem by observe(::item) private val time: Instant by collected( @@ -78,7 +82,11 @@ class SessionDetailViewModel( val observeState by observe(::state) val abstract by observeItem.map { it.session.description } val observeAbstract by observe(::abstract) - val abstractLinks: List by observeItem.map { it.session.description?.let(parseUrlViewService::parse) ?: emptyList() } + val abstractLinks: List by observeItem.map { + it.session.description?.let( + parseUrlViewService::parse + ) ?: emptyList() + } val observeAbstractLinks by observe(::abstractLinks) val speakers: List by managedList( @@ -141,7 +149,7 @@ class SessionDetailViewModel( private fun parseUrl(text: String): List { val urlRegex = - "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)".toRegex() + "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)".toRegex() // ktlint-disable max-line-length return urlRegex.findAll(text).map { WebLink(it.range, it.value) }.toList() } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/settings/AboutViewModel.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/settings/AboutViewModel.kt index 14c092e0..4deb4c1b 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/settings/AboutViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/settings/AboutViewModel.kt @@ -24,7 +24,10 @@ class AboutViewModel( } } - class Factory(private val aboutRepository: AboutRepository, private val parseUrlViewService: ParseUrlViewService) { + class Factory( + private val aboutRepository: AboutRepository, + private val parseUrlViewService: ParseUrlViewService + ) { fun create() = AboutViewModel(aboutRepository, parseUrlViewService) } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/sponsor/SponsorDetailViewModel.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/sponsor/SponsorDetailViewModel.kt index b313f94c..b841691b 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/sponsor/SponsorDetailViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/sponsor/SponsorDetailViewModel.kt @@ -44,6 +44,12 @@ class SponsorDetailViewModel( ) { fun create(sponsor: Sponsor, groupName: String) = - SponsorDetailViewModel(sponsorGateway, speakerListItemFactory, speakerDetailFactory, sponsor, groupName) + SponsorDetailViewModel( + sponsorGateway, + speakerListItemFactory, + speakerDetailFactory, + sponsor, + groupName + ) } } diff --git a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/sponsor/SponsorListViewModel.kt b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/sponsor/SponsorListViewModel.kt index 067eda99..d07058d6 100644 --- a/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/sponsor/SponsorListViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/co.touchlab.droidcon/viewmodel/sponsor/SponsorListViewModel.kt @@ -21,7 +21,10 @@ class SponsorListViewModel( sponsorGroup, onSponsorSelected = { sponsor -> if (sponsor.hasDetail) { - presentedSponsorDetail = sponsorDetailFactory.create(sponsor, sponsorGroup.group.name) + presentedSponsorDetail = sponsorDetailFactory.create( + sponsor, + sponsorGroup.group.name + ) } else { // UIApplication.sharedApplication.openURL(NSURL(string = sponsor.url.string)) presentedUrl = sponsor.url @@ -45,6 +48,7 @@ class SponsorListViewModel( private val sponsorDetailFactory: SponsorDetailViewModel.Factory, ) { - fun create() = SponsorListViewModel(sponsorGateway, sponsorGroupFactory, sponsorDetailFactory) + fun create() = + SponsorListViewModel(sponsorGateway, sponsorGroupFactory, sponsorDetailFactory) } } diff --git a/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/LocalImage.kt b/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/LocalImage.kt index 75e2aad5..b5acce9d 100644 --- a/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/LocalImage.kt +++ b/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/LocalImage.kt @@ -23,8 +23,15 @@ import platform.UIKit.UIImage @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) @Composable -internal actual fun __LocalImage(imageResourceName: String, modifier: Modifier, contentDescription: String?) { - val painter = remember { UIImage.imageNamed(imageResourceName)?.toSkiaImage()?.toComposeImageBitmap()?.let(::BitmapPainter) } +internal actual fun __LocalImage( + imageResourceName: String, + modifier: Modifier, + contentDescription: String? +) { + val painter = remember { + UIImage.imageNamed(imageResourceName)?.toSkiaImage()?.toComposeImageBitmap() + ?.let(::BitmapPainter) + } if (painter != null) { androidx.compose.foundation.Image( modifier = modifier, @@ -34,7 +41,10 @@ internal actual fun __LocalImage(imageResourceName: String, modifier: Modifier, ) } else { Row( - modifier = modifier.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(Dimensions.Padding.half)), + modifier = modifier.background( + MaterialTheme.colorScheme.primary, + RoundedCornerShape(Dimensions.Padding.half) + ), verticalAlignment = Alignment.CenterVertically, ) { Spacer(modifier = Modifier.weight(1f)) @@ -44,7 +54,11 @@ internal actual fun __LocalImage(imageResourceName: String, modifier: Modifier, modifier = Modifier.padding(Dimensions.Padding.half), tint = Color.White, ) - Text("Image not supported", modifier = Modifier.padding(Dimensions.Padding.default), color = Color.White) + Text( + "Image not supported", + modifier = Modifier.padding(Dimensions.Padding.default), + color = Color.White + ) Spacer(modifier = Modifier.weight(1f)) } } diff --git a/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/NavigationBackPressWrapper.kt b/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/NavigationBackPressWrapper.kt index 0c6649d7..171b18e6 100644 --- a/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/NavigationBackPressWrapper.kt +++ b/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/NavigationBackPressWrapper.kt @@ -55,7 +55,10 @@ internal actual fun NavigationBackPressWrapper(content: @Composable () -> Unit) AnimatedVisibility( visible = dragDistance > triggerBackPressDragDistance, - enter = slideInHorizontally(initialOffsetX = { -it }, animationSpec = tween(durationMillis = 100)), + enter = slideInHorizontally( + initialOffsetX = { -it }, + animationSpec = tween(durationMillis = 100) + ), exit = slideOutHorizontally(targetOffsetX = { -it }) ) { Icon( diff --git a/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/ToSkiaImage.kt b/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/ToSkiaImage.kt index 8aa2044f..a868d785 100644 --- a/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/ToSkiaImage.kt +++ b/shared-ui/src/iosMain/kotlin/co/touchlab/droidcon/ui/util/ToSkiaImage.kt @@ -23,7 +23,8 @@ import platform.UIKit.UIImage // TODO: Add support for remaining color spaces when the Skia library supports them. @ExperimentalForeignApi internal fun UIImage.toSkiaImage(): Image? { - val imageRef = CGImageCreateCopyWithColorSpace(this.CGImage, CGColorSpaceCreateDeviceRGB()) ?: return null + val imageRef = + CGImageCreateCopyWithColorSpace(this.CGImage, CGColorSpaceCreateDeviceRGB()) ?: return null val width = CGImageGetWidth(imageRef).toInt() val height = CGImageGetHeight(imageRef).toInt() @@ -35,9 +36,16 @@ internal fun UIImage.toSkiaImage(): Image? { val alphaInfo = CGImageGetAlphaInfo(imageRef) val alphaType = when (alphaInfo) { - CGImageAlphaInfo.kCGImageAlphaPremultipliedFirst, CGImageAlphaInfo.kCGImageAlphaPremultipliedLast -> ColorAlphaType.PREMUL - CGImageAlphaInfo.kCGImageAlphaFirst, CGImageAlphaInfo.kCGImageAlphaLast -> ColorAlphaType.UNPREMUL - CGImageAlphaInfo.kCGImageAlphaNone, CGImageAlphaInfo.kCGImageAlphaNoneSkipFirst, CGImageAlphaInfo.kCGImageAlphaNoneSkipLast -> ColorAlphaType.OPAQUE + CGImageAlphaInfo.kCGImageAlphaPremultipliedFirst, + CGImageAlphaInfo.kCGImageAlphaPremultipliedLast -> ColorAlphaType.PREMUL + + CGImageAlphaInfo.kCGImageAlphaFirst, + CGImageAlphaInfo.kCGImageAlphaLast -> ColorAlphaType.UNPREMUL + + CGImageAlphaInfo.kCGImageAlphaNone, + CGImageAlphaInfo.kCGImageAlphaNoneSkipFirst, + CGImageAlphaInfo.kCGImageAlphaNoneSkipLast -> ColorAlphaType.OPAQUE + else -> ColorAlphaType.UNKNOWN } @@ -48,7 +56,12 @@ internal fun UIImage.toSkiaImage(): Image? { CFRelease(imageRef) return Image.makeRaster( - imageInfo = ImageInfo(width = width, height = height, colorType = ColorType.RGBA_8888, alphaType = alphaType), + imageInfo = ImageInfo( + width = width, + height = height, + colorType = ColorType.RGBA_8888, + alphaType = alphaType + ), bytes = byteArray, rowBytes = bytesPerRow.toInt(), ) diff --git a/shared/src/androidMain/kotlin/co/touchlab/droidcon/Koin.android.kt b/shared/src/androidMain/kotlin/co/touchlab/droidcon/Koin.android.kt index 196c7054..59fff784 100644 --- a/shared/src/androidMain/kotlin/co/touchlab/droidcon/Koin.android.kt +++ b/shared/src/androidMain/kotlin/co/touchlab/droidcon/Koin.android.kt @@ -45,6 +45,14 @@ actual val platformModule: Module = module { AndroidDateFormatter(dateTimeService = get()) } - val baseKermit = Logger(config = StaticConfig(logWriterList = listOf(LogcatWriter(), CrashlyticsLogWriter())), tag = "Droidcon") + val baseKermit = Logger( + config = StaticConfig( + logWriterList = listOf( + LogcatWriter(), + CrashlyticsLogWriter() + ) + ), + tag = "Droidcon" + ) factory { (tag: String?) -> if (tag != null) baseKermit.withTag(tag) else baseKermit } } diff --git a/shared/src/androidMain/kotlin/co/touchlab/droidcon/service/AndroidNotificationService.kt b/shared/src/androidMain/kotlin/co/touchlab/droidcon/service/AndroidNotificationService.kt index 78b26813..e20203ae 100644 --- a/shared/src/androidMain/kotlin/co/touchlab/droidcon/service/AndroidNotificationService.kt +++ b/shared/src/androidMain/kotlin/co/touchlab/droidcon/service/AndroidNotificationService.kt @@ -39,9 +39,10 @@ class AndroidNotificationService( settings[NOTIFICATION_ID_COUNTER_KEY] = value } - private val registeredNotifications: MutableMap> = settings.getStringOrNull(NOTIFICATION_ID_MAP_KEY)?.let { - json.decodeFromString(it) - } ?: mutableMapOf() + private val registeredNotifications: MutableMap> = + settings.getStringOrNull(NOTIFICATION_ID_MAP_KEY)?.let { + json.decodeFromString(it) + } ?: mutableMapOf() // TODO: Not called on Android. private var notificationHandler: DeepLinkNotificationHandler? = null @@ -72,7 +73,13 @@ class AndroidNotificationService( return true } - override suspend fun schedule(notification: Notification.Local, title: String, body: String, delivery: Instant, dismiss: Instant?) { + override suspend fun schedule( + notification: Notification.Local, + title: String, + body: String, + delivery: Instant, + dismiss: Instant? + ) { log.v { "Scheduling local notification at $delivery." } val deliveryTime = delivery.toEpochMilliseconds() @@ -134,14 +141,20 @@ class AndroidNotificationService( putExtra(NOTIFICATION_PAYLOAD_ID, intentId) putExtra(NOTIFICATION_PAYLOAD_TYPE, NOTIFICATION_TYPE_DISMISS) } - alarmManager.set(AlarmManager.RTC_WAKEUP, dismiss.toEpochMilliseconds(), dismissPendingIntent) + alarmManager.set( + AlarmManager.RTC_WAKEUP, + dismiss.toEpochMilliseconds(), + dismissPendingIntent + ) saveRegisteredNotificationId(sessionId, dismissIntentId) } } override suspend fun cancel(sessionIds: List) { - if (sessionIds.isEmpty()) { return } + if (sessionIds.isEmpty()) { + return + } log.v { "Cancelling scheduled notifications with IDs: [${sessionIds.joinToString { it.value }}]" } @@ -178,6 +191,7 @@ class AndroidNotificationService( log.w { "notificationHandler not registered when received $notification" } } } + Notification.Remote.RefreshData -> syncService.forceSynchronize() } } @@ -217,8 +231,13 @@ class AndroidNotificationService( ) } - private fun createPendingIntent(id: Int, intentTransform: Intent.() -> Unit = {}): PendingIntent { - val intent = IdentifiableIntent("$id", context, NotificationPublisher::class.java).apply(intentTransform) + private fun createPendingIntent( + id: Int, + intentTransform: Intent.() -> Unit = {} + ): PendingIntent { + val intent = IdentifiableIntent("$id", context, NotificationPublisher::class.java).apply( + intentTransform + ) return PendingIntent.getBroadcast( context, id, @@ -238,7 +257,8 @@ class AndroidNotificationService( } private fun saveRegisteredNotificationId(sessionId: Session.Id, notificationId: Int) { - val currentNotificationIds = (registeredNotifications[sessionId.value] ?: emptyList()).toMutableList() + val currentNotificationIds = + (registeredNotifications[sessionId.value] ?: emptyList()).toMutableList() currentNotificationIds.add(notificationId) registeredNotifications[sessionId.value] = currentNotificationIds saveRegisteredNotifications() diff --git a/shared/src/androidMain/kotlin/co/touchlab/droidcon/service/NotificationPublisher.kt b/shared/src/androidMain/kotlin/co/touchlab/droidcon/service/NotificationPublisher.kt index ec3a8ce2..76498001 100644 --- a/shared/src/androidMain/kotlin/co/touchlab/droidcon/service/NotificationPublisher.kt +++ b/shared/src/androidMain/kotlin/co/touchlab/droidcon/service/NotificationPublisher.kt @@ -9,9 +9,12 @@ import android.content.Intent class NotificationPublisher : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - val notificationId = intent.getIntExtra(AndroidNotificationService.NOTIFICATION_PAYLOAD_ID, 0) - val notificationType = intent.getStringExtra(AndroidNotificationService.NOTIFICATION_PAYLOAD_TYPE) - val notification = intent.getParcelableExtra(AndroidNotificationService.NOTIFICATION_PAYLOAD_NOTIFICATION) + val notificationId = + intent.getIntExtra(AndroidNotificationService.NOTIFICATION_PAYLOAD_ID, 0) + val notificationType = + intent.getStringExtra(AndroidNotificationService.NOTIFICATION_PAYLOAD_TYPE) + val notification = + intent.getParcelableExtra(AndroidNotificationService.NOTIFICATION_PAYLOAD_NOTIFICATION) with(context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager) { if (notificationType == AndroidNotificationService.NOTIFICATION_TYPE_DISMISS) { diff --git a/shared/src/androidMain/kotlin/co/touchlab/droidcon/util/formatter/AndroidDateFormatter.kt b/shared/src/androidMain/kotlin/co/touchlab/droidcon/util/formatter/AndroidDateFormatter.kt index 2c095524..680a73f6 100644 --- a/shared/src/androidMain/kotlin/co/touchlab/droidcon/util/formatter/AndroidDateFormatter.kt +++ b/shared/src/androidMain/kotlin/co/touchlab/droidcon/util/formatter/AndroidDateFormatter.kt @@ -2,25 +2,32 @@ package co.touchlab.droidcon.util.formatter import co.touchlab.droidcon.Constants.conferenceTimeZone import co.touchlab.droidcon.domain.service.DateTimeService -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime -import kotlinx.datetime.atTime import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.atTime class AndroidDateFormatter(private val dateTimeService: DateTimeService) : DateFormatter { // TODOKPG - May not need to set timezone. Java date has no TZ private val shortDateFormat = - SimpleDateFormat("MMM d", Locale.getDefault()).apply { timeZone = java.util.TimeZone.getTimeZone(conferenceTimeZone.id) } - private val minuteHourTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()) - .apply { timeZone = java.util.TimeZone.getTimeZone(conferenceTimeZone.id) } + SimpleDateFormat("MMM d", Locale.getDefault()).apply { + timeZone = java.util.TimeZone.getTimeZone(conferenceTimeZone.id) + } + private val minuteHourTimeFormat = + DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()) + .apply { timeZone = java.util.TimeZone.getTimeZone(conferenceTimeZone.id) } override fun monthWithDay(date: LocalDate): String { return shortDateFormat.format( - Date(with(dateTimeService) { date.atTime(0, 0).fromConferenceDateTime() }.toEpochMilliseconds()) + Date( + with(dateTimeService) { + date.atTime(0, 0).fromConferenceDateTime() + }.toEpochMilliseconds() + ) ).uppercase() } diff --git a/shared/src/commonMain/kotlin/co/touchlab/droidcon/application/service/NotificationService.kt b/shared/src/commonMain/kotlin/co/touchlab/droidcon/application/service/NotificationService.kt index 6c9a1f54..002bd7a3 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/droidcon/application/service/NotificationService.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/droidcon/application/service/NotificationService.kt @@ -7,7 +7,13 @@ import kotlinx.datetime.Instant interface NotificationService { suspend fun initialize(): Boolean - suspend fun schedule(notification: Notification.Local, title: String, body: String, delivery: Instant, dismiss: Instant?) + suspend fun schedule( + notification: Notification.Local, + title: String, + body: String, + delivery: Instant, + dismiss: Instant? + ) suspend fun cancel(sessionIds: List) diff --git a/shared/src/commonMain/kotlin/co/touchlab/droidcon/application/service/impl/DefaultNotificationSchedulingService.kt b/shared/src/commonMain/kotlin/co/touchlab/droidcon/application/service/impl/DefaultNotificationSchedulingService.kt index 2363e53b..cb5a2c4d 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/droidcon/application/service/impl/DefaultNotificationSchedulingService.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/droidcon/application/service/impl/DefaultNotificationSchedulingService.kt @@ -65,11 +65,20 @@ class DefaultNotificationSchedulingService( ) } - private suspend fun scheduleNotifications(sessionFlow: Flow>, settingsFlow: Flow) { + private suspend fun scheduleNotifications( + sessionFlow: Flow>, + settingsFlow: Flow + ) { sessionFlow .combine( settingsFlow, - transform = { agenda, settings -> Triple(agenda, settings.isRemindersEnabled, settings.isFeedbackEnabled) } + transform = { agenda, settings -> + Triple( + agenda, + settings.isRemindersEnabled, + settings.isFeedbackEnabled + ) + } ) .distinctUntilChanged() .collect { (agenda, isRemindersEnabled, isFeedbackEnabled) -> @@ -80,7 +89,8 @@ class DefaultNotificationSchedulingService( val oldSessionIds = scheduledSessionIds.filterNot { sessionId -> agenda.map { it.id.value }.contains(sessionId.value) } - scheduledNotifications = scheduledNotifications.filterNot { oldSessionIds.contains(it) } + scheduledNotifications = + scheduledNotifications.filterNot { oldSessionIds.contains(it) } notificationService.cancel(oldSessionIds) // Schedule new upcoming sessions. @@ -89,7 +99,10 @@ class DefaultNotificationSchedulingService( if (isRemindersEnabled) { val roomName = session.room?.let { roomRepository.get(it).name } val reminderDelivery = - session.startsAt.plus(NotificationSchedulingService.REMINDER_DELIVERY_START_OFFSET, DateTimeUnit.MINUTE) + session.startsAt.plus( + NotificationSchedulingService.REMINDER_DELIVERY_START_OFFSET, + DateTimeUnit.MINUTE + ) if (session.endsAt >= dateTimeService.now()) { notificationService.schedule( notification = Notification.Local.Reminder( @@ -108,8 +121,15 @@ class DefaultNotificationSchedulingService( if (isFeedbackEnabled) { val feedbackDelivery = - session.endsAt.plus(NotificationSchedulingService.FEEDBACK_DISMISS_END_OFFSET, DateTimeUnit.MINUTE) - if (feedbackDelivery.plus(24, DateTimeUnit.HOUR) >= dateTimeService.now() && session.feedback == null) { + session.endsAt.plus( + NotificationSchedulingService.FEEDBACK_DISMISS_END_OFFSET, + DateTimeUnit.MINUTE + ) + if (feedbackDelivery.plus( + 24, + DateTimeUnit.HOUR + ) >= dateTimeService.now() && session.feedback == null + ) { notificationService.schedule( notification = Notification.Local.Feedback( sessionId = session.id, diff --git a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSessionRepository.kt b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSessionRepository.kt index 42a25888..7eee9908 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSessionRepository.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSessionRepository.kt @@ -19,17 +19,21 @@ class SqlDelightSessionRepository( private val sessionQueries: SessionQueries, ) : BaseRepository(), SessionRepository { override fun observe(id: Session.Id): Flow { - return sessionQueries.sessionById(id.value, ::sessionFactory).asFlow().mapToOne(Dispatchers.Main) + return sessionQueries.sessionById(id.value, ::sessionFactory).asFlow() + .mapToOne(Dispatchers.Main) } - fun sessionById(id: Session.Id): Session? = sessionQueries.sessionById(id.value, ::sessionFactory).executeAsOneOrNull() + fun sessionById(id: Session.Id): Session? = + sessionQueries.sessionById(id.value, ::sessionFactory).executeAsOneOrNull() override fun observeOrNull(id: Session.Id): Flow { - return sessionQueries.sessionById(id.value, ::sessionFactory).asFlow().mapToOneOrNull(Dispatchers.Main) + return sessionQueries.sessionById(id.value, ::sessionFactory).asFlow() + .mapToOneOrNull(Dispatchers.Main) } override fun observeAllAttending(): Flow> { - return sessionQueries.attendingSessions(::sessionFactory).asFlow().mapToList(Dispatchers.Main) + return sessionQueries.attendingSessions(::sessionFactory).asFlow() + .mapToList(Dispatchers.Main) } override suspend fun allAttending(): List { @@ -52,9 +56,11 @@ class SqlDelightSessionRepository( sessionQueries.updateFeedBackSent(if (isSent) 1 else 0, sessionId.value) } - override fun allSync(): List = sessionQueries.allSessions(::sessionFactory).executeAsList() + override fun allSync(): List = + sessionQueries.allSessions(::sessionFactory).executeAsList() - override fun findSync(id: Session.Id): Session? = sessionQueries.sessionById(id.value, mapper = ::sessionFactory).executeAsOneOrNull() + override fun findSync(id: Session.Id): Session? = + sessionQueries.sessionById(id.value, mapper = ::sessionFactory).executeAsOneOrNull() override fun observeAll(): Flow> { return sessionQueries.allSessions(::sessionFactory).asFlow().mapToList(Dispatchers.Main) diff --git a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSponsorGroupRepository.kt b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSponsorGroupRepository.kt index 6b310860..1c127190 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSponsorGroupRepository.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSponsorGroupRepository.kt @@ -14,18 +14,22 @@ class SqlDelightSponsorGroupRepository( private val sponsorGroupQueries: SponsorGroupQueries, ) : BaseRepository(), SponsorGroupRepository { - override fun allSync(): List = sponsorGroupQueries.selectAll(::sponsorGroupFactory).executeAsList() + override fun allSync(): List = + sponsorGroupQueries.selectAll(::sponsorGroupFactory).executeAsList() override fun observe(id: SponsorGroup.Id): Flow { - return sponsorGroupQueries.sponsorGroupByName(id.value, ::sponsorGroupFactory).asFlow().mapToOne(Dispatchers.Main) + return sponsorGroupQueries.sponsorGroupByName(id.value, ::sponsorGroupFactory).asFlow() + .mapToOne(Dispatchers.Main) } override fun observeOrNull(id: SponsorGroup.Id): Flow { - return sponsorGroupQueries.sponsorGroupByName(id.value, ::sponsorGroupFactory).asFlow().mapToOneOrNull(Dispatchers.Main) + return sponsorGroupQueries.sponsorGroupByName(id.value, ::sponsorGroupFactory).asFlow() + .mapToOneOrNull(Dispatchers.Main) } override fun observeAll(): Flow> { - return sponsorGroupQueries.selectAll(::sponsorGroupFactory).asFlow().mapToList(Dispatchers.Main) + return sponsorGroupQueries.selectAll(::sponsorGroupFactory).asFlow() + .mapToList(Dispatchers.Main) } override fun contains(id: SponsorGroup.Id): Boolean { diff --git a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSponsorRepository.kt b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSponsorRepository.kt index 5f798697..90139b6a 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSponsorRepository.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/repository/impl/SqlDelightSponsorRepository.kt @@ -16,11 +16,13 @@ class SqlDelightSponsorRepository( ) : BaseRepository(), SponsorRepository { override fun observe(id: Sponsor.Id): Flow { - return sponsorQueries.sponsorById(id.name, id.group, ::sponsorFactory).asFlow().mapToOne(Dispatchers.Main) + return sponsorQueries.sponsorById(id.name, id.group, ::sponsorFactory).asFlow() + .mapToOne(Dispatchers.Main) } override fun observeOrNull(id: Sponsor.Id): Flow { - return sponsorQueries.sponsorById(id.name, id.group, ::sponsorFactory).asFlow().mapToOneOrNull(Dispatchers.Main) + return sponsorQueries.sponsorById(id.name, id.group, ::sponsorFactory).asFlow() + .mapToOneOrNull(Dispatchers.Main) } override fun observeAll(): Flow> { @@ -35,7 +37,8 @@ class SqlDelightSponsorRepository( return sponsorQueries.sponsorsByGroup(group, ::sponsorFactory).executeAsList() } - override fun allSync(): List = sponsorQueries.selectAll(::sponsorFactory).executeAsList() + override fun allSync(): List = + sponsorQueries.selectAll(::sponsorFactory).executeAsList() override fun doUpsert(entity: Sponsor) { sponsorQueries.upsert( diff --git a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/DefaultSyncService.kt b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/DefaultSyncService.kt index 4e72b7f3..8f8c634e 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/DefaultSyncService.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/DefaultSyncService.kt @@ -60,8 +60,10 @@ class DefaultSyncService( private const val SESSIONIZE_SYNC_SINCE_LAST_MINUTES = 15 private const val SESSIONIZE_SYNC_NEXT_DELAY: Long = 1L * 60L * 60L * 1000L + // 5 minutes private const val RSVP_SYNC_DELAY: Long = 5L * 60L * 1000L + // 5 minutes private const val FEEDBACK_SYNC_DELAY: Long = 5L * 60L * 1000L } @@ -73,7 +75,8 @@ class DefaultSyncService( } private var lastSessionizeSync: Instant? - get() = settings.getLongOrNull(LAST_SESSIONIZE_SYNC_KEY)?.let { Instant.fromEpochMilliseconds(it) } + get() = settings.getLongOrNull(LAST_SESSIONIZE_SYNC_KEY) + ?.let { Instant.fromEpochMilliseconds(it) } set(value) { settings[LAST_SESSIONIZE_SYNC_KEY] = value?.toEpochMilliseconds() } @@ -86,7 +89,9 @@ class DefaultSyncService( while (isActive) { val lastSessionizeSync = lastSessionizeSync // If this is the first Sessionize sync or if the last sync occurred more than 2 hours ago. - if (lastSessionizeSync == null || lastSessionizeSync <= dateTimeService.now().minus(SESSIONIZE_SYNC_SINCE_LAST_MINUTES, DateTimeUnit.MINUTE)) { + if (lastSessionizeSync == null || lastSessionizeSync <= dateTimeService.now() + .minus(SESSIONIZE_SYNC_SINCE_LAST_MINUTES, DateTimeUnit.MINUTE) + ) { try { runApiDataSourcesSynchronization() } catch (e: Exception) { @@ -94,10 +99,16 @@ class DefaultSyncService( delay(SESSIONIZE_SYNC_POLL_DELAY) continue } - log.d { "Sync successful, waiting for next sync in $SESSIONIZE_SYNC_NEXT_DELAY ms." } + log.d { + "Sync successful, " + + "waiting for next sync in $SESSIONIZE_SYNC_NEXT_DELAY ms." + } delay(SESSIONIZE_SYNC_NEXT_DELAY) } else { - log.d { "The sync didn't happen, so we'll try again in a short while ($SESSIONIZE_SYNC_POLL_DELAY ms)." } + log.d { + "The sync didn't happen, so we'll try again " + + "in a short while ($SESSIONIZE_SYNC_POLL_DELAY ms)." + } delay(SESSIONIZE_SYNC_POLL_DELAY) } } @@ -147,9 +158,16 @@ class DefaultSyncService( .forEach { (sessionId, feedback) -> while (isActive) { try { - val isFeedbackSent = serverApi.setFeedback(sessionId, feedback.rating, feedback.comment) + val isFeedbackSent = serverApi.setFeedback( + sessionId, + feedback.rating, + feedback.comment + ) if (isFeedbackSent) { - sessionRepository.setFeedbackSent(sessionId, isFeedbackSent) + sessionRepository.setFeedbackSent( + sessionId, + isFeedbackSent + ) } break } catch (e: Exception) { @@ -238,8 +256,10 @@ class DefaultSyncService( id = Session.Id(dto.id), title = dto.title, description = dto.description, - startsAt = LocalDateTime.parse(dto.startsAt).fromConferenceDateTime(dateTimeService), - endsAt = LocalDateTime.parse(dto.endsAt).fromConferenceDateTime(dateTimeService), + startsAt = LocalDateTime.parse(dto.startsAt) + .fromConferenceDateTime(dateTimeService), + endsAt = LocalDateTime.parse(dto.endsAt) + .fromConferenceDateTime(dateTimeService), isServiceSession = dto.isServiceSession, room = Room.Id(dto.roomID), rsvp = Session.RSVP( @@ -280,12 +300,17 @@ class DefaultSyncService( } } - private fun updateSponsorsFromDataSource(sponsorSessionsGroups: List, sponsors: SponsorsDto.SponsorCollectionDto) { + private fun updateSponsorsFromDataSource( + sponsorSessionsGroups: List, + sponsors: SponsorsDto.SponsorCollectionDto + ) { val sponsorSessions = sponsorSessionsGroups.flatMap { it.sessions }.associateBy { it.id } val sponsorGroupsToSponsorDtos = sponsors.groups.map { group -> val groupName = (group.name.split('/').lastOrNull() ?: group.name) .split(' ').joinToString(" ") { - it.replaceFirstChar { s -> if (s.isLowerCase()) s.titlecase() else s.toString() } + it.replaceFirstChar { s -> + if (s.isLowerCase()) s.titlecase() else s.toString() + } } SponsorGroup( @@ -295,20 +320,23 @@ class DefaultSyncService( ) to group.fields.sponsors.arrayValue.values.map { it.mapValue.fields } } - val sponsorsAndRepresentativeIds = sponsorGroupsToSponsorDtos.flatMap { (group, sponsorDtos) -> - sponsorDtos.map { sponsorDto -> - val sponsorSession = sponsorDto.sponsorId?.stringValue?.let(sponsorSessions::get) - val representativeIds = sponsorSession?.speakers?.map { Profile.Id(it.id) } ?: emptyList() - - Sponsor( - id = Sponsor.Id(sponsorDto.name.stringValue, group.name), - hasDetail = sponsorSession != null, - description = sponsorSession?.description, - icon = Url(sponsorDto.icon.stringValue), - url = Url(sponsorDto.url.stringValue), - ) to representativeIds + val sponsorsAndRepresentativeIds = + sponsorGroupsToSponsorDtos.flatMap { (group, sponsorDtos) -> + sponsorDtos.map { sponsorDto -> + val sponsorSession = + sponsorDto.sponsorId?.stringValue?.let(sponsorSessions::get) + val representativeIds = + sponsorSession?.speakers?.map { Profile.Id(it.id) } ?: emptyList() + + Sponsor( + id = Sponsor.Id(sponsorDto.name.stringValue, group.name), + hasDetail = sponsorSession != null, + description = sponsorSession?.description, + icon = Url(sponsorDto.icon.stringValue), + url = Url(sponsorDto.url.stringValue), + ) to representativeIds + } } - } sponsorRepository.allSync().map { it.id } .subtract(sponsorsAndRepresentativeIds.map { it.first.id }.toSet()) @@ -340,7 +368,8 @@ class DefaultSyncService( twitter = groupedLinks[LinkType.Twitter]?.firstOrNull()?.url?.let(::Url), linkedIn = groupedLinks[LinkType.LinkedIn]?.firstOrNull()?.url?.let(::Url), website = ( - groupedLinks[LinkType.CompanyWebsite] ?: groupedLinks[LinkType.Blog] ?: groupedLinks[LinkType.Other] + groupedLinks[LinkType.CompanyWebsite] ?: groupedLinks[LinkType.Blog] + ?: groupedLinks[LinkType.Other] )?.firstOrNull()?.url?.let(::Url), ) } diff --git a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/dto/SpeakersDto.kt b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/dto/SpeakersDto.kt index 3aa16801..6176ad09 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/dto/SpeakersDto.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/dto/SpeakersDto.kt @@ -53,7 +53,10 @@ object SpeakersDto { override val descriptor: SerialDescriptor get() { - return PrimitiveSerialDescriptor("co.touchlab.droidcon.domain.service.impl.dto.LinkType", PrimitiveKind.STRING) + return PrimitiveSerialDescriptor( + "co.touchlab.droidcon.domain.service.impl.dto.LinkType", + PrimitiveKind.STRING + ) } override fun deserialize(decoder: Decoder): LinkType = decoder.decodeString().let { diff --git a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/json/AboutJsonResourceDataSource.kt b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/json/AboutJsonResourceDataSource.kt index 7d9e7419..38184940 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/json/AboutJsonResourceDataSource.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/droidcon/domain/service/impl/json/AboutJsonResourceDataSource.kt @@ -6,6 +6,9 @@ import kotlinx.serialization.builtins.ListSerializer class AboutJsonResourceDataSource(private val jsonResourceReader: JsonResourceReader) { fun getAboutItems(): List { - return jsonResourceReader.readAndDecodeResource("about.json", ListSerializer(AboutDto.AboutItemDto.serializer())) + return jsonResourceReader.readAndDecodeResource( + "about.json", + ListSerializer(AboutDto.AboutItemDto.serializer()) + ) } } diff --git a/shared/src/iosMain/kotlin/co/touchlab/droidcon/Koin.ios.kt b/shared/src/iosMain/kotlin/co/touchlab/droidcon/Koin.ios.kt index 6a75154e..66751bd6 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/droidcon/Koin.ios.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/droidcon/Koin.ios.kt @@ -38,7 +38,8 @@ actual val platformModule = module { get() } - val baseKermit = Logger(config = StaticConfig(logWriterList = listOf(NSLogWriter())), tag = "Droidcon") + val baseKermit = + Logger(config = StaticConfig(logWriterList = listOf(NSLogWriter())), tag = "Droidcon") factory { (tag: String?) -> if (tag != null) baseKermit.withTag(tag) else baseKermit } single { AppChecker } @@ -46,35 +47,54 @@ actual val platformModule = module { @BetaInteropApi fun Koin.get(objCClass: ObjCClass, qualifier: Qualifier?, parameter: Any): Any { - val kClazz = requireNotNull(getOriginalKotlinClass(objCClass)) { "Could not get original kotlin class for $objCClass." } + val kClazz = + requireNotNull(getOriginalKotlinClass(objCClass)) { + "Could not get original kotlin class for $objCClass." + } return get(kClazz, qualifier) { parametersOf(parameter) } } @BetaInteropApi fun Koin.get(objCClass: ObjCClass, parameter: Any): Any { - val kClazz = requireNotNull(getOriginalKotlinClass(objCClass)) { "Could not get original kotlin class for $objCClass." } + val kClazz = + requireNotNull(getOriginalKotlinClass(objCClass)) { + "Could not get original kotlin class for $objCClass." + } return get(kClazz, null) { parametersOf(parameter) } } @BetaInteropApi fun Koin.get(objCClass: ObjCClass, qualifier: Qualifier?): Any { - val kClazz = requireNotNull(getOriginalKotlinClass(objCClass)) { "Could not get original kotlin class for $objCClass." } + val kClazz = + requireNotNull(getOriginalKotlinClass(objCClass)) { + "Could not get original kotlin class for $objCClass." + } return get(kClazz, qualifier, null) } @BetaInteropApi fun Koin.get(objCClass: ObjCClass): Any { - val kClazz = requireNotNull(getOriginalKotlinClass(objCClass)) { "Could not get original kotlin class for $objCClass." } + val kClazz = + requireNotNull(getOriginalKotlinClass(objCClass)) { + "Could not get original kotlin class for $objCClass." + } return get(kClazz, null) } @BetaInteropApi fun Koin.get(objCProtocol: ObjCProtocol, qualifier: Qualifier?): Any { - val kClazz = requireNotNull(getOriginalKotlinClass(objCProtocol)) { "Could not get original kotlin class for $objCProtocol." } + val kClazz = + requireNotNull(getOriginalKotlinClass(objCProtocol)) { + "Could not get original kotlin class for $objCProtocol." + } return get(kClazz, qualifier, null) } -fun Koin.getAny(objCObject: ObjCObject, qualifier: Qualifier?, parameters: ParametersDefinition?): Any { +fun Koin.getAny( + objCObject: ObjCObject, + qualifier: Qualifier?, + parameters: ParametersDefinition? +): Any { val kclass = when (objCObject) { is ObjCClass -> getOriginalKotlinClass(objCObject) is ObjCProtocol -> getOriginalKotlinClass(objCObject) diff --git a/shared/src/iosMain/kotlin/co/touchlab/droidcon/MainScope.kt b/shared/src/iosMain/kotlin/co/touchlab/droidcon/MainScope.kt index e36ec876..31d25606 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/droidcon/MainScope.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/droidcon/MainScope.kt @@ -2,12 +2,13 @@ package co.touchlab.droidcon import co.touchlab.droidcon.util.printThrowable import co.touchlab.kermit.Logger +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob -import kotlin.coroutines.CoroutineContext -class MainScope(private val mainContext: CoroutineContext, private val log: Logger) : CoroutineScope { +class MainScope(private val mainContext: CoroutineContext, private val log: Logger) : + CoroutineScope { override val coroutineContext: CoroutineContext get() = mainContext + job + exceptionHandler diff --git a/shared/src/iosMain/kotlin/co/touchlab/droidcon/service/IOSNotificationService.kt b/shared/src/iosMain/kotlin/co/touchlab/droidcon/service/IOSNotificationService.kt index f47f9a2f..5f4e7bce 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/droidcon/service/IOSNotificationService.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/droidcon/service/IOSNotificationService.kt @@ -42,7 +42,9 @@ class IOSNotificationService( override suspend fun initialize(): Boolean { log.d { "Initializing." } - val notificationSettings = wrapMultiThreadCallback(notificationCenter::getNotificationSettingsWithCompletionHandler) + val notificationSettings = wrapMultiThreadCallback( + notificationCenter::getNotificationSettingsWithCompletionHandler + ) if (notificationSettings == null) { log.i { "Failed to get current notification authorization." } return false @@ -50,25 +52,39 @@ class IOSNotificationService( when (notificationSettings.authorizationStatus) { UNAuthorizationStatusNotDetermined -> { val requestOptions = UNAuthorizationOptionAlert or UNAuthorizationOptionSound - val (isAuthorized, error) = wrapMultiThreadCallback { notificationCenter.requestAuthorizationWithOptions(requestOptions, it) } + val (isAuthorized, error) = wrapMultiThreadCallback { + notificationCenter.requestAuthorizationWithOptions( + requestOptions, + it + ) + } if (error != null) { log.i { "Notifications authorization request failed with '$error'." } } return isAuthorized } + UNAuthorizationStatusDenied -> { log.i { "Notifications not authorized." } return false } + UNAuthorizationStatusAuthorized -> { log.i { "Notifications authorized." } return true } + else -> return false } } - override suspend fun schedule(notification: Notification.Local, title: String, body: String, delivery: Instant, dismiss: Instant?) { + override suspend fun schedule( + notification: Notification.Local, + title: String, + body: String, + delivery: Instant, + dismiss: Instant? + ) { log.v { "Scheduling local notification at ${delivery.toNSDate().description}." } val deliveryDate = delivery.toNSDate() val allUnits = NSCalendarUnitSecond or @@ -80,15 +96,21 @@ class IOSNotificationService( NSCalendarUnitTimeZone val dateComponents = NSCalendar.currentCalendar.components(allUnits, deliveryDate) - val trigger = UNCalendarNotificationTrigger.triggerWithDateMatchingComponents(dateComponents, repeats = false) + val trigger = UNCalendarNotificationTrigger.triggerWithDateMatchingComponents( + dateComponents, + repeats = false + ) val content = UNMutableNotificationContent() content.setTitle(title) content.setBody(body) content.setSound(UNNotificationSound.defaultSound) val (typeValue, sessionId) = when (notification) { - is Notification.Local.Feedback -> Notification.Values.feedbackType to notification.sessionId - is Notification.Local.Reminder -> Notification.Values.reminderType to notification.sessionId + is Notification.Local.Feedback -> + Notification.Values.feedbackType to notification.sessionId + + is Notification.Local.Reminder -> + Notification.Values.reminderType to notification.sessionId } content.setUserInfo( mapOf( @@ -97,9 +119,18 @@ class IOSNotificationService( ) ) - val request = UNNotificationRequest.requestWithIdentifier("${sessionId.value}-$typeValue", content, trigger) + val request = UNNotificationRequest.requestWithIdentifier( + "${sessionId.value}-$typeValue", + content, + trigger + ) - val error = wrapMultiThreadCallback { notificationCenter.addNotificationRequest(request, it) } + val error = wrapMultiThreadCallback { + notificationCenter.addNotificationRequest( + request, + it + ) + } if (error == null) { log.v { "Scheduling notification complete." } } else { @@ -108,9 +139,14 @@ class IOSNotificationService( } override suspend fun cancel(sessionIds: List) { - if (sessionIds.isEmpty()) { return } - log.v { "Cancelling scheduled notifications with IDs: [${sessionIds.joinToString { it.value }}]" } - notificationCenter.removePendingNotificationRequestsWithIdentifiers(sessionIds.map { it.value }) + if (sessionIds.isEmpty()) { + return + } + log.v { + "Cancelling scheduled notifications with IDs: [${sessionIds.joinToString { it.value }}]" + } + notificationCenter + .removePendingNotificationRequestsWithIdentifiers(sessionIds.map { it.value }) } @Suppress("unused") @@ -138,6 +174,7 @@ class IOSNotificationService( log.w { "notificationHandler not registered when received $notification" } } } + Notification.Remote.RefreshData -> syncService.forceSynchronize() } } @@ -156,7 +193,9 @@ class IOSNotificationService( private fun Map.parseReminderNotification(): Notification.Local.Reminder? { val sessionId = this[Notification.Keys.sessionId] as? String ?: run { - log.e { "Couldn't parse reminder notification. Session ID doesn't exist or isn't String." } + log.e { + "Couldn't parse reminder notification. Session ID doesn't exist or isn't String." + } return null } @@ -167,7 +206,9 @@ class IOSNotificationService( private fun Map.parseFeedbackNotification(): Notification.Local.Feedback? { val sessionId = this[Notification.Keys.sessionId] as? String ?: run { - log.e { "Couldn't parse feedback notification. Session ID doesn't exist or isn't String." } + log.e { + "Couldn't parse feedback notification. Session ID doesn't exist or isn't String." + } return null } diff --git a/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/AppInit.kt b/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/AppInit.kt index 677fa2cf..f8ca9e85 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/AppInit.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/AppInit.kt @@ -9,7 +9,12 @@ import co.touchlab.kermit.crashlytics.CrashlyticsLogWriter @OptIn(ExperimentalKermitApi::class) fun setupKermit() { - Logger.addLogWriter(CrashlyticsLogWriter(minSeverity = Severity.Info, minCrashSeverity = Severity.Warn)) + Logger.addLogWriter( + CrashlyticsLogWriter( + minSeverity = Severity.Info, + minCrashSeverity = Severity.Warn + ) + ) enableCrashlytics() setCrashlyticsUnhandledExceptionHook() } diff --git a/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/BundleResourceReader.kt b/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/BundleResourceReader.kt index bbff7c72..d536cd4d 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/BundleResourceReader.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/BundleResourceReader.kt @@ -26,21 +26,37 @@ class BundleResourceReader( 0 -> { null to name.drop(1) } + in 1..Int.MAX_VALUE -> { name.take(lastPeriodIndex) to name.drop(lastPeriodIndex + 1) } + else -> { name to null } } - val path = bundle.pathForResource(filename, type) ?: error("Couldn't get path of $name (parsed as: ${listOfNotNull(filename, type).joinToString(".")})") + val path = bundle.pathForResource(filename, type) ?: error( + "Couldn't get path of $name (parsed as: ${ + listOfNotNull( + filename, + type + ).joinToString(".") + })" + ) return memScoped { val errorPtr = alloc>() - NSString.stringWithContentsOfFile(path, encoding = NSUTF8StringEncoding, error = errorPtr.ptr) ?: run { + NSString.stringWithContentsOfFile( + path, + encoding = NSUTF8StringEncoding, + error = errorPtr.ptr + ) ?: run { // TODO: Check the NSError and throw common exception. - error("Couldn't load resource: $name. Error: ${errorPtr.value?.localizedDescription} - ${errorPtr.value}") + error( + "Couldn't load resource: $name. " + + "Error: ${errorPtr.value?.localizedDescription} - ${errorPtr.value}" + ) } } } diff --git a/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/wrapMultiThreadCallback.kt b/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/wrapMultiThreadCallback.kt index a313cf03..d778ee5a 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/wrapMultiThreadCallback.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/droidcon/util/wrapMultiThreadCallback.kt @@ -10,9 +10,10 @@ suspend fun wrapMultiThreadCallback(call: (callback: (T) -> Unit) -> Unit): return completable.await() } -suspend fun wrapMultiThreadCallback(call: (callback: (T1, T2) -> Unit) -> Unit): Pair { - val completable = CompletableDeferred>() - val closure: (T1, T2) -> Unit = { t1, t2 -> completable.complete(t1 to t2) } - call(closure) - return completable.await() -} +suspend fun wrapMultiThreadCallback(call: (callback: (T1, T2) -> Unit) -> Unit): + Pair { + val completable = CompletableDeferred>() + val closure: (T1, T2) -> Unit = { t1, t2 -> completable.complete(t1 to t2) } + call(closure) + return completable.await() + }