Clock widget: smaller by default, with a choice of alignment and size
The first version filled its widget with the figures, which the owner found huge. The figures are now measured against the icons: small is half an icon tall, medium (the default) most of an icon, large taller than an icon, with the widget's own height and width as the limit. The owner chooses left, center or right and the size from the widget's edit button. The options are a sheet over the home screen, kept per widget, and each choice shows on the widget at once. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
31ab0d52e1
commit
8e6b737894
7 files changed
+316
-42
No files matched your search
@@ -50,6 +50,13 @@
|
||||
android:resource="@xml/flexera_clock_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<!-- The clock widget's options, a sheet over the home screen. -->
|
||||
<activity
|
||||
android:name="app.flexera.widget.ClockWidgetConfigActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Transparent" />
|
||||
|
||||
<!-- Opens Android's photo picker for the picture widget; it has no screen of its own. -->
|
||||
<activity
|
||||
android:name="app.flexera.widget.PictureWidgetConfigActivity"
|
||||
|
||||
@@ -102,6 +102,15 @@
|
||||
<!-- Its name in the list of widgets -->
|
||||
<string name="flexera_clock_widget">Clock</string>
|
||||
<string name="flexera_clock_widget_description">The time and the date, lined up with your icons</string>
|
||||
<!-- The clock widget's options, reached from its edit button -->
|
||||
<string name="flexera_clock_widget_alignment">Alignment</string>
|
||||
<string name="flexera_clock_widget_left">Left</string>
|
||||
<string name="flexera_clock_widget_center">Center</string>
|
||||
<string name="flexera_clock_widget_right">Right</string>
|
||||
<string name="flexera_clock_widget_size">Size</string>
|
||||
<string name="flexera_clock_widget_small">Small</string>
|
||||
<string name="flexera_clock_widget_medium">Medium</string>
|
||||
<string name="flexera_clock_widget_large">Large</string>
|
||||
|
||||
<!-- The picture widget that comes with the launcher (app.flexera.widget) -->
|
||||
<!-- Its name in the list of widgets -->
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
Flexera Launcher's clock widget (app.flexera.widget.ClockWidgetProvider). Four cells by two
|
||||
when first placed, resizable from two by one upwards. The launcher draws it itself, so the
|
||||
layouts named here are never shown on the home screen; previewLayout is what makes the widget
|
||||
list ask for a live preview, which the launcher also draws. Nothing to set up.
|
||||
list ask for a live preview, which the launcher also draws. Nothing has to be set up when it
|
||||
is added (configuration_optional); its edit button opens the options for alignment and size.
|
||||
-->
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="250dp"
|
||||
@@ -14,7 +15,9 @@
|
||||
android:targetCellHeight="2"
|
||||
android:updatePeriodMillis="0"
|
||||
android:description="@string/flexera_clock_widget_description"
|
||||
android:configure="app.flexera.widget.ClockWidgetConfigActivity"
|
||||
android:initialLayout="@layout/flexera_clock_widget_placeholder"
|
||||
android:previewLayout="@layout/flexera_clock_widget_placeholder"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:widgetCategory="home_screen" />
|
||||
android:widgetCategory="home_screen"
|
||||
android:widgetFeatures="reconfigurable|configuration_optional" />
|
||||
@@ -0,0 +1,141 @@
|
||||
package app.flexera.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.flexera.widget.ClockWidgetStore.Align
|
||||
import app.flexera.widget.ClockWidgetStore.Size
|
||||
import app.lawnchair.ui.theme.EdgeToEdge
|
||||
import app.lawnchair.ui.theme.LawnchairTheme
|
||||
import com.android.launcher3.R
|
||||
|
||||
/**
|
||||
* The clock widget's options: where its text stands and how large its figures are. Reached from
|
||||
* the widget's edit button. It is a sheet over the home screen, which stays in view, and every
|
||||
* choice shows on the widget at once; closing the sheet is all there is to finishing.
|
||||
*/
|
||||
class ClockWidgetConfigActivity : ComponentActivity() {
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
val widget = intent?.getIntExtra(
|
||||
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID,
|
||||
) ?: AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
if (widget == AppWidgetManager.INVALID_APPWIDGET_ID) {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
// The widget is fine with or without a visit here.
|
||||
setResult(RESULT_OK, Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget))
|
||||
|
||||
setContent {
|
||||
LawnchairTheme {
|
||||
EdgeToEdge()
|
||||
var options by remember { mutableStateOf(ClockWidgetStore.options(this, widget)) }
|
||||
val change = { changed: ClockWidgetStore.Options ->
|
||||
options = changed
|
||||
ClockWidgetStore.set(this, widget, changed)
|
||||
}
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { finish() },
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
// No dimming: the clock behind the sheet is what the choices are judged by.
|
||||
scrimColor = Color.Transparent,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp, end = 24.dp, bottom = 32.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.flexera_clock_widget),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Choice(
|
||||
heading = R.string.flexera_clock_widget_alignment,
|
||||
choices = listOf(
|
||||
Align.LEFT to R.string.flexera_clock_widget_left,
|
||||
Align.CENTER to R.string.flexera_clock_widget_center,
|
||||
Align.RIGHT to R.string.flexera_clock_widget_right,
|
||||
),
|
||||
chosen = options.align,
|
||||
onChoose = { change(options.copy(align = it)) },
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Choice(
|
||||
heading = R.string.flexera_clock_widget_size,
|
||||
choices = listOf(
|
||||
Size.SMALL to R.string.flexera_clock_widget_small,
|
||||
Size.MEDIUM to R.string.flexera_clock_widget_medium,
|
||||
Size.LARGE to R.string.flexera_clock_widget_large,
|
||||
),
|
||||
chosen = options.size,
|
||||
onChoose = { change(options.copy(size = it)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A heading with one row of choices under it, of which one is taken. */
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun <T> Choice(
|
||||
@StringRes heading: Int,
|
||||
choices: List<Pair<T, Int>>,
|
||||
chosen: T,
|
||||
onChoose: (T) -> Unit,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = heading),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
|
||||
choices.forEachIndexed { index, (value, label) ->
|
||||
SegmentedButton(
|
||||
selected = value == chosen,
|
||||
onClick = { onChoose(value) },
|
||||
shape = SegmentedButtonDefaults.itemShape(index = index, count = choices.size),
|
||||
) {
|
||||
Text(text = stringResource(id = label))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,20 @@ package app.flexera.widget
|
||||
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import com.android.launcher3.BuildConfig
|
||||
|
||||
/**
|
||||
* The launcher's own clock widget: the time, large, with the date under it. Android knows it as
|
||||
* an ordinary app widget; the launcher draws it itself with a [ClockWidgetView]. It keeps nothing
|
||||
* and needs no setting up. See [FlexeraWidgets].
|
||||
* an ordinary app widget; the launcher draws it itself with a [ClockWidgetView]. It needs no
|
||||
* setting up; what the owner may choose for it is kept in [ClockWidgetStore]. See [FlexeraWidgets].
|
||||
*/
|
||||
class ClockWidgetProvider : AppWidgetProvider() {
|
||||
|
||||
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||
appWidgetIds.forEach { ClockWidgetStore.delete(context, it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val componentName = ComponentName(BuildConfig.APPLICATION_ID, ClockWidgetProvider::class.java.name)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package app.flexera.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
/**
|
||||
* What the owner has chosen for each clock widget on the home screen: where its text stands and
|
||||
* how large its figures are. Kept per widget, so that two clocks can differ.
|
||||
*/
|
||||
object ClockWidgetStore {
|
||||
|
||||
/** Which side of the widget the time and the date stand on. */
|
||||
enum class Align(val key: String) { LEFT("left"), CENTER("center"), RIGHT("right") }
|
||||
|
||||
/** How large the figures are, measured against the icons (see ClockWidgetView). */
|
||||
enum class Size(val key: String) { SMALL("small"), MEDIUM("medium"), LARGE("large") }
|
||||
|
||||
data class Options(val align: Align = Align.LEFT, val size: Size = Size.MEDIUM)
|
||||
|
||||
/** Told, on the thread that made the change, that a widget's options are different now. */
|
||||
fun interface Listener {
|
||||
fun onClockOptionsChanged(appWidgetId: Int)
|
||||
}
|
||||
|
||||
private val listeners = CopyOnWriteArraySet<Listener>()
|
||||
|
||||
fun addListener(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
fun removeListener(listener: Listener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
fun options(context: Context, appWidgetId: Int): Options {
|
||||
val prefs = prefs(context)
|
||||
val align = prefs.getString(ALIGN + appWidgetId, null)
|
||||
val size = prefs.getString(SIZE + appWidgetId, null)
|
||||
return Options(
|
||||
align = Align.entries.firstOrNull { it.key == align } ?: Align.LEFT,
|
||||
size = Size.entries.firstOrNull { it.key == size } ?: Size.MEDIUM,
|
||||
)
|
||||
}
|
||||
|
||||
fun set(context: Context, appWidgetId: Int, options: Options) {
|
||||
prefs(context).edit()
|
||||
.putString(ALIGN + appWidgetId, options.align.key)
|
||||
.putString(SIZE + appWidgetId, options.size.key)
|
||||
.apply()
|
||||
listeners.forEach { it.onClockOptionsChanged(appWidgetId) }
|
||||
}
|
||||
|
||||
/** Forgets the options of a widget that left the home screen. */
|
||||
fun delete(context: Context, appWidgetId: Int) {
|
||||
prefs(context).edit().remove(ALIGN + appWidgetId).remove(SIZE + appWidgetId).apply()
|
||||
}
|
||||
|
||||
private fun prefs(context: Context): SharedPreferences =
|
||||
context.applicationContext.getSharedPreferences(FILE, Context.MODE_PRIVATE)
|
||||
|
||||
private const val FILE = "flexera_clock_widget"
|
||||
private const val ALIGN = "align_"
|
||||
private const val SIZE = "size_"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.flexera.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -18,6 +19,8 @@ import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import app.flexera.widget.ClockWidgetStore.Align
|
||||
import app.flexera.widget.ClockWidgetStore.Size
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.util.Themes
|
||||
import java.util.Calendar
|
||||
@@ -27,25 +30,34 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* What the clock widget ([ClockWidgetProvider]) looks like: the time in large light figures and
|
||||
* the date under it, written straight onto the wallpaper in the colour of the icon labels.
|
||||
* What the clock widget ([ClockWidgetProvider]) looks like: the time in light figures and the
|
||||
* date under it, written straight onto the wallpaper in the colour of the icon labels.
|
||||
*
|
||||
* Like the picture widget it keeps to the box of the icons in its cells ([IconFrame]): the text
|
||||
* starts at the left edge of the first column's icons and is centred between the top of the
|
||||
* first row's icons and the bottom of the last row's. The figures grow with the widget, as far
|
||||
* as its height and a share of its width allow.
|
||||
* Like the picture widget it keeps to the box of the icons in its cells ([IconFrame]). The
|
||||
* owner chooses which side of that box the text stands on, or its middle, and how large the
|
||||
* figures are ([ClockWidgetStore], set from the widget's edit button). The sizes are measured
|
||||
* against the icons, so that the clock keeps its proportion to them on any grid: small figures
|
||||
* are about half an icon tall, medium ones most of an icon, large ones taller than an icon. The
|
||||
* widget's own height and width are the limit in every case.
|
||||
*
|
||||
* A tap opens the phone's clock app.
|
||||
*/
|
||||
class ClockWidgetView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
) : FrameLayout(context, attrs) {
|
||||
) : FrameLayout(context, attrs),
|
||||
ClockWidgetStore.Listener {
|
||||
|
||||
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
|
||||
/** True while this is only a sample of the widget, in the list of widgets. */
|
||||
private var sample = true
|
||||
private var options = ClockWidgetStore.Options()
|
||||
|
||||
private val frame = RectF()
|
||||
|
||||
/** What the figures are measured against: the height of what is seen of an icon. */
|
||||
private var iconHeight = 0f
|
||||
private val ink = Rect()
|
||||
private val now = Calendar.getInstance()
|
||||
private var time = ""
|
||||
@@ -79,7 +91,6 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
// Says to Launcher3 that this widget shapes itself (see RoundedCornerEnforcement).
|
||||
id = android.R.id.background
|
||||
clipToOutline = true
|
||||
|
||||
colour()
|
||||
}
|
||||
|
||||
@@ -109,9 +120,10 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
|
||||
/** Tells the view which widget it shows. Ids that Android never hands out mean a sample. */
|
||||
fun bind(appWidgetId: Int) {
|
||||
this.appWidgetId = appWidgetId
|
||||
sample = appWidgetId <= 0
|
||||
options = if (sample) ClockWidgetStore.Options() else ClockWidgetStore.options(context, appWidgetId)
|
||||
colour()
|
||||
invalidate()
|
||||
if (sample) {
|
||||
// In the list of widgets a tap belongs to the list.
|
||||
setOnClickListener(null)
|
||||
@@ -119,10 +131,20 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
} else {
|
||||
setOnClickListener { openClock() }
|
||||
}
|
||||
arrange()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onClockOptionsChanged(appWidgetId: Int) {
|
||||
if (sample || appWidgetId != this.appWidgetId) return
|
||||
options = ClockWidgetStore.options(context, appWidgetId)
|
||||
arrange()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
ClockWidgetStore.addListener(this)
|
||||
if (!listening) {
|
||||
val changes = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_TIME_TICK)
|
||||
@@ -134,10 +156,12 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
listening = true
|
||||
}
|
||||
now.timeZone = TimeZone.getDefault()
|
||||
if (!sample) options = ClockWidgetStore.options(context, appWidgetId)
|
||||
tell()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
ClockWidgetStore.removeListener(this)
|
||||
if (listening) {
|
||||
context.unregisterReceiver(clock)
|
||||
listening = false
|
||||
@@ -156,10 +180,12 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
val aligned = IconFrame.of(this)
|
||||
if (aligned != null) {
|
||||
frame.set(aligned.left, aligned.top, aligned.right, aligned.bottom)
|
||||
iconHeight = aligned.visibleIconSize
|
||||
} else {
|
||||
// In the list of widgets: the whole view, with a little air.
|
||||
// In the list of widgets: the whole view with a little air, and a made-up icon.
|
||||
val margin = min(width, height) * LOOSE_MARGIN_SHARE
|
||||
frame.set(margin, margin, width - margin, height - margin)
|
||||
iconHeight = frame.height() * LOOSE_ICON_SHARE
|
||||
}
|
||||
arrange()
|
||||
}
|
||||
@@ -183,16 +209,23 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/** Sizes the two lines for the frame and works out where they go. */
|
||||
/** Sizes the two lines for the frame and the owner's choices, and works out where they go. */
|
||||
private fun arrange() {
|
||||
val width = frame.width()
|
||||
val height = frame.height()
|
||||
if (width <= 0f || height <= 0f || time.isEmpty()) return
|
||||
val scaled = resources.displayMetrics.scaledDensity
|
||||
|
||||
// The date: a share of the height, within the sizes that read well, and never wider
|
||||
// How tall the figures would like to be.
|
||||
val wanted = iconHeight * when (options.size) {
|
||||
Size.SMALL -> SMALL_IN_ICONS
|
||||
Size.MEDIUM -> MEDIUM_IN_ICONS
|
||||
Size.LARGE -> LARGE_IN_ICONS
|
||||
}
|
||||
|
||||
// The date goes with the figures, within the sizes that read well, and is never wider
|
||||
// than the frame.
|
||||
var dateSize = (height * DATE_HEIGHT_SHARE).coerceIn(DATE_MIN_SP * scaled, DATE_MAX_SP * scaled)
|
||||
var dateSize = (wanted * DATE_IN_FIGURES).coerceIn(DATE_MIN_SP * scaled, DATE_MAX_SP * scaled)
|
||||
datePaint.textSize = dateSize
|
||||
val dateWidth = datePaint.measureText(date)
|
||||
if (dateWidth > width) {
|
||||
@@ -203,18 +236,14 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
val dateHeight = dateMetrics.descent - dateMetrics.ascent
|
||||
val gap = dateSize * GAP_IN_DATE_SIZES
|
||||
|
||||
// The time: as tall as the room above the date allows, and no wider than its share of
|
||||
// the frame, measured on the figures themselves and not on the line they stand in.
|
||||
// The figures: as tall as wanted, unless the room above the date or the frame's width
|
||||
// is less. Measured on the figures themselves, not on the line they stand in.
|
||||
timePaint.textSize = MEASURE_SIZE
|
||||
timePaint.getTextBounds(time, 0, time.length, ink)
|
||||
val inkHeightAtMeasure = max(1, ink.height()).toFloat()
|
||||
val inkWidthAtMeasure = max(1, ink.width()).toFloat()
|
||||
val widthShare = if (width > height * WIDE_FROM_RATIO) TIME_WIDTH_SHARE_WIDE else 1f
|
||||
val timeSize = min(
|
||||
MEASURE_SIZE * (height - dateHeight - gap) / inkHeightAtMeasure,
|
||||
MEASURE_SIZE * (width * widthShare) / inkWidthAtMeasure,
|
||||
)
|
||||
timePaint.textSize = max(1f, timeSize)
|
||||
val figures = min(wanted, min(height - dateHeight - gap, width * inkHeightAtMeasure / inkWidthAtMeasure))
|
||||
timePaint.textSize = max(1f, MEASURE_SIZE * figures / inkHeightAtMeasure)
|
||||
timePaint.getTextBounds(time, 0, time.length, ink)
|
||||
|
||||
// Both lines together, centred between the top and the bottom of the frame.
|
||||
@@ -223,14 +252,24 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
timeBaseline = top - ink.top
|
||||
dateBaseline = top + ink.height() + gap - dateMetrics.ascent
|
||||
|
||||
if (layoutDirection == LAYOUT_DIRECTION_RTL) {
|
||||
timeX = frame.right - ink.right
|
||||
dateX = frame.right - datePaint.measureText(date)
|
||||
} else {
|
||||
// The first figure's own edge on the frame's edge, not the space the font leaves
|
||||
// before it.
|
||||
timeX = frame.left - ink.left
|
||||
dateX = frame.left
|
||||
// Sideways it is the figures' own edges that meet the frame's, not the space the font
|
||||
// leaves beside them.
|
||||
val dateLength = datePaint.measureText(date)
|
||||
when (options.align) {
|
||||
Align.LEFT -> {
|
||||
timeX = frame.left - ink.left
|
||||
dateX = frame.left
|
||||
}
|
||||
|
||||
Align.CENTER -> {
|
||||
timeX = frame.centerX() - ink.exactCenterX()
|
||||
dateX = frame.centerX() - dateLength / 2f
|
||||
}
|
||||
|
||||
Align.RIGHT -> {
|
||||
timeX = frame.right - ink.right
|
||||
dateX = frame.right - dateLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,20 +305,25 @@ class ClockWidgetView @JvmOverloads constructor(
|
||||
const val TIME_WEIGHT = 300
|
||||
const val DATE_WEIGHT = 500
|
||||
|
||||
/** The date line's text size as a share of the frame's height, and its limits. */
|
||||
const val DATE_HEIGHT_SHARE = 0.13f
|
||||
const val DATE_MIN_SP = 13f
|
||||
const val DATE_MAX_SP = 20f
|
||||
/**
|
||||
* How tall the figures are for each size, in icon heights. (The first version filled
|
||||
* the widget, about 1.7 icons on a widget two rows high, which the owner found huge.)
|
||||
*/
|
||||
const val SMALL_IN_ICONS = 0.5f
|
||||
const val MEDIUM_IN_ICONS = 0.8f
|
||||
const val LARGE_IN_ICONS = 1.25f
|
||||
|
||||
/** The date's text size as a share of the figures' height, and its limits. */
|
||||
const val DATE_IN_FIGURES = 0.26f
|
||||
const val DATE_MIN_SP = 12f
|
||||
const val DATE_MAX_SP = 18f
|
||||
|
||||
/** The space between the figures and the date, in date text sizes. */
|
||||
const val GAP_IN_DATE_SIZES = 0.7f
|
||||
|
||||
/** In a frame wider than this many times its height, the time keeps to a share of the width. */
|
||||
const val WIDE_FROM_RATIO = 1.6f
|
||||
const val TIME_WIDTH_SHARE_WIDE = 0.72f
|
||||
const val GAP_IN_DATE_SIZES = 0.6f
|
||||
|
||||
const val MEASURE_SIZE = 200f
|
||||
const val LOOSE_MARGIN_SHARE = 0.12f
|
||||
const val LOOSE_ICON_SHARE = 0.45f
|
||||
|
||||
const val SHADOW_RADIUS_DP = 3f
|
||||
const val SHADOW_DROP_DP = 1f
|
||||
|
||||
Reference in new issue
Block a user