diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dbd51999..d670b479 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,6 +28,13 @@ + + + + + + android:taskAffinity="" + android:theme="@style/Theme.ClockYou" /> + for (appWidgetId in appWidgetIds) { val pendingIntent: PendingIntent = PendingIntent.getActivity( context, 0, @@ -27,8 +30,16 @@ class AnalogClockWidget : AppWidgetProvider() { val views = RemoteViews(context.packageName, R.layout.analog_clock).apply { setOnClickPendingIntent(R.id.analog_clock, pendingIntent) } - + val options = context.loadAnalogClockWidgetSettings(appWidgetId) + views.applyAnalogClockWidgetOptions(options, context) appWidgetManager.updateAppWidget(appWidgetId, views) } } + + override fun onDeleted(context: Context, appWidgetIds: IntArray) { + for (appWidgetId in appWidgetIds) { + context.deleteAnalogClockWidgetPref(appWidgetId) + } + super.onDeleted(context, appWidgetIds) + } } diff --git a/app/src/main/java/com/bnyro/clock/presentation/widgets/AnalogClockWidgetConfig.kt b/app/src/main/java/com/bnyro/clock/presentation/widgets/AnalogClockWidgetConfig.kt new file mode 100644 index 00000000..c96c1a6e --- /dev/null +++ b/app/src/main/java/com/bnyro/clock/presentation/widgets/AnalogClockWidgetConfig.kt @@ -0,0 +1,258 @@ +package com.bnyro.clock.presentation.widgets + +import android.app.Activity +import android.appwidget.AppWidgetManager +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.widget.AnalogClock +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.annotation.RequiresApi +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +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.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.viewmodel.compose.viewModel +import com.bnyro.clock.R +import com.bnyro.clock.domain.model.AnalogClockFace +import com.bnyro.clock.domain.model.AnalogClockWidgetOptions +import com.bnyro.clock.presentation.screens.settings.model.SettingsModel +import com.bnyro.clock.ui.theme.ClockYouTheme +import com.bnyro.clock.util.ThemeUtil +import com.bnyro.clock.util.widgets.loadAnalogClockWidgetSettings +import com.bnyro.clock.util.widgets.saveAnalogClockWidgetSettings +import com.bnyro.clock.util.widgets.updateAnalogClockWidget +import android.graphics.drawable.Icon as AndroidIcon + + +class AnalogClockWidgetConfig : ComponentActivity() { + + private var appWidgetId: Int = AppWidgetManager.INVALID_APPWIDGET_ID + + @OptIn(ExperimentalMaterial3Api::class) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + intent?.extras?.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + )?.let { + appWidgetId = it + } + val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + setResult(Activity.RESULT_CANCELED, resultValue) + + // get settings + + val options = loadAnalogClockWidgetSettings(appWidgetId) + enableEdgeToEdge() + setContent { + val settingsModel: SettingsModel = viewModel() + + val darkTheme = when (settingsModel.themeMode) { + SettingsModel.Theme.SYSTEM -> isSystemInDarkTheme() + SettingsModel.Theme.DARK, SettingsModel.Theme.AMOLED -> true + else -> false + } + ClockYouTheme( + darkTheme = darkTheme, + customColorScheme = ThemeUtil.getSchemeFromSeed( + settingsModel.customColor, + darkTheme + ) + ) { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Scaffold(topBar = { + CenterAlignedTopAppBar(title = { Text(text = stringResource(R.string.select_clock_face)) }) + }) { pV -> + AnalogClockWidgetSettings( + modifier = Modifier.padding(pV), + options = options, + onComplete = this::complete + ) + } + } + } + } + } + + private fun complete(options: AnalogClockWidgetOptions) { + saveAnalogClockWidgetSettings(appWidgetId, options) + updateAnalogClockWidget(appWidgetId, options) + // return the result + val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + setResult(Activity.RESULT_OK, resultValue) + finish() + } +} + +@Composable +fun AnalogClockWidgetSettings( + modifier: Modifier = Modifier, + options: AnalogClockWidgetOptions, + onComplete: (AnalogClockWidgetOptions) -> Unit +) { + + Column( + modifier = modifier.padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + var clockFace by remember { + val face = AnalogClockFace.all.let { all -> + all.firstOrNull { it.name == options.clockFaceName } ?: all.first() + } + mutableStateOf(face) + } + Column( + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + LazyVerticalGrid( + modifier = Modifier.weight(1f), + columns = GridCells.Adaptive(350.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(horizontal = 16.dp) + ) { + items(AnalogClockFace.all) { face -> + AnalogClockPreview( + face = face, + selected = face == clockFace, + onClick = { + clockFace = face + }) + } + } + } + } + Button( + modifier = Modifier.padding(bottom = 16.dp), + onClick = { + options.apply { + clockFaceName = clockFace.name + dial = clockFace.dial + hourHand = clockFace.hourHand + minuteHand = clockFace.minuteHand + secondHand = clockFace.secondHand + } + onComplete.invoke(options) + }) { + Text(stringResource(R.string.save)) + } + } +} + +@OptIn(ExperimentalTextApi::class) +@RequiresApi(Build.VERSION_CODES.S) +@Composable +fun AnalogClockPreview(face: AnalogClockFace, selected: Boolean, onClick: () -> Unit) { + Card( + onClick = onClick, + shape = RoundedCornerShape(32.dp), + border = BorderStroke(if (selected) 3.dp else 0.dp, MaterialTheme.colorScheme.primary) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Box( + Modifier + .fillMaxWidth() + .aspectRatio(1f) + .clip(RoundedCornerShape(32.dp)) + .background(MaterialTheme.colorScheme.surfaceContainerLowest), + contentAlignment = Alignment.Center + ) { + AndroidView( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + factory = { context -> + AnalogClock(context).apply { + if (face.dial != 0) setDial( + AndroidIcon.createWithResource( + context, + face.dial + ) + ) + if (face.hourHand != 0) setHourHand( + AndroidIcon.createWithResource( + context, + face.hourHand + ) + ) + if (face.minuteHand != 0) setMinuteHand( + AndroidIcon.createWithResource( + context, + face.minuteHand + ) + ) + if (face.secondHand != 0) setSecondHand( + AndroidIcon.createWithResource( + context, + face.secondHand + ) + ) + } + }) + } + Text(face.name, style = MaterialTheme.typography.headlineSmall) + if (face.author != null) { + val uriHandler = LocalUriHandler.current + Text( + text = stringResource(id = R.string.design_by, face.author), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.clickable { + if (face.authorUrl != null) { + uriHandler.openUri(face.authorUrl) + } + } + ) + } + Spacer(Modifier.height(16.dp)) + } + } +} diff --git a/app/src/main/java/com/bnyro/clock/presentation/widgets/DigitalClockWidgetConfig.kt b/app/src/main/java/com/bnyro/clock/presentation/widgets/DigitalClockWidgetConfig.kt index 3f74fad7..c492dede 100644 --- a/app/src/main/java/com/bnyro/clock/presentation/widgets/DigitalClockWidgetConfig.kt +++ b/app/src/main/java/com/bnyro/clock/presentation/widgets/DigitalClockWidgetConfig.kt @@ -91,7 +91,7 @@ class DigitalClockWidgetConfig : ComponentActivity() { darkTheme = darkTheme, customColorScheme = ThemeUtil.getSchemeFromSeed( settingsModel.customColor, - true + darkTheme ) ) { Surface( diff --git a/app/src/main/java/com/bnyro/clock/util/widgets/AnalogClockWidget.kt b/app/src/main/java/com/bnyro/clock/util/widgets/AnalogClockWidget.kt new file mode 100644 index 00000000..a88d1a62 --- /dev/null +++ b/app/src/main/java/com/bnyro/clock/util/widgets/AnalogClockWidget.kt @@ -0,0 +1,97 @@ +package com.bnyro.clock.util.widgets + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.graphics.drawable.Icon +import android.os.Build +import android.widget.RemoteViews +import androidx.core.content.edit +import com.bnyro.clock.R +import com.bnyro.clock.domain.model.AnalogClockWidgetOptions + +fun Context.saveAnalogClockWidgetSettings( + appWidgetId: Int, options: AnalogClockWidgetOptions +) = widgetPreferences.edit { + putInt(PREF_CLOCK_HOUR_HAND + appWidgetId, options.hourHand) + putInt(PREF_CLOCK_MINUTE_HAND + appWidgetId, options.minuteHand) + putInt(PREF_CLOCK_SECOND_HAND + appWidgetId, options.secondHand) + putInt(PREF_CLOCK_DIAL + appWidgetId, options.dial) + putString(PREF_CLOCK_FACE_NAME + appWidgetId, options.clockFaceName) +} + +fun Context.loadAnalogClockWidgetSettings( + appWidgetId: Int +): AnalogClockWidgetOptions = with(widgetPreferences) { + val hourHand = getInt( + PREF_CLOCK_HOUR_HAND + appWidgetId, 0 + ) + + val minuteHand = getInt( + PREF_CLOCK_MINUTE_HAND + appWidgetId, 0 + ) + val secondHand = getInt( + PREF_CLOCK_SECOND_HAND + appWidgetId, 0 + ) + val dial = getInt( + PREF_CLOCK_DIAL + appWidgetId, 0 + ) + val clockFaceName = getString( + PREF_CLOCK_FACE_NAME + appWidgetId, "Default" + ) ?: "Default" + + return AnalogClockWidgetOptions( + hourHand = hourHand, + minuteHand = minuteHand, + secondHand = secondHand, + dial = dial, + clockFaceName = clockFaceName + ) +} + +fun Context.deleteAnalogClockWidgetPref(appWidgetId: Int) = + widgetPreferences.edit { + remove(PREF_CLOCK_HOUR_HAND + appWidgetId) + remove(PREF_CLOCK_MINUTE_HAND + appWidgetId) + remove(PREF_CLOCK_SECOND_HAND + appWidgetId) + remove(PREF_CLOCK_DIAL + appWidgetId) + } + +fun Context.updateAnalogClockWidget( + appWidgetId: Int, options: AnalogClockWidgetOptions +) { + val appWidgetManager = AppWidgetManager.getInstance(this) + val views = RemoteViews(packageName, R.layout.analog_clock) + views.applyAnalogClockWidgetOptions(options, this) + appWidgetManager.updateAppWidget(appWidgetId, views) +} + +fun RemoteViews.applyAnalogClockWidgetOptions( + options: AnalogClockWidgetOptions, context: Context +) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (options.dial != 0) { + setIcon( + R.id.analog_clock, "setDial", Icon.createWithResource(context, options.dial) + ) + } + if (options.hourHand != 0) { + setIcon( + R.id.analog_clock, "setHourHand", Icon.createWithResource(context, options.hourHand) + ) + } + if (options.minuteHand != 0) { + setIcon( + R.id.analog_clock, + "setMinuteHand", + Icon.createWithResource(context, options.minuteHand) + ) + } + if (options.secondHand != 0) { + setIcon( + R.id.analog_clock, + "setSecondHand", + Icon.createWithResource(context, options.secondHand) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/clock/util/widgets/WidgetPreferences.kt b/app/src/main/java/com/bnyro/clock/util/widgets/WidgetPreferences.kt index 951c9abf..036eb25c 100644 --- a/app/src/main/java/com/bnyro/clock/util/widgets/WidgetPreferences.kt +++ b/app/src/main/java/com/bnyro/clock/util/widgets/WidgetPreferences.kt @@ -14,4 +14,11 @@ internal const val PREF_SHOW_BACKGROUND = "showBackground:" internal const val PREF_DATE_TEXT_SIZE = "dateTextSize:" internal const val PREF_TIME_TEXT_SIZE = "timeTextSize:" internal const val PREF_TIME_ZONE = "timeZone:" -internal const val PREF_TIME_ZONE_NAME = "timeZoneName:" \ No newline at end of file +internal const val PREF_TIME_ZONE_NAME = "timeZoneName:" + +// Analog Clock widget +internal const val PREF_CLOCK_HOUR_HAND = "analogClockHour:" +internal const val PREF_CLOCK_MINUTE_HAND = "analogClockMinute:" +internal const val PREF_CLOCK_SECOND_HAND = "analogClockSecond:" +internal const val PREF_CLOCK_DIAL = "analogClockDial:" +internal const val PREF_CLOCK_FACE_NAME = "analogClockFaceName:" \ No newline at end of file diff --git a/app/src/main/res/drawable/analog_clock_black_dial.xml b/app/src/main/res/drawable/analog_clock_black_dial.xml new file mode 100644 index 00000000..be0328e2 --- /dev/null +++ b/app/src/main/res/drawable/analog_clock_black_dial.xml @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/analog_clock_black_hour_hand.xml b/app/src/main/res/drawable/analog_clock_black_hour_hand.xml new file mode 100644 index 00000000..1fbfc469 --- /dev/null +++ b/app/src/main/res/drawable/analog_clock_black_hour_hand.xml @@ -0,0 +1,21 @@ + + + + diff --git a/app/src/main/res/drawable/analog_clock_black_minute_hand.xml b/app/src/main/res/drawable/analog_clock_black_minute_hand.xml new file mode 100644 index 00000000..04afb392 --- /dev/null +++ b/app/src/main/res/drawable/analog_clock_black_minute_hand.xml @@ -0,0 +1,21 @@ + + + + diff --git a/app/src/main/res/drawable/analog_clock_black_second_hand.xml b/app/src/main/res/drawable/analog_clock_black_second_hand.xml new file mode 100644 index 00000000..2e227fa3 --- /dev/null +++ b/app/src/main/res/drawable/analog_clock_black_second_hand.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/classic76_dial.xml b/app/src/main/res/drawable/classic76_dial.xml new file mode 100644 index 00000000..b0118549 --- /dev/null +++ b/app/src/main/res/drawable/classic76_dial.xml @@ -0,0 +1,17 @@ + + + + diff --git a/app/src/main/res/drawable/classic76_hour_hand.xml b/app/src/main/res/drawable/classic76_hour_hand.xml new file mode 100644 index 00000000..78881d01 --- /dev/null +++ b/app/src/main/res/drawable/classic76_hour_hand.xml @@ -0,0 +1,15 @@ + + + diff --git a/app/src/main/res/drawable/classic76_minute_hand.xml b/app/src/main/res/drawable/classic76_minute_hand.xml new file mode 100644 index 00000000..756b45d4 --- /dev/null +++ b/app/src/main/res/drawable/classic76_minute_hand.xml @@ -0,0 +1,15 @@ + + + diff --git a/app/src/main/res/drawable/classic76_second_hand.xml b/app/src/main/res/drawable/classic76_second_hand.xml new file mode 100644 index 00000000..a0a73a84 --- /dev/null +++ b/app/src/main/res/drawable/classic76_second_hand.xml @@ -0,0 +1,14 @@ + + + diff --git a/app/src/main/res/drawable/classic_clock_you_dial.xml b/app/src/main/res/drawable/classic_clock_you_dial.xml new file mode 100644 index 00000000..91bcd921 --- /dev/null +++ b/app/src/main/res/drawable/classic_clock_you_dial.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/app/src/main/res/drawable/classic_clock_you_hour_hand.xml b/app/src/main/res/drawable/classic_clock_you_hour_hand.xml new file mode 100644 index 00000000..0d02cf68 --- /dev/null +++ b/app/src/main/res/drawable/classic_clock_you_hour_hand.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/classic_clock_you_minute_hand.xml b/app/src/main/res/drawable/classic_clock_you_minute_hand.xml new file mode 100644 index 00000000..1003aa51 --- /dev/null +++ b/app/src/main/res/drawable/classic_clock_you_minute_hand.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/minimalistic_bar_dial.xml b/app/src/main/res/drawable/minimalistic_bar_dial.xml new file mode 100644 index 00000000..b171f6a1 --- /dev/null +++ b/app/src/main/res/drawable/minimalistic_bar_dial.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/minimalistic_bar_hour_hand.xml b/app/src/main/res/drawable/minimalistic_bar_hour_hand.xml new file mode 100644 index 00000000..86a989dd --- /dev/null +++ b/app/src/main/res/drawable/minimalistic_bar_hour_hand.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/minimalistic_bar_minute_hand.xml b/app/src/main/res/drawable/minimalistic_bar_minute_hand.xml new file mode 100644 index 00000000..c3c1bf44 --- /dev/null +++ b/app/src/main/res/drawable/minimalistic_bar_minute_hand.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/minimalistic_circular_dial.xml b/app/src/main/res/drawable/minimalistic_circular_dial.xml new file mode 100644 index 00000000..febca96b --- /dev/null +++ b/app/src/main/res/drawable/minimalistic_circular_dial.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/minimalistic_circular_hour_hand.xml b/app/src/main/res/drawable/minimalistic_circular_hour_hand.xml new file mode 100644 index 00000000..cec15116 --- /dev/null +++ b/app/src/main/res/drawable/minimalistic_circular_hour_hand.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/minimalistic_circular_minute_hand.xml b/app/src/main/res/drawable/minimalistic_circular_minute_hand.xml new file mode 100644 index 00000000..36b9482c --- /dev/null +++ b/app/src/main/res/drawable/minimalistic_circular_minute_hand.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/twod_ball_dial.xml b/app/src/main/res/drawable/twod_ball_dial.xml new file mode 100644 index 00000000..5b07bab8 --- /dev/null +++ b/app/src/main/res/drawable/twod_ball_dial.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/twod_ball_hour_hand.xml b/app/src/main/res/drawable/twod_ball_hour_hand.xml new file mode 100644 index 00000000..e317ed13 --- /dev/null +++ b/app/src/main/res/drawable/twod_ball_hour_hand.xml @@ -0,0 +1,17 @@ + + + + diff --git a/app/src/main/res/drawable/twod_ball_minute_hand.xml b/app/src/main/res/drawable/twod_ball_minute_hand.xml new file mode 100644 index 00000000..d48b31a7 --- /dev/null +++ b/app/src/main/res/drawable/twod_ball_minute_hand.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/twod_ball_second_hand.xml b/app/src/main/res/drawable/twod_ball_second_hand.xml new file mode 100644 index 00000000..5fc88917 --- /dev/null +++ b/app/src/main/res/drawable/twod_ball_second_hand.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/app/src/main/res/layout-v31/analog_clock.xml b/app/src/main/res/layout-v31/analog_clock.xml index 3e7938a6..949853b6 100644 --- a/app/src/main/res/layout-v31/analog_clock.xml +++ b/app/src/main/res/layout-v31/analog_clock.xml @@ -8,9 +8,6 @@ android:id="@+id/analog_clock" android:layout_gravity="center" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:hand_hourTint="?attr/colorSecondary" - android:hand_minuteTint="?attr/colorPrimary" - android:dialTint="?attr/colorSurfaceContainer" /> + android:layout_height="wrap_content" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5bcb4f2a..1e670008 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -127,4 +127,6 @@ Alarm Permission Allow Maybe later + Design by %1$s + Select Clock Face \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index ea3f28c5..ee98976e 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,7 +1,7 @@ -