From e313931b4be6ff077ccb14e90dc2ef3558f42426 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:21:02 +0100 Subject: [PATCH] Add Smartspacer integration --- app/ui/build.gradle.kts | 4 + app/ui/consumer-rules.pro | 1 + app/ui/src/main/AndroidManifest.xml | 7 +- .../de/mm20/launcher2/ui/component/Banner.kt | 3 +- .../ui/launcher/widgets/clock/ClockWidget.kt | 93 ++++--- .../launcher/widgets/clock/ClockWidgetVM.kt | 18 +- .../clock/parts/SmartspacerPartProvider.kt | 255 ++++++++++++++++++ .../launcher2/ui/settings/SettingsActivity.kt | 5 + .../ClockWidgetSettingsScreenVM.kt | 8 + .../IntegrationsSettingsScreen.kt | 17 +- .../smartspacer/SmartspacerSettingsScreen.kt | 88 ++++++ .../SmartspacerSettingsScreenVM.kt | 52 ++++ .../src/main/res/layout-v29/smartspacer.xml | 23 ++ .../launcher2/licenses/OpenSourceLicenses.kt | 7 + .../src/main/res/drawable/feedback_24px.xml | 10 + .../src/main/res/drawable/smartspacer.xml | 25 ++ core/i18n/src/main/res/values/strings.xml | 5 + .../preferences/LauncherSettingsData.kt | 2 + .../preferences/ui/ClockWidgetSettings.kt | 10 + gradle/libs.versions.toml | 2 + 20 files changed, 591 insertions(+), 44 deletions(-) create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/SmartspacerPartProvider.kt create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/settings/smartspacer/SmartspacerSettingsScreen.kt create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/settings/smartspacer/SmartspacerSettingsScreenVM.kt create mode 100644 app/ui/src/main/res/layout-v29/smartspacer.xml create mode 100644 core/base/src/main/res/drawable/feedback_24px.xml create mode 100644 core/base/src/main/res/drawable/smartspacer.xml diff --git a/app/ui/build.gradle.kts b/app/ui/build.gradle.kts index 9f644de4d..f9cc916d7 100644 --- a/app/ui/build.gradle.kts +++ b/app/ui/build.gradle.kts @@ -118,6 +118,10 @@ dependencies { implementation(libs.coil.core) implementation(libs.coil.compose) + implementation(libs.smartspacer) { + exclude(group = "com.github.skydoves", module = "balloon") + } + implementation(project(":libs:material-color-utilities")) implementation(project(":core:base")) diff --git a/app/ui/consumer-rules.pro b/app/ui/consumer-rules.pro index e69de29bb..372e22add 100644 --- a/app/ui/consumer-rules.pro +++ b/app/ui/consumer-rules.pro @@ -0,0 +1 @@ +-keep class com.kieronquinn.app.smartspacer.sdk.** { *; } \ No newline at end of file diff --git a/app/ui/src/main/AndroidManifest.xml b/app/ui/src/main/AndroidManifest.xml index fb84288b2..d70660ba4 100644 --- a/app/ui/src/main/AndroidManifest.xml +++ b/app/ui/src/main/AndroidManifest.xml @@ -1,9 +1,14 @@ - + + + + + + p to s + }.map { (parts, smartspacer) -> + if (smartspacer && isAtLeastApiLevel(29)) { + return@map listOf(SmartspacerPartProvider()) + } + val providers = mutableListOf() - if (it.date) providers += DatePartProvider() - if (it.music) providers += MusicPartProvider() - if (it.battery) providers += BatteryPartProvider() - if (it.alarm) providers += AlarmPartProvider() + if (parts.date) providers += DatePartProvider() + if (parts.music) providers += MusicPartProvider() + if (parts.battery) providers += BatteryPartProvider() + if (parts.alarm) providers += AlarmPartProvider() providers }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/SmartspacerPartProvider.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/SmartspacerPartProvider.kt new file mode 100644 index 000000000..28b8d28c0 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/SmartspacerPartProvider.kt @@ -0,0 +1,255 @@ +package de.mm20.launcher2.ui.launcher.widgets.clock.parts + +import android.content.Context +import android.content.Intent +import android.view.LayoutInflater +import android.view.View +import androidx.annotation.RequiresApi +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DropdownMenuGroup +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.DropdownMenuPopup +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MenuDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.kieronquinn.app.smartspacer.sdk.client.SmartspacerClient +import com.kieronquinn.app.smartspacer.sdk.client.views.BcSmartspaceView +import com.kieronquinn.app.smartspacer.sdk.client.views.popup.Popup +import com.kieronquinn.app.smartspacer.sdk.client.views.popup.PopupFactory +import com.kieronquinn.app.smartspacer.sdk.model.SmartspaceTarget +import de.mm20.launcher2.ui.R +import kotlinx.coroutines.flow.flow + +@RequiresApi(29) +class SmartspacerPartProvider : PartProvider { + override fun getRanking(context: Context) = flow { + emit(Int.MAX_VALUE) + } + + @Composable + override fun Component(compactLayout: Boolean) { + + val contentColor = LocalContentColor.current + + val popupFactory = remember { + ComposePopupFactory() + } + + Box( + modifier = Modifier + .fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Box(modifier = Modifier.align(Alignment.TopCenter)) { + popupFactory.Popup() + } + AndroidView( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = if (compactLayout) 0.dp else 12.dp) + .height(104.dp), + factory = { + val view = LayoutInflater + .from(it) + .inflate(R.layout.smartspacer, null, false) as BcSmartspaceView + + view.setApplyShadowIfRequired(false) + view.setTintColour(contentColor.toArgb()) + view.popupFactory = popupFactory + + view + }, + update = { view -> + view.setTintColour(contentColor.toArgb()) + }, + onRelease = { + SmartspacerClient.close() + } + ) + } + + } + + private data class PopupState( + val target: SmartspaceTarget, + val launchIntent: (Intent?) -> Unit, + val dismissAction: ((SmartspaceTarget) -> Unit)?, + val aboutIntent: Intent?, + val feedbackIntent: Intent?, + val settingsIntent: Intent? + ) + + private class ComposePopupFactory : PopupFactory { + var state by mutableStateOf(null) + + override fun createPopup( + context: Context, + anchorView: View, + target: SmartspaceTarget, + backgroundColor: Int, + textColour: Int, + launchIntent: (Intent?) -> Unit, + dismissAction: ((SmartspaceTarget) -> Unit)?, + aboutIntent: Intent?, + feedbackIntent: Intent?, + settingsIntent: Intent? + ): Popup { + state = PopupState( + target = target, + launchIntent = launchIntent, + dismissAction = dismissAction, + aboutIntent = aboutIntent, + feedbackIntent = feedbackIntent, + settingsIntent = settingsIntent + ) + return object : Popup { + override fun dismiss() { + state = null + } + + } + } + + @Composable + fun Popup() { + val state = state + if (state != null) { + DropdownMenuPopup( + expanded = true, + onDismissRequest = { + this.state = null + } + ) { + + val itemCount = (if (state.settingsIntent != null) 1 else 0) + + (if (state.feedbackIntent != null) 1 else 0) + + (if (state.aboutIntent != null) 1 else 0) + + (if (state.dismissAction != null) 1 else 0) + + var item = 0 + + + DropdownMenuGroup( + shapes = MenuDefaults.groupShapes(), + ) { + if (state.settingsIntent != null) { + DropdownMenuItem( + shape = when { + item == 0 && itemCount - 1 == item -> MenuDefaults.standaloneItemShape + item == 0 -> MenuDefaults.leadingItemShape + item == itemCount - 1 -> MenuDefaults.trailingItemShape + else -> MenuDefaults.middleItemShape + }, + text = { + Text(stringResource(R.string.smartspace_long_press_popup_settings)) + }, + leadingIcon = { + Icon( + painterResource(R.drawable.settings_24px), + null, + ) + }, + onClick = { + state.launchIntent(state.settingsIntent) + } + ) + + item++ + } + + if (state.feedbackIntent != null) { + DropdownMenuItem( + shape = when { + item == 0 && itemCount - 1 == item -> MenuDefaults.standaloneItemShape + item == 0 -> MenuDefaults.leadingItemShape + item == itemCount - 1 -> MenuDefaults.trailingItemShape + else -> MenuDefaults.middleItemShape + }, + text = { + Text(stringResource(R.string.smartspace_long_press_popup_feedback)) + }, + leadingIcon = { + Icon( + painterResource(R.drawable.feedback_24px), + null, + ) + }, + onClick = { + state.launchIntent(state.feedbackIntent) + } + ) + + item++ + } + + if (state.aboutIntent != null) { + DropdownMenuItem( + shape = when { + item == 0 && itemCount - 1 == item -> MenuDefaults.standaloneItemShape + item == 0 -> MenuDefaults.leadingItemShape + item == itemCount - 1 -> MenuDefaults.trailingItemShape + else -> MenuDefaults.middleItemShape + }, + text = { + Text(stringResource(R.string.smartspace_long_press_popup_about)) + }, + leadingIcon = { + Icon( + painterResource(R.drawable.info_24px), + null, + ) + }, + onClick = { + state.launchIntent(state.aboutIntent) + } + ) + + item++ + } + + if (state.dismissAction != null) { + DropdownMenuItem( + shape = when { + item == 0 && itemCount - 1 == item -> MenuDefaults.standaloneItemShape + item == 0 -> MenuDefaults.leadingItemShape + item == itemCount - 1 -> MenuDefaults.trailingItemShape + else -> MenuDefaults.middleItemShape + }, + text = { + Text(stringResource(R.string.smartspace_long_press_popup_dismiss)) + }, + leadingIcon = { + Icon( + painterResource(R.drawable.close_24px), + null, + ) + }, + onClick = { + state.dismissAction(state.target) + } + ) + + item++ + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt index f684cdf66..d5ee23ade 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt @@ -114,6 +114,8 @@ import de.mm20.launcher2.ui.settings.shapes.ShapeSchemeSettingsRoute import de.mm20.launcher2.ui.settings.shapes.ShapeSchemeSettingsScreen import de.mm20.launcher2.ui.settings.shapes.ShapeSchemesSettingsRoute import de.mm20.launcher2.ui.settings.shapes.ShapeSchemesSettingsScreen +import de.mm20.launcher2.ui.settings.smartspacer.SmartspacerSettingsRoute +import de.mm20.launcher2.ui.settings.smartspacer.SmartspacerSettingsScreen import de.mm20.launcher2.ui.settings.tags.TagsSettingsRoute import de.mm20.launcher2.ui.settings.tags.TagsSettingsScreen import de.mm20.launcher2.ui.settings.tasks.TasksIntegrationSettingsRoute @@ -300,6 +302,9 @@ class SettingsActivity : BaseActivity() { entry { AppSearchSettingsScreen() } + entry { + SmartspacerSettingsScreen() + } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt index 41699bb11..12a25b905 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt @@ -106,4 +106,12 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent { fun setAlignment(alignment: ClockWidgetAlignment) { settings.setAlignment(alignment) } + + val useSmartspacer = settings.useSmartspacer + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) + + fun disableSmartspacer() { + settings.setUseSmartspacer(false) + } + } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/integrations/IntegrationsSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/integrations/IntegrationsSettingsScreen.kt index 10dc5028e..42074c26c 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/integrations/IntegrationsSettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/integrations/IntegrationsSettingsScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.NavKey +import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.preferences.Preference import de.mm20.launcher2.ui.component.preferences.PreferenceCategory @@ -13,13 +14,14 @@ import de.mm20.launcher2.ui.settings.breezyweather.BreezyWeatherSettingsRoute import de.mm20.launcher2.ui.settings.media.MediaIntegrationSettingsRoute import de.mm20.launcher2.ui.settings.nextcloud.NextcloudSettingsRoute import de.mm20.launcher2.ui.settings.owncloud.OwncloudSettingsRoute +import de.mm20.launcher2.ui.settings.smartspacer.SmartspacerSettingsRoute import de.mm20.launcher2.ui.settings.tasks.TasksIntegrationSettingsRoute import de.mm20.launcher2.ui.settings.weather.WeatherIntegrationSettingsRoute import de.mm20.launcher2.ui.settings.wikipedia.WikipediaSettingsRoute import kotlinx.serialization.Serializable @Serializable -data object IntegrationsSettingsRoute: NavKey +data object IntegrationsSettingsRoute : NavKey @Composable fun IntegrationsSettingsScreen() { @@ -68,6 +70,10 @@ fun IntegrationsSettingsScreen() { backStack.add(WikipediaSettingsRoute) } ) + } + } + item { + PreferenceCategory { Preference( title = stringResource(R.string.preference_tasks_integration), icon = R.drawable.check_24px_sharp, @@ -82,6 +88,15 @@ fun IntegrationsSettingsScreen() { backStack.add(BreezyWeatherSettingsRoute) } ) + if (isAtLeastApiLevel(29)) { + Preference( + title = stringResource(R.string.preference_smartspacer_integration), + icon = R.drawable.smartspacer, + onClick = { + backStack.add(SmartspacerSettingsRoute) + } + ) + } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/smartspacer/SmartspacerSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/smartspacer/SmartspacerSettingsScreen.kt new file mode 100644 index 000000000..24bc7c33b --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/smartspacer/SmartspacerSettingsScreen.kt @@ -0,0 +1,88 @@ +package de.mm20.launcher2.ui.settings.smartspacer + +import androidx.activity.compose.LocalActivity +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation3.runtime.NavKey +import de.mm20.launcher2.ui.R +import de.mm20.launcher2.ui.component.Banner +import de.mm20.launcher2.ui.component.preferences.Preference +import de.mm20.launcher2.ui.component.preferences.PreferenceCategory +import de.mm20.launcher2.ui.component.preferences.PreferenceScreen +import de.mm20.launcher2.ui.component.preferences.SwitchPreference +import kotlinx.serialization.Serializable + +@Serializable +data object SmartspacerSettingsRoute : NavKey + +@Composable +fun SmartspacerSettingsScreen() { + val viewModel = viewModel() + val activity = LocalActivity.current + + val isSmartspacerAppInstalled by viewModel.isSmartspacerAppInstalled.collectAsStateWithLifecycle( + null + ) + val isSmartspacerEnabled by viewModel.isSmartspacerEnabled.collectAsStateWithLifecycle(null) + + PreferenceScreen( + title = stringResource(R.string.preference_smartspacer_integration) + ) { + if (isSmartspacerAppInstalled == false) { + item { + PreferenceCategory { + Banner( + text = stringResource( + R.string.preference_smartspacer_integration_description, + stringResource(R.string.app_name) + ), + icon = R.drawable.info_24px, + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + .padding(16.dp), + primaryAction = { + Button(onClick = { + viewModel.downloadSmartspacerApp(activity as AppCompatActivity) + }) { + Text(stringResource(R.string.action_install)) + } + } + ) + } + } + } + if (isSmartspacerAppInstalled == true) { + item { + PreferenceCategory { + SwitchPreference( + icon = R.drawable.smartspacer, + title = stringResource(R.string.preference_smartspacer_enable), + value = isSmartspacerEnabled == true, + onValueChanged = { + viewModel.setSmartspacerEnabled(it) + } + ) + + Preference( + title = stringResource(R.string.preference_launch_smartspacer_app), + icon = R.drawable.open_in_new_24px, + onClick = { + viewModel.launchSmartspacerApp(activity as AppCompatActivity) + } + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/smartspacer/SmartspacerSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/smartspacer/SmartspacerSettingsScreenVM.kt new file mode 100644 index 000000000..48d0c8a77 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/smartspacer/SmartspacerSettingsScreenVM.kt @@ -0,0 +1,52 @@ +package de.mm20.launcher2.ui.settings.smartspacer + +import android.content.Intent +import android.os.Process +import androidx.appcompat.app.AppCompatActivity +import androidx.core.net.toUri +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import de.mm20.launcher2.applications.AppRepository +import de.mm20.launcher2.preferences.ui.ClockWidgetSettings +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import kotlin.getValue + +class SmartspacerSettingsScreenVM: ViewModel(), KoinComponent { + private val appRepository: AppRepository by inject() + private val clockWidgetSettings: ClockWidgetSettings by inject() + + val smartspacerApp = appRepository.findOne("com.kieronquinn.app.smartspacer", Process.myUserHandle()) + .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1) + + val isSmartspacerAppInstalled = smartspacerApp + .map { it != null } + .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1) + + fun downloadSmartspacerApp(activity: AppCompatActivity) { + activity.startActivity( + Intent(Intent.ACTION_VIEW).apply { + data = "https://github.com/KieronQuinn/Smartspacer".toUri() + } + ) + } + + fun launchSmartspacerApp(activity: AppCompatActivity) { + viewModelScope.launch { + smartspacerApp.first()?.launch(activity, null) + } + } + + val isSmartspacerEnabled = clockWidgetSettings.useSmartspacer + .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1) + + fun setSmartspacerEnabled(enabled: Boolean) { + clockWidgetSettings.setUseSmartspacer(enabled) + } + +} \ No newline at end of file diff --git a/app/ui/src/main/res/layout-v29/smartspacer.xml b/app/ui/src/main/res/layout-v29/smartspacer.xml new file mode 100644 index 000000000..37ebfc025 --- /dev/null +++ b/app/ui/src/main/res/layout-v29/smartspacer.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/core/base/src/main/java/de/mm20/launcher2/licenses/OpenSourceLicenses.kt b/core/base/src/main/java/de/mm20/launcher2/licenses/OpenSourceLicenses.kt index 402a548b5..d7471a800 100644 --- a/core/base/src/main/java/de/mm20/launcher2/licenses/OpenSourceLicenses.kt +++ b/core/base/src/main/java/de/mm20/launcher2/licenses/OpenSourceLicenses.kt @@ -197,5 +197,12 @@ val OpenSourceLicenses = arrayOf( licenseText = R.raw.license_apache_2, url = "https://chrisbanes.github.io/haze", copyrightNote = "Copyright 2024 Chris Banes", + ), + OpenSourceLibrary( + name = "Smartspacer Client SDK", + description = "A library to integrate Smartspacer in your app", + licenseName = R.string.apache_license_name, + licenseText = R.raw.license_apache_2, + url = "https://chrisbanes.github.io/haze", ) ) \ No newline at end of file diff --git a/core/base/src/main/res/drawable/feedback_24px.xml b/core/base/src/main/res/drawable/feedback_24px.xml new file mode 100644 index 000000000..72973b098 --- /dev/null +++ b/core/base/src/main/res/drawable/feedback_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/base/src/main/res/drawable/smartspacer.xml b/core/base/src/main/res/drawable/smartspacer.xml new file mode 100644 index 000000000..a6d1aa74d --- /dev/null +++ b/core/base/src/main/res/drawable/smartspacer.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index 3bcb2fa37..d113b2775 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -592,6 +592,7 @@ Center Bottom Dynamic zone + Managed by Smartspacer Date Show the current date Dock @@ -695,6 +696,10 @@ Breezy Weather is a free and open source weather app. If installed, %1$s can use Breezy Weather as a data source for weather data. To use Breezy Weather as weather provider:\n\n1. Go to Breezy Weather > Settings > Widgets & Live wallpaper > Send Gadgetbridge data > Enable %1$s\n\n2. In %1$s, select Breezy Weather as weather provider Open Breezy Weather + Smartspacer + Smartspacer is a free and open source At a Glance widget. If installed, it can be integrated into the dynamic zone of %1$s\'s clock widget + Enable Smartspacer integration + Open Smartspacer Tap to call Start a call without confirmation when tapping a phone number diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt index f703a6cac..d90939dc2 100644 --- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt +++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt @@ -37,6 +37,8 @@ data class LauncherSettingsData internal constructor( val mediaDenyList: Set = emptySet(), val clockWidgetCompact: Boolean = false, + val clockWidgetSmartspacer: Boolean = false, + @Deprecated("") @SerialName("clockWidgetStyle") val _clockWidgetStyle: ClockWidgetStyle = ClockWidgetStyle.Digital1(), diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt index 6a8c42c11..8e4cea8af 100644 --- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt +++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt @@ -153,6 +153,16 @@ class ClockWidgetSettings internal constructor( it.copy(clockWidgetUseThemeColor = enabled) } } + + val useSmartspacer + get() = launcherDataStore.data.map { it.clockWidgetSmartspacer } + + fun setUseSmartspacer(enabled: Boolean) { + launcherDataStore.update { + it.copy(clockWidgetSmartspacer = enabled) + } + } + } internal val ClockWidgetStyle.enumValue diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bd9cc595a..65d28ec28 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -134,6 +134,8 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co osmopeninghours = { group = "de.westnordost", name = "osm-opening-hours", version.ref = "osmOpeningHours" } +smartspacer = { group = "com.kieronquinn.smartspacer", name = "sdk-client", version = "1.1.2" } + [bundles] kotlin = ["kotlin-stdlib", "kotlinx-coroutines-core", "kotlinx-coroutines-android", "kotlinx-collections-immutable", "kotlinx-serialization-json"] androidx-lifecycle = ["androidx-lifecycle-viewmodel", "androidx-lifecycle-common", "androidx-lifecycle-runtime", "androidx-lifecycle-viewmodelcompose", "androidx-lifecycle-runtimecompose"]