diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockLayoutTextFactory.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockLayoutTextFactory.java index 27f7686..64d5e78 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockLayoutTextFactory.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockLayoutTextFactory.java @@ -22,21 +22,30 @@ public final class ClockLayoutTextFactory { } public static Model build(TextView source, SbtSettings settings) { + return build(source, settings, source != null ? source.getCurrentTextColor() : 0); + } + + public static Model build(TextView source, SbtSettings settings, int referenceColor) { Context context = source != null ? source.getContext() : null; if (context == null || settings == null) { return Model.empty(); } - CacheKey cacheKey = cacheKey(context, source, settings); + CacheKey cacheKey = cacheKey(context, source, settings, referenceColor); CachedModel cached = MODEL_CACHE.get(source); if (cached != null && cached.key.equals(cacheKey)) { return cached.model; } - Model model = buildUncached(context, source, settings); + Model model = buildUncached(context, source, settings, referenceColor); MODEL_CACHE.put(source, new CachedModel(cacheKey, model)); return model; } - private static Model buildUncached(Context context, TextView source, SbtSettings settings) { + private static Model buildUncached( + Context context, + TextView source, + SbtSettings settings, + int referenceColor + ) { if (!hasCustomClockTextEnabled(settings)) { ArrayList rows = new ArrayList<>(); CharSequence text = source.getText(); @@ -49,7 +58,7 @@ public final class ClockLayoutTextFactory { && !settings.clockCustomFormat.isEmpty()) { ClockParsedPattern parsedPattern = PATTERN_PARSER.parse(settings.clockCustomFormat); ArrayList renderedRows = - TEXT_RENDERER.renderLayoutRows(context, parsedPattern, source.getCurrentTextColor()); + TEXT_RENDERER.renderLayoutRows(context, parsedPattern, referenceColor); ArrayList rows = new ArrayList<>(); StringBuilder description = new StringBuilder(); for (int i = 0; i < renderedRows.size(); i++) { @@ -73,7 +82,12 @@ public final class ClockLayoutTextFactory { return new Model(rows, text != null ? text.toString() : "", settings.clockShowSeconds); } - private static CacheKey cacheKey(Context context, TextView source, SbtSettings settings) { + private static CacheKey cacheKey( + Context context, + TextView source, + SbtSettings settings, + int referenceColor + ) { String pattern = settings.clockCustomFormat != null ? settings.clockCustomFormat : ""; boolean customPattern = settings.clockCustomFormatEnabled && !pattern.isEmpty(); boolean generatedText = hasCustomClockTextEnabled(settings); @@ -85,7 +99,7 @@ public final class ClockLayoutTextFactory { return new CacheKey( localeFor(context).toLanguageTag(), System.currentTimeMillis() / divisor, - source.getCurrentTextColor(), + referenceColor, settings.clockShowSeconds, settings.clockShowDatePrefix, customPattern, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java index 6f78d8c..a7d3a67 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java @@ -193,6 +193,16 @@ final class StockLayoutCanvasController { removeDebugOverlay(root); } hideTrackedUnlockedStockSurfaces(root); + UnlockedHosts unlockedHosts = unlockedHostsByRoot.get(root); + if (unlockedHosts != null) { + unlockedIconRenderController.refreshUnlockedAppearance( + root, + unlockedHosts.clockHost, + unlockedHosts.chipHost, + unlockedHosts.statusHost, + unlockedHosts.notificationHost, + activeScene); + } } } 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 bb2562e..2fa1bdd 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,5 +1,7 @@ package se.ajpanton.statusbartweak.runtime.render; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -47,7 +49,7 @@ final class ClockProxyRenderer { if (copyTextStyleIfChanged(placement.source, view)) { hostChanged = true; } - if (!String.valueOf(view.getText()).contentEquals(row.text)) { + if (!sameTextAndColorSpans(view.getText(), row.text)) { view.setText(row.text); hostChanged = true; } @@ -87,6 +89,31 @@ final class ClockProxyRenderer { } } + private static boolean sameTextAndColorSpans(CharSequence current, CharSequence desired) { + if (!String.valueOf(current).contentEquals(String.valueOf(desired))) { + return false; + } + return colorSpanSignature(current) == colorSpanSignature(desired); + } + + private static int colorSpanSignature(CharSequence text) { + if (!(text instanceof Spanned spanned)) { + return 0; + } + ForegroundColorSpan[] spans = spanned.getSpans(0, spanned.length(), ForegroundColorSpan.class); + if (spans == null || spans.length == 0) { + return 0; + } + int result = 17; + for (ForegroundColorSpan span : spans) { + result = 31 * result + span.getForegroundColor(); + result = 31 * result + spanned.getSpanStart(span); + result = 31 * result + spanned.getSpanEnd(span); + result = 31 * result + spanned.getSpanFlags(span); + } + return result; + } + void clear(ViewGroup host) { if (host == null) { return; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotAppearanceSignature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotAppearanceSignature.java new file mode 100644 index 0000000..1c02b8d --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotAppearanceSignature.java @@ -0,0 +1,131 @@ +package se.ajpanton.statusbartweak.runtime.render; + +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; + +final class SnapshotAppearanceSignature { + private static final int NO_COLOR = 0; + + private static final String[] COLOR_FIELD_NAMES = { + "mIconColor", + "mDecorColor", + "mStaticDrawableColor", + "mIconTint", + "mTint", + "mTintColor", + "mCurrentColor", + "mCurrentIconColor" + }; + + private SnapshotAppearanceSignature() { + } + + static int view(View view) { + if (view == null) { + return 0; + } + int result = 17; + result = 31 * result + java.util.Arrays.hashCode(view.getDrawableState()); + if (view instanceof TextView textView) { + result = 31 * result + textView.getCurrentTextColor(); + result = 31 * result + textView.getTextColors().hashCode(); + } + if (view instanceof ImageView imageView) { + result = 31 * result + drawable(imageView.getDrawable()); + result = 31 * result + colorStateList(imageView.getImageTintList()); + PorterDuff.Mode tintMode = imageView.getImageTintMode(); + result = 31 * result + (tintMode != null ? tintMode.hashCode() : 0); + ColorFilter colorFilter = imageView.getColorFilter(); + result = 31 * result + (colorFilter != null ? System.identityHashCode(colorFilter) : 0); + } + result = 31 * result + reflectedColor(view, COLOR_FIELD_NAMES); + return result; + } + + static int drawable(Drawable drawable) { + if (drawable == null) { + return 0; + } + int result = 17; + result = 31 * result + System.identityHashCode(drawable); + result = 31 * result + drawable.getLevel(); + result = 31 * result + drawable.getAlpha(); + result = 31 * result + java.util.Arrays.hashCode(drawable.getState()); + ColorFilter colorFilter = drawable.getColorFilter(); + result = 31 * result + (colorFilter != null ? System.identityHashCode(colorFilter) : 0); + return result; + } + + static int preferredColor(View view, int fallbackColor) { + if (view == null) { + return fallbackColor; + } + if (view instanceof TextView textView) { + return textView.getCurrentTextColor(); + } + if (view instanceof ImageView imageView) { + int tintColor = colorStateListColor(imageView.getImageTintList(), view, NO_COLOR); + if (isOpaqueColor(tintColor)) { + return tintColor; + } + } + int reflectedColor = reflectedColor(view, COLOR_FIELD_NAMES); + if (isOpaqueColor(reflectedColor)) { + return reflectedColor; + } + return fallbackColor; + } + + private static int reflectedColor(Object target, String[] fieldNames) { + for (String fieldName : fieldNames) { + Object value = ReflectionSupport.getFieldValue(target, fieldName); + int color = colorValue(value, NO_COLOR); + if (isOpaqueColor(color)) { + return color; + } + } + return NO_COLOR; + } + + private static int colorValue(Object value, int fallbackColor) { + if (value instanceof Integer integer) { + return integer; + } + if (value instanceof ColorStateList colorStateList) { + return colorStateList.getDefaultColor(); + } + return fallbackColor; + } + + private static int colorStateListColor(ColorStateList colorStateList, View view, int fallbackColor) { + if (colorStateList == null) { + return fallbackColor; + } + return colorStateList.getColorForState( + view != null ? view.getDrawableState() : null, + colorStateList.getDefaultColor()); + } + + private static boolean isOpaqueColor(int color) { + return Color.alpha(color) != 0; + } + + private static int colorStateList(ColorStateList colorStateList) { + if (colorStateList == null) { + return 0; + } + int result = 17; + result = 31 * result + colorStateList.getDefaultColor(); + result = 31 * result + (colorStateList.isStateful() ? 1 : 0); + result = 31 * result + colorStateList.hashCode(); + return result; + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java index af35c4c..f8d5f5e 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java @@ -1,6 +1,7 @@ package se.ajpanton.statusbartweak.runtime.render; import android.graphics.Bitmap; +import android.graphics.Color; import android.view.View; import android.widget.TextView; @@ -34,6 +35,7 @@ final class SnapshotPlacement { final String key; final boolean signal; final boolean overflowDot; + final int overflowDotColor; final HeldSignal heldSignal; final Bounds contentBounds; final int sourceOffsetX; @@ -47,12 +49,36 @@ final class SnapshotPlacement { HeldSignal heldSignal, Bounds contentBounds, int sourceOffsetX + ) { + this( + source, + bounds, + key, + signal, + overflowDot, + Color.WHITE, + heldSignal, + contentBounds, + sourceOffsetX); + } + + SnapshotPlacement( + SnapshotSource source, + Bounds bounds, + String key, + boolean signal, + boolean overflowDot, + int overflowDotColor, + HeldSignal heldSignal, + Bounds contentBounds, + int sourceOffsetX ) { this.source = source; this.bounds = bounds; this.key = key; this.signal = signal; this.overflowDot = overflowDot; + this.overflowDotColor = overflowDotColor; this.heldSignal = heldSignal; this.contentBounds = contentBounds; this.sourceOffsetX = sourceOffsetX; @@ -78,6 +104,10 @@ final class SnapshotPlacement { return overflowDot; } + int overflowDotColor() { + return overflowDotColor; + } + HeldSignal heldSignal() { return heldSignal; } @@ -102,6 +132,7 @@ record SnapshotRenderState( int height, boolean signal, boolean overflowDot, + int overflowDotColor, int sourceOffsetX, String heldSignalKey, int heldBitmapId, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java index 0d9d3a5..c6b2531 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java @@ -176,7 +176,7 @@ final class SnapshotRenderer { || !desiredState.equals(statesByProxy.get(proxy)); if (bitmapChanged) { if (placement.overflowDot()) { - drawOverflowDot(bitmap); + drawOverflowDot(bitmap, placement.overflowDotColor()); } else if (placement.heldSignal() != null) { drawHeldSignal(placement.heldSignal(), bitmap); } else if (placement.source() != null) { @@ -471,6 +471,7 @@ final class SnapshotRenderer { bounds.height, placement.signal(), placement.overflowDot(), + placement.overflowDotColor(), placement.sourceOffsetX(), placement.heldSignal() != null ? placement.heldSignal().key : "", placement.heldSignal() != null ? System.identityHashCode(placement.heldSignal().bitmap) : 0, @@ -493,6 +494,7 @@ final class SnapshotRenderer { CharSequence contentDescription = view.getContentDescription(); result = 31 * result + (contentDescription != null ? contentDescription.toString().hashCode() : 0); result = 31 * result + java.util.Arrays.hashCode(view.getDrawableState()); + result = 31 * result + SnapshotAppearanceSignature.view(view); if (view instanceof TextView textView) { CharSequence text = textView.getText(); result = 31 * result + (text != null ? text.toString().hashCode() : 0); @@ -500,7 +502,7 @@ final class SnapshotRenderer { result = 31 * result + Float.floatToIntBits(textView.getTextSize()); } if (view instanceof ImageView imageView) { - result = 31 * result + drawableSignature(imageView.getDrawable()); + result = 31 * result + SnapshotAppearanceSignature.drawable(imageView.getDrawable()); } if (view instanceof ViewGroup group) { result = 31 * result + group.getChildCount(); @@ -516,10 +518,7 @@ final class SnapshotRenderer { return 0; } int result = 17; - result = 31 * result + System.identityHashCode(drawable); - result = 31 * result + drawable.getLevel(); - result = 31 * result + drawable.getAlpha(); - result = 31 * result + java.util.Arrays.hashCode(drawable.getState()); + result = 31 * result + SnapshotAppearanceSignature.drawable(drawable); Rect bounds = drawable.getBounds(); result = 31 * result + bounds.left; result = 31 * result + bounds.top; @@ -570,7 +569,7 @@ final class SnapshotRenderer { canvas.drawBitmap(heldSignal.bitmap, src, dst, null); } - private void drawOverflowDot(Bitmap bitmap) { + private void drawOverflowDot(Bitmap bitmap, int color) { bitmap.eraseColor(Color.TRANSPARENT); int base = Math.min(bitmap.getWidth(), bitmap.getHeight()); if (base <= 0) { @@ -578,7 +577,7 @@ final class SnapshotRenderer { } float radius = Math.max(1f, base * 0.16f); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setColor(Color.WHITE); + paint.setColor(color); Canvas canvas = new Canvas(bitmap); canvas.drawCircle(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f, radius, paint); } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedChipPlacementController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedChipPlacementController.java index 4b242e6..5adc8a8 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedChipPlacementController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedChipPlacementController.java @@ -190,6 +190,7 @@ final class UnlockedChipPlacementController { placement.key, false, false, + placement.overflowDotColor, null, placement.contentBounds, placement.sourceOffsetX)); @@ -211,6 +212,7 @@ final class UnlockedChipPlacementController { placement.key, placement.signal, placement.overflowDot, + placement.overflowDotColor, placement.heldSignal, placement.contentBounds, placement.sourceOffsetX)); 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 db476c5..53dfd23 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 @@ -34,6 +34,7 @@ public final class UnlockedIconSnapshotRenderController { private final WeakHashMap lastLayoutPlanByRoot = new WeakHashMap<>(); private final WeakHashMap lastClockGeometryByRoot = new WeakHashMap<>(); private final WeakHashMap lastClockSourceGeometryByRoot = new WeakHashMap<>(); + private final WeakHashMap lastClockAppearanceByRoot = new WeakHashMap<>(); public UnlockedIconSnapshotRenderController() { statusRenderer = new SnapshotRenderer(true, true, false); @@ -164,6 +165,8 @@ public final class UnlockedIconSnapshotRenderController { lastLayoutPlanByRoot.put(root, layoutPlan); lastClockGeometryByRoot.put(root, currentClockGeometry != null ? currentClockGeometry : 0); lastClockSourceGeometryByRoot.put(root, currentClockSourceGeometry); + layoutPlan = withCurrentDotColors(layoutPlan, collectedIcons); + lastLayoutPlanByRoot.put(root, layoutPlan); if (settings.clockEnabledUnlocked && layoutPlan.clockPlacement != null) { clockRenderer.render(clockHost, layoutPlan.clockPlacement); } else { @@ -187,6 +190,47 @@ public final class UnlockedIconSnapshotRenderController { return new RenderResult(true, stockSourcesChanged, chipSourcesChanged); } + public void refreshUnlockedAppearance( + View root, + ViewGroup clockHost, + ViewGroup chipHost, + ViewGroup statusHost, + ViewGroup notificationHost, + SceneKey scene + ) { + if (root == null || scene == null || !scene.isUnlocked() || !root.isAttachedToWindow()) { + return; + } + LayoutPlan layoutPlan = lastLayoutPlanByRoot.get(root); + if (layoutPlan == null) { + return; + } + SbtSettings settings = RuntimeSettingsCache.get(root.getContext()); + CollectedIcons collectedIcons = lastCollectedIconsByRoot.get(root); + LayoutPlan updatedPlan = withCurrentDotColors(layoutPlan, collectedIcons); + if (updatedPlan != layoutPlan) { + lastLayoutPlanByRoot.put(root, updatedPlan); + layoutPlan = updatedPlan; + } + if (settings.layoutChipEnabledUnlocked && layoutPlan.chipPlacements != null) { + chipRenderer.renderPlacements(chipHost, layoutPlan.chipPlacements); + } + if (settings.layoutStatusEnabledUnlocked && layoutPlan.statusPlacements != null) { + statusRenderer.renderPlacements(statusHost, layoutPlan.statusPlacements); + } + if (settings.layoutNotifEnabledUnlocked && layoutPlan.notificationPlacements != null) { + notificationRenderer.renderPlacements(notificationHost, layoutPlan.notificationPlacements); + } + updatedPlan = withCurrentClockAppearance(root, layoutPlan, collectedIcons, settings); + if (updatedPlan != layoutPlan) { + lastLayoutPlanByRoot.put(root, updatedPlan); + layoutPlan = updatedPlan; + } + if (settings.clockEnabledUnlocked && layoutPlan.clockPlacement != null) { + clockRenderer.render(clockHost, layoutPlan.clockPlacement); + } + } + private void layoutHostToRoot(View root, ViewGroup host) { if (root == null || host == null || root.getWidth() <= 0 || root.getHeight() <= 0) { return; @@ -205,6 +249,180 @@ public final class UnlockedIconSnapshotRenderController { host.layout(0, 0, root.getWidth(), root.getHeight()); } + private LayoutPlan withCurrentDotColors(LayoutPlan plan, CollectedIcons icons) { + if (plan == null) { + return null; + } + ArrayList status = withCurrentDotColors( + plan.statusPlacements, + icons != null ? icons.statusIcons : null); + ArrayList notifications = withCurrentDotColors( + plan.notificationPlacements, + icons != null ? icons.notificationIcons : null); + if (status == plan.statusPlacements && notifications == plan.notificationPlacements) { + return plan; + } + return new LayoutPlan( + plan.clockPlacement, + plan.chipPlacements, + status, + notifications); + } + + private ArrayList withCurrentDotColors( + ArrayList placements, + ArrayList fallbackSources + ) { + if (placements == null || placements.isEmpty()) { + return placements; + } + int color = dotColor(placements, fallbackSources); + if (color == 0) { + return placements; + } + ArrayList updated = null; + for (int i = 0; i < placements.size(); i++) { + SnapshotPlacement placement = placements.get(i); + if (!placement.overflowDot || placement.overflowDotColor == color) { + continue; + } + if (updated == null) { + updated = new ArrayList<>(placements); + } + updated.set(i, new SnapshotPlacement( + placement.source, + placement.bounds, + placement.key, + placement.signal, + placement.overflowDot, + color, + placement.heldSignal, + placement.contentBounds, + placement.sourceOffsetX)); + } + return updated != null ? updated : placements; + } + + private int dotColor( + ArrayList placements, + ArrayList fallbackSources + ) { + for (SnapshotPlacement placement : placements) { + if (placement != null && placement.source != null) { + int color = sourceColor(placement.source); + if (color != 0) { + return color; + } + } + } + if (fallbackSources != null) { + for (SnapshotSource source : fallbackSources) { + int color = sourceColor(source); + if (color != 0) { + return color; + } + } + } + return 0; + } + + private int sourceColor(SnapshotSource source) { + if (source == null || source.view() == null) { + return 0; + } + int color = SnapshotAppearanceSignature.preferredColor( + source.view(), + android.graphics.Color.TRANSPARENT); + return android.graphics.Color.alpha(color) != 0 ? color : 0; + } + + private LayoutPlan withCurrentClockAppearance( + View root, + LayoutPlan plan, + CollectedIcons icons, + SbtSettings settings + ) { + if (root == null + || plan == null + || plan.clockPlacement == null + || !settings.clockEnabledUnlocked) { + return plan; + } + ClockPlacement previous = plan.clockPlacement; + int referenceColor = clockReferenceColor(previous, icons); + int signature = clockAppearanceSignature(previous, referenceColor, settings); + Integer lastSignature = lastClockAppearanceByRoot.get(root); + if (lastSignature != null && lastSignature == signature) { + return plan; + } + ClockPlacement updatedClock = clockPlacementWithTextFromModel( + previous, + previous.source, + settings, + referenceColor); + lastClockAppearanceByRoot.put(root, signature); + if (updatedClock == null) { + return plan; + } + return plan.withClockPlacement(updatedClock); + } + + private int clockReferenceColor(ClockPlacement placement, CollectedIcons icons) { + if (icons != null && icons.statusIcons != null) { + for (SnapshotSource source : icons.statusIcons) { + int color = sourceColor(source); + if (color != 0) { + return color; + } + } + } + if (icons != null && icons.notificationIcons != null) { + for (SnapshotSource source : icons.notificationIcons) { + int color = sourceColor(source); + if (color != 0) { + return color; + } + } + } + if (placement != null && placement.source != null) { + return placement.source.getCurrentTextColor(); + } + return android.graphics.Color.WHITE; + } + + private int clockAppearanceSignature( + ClockPlacement placement, + int referenceColor, + SbtSettings settings + ) { + int result = 17; + result = 31 * result + referenceColor; + result = 31 * result + clockTextSettingsSignature(settings); + if (placement != null && placement.source != null) { + result = 31 * result + placement.source.getCurrentTextColor(); + } + if (placement != null && placement.rows != null) { + result = 31 * result + placement.rows.size(); + for (ClockRow row : placement.rows) { + result = 31 * result + row.rowIndex; + result = 31 * result + row.segment.ordinal(); + result = 31 * result + (row.text != null ? row.text.toString().hashCode() : 0); + } + } + return result; + } + + private int clockTextSettingsSignature(SbtSettings settings) { + if (settings == null) { + return 0; + } + return java.util.Objects.hash( + settings.clockShowSeconds, + settings.clockShowDatePrefix, + settings.clockCustomFormatEnabled, + settings.clockCustomFormat); + } + public void clearAll() { clockRenderer.clearAll(); statusRenderer.clearAll(); @@ -218,6 +436,7 @@ public final class UnlockedIconSnapshotRenderController { lastLayoutPlanByRoot.clear(); lastClockGeometryByRoot.clear(); lastClockSourceGeometryByRoot.clear(); + lastClockAppearanceByRoot.clear(); chipPlacementController.clear(); } @@ -228,6 +447,7 @@ public final class UnlockedIconSnapshotRenderController { lastCollectedIconsByRoot.clear(); lastLayoutPlanByRoot.clear(); lastClockGeometryByRoot.clear(); + lastClockAppearanceByRoot.clear(); } private boolean isClockOnlySignatureChange( @@ -368,6 +588,19 @@ public final class UnlockedIconSnapshotRenderController { ClockPlacement previousPlacement, TextView source, SbtSettings settings + ) { + return clockPlacementWithTextFromModel( + previousPlacement, + source, + settings, + source != null ? source.getCurrentTextColor() : 0); + } + + private ClockPlacement clockPlacementWithTextFromModel( + ClockPlacement previousPlacement, + TextView source, + SbtSettings settings, + int referenceColor ) { if (previousPlacement == null || previousPlacement.rows == null @@ -375,7 +608,7 @@ public final class UnlockedIconSnapshotRenderController { || settings == null) { return null; } - ClockLayoutTextFactory.Model model = ClockLayoutTextFactory.build(source, settings); + ClockLayoutTextFactory.Model model = ClockLayoutTextFactory.build(source, settings, referenceColor); if (model == null || model.isEmpty()) { return null; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutBand.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutBand.java index e1b5e27..4d89feb 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutBand.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutBand.java @@ -493,11 +493,40 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand { SnapshotKeys.OVERFLOW_DOT + ":" + kind.name().toLowerCase(java.util.Locale.ROOT), false, true, + dotColor(), null, new Bounds(0, 0, Math.max(1, width), Math.max(1, height)), 0); } + private int dotColor() { + for (SnapshotPlacement placement : activePlacements()) { + int color = sourceColor(placement); + if (color != 0) { + return color; + } + } + if (rawPlacements != null) { + for (SnapshotPlacement placement : rawPlacements) { + int color = sourceColor(placement); + if (color != 0) { + return color; + } + } + } + return android.graphics.Color.WHITE; + } + + private int sourceColor(SnapshotPlacement placement) { + if (placement == null || placement.source == null || placement.source.view() == null) { + return 0; + } + int color = SnapshotAppearanceSignature.preferredColor( + placement.source.view(), + android.graphics.Color.TRANSPARENT); + return android.graphics.Color.alpha(color) != 0 ? color : 0; + } + private SnapshotPlacement emptyIconContainerPlacement() { int height = Math.max(1, dotHeight(activePlacements())); return new SnapshotPlacement( @@ -548,6 +577,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand { placement.key, placement.signal, placement.overflowDot, + placement.overflowDotColor, placement.heldSignal, clippedContent, placement.sourceOffsetX + removeLeft); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java index 287a6ba..e71a211 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java @@ -218,6 +218,7 @@ final class UnlockedLayoutPlanner { placement.key, placement.signal, placement.overflowDot, + placement.overflowDotColor, placement.heldSignal, placement.contentBounds, placement.sourceOffsetX)); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java index d2ae3aa..da4bb0d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java @@ -2,7 +2,6 @@ package se.ajpanton.statusbartweak.runtime.render; import android.content.res.Resources; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; @@ -636,19 +635,18 @@ final class UnlockedStockSourceCollector { : 0); } if (view instanceof ImageView imageView) { - result = 31 * result + drawableSignature(imageView.getDrawable()); + result = 31 * result + drawableLayoutSignature(imageView.getDrawable()); } return result; } - private static int drawableSignature(Drawable drawable) { + private static int drawableLayoutSignature(android.graphics.drawable.Drawable drawable) { if (drawable == null) { return 0; } int result = 17; result = 31 * result + System.identityHashCode(drawable); result = 31 * result + drawable.getLevel(); - result = 31 * result + drawable.getAlpha(); result = 31 * result + java.util.Arrays.hashCode(drawable.getState()); Rect bounds = drawable.getBounds(); result = 31 * result + bounds.left;