diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockOutlineTextView.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockOutlineTextView.java new file mode 100644 index 0000000..43b25b5 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockOutlineTextView.java @@ -0,0 +1,130 @@ +package se.ajpanton.statusbartweak.runtime.render; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.Spannable; +import android.text.style.ForegroundColorSpan; + +import java.util.ArrayList; + +import androidx.appcompat.widget.AppCompatTextView; + +/** Draws the outline with one colour, then restores the original span colours for the text fill. */ +final class ClockOutlineTextView extends AppCompatTextView { + private int sourcePaddingLeft; + private int sourcePaddingTop; + private int sourcePaddingRight; + private int sourcePaddingBottom; + private int outlineColor; + private float outlineThicknessPx; + private ForegroundColorSpan outlineSpan; + + ClockOutlineTextView(Context context) { + super(context); + } + + boolean setSourcePadding(int left, int top, int right, int bottom) { + if (sourcePaddingLeft == left + && sourcePaddingTop == top + && sourcePaddingRight == right + && sourcePaddingBottom == bottom) { + return false; + } + sourcePaddingLeft = left; + sourcePaddingTop = top; + sourcePaddingRight = right; + sourcePaddingBottom = bottom; + applyPaddedInsets(); + return true; + } + + boolean setOutline(ClockOutlineSpec outline) { + int color = outline != null ? outline.color : 0; + float thickness = outline != null ? outline.thicknessPx : 0f; + if (outlineColor == color && outlineThicknessPx == thickness) { + return false; + } + outlineColor = color; + outlineThicknessPx = thickness; + outlineSpan = new ForegroundColorSpan(color); + applyPaddedInsets(); + return true; + } + + int outlineInsetPx() { + return enabled() ? (int) Math.ceil(outlineThicknessPx) : 0; + } + + @Override + protected void onDraw(Canvas canvas) { + if (!enabled()) { + super.onDraw(canvas); + return; + } + Paint paint = getPaint(); + Paint.Style style = paint.getStyle(); + float strokeWidth = paint.getStrokeWidth(); + int color = paint.getColor(); + ArrayList foregrounds = replaceForegroundColors(); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(outlineThicknessPx * 2f); + paint.setColor(outlineColor); + try { + super.onDraw(canvas); + } finally { + restoreForegroundColors(foregrounds); + paint.setStyle(style); + paint.setStrokeWidth(strokeWidth); + paint.setColor(color); + } + super.onDraw(canvas); + } + + private ArrayList replaceForegroundColors() { + if (!(getText() instanceof Spannable text) || text.length() == 0) { + return null; + } + ForegroundColorSpan[] spans = text.getSpans(0, text.length(), ForegroundColorSpan.class); + ArrayList states = new ArrayList<>(spans.length); + for (ForegroundColorSpan span : spans) { + states.add(new SpanState( + span, + text.getSpanStart(span), + text.getSpanEnd(span), + text.getSpanFlags(span))); + text.removeSpan(span); + } + text.setSpan(outlineSpan, 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return states; + } + + private void restoreForegroundColors(ArrayList states) { + if (!(getText() instanceof Spannable text) || outlineSpan == null) { + return; + } + text.removeSpan(outlineSpan); + if (states == null) { + return; + } + for (SpanState state : states) { + text.setSpan(state.span, state.start, state.end, state.flags); + } + } + + private boolean enabled() { + return outlineThicknessPx > 0f && ((outlineColor >>> 24) & 0xff) > 0; + } + + private void applyPaddedInsets() { + int inset = outlineInsetPx(); + setPadding( + sourcePaddingLeft + inset, + sourcePaddingTop + inset, + sourcePaddingRight + inset, + sourcePaddingBottom + inset); + } + + private record SpanState(ForegroundColorSpan span, int start, int end, int flags) { + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockProxyRenderer.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockProxyRenderer.java index 91b730c..01f0fc6 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockProxyRenderer.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockProxyRenderer.java @@ -1,7 +1,7 @@ package se.ajpanton.statusbartweak.runtime.render; -import android.text.TextUtils; import android.text.Spanned; +import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.view.View; import android.view.ViewGroup; @@ -19,6 +19,10 @@ final class ClockProxyRenderer { private final WeakHashMap> rowsByHost = new WeakHashMap<>(); void render(ViewGroup host, ClockPlacement placement) { + render(host, placement, ClockOutlineSpec.NONE); + } + + void render(ViewGroup host, ClockPlacement placement, ClockOutlineSpec outline) { if (host == null || placement == null || placement.rows.isEmpty()) { clear(host); return; @@ -36,13 +40,8 @@ final class ClockProxyRenderer { views.clear(); hostChanged = true; } - while (views.size() > placement.rows.size()) { - TextView view = views.remove(views.size() - 1); - detachFromParent(view); - hostChanged = true; - } while (views.size() < placement.rows.size()) { - TextView view = new TextView(host.getContext()); + TextView view = new ClockOutlineTextView(host.getContext()); view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); views.add(view); host.addView(view); @@ -50,10 +49,13 @@ final class ClockProxyRenderer { } for (int i = 0; i < placement.rows.size(); i++) { ClockRow row = placement.rows.get(i); - TextView view = views.get(i); + ClockOutlineTextView view = (ClockOutlineTextView) views.get(i); if (copyTextStyleIfChanged(placement.source, view, placement.textSizePx)) { hostChanged = true; } + if (view.setOutline(outline)) { + hostChanged = true; + } if (applyTextColorOverrideIfNeeded(view, placement.textColorOverride)) { hostChanged = true; } @@ -61,7 +63,7 @@ final class ClockProxyRenderer { hostChanged = true; } if (!sameTextAndColorSpans(view.getText(), row.text)) { - view.setText(row.text); + view.setText(row.text, TextView.BufferType.SPANNABLE); hostChanged = true; } if (!Objects.equals(view.getContentDescription(), placement.contentDescription)) { @@ -76,21 +78,24 @@ final class ClockProxyRenderer { view.setAlpha(1f); hostChanged = true; } - int left = placement.bounds.left + row.x; - int top = placement.bounds.top + row.y; + int inset = view.outlineInsetPx(); + int left = placement.bounds.left + row.x - inset; + int top = placement.bounds.top + row.y - inset; + int width = row.width + 2 * inset; + int height = row.height + 2 * inset; int scrollX = Math.max(0, row.clipLeft); if (view.getScrollX() != scrollX) { view.setScrollX(scrollX); hostChanged = true; } - if (applyTextLayoutParams(host, view, left, top, row.width, row.height)) { + if (applyTextLayoutParams(host, view, left, top, width, height)) { hostChanged = true; } if (view.getLeft() != left || view.getTop() != top - || view.getRight() != left + row.width - || view.getBottom() != top + row.height) { - view.layout(left, top, left + row.width, top + row.height); + || view.getRight() != left + width + || view.getBottom() != top + height) { + view.layout(left, top, left + width, top + height); hostChanged = true; } } @@ -211,7 +216,13 @@ final class ClockProxyRenderer { target.setHorizontallyScrolling(true); changed = true; } - if (target.getPaddingLeft() != source.getPaddingLeft() + if (target instanceof ClockOutlineTextView outlineTarget) { + changed |= outlineTarget.setSourcePadding( + source.getPaddingLeft(), + source.getPaddingTop(), + source.getPaddingRight(), + source.getPaddingBottom()); + } else if (target.getPaddingLeft() != source.getPaddingLeft() || target.getPaddingTop() != source.getPaddingTop() || target.getPaddingRight() != source.getPaddingRight() || target.getPaddingBottom() != source.getPaddingBottom()) { @@ -234,10 +245,7 @@ final class ClockProxyRenderer { } private static boolean applyTextColorOverrideIfNeeded(TextView view, int color) { - if (view == null || (color >>> 24) == 0) { - return false; - } - if (view.getCurrentTextColor() == color) { + if (view == null || (color >>> 24) == 0 || view.getCurrentTextColor() == color) { return false; } view.setTextColor(color); @@ -276,3 +284,15 @@ final class ClockProxyRenderer { } } } + +final class ClockOutlineSpec { + static final ClockOutlineSpec NONE = new ClockOutlineSpec(0, 0f); + + final int color; + final float thicknessPx; + + ClockOutlineSpec(int color, float thicknessPx) { + this.color = color; + this.thicknessPx = thicknessPx; + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java index 8075433..c820b9f 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java @@ -12,6 +12,7 @@ import java.util.WeakHashMap; import se.ajpanton.statusbartweak.platform.SystemUiCapabilities; import se.ajpanton.statusbartweak.runtime.features.clock.ClockLayoutTextFactory; +import se.ajpanton.statusbartweak.runtime.features.clock.ClockColorResolver; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle; import se.ajpanton.statusbartweak.runtime.mode.SceneKey; @@ -29,6 +30,7 @@ public final class UnlockedIconSnapshotRenderController { private final SnapshotRenderer notificationRenderer; private final SnapshotRenderer chipRenderer; private final ClockProxyRenderer clockRenderer = new ClockProxyRenderer(); + private final ClockColorResolver clockColorResolver = new ClockColorResolver(); private final ClockProxyRenderer carrierRenderer = new ClockProxyRenderer(); private final UnlockedStockSourceCollector sourceCollector; private final TextVerticalOffsetScaler textOffsetScaler = new TextVerticalOffsetScaler(); @@ -154,7 +156,7 @@ public final class UnlockedIconSnapshotRenderController { LayoutPlan updatedPlan = previousPlan.withClockPlacement(updatedClock); lastLayoutPlanByRoot.put(root, updatedPlan); lastClockSourceGeometryByRoot.put(root, currentClockSourceGeometry); - clockRenderer.render(clockHost, updatedClock); + renderClock(clockHost, updatedClock, settings); return new RenderResult(false, false, false); } } @@ -181,7 +183,7 @@ public final class UnlockedIconSnapshotRenderController { updatedClock)) { LayoutPlan updatedPlan = previousPlan.withClockPlacement(updatedClock); lastLayoutPlanByRoot.put(root, updatedPlan); - clockRenderer.render(clockHost, updatedClock); + renderClock(clockHost, updatedClock, settings); return new RenderResult(false, false, false); } } @@ -239,7 +241,7 @@ public final class UnlockedIconSnapshotRenderController { layoutPlan = withCurrentCarrierAppearance(layoutPlan); lastLayoutPlanByRoot.put(root, layoutPlan); if (clockEnabledForScene(settings, scene) && layoutPlan.clockPlacement != null) { - clockRenderer.render(clockHost, layoutPlan.clockPlacement); + renderClock(clockHost, layoutPlan.clockPlacement, settings); } else { clockRenderer.clear(clockHost); } @@ -315,7 +317,7 @@ public final class UnlockedIconSnapshotRenderController { layoutPlan = updatedPlan; } if (settings.clockEnabledUnlocked && layoutPlan.clockPlacement != null) { - clockRenderer.render(clockHost, layoutPlan.clockPlacement); + renderClock(clockHost, layoutPlan.clockPlacement, settings); } } @@ -381,7 +383,7 @@ public final class UnlockedIconSnapshotRenderController { LayoutPlan updatedPlan = layoutPlan.withClockPlacement(updatedClock); lastLayoutPlanByRoot.put(root, updatedPlan); lastClockAppearanceByRoot.put(root, clockAppearanceSignature(updatedClock, referenceColor, settings)); - clockRenderer.render(clockHost, updatedClock); + renderClock(clockHost, updatedClock, settings); return true; } @@ -654,6 +656,51 @@ public final class UnlockedIconSnapshotRenderController { return statusBarTintTargetEnabled ? StatusBarTintTarget.color() : android.graphics.Color.TRANSPARENT; } + private void renderClock( + ViewGroup clockHost, + ClockPlacement placement, + SbtSettings settings + ) { + clockRenderer.render(clockHost, placement, clockOutlineSpec(clockHost, placement, settings)); + } + + private ClockOutlineSpec clockOutlineSpec( + ViewGroup host, + ClockPlacement placement, + SbtSettings settings + ) { + if (host == null + || placement == null + || settings == null + || !settings.clockOutlineEnabled + || settings.clockOutlineThicknessDp <= 0 + || settings.clockOutlineColor == null + || settings.clockOutlineColor.trim().isEmpty()) { + return ClockOutlineSpec.NONE; + } + int referenceColor = hasAlpha(placement.textColorOverride) + ? placement.textColorOverride + : placement.source != null + ? placement.source.getCurrentTextColor() + : android.graphics.Color.WHITE; + try { + int color = clockColorResolver.resolveColor( + host.getContext(), + settings.clockOutlineColor, + referenceColor, + android.graphics.Color.TRANSPARENT, + ClockColorResolver.Options.clock()); + if (!hasAlpha(color)) { + return ClockOutlineSpec.NONE; + } + return new ClockOutlineSpec( + color, + settings.clockOutlineThicknessDp * host.getResources().getDisplayMetrics().density); + } catch (IllegalArgumentException ignored) { + return ClockOutlineSpec.NONE; + } + } + private boolean hasAlpha(int color) { return (color >>> 24) != 0; } @@ -689,7 +736,10 @@ public final class UnlockedIconSnapshotRenderController { settings.clockShowSeconds, settings.clockShowDatePrefix, settings.clockCustomFormatEnabled, - settings.clockCustomFormat); + settings.clockCustomFormat, + settings.clockOutlineEnabled, + settings.clockOutlineThicknessDp, + settings.clockOutlineColor); } public void clearAll() { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtDefaults.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtDefaults.java index a06e599..b6f8d72 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtDefaults.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtDefaults.java @@ -87,6 +87,11 @@ public final class SbtDefaults { public static final boolean CLOCK_SHOW_DATE_PREFIX_DEFAULT = false; public static final boolean CLOCK_CUSTOM_FORMAT_ENABLED_DEFAULT = false; public static final String CLOCK_CUSTOM_FORMAT_DEFAULT = ""; + public static final boolean CLOCK_OUTLINE_ENABLED_DEFAULT = false; + public static final int CLOCK_OUTLINE_THICKNESS_DP_DEFAULT = 1; + public static final int CLOCK_OUTLINE_THICKNESS_DP_MIN = 1; + public static final int CLOCK_OUTLINE_THICKNESS_DP_MAX = 6; + public static final String CLOCK_OUTLINE_COLOR_DEFAULT = "#000 ; #FFF"; public static final boolean DRAWER_CLOCK_CUSTOM_FORMAT_ENABLED_DEFAULT = false; public static final String DRAWER_CLOCK_CUSTOM_FORMAT_DEFAULT = ""; public static final boolean DRAWER_DATE_CUSTOM_FORMAT_ENABLED_DEFAULT = false; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java index be234e3..681b699 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java @@ -213,6 +213,9 @@ public final class SbtSettings { public static final String KEY_CLOCK_SHOW_DATE_PREFIX = "clock_show_date_prefix"; public static final String KEY_CLOCK_CUSTOM_FORMAT_ENABLED = "clock_custom_format_enabled"; public static final String KEY_CLOCK_CUSTOM_FORMAT = "clock_custom_format"; + public static final String KEY_CLOCK_OUTLINE_ENABLED = "clock_outline_enabled"; + public static final String KEY_CLOCK_OUTLINE_THICKNESS_DP = "clock_outline_thickness_dp"; + public static final String KEY_CLOCK_OUTLINE_COLOR = "clock_outline_color"; public static final String KEY_DRAWER_CLOCK_CUSTOM_FORMAT_ENABLED = "drawer_clock_custom_format_enabled"; public static final String KEY_DRAWER_CLOCK_CUSTOM_FORMAT = "drawer_clock_custom_format"; @@ -454,6 +457,9 @@ public final class SbtSettings { public final boolean clockShowDatePrefix; public final boolean clockCustomFormatEnabled; public final String clockCustomFormat; + public final boolean clockOutlineEnabled; + public final int clockOutlineThicknessDp; + public final String clockOutlineColor; public final boolean drawerClockCustomFormatEnabled; public final String drawerClockCustomFormat; public final boolean drawerDateCustomFormatEnabled; @@ -589,6 +595,9 @@ public final class SbtSettings { boolean clockShowDatePrefix, boolean clockCustomFormatEnabled, String clockCustomFormat, + boolean clockOutlineEnabled, + int clockOutlineThicknessDp, + String clockOutlineColor, boolean drawerClockCustomFormatEnabled, String drawerClockCustomFormat, boolean drawerDateCustomFormatEnabled, @@ -762,6 +771,13 @@ public final class SbtSettings { this.clockCustomFormat = clockCustomFormat != null ? clockCustomFormat : SbtDefaults.CLOCK_CUSTOM_FORMAT_DEFAULT; + this.clockOutlineEnabled = clockOutlineEnabled; + this.clockOutlineThicknessDp = Math.max( + SbtDefaults.CLOCK_OUTLINE_THICKNESS_DP_MIN, + Math.min(clockOutlineThicknessDp, SbtDefaults.CLOCK_OUTLINE_THICKNESS_DP_MAX)); + this.clockOutlineColor = clockOutlineColor != null + ? clockOutlineColor + : SbtDefaults.CLOCK_OUTLINE_COLOR_DEFAULT; this.drawerClockCustomFormatEnabled = drawerClockCustomFormatEnabled; this.drawerClockCustomFormat = drawerClockCustomFormat != null ? drawerClockCustomFormat @@ -1219,6 +1235,9 @@ public final class SbtSettings { prefs.getBoolean(KEY_CLOCK_SHOW_DATE_PREFIX, SbtDefaults.CLOCK_SHOW_DATE_PREFIX_DEFAULT), prefs.getBoolean(KEY_CLOCK_CUSTOM_FORMAT_ENABLED, SbtDefaults.CLOCK_CUSTOM_FORMAT_ENABLED_DEFAULT), prefs.getString(KEY_CLOCK_CUSTOM_FORMAT, SbtDefaults.CLOCK_CUSTOM_FORMAT_DEFAULT), + prefs.getBoolean(KEY_CLOCK_OUTLINE_ENABLED, SbtDefaults.CLOCK_OUTLINE_ENABLED_DEFAULT), + prefs.getInt(KEY_CLOCK_OUTLINE_THICKNESS_DP, SbtDefaults.CLOCK_OUTLINE_THICKNESS_DP_DEFAULT), + prefs.getString(KEY_CLOCK_OUTLINE_COLOR, SbtDefaults.CLOCK_OUTLINE_COLOR_DEFAULT), prefs.getBoolean(KEY_DRAWER_CLOCK_CUSTOM_FORMAT_ENABLED, SbtDefaults.DRAWER_CLOCK_CUSTOM_FORMAT_ENABLED_DEFAULT), prefs.getString(KEY_DRAWER_CLOCK_CUSTOM_FORMAT, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java index b59f870..8c55209 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java @@ -155,6 +155,15 @@ public class SbtSettingsProvider extends ContentProvider { prefs.getBoolean(SbtSettings.KEY_CLOCK_CUSTOM_FORMAT_ENABLED, SbtDefaults.CLOCK_CUSTOM_FORMAT_ENABLED_DEFAULT)); out.putString(SbtSettings.KEY_CLOCK_CUSTOM_FORMAT, prefs.getString(SbtSettings.KEY_CLOCK_CUSTOM_FORMAT, SbtDefaults.CLOCK_CUSTOM_FORMAT_DEFAULT)); + out.putBoolean(SbtSettings.KEY_CLOCK_OUTLINE_ENABLED, + prefs.getBoolean(SbtSettings.KEY_CLOCK_OUTLINE_ENABLED, + SbtDefaults.CLOCK_OUTLINE_ENABLED_DEFAULT)); + out.putInt(SbtSettings.KEY_CLOCK_OUTLINE_THICKNESS_DP, + prefs.getInt(SbtSettings.KEY_CLOCK_OUTLINE_THICKNESS_DP, + SbtDefaults.CLOCK_OUTLINE_THICKNESS_DP_DEFAULT)); + out.putString(SbtSettings.KEY_CLOCK_OUTLINE_COLOR, + prefs.getString(SbtSettings.KEY_CLOCK_OUTLINE_COLOR, + SbtDefaults.CLOCK_OUTLINE_COLOR_DEFAULT)); out.putBoolean(SbtSettings.KEY_DRAWER_CLOCK_CUSTOM_FORMAT_ENABLED, prefs.getBoolean( SbtSettings.KEY_DRAWER_CLOCK_CUSTOM_FORMAT_ENABLED, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ClockFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ClockFragment.java index a4939a6..5908a7f 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ClockFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ClockFragment.java @@ -40,6 +40,7 @@ import androidx.fragment.app.Fragment; import com.google.android.material.button.MaterialButton; import com.google.android.material.card.MaterialCardView; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.slider.Slider; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -93,6 +94,11 @@ public final class ClockFragment extends Fragment { EditText customFormatInput = root.findViewById(R.id.clock_custom_format_input); ClockPatternPreviewView customFormatPreview = root.findViewById(R.id.clock_custom_format_preview); + CheckBox clockOutlineEnabled = root.findViewById(R.id.clock_outline_enabled); + View clockOutlineContainer = root.findViewById(R.id.clock_outline_container); + Slider clockOutlineThickness = root.findViewById(R.id.clock_outline_thickness); + TextView clockOutlineThicknessLabel = root.findViewById(R.id.clock_outline_thickness_value); + EditText clockOutlineColor = root.findViewById(R.id.clock_outline_color); MaterialCardView drawerClockCustomFormatCard = root.findViewById(R.id.drawer_clock_custom_format_card); CheckBox drawerClockCustomFormatEnabled = @@ -129,6 +135,12 @@ public final class ClockFragment extends Fragment { String customFormatValue = prefs.getString( SbtSettings.KEY_CLOCK_CUSTOM_FORMAT, SbtDefaults.CLOCK_CUSTOM_FORMAT_DEFAULT); + int clockOutlineThicknessDp = prefs.getInt( + SbtSettings.KEY_CLOCK_OUTLINE_THICKNESS_DP, + SbtDefaults.CLOCK_OUTLINE_THICKNESS_DP_DEFAULT); + String clockOutlineColorValue = prefs.getString( + SbtSettings.KEY_CLOCK_OUTLINE_COLOR, + SbtDefaults.CLOCK_OUTLINE_COLOR_DEFAULT); boolean drawerClockCustomFormatEnabledValue = prefs.getBoolean( SbtSettings.KEY_DRAWER_CLOCK_CUSTOM_FORMAT_ENABLED, SbtDefaults.DRAWER_CLOCK_CUSTOM_FORMAT_ENABLED_DEFAULT); @@ -160,6 +172,15 @@ public final class ClockFragment extends Fragment { customFormatValue, 18f, true); + bindClockOutline( + prefs, + clockOutlineEnabled, + clockOutlineContainer, + clockOutlineThickness, + clockOutlineThicknessLabel, + clockOutlineThicknessDp, + clockOutlineColor, + clockOutlineColorValue); bindCustomFormat( prefs, drawerClockCustomFormatCard, @@ -218,6 +239,69 @@ public final class ClockFragment extends Fragment { return root; } + private void bindClockOutline( + SharedPreferences prefs, + CheckBox enabledView, + View container, + Slider thicknessView, + TextView thicknessValueView, + int thicknessDp, + EditText colorView, + String colorExpression + ) { + if (enabledView == null || thicknessView == null || colorView == null) { + return; + } + int clampedThickness = Math.max( + SbtDefaults.CLOCK_OUTLINE_THICKNESS_DP_MIN, + Math.min(thicknessDp, SbtDefaults.CLOCK_OUTLINE_THICKNESS_DP_MAX)); + enabledView.setChecked(prefs.getBoolean( + SbtSettings.KEY_CLOCK_OUTLINE_ENABLED, + SbtDefaults.CLOCK_OUTLINE_ENABLED_DEFAULT)); + thicknessView.setValue(clampedThickness); + updateClockOutlineThicknessLabel(thicknessValueView, clampedThickness); + colorView.setText(colorExpression != null ? colorExpression : ""); + updateCustomFormatVisibility(container, enabledView.isChecked()); + enabledView.setOnCheckedChangeListener((buttonView, isChecked) -> { + updateCustomFormatVisibility(container, isChecked); + prefs.edit().putBoolean(SbtSettings.KEY_CLOCK_OUTLINE_ENABLED, isChecked).apply(); + SbtSettings.ensureReadable(requireContext()); + }); + thicknessView.addOnChangeListener((slider, value, fromUser) -> { + int thickness = Math.round(value); + updateClockOutlineThicknessLabel(thicknessValueView, thickness); + if (!fromUser) { + return; + } + prefs.edit().putInt(SbtSettings.KEY_CLOCK_OUTLINE_THICKNESS_DP, thickness).apply(); + SbtSettings.ensureReadable(requireContext()); + }); + colorView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + prefs.edit() + .putString(SbtSettings.KEY_CLOCK_OUTLINE_COLOR, + s != null ? s.toString() : "") + .apply(); + SbtSettings.ensureReadable(requireContext()); + } + }); + } + + private void updateClockOutlineThicknessLabel(TextView view, int thicknessDp) { + if (view != null) { + view.setText(getString(R.string.clock_outline_thickness_value, thicknessDp)); + } + } + private void updateCustomFormatVisibility(View customFormatContainer, boolean enabled) { if (customFormatContainer == null) { return; diff --git a/app/src/main/res/layout/fragment_clock.xml b/app/src/main/res/layout/fragment_clock.xml index dfdb1e3..2a72fd8 100644 --- a/app/src/main/res/layout/fragment_clock.xml +++ b/app/src/main/res/layout/fragment_clock.xml @@ -79,6 +79,71 @@ + + + + + + + + + + + + + + + + + + + Drawer date Tools and instructions Enable custom date/time pattern + Statusbar clock outline + Add outline + Thickness: %1$d dp + Outline colour, for example #000 ; #FFF Enable custom date/time pattern Enable custom date/time pattern Date/time patterns:\n\u2022 Uses Java SimpleDateFormat-style pattern letters\n\u2022 Common symbols: yyyy yy MMMM MMM MM EEEE EEE dd d HH hh mm ss a\n\nTags:\n\u2022 Supported tags: <i>, <u>, <small>, <big>, <font color=\'...\'>, <font face=\'...\'>\n\u2022 <b> is not supported\n\u2022 Custom font tags: <sans>, <serif>, <mono>, <condensed>\n\u2022 {/font} closes any open <font ...> tag\n\u2022 Opening a new colour or font face automatically closes the previous one