Port packed layout solver and overhaul layout settings

This commit is contained in:
ajp_anton
2026-05-13 03:06:17 +00:00
parent ba3b92d4d0
commit 06cc2e21d9
29 changed files with 5241 additions and 1302 deletions
@@ -423,7 +423,8 @@ final class StockLayoutCanvasController {
}
private void hideOrDiscoverLockedStatusBarIconSurfaces(View root) {
if (!hideTrackedLockedStatusBarIconSurfaces(root)) {
hideTrackedLockedStatusBarIconSurfaces(root);
if (root != null) {
trackAndHideLockedStatusBarIconSurfaces(root);
}
}
@@ -17,6 +17,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
public final class DebugBoundsOverlayController {
@@ -55,37 +57,31 @@ public final class DebugBoundsOverlayController {
addCutoutSpecs(root, specs);
}
if (layoutEnabled && settings.debugVisualizeNotificationContainer) {
Bounds bounds = unionHostChildren(
addHostContainerSpecs(
root,
parent,
OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST,
true);
if (bounds != null) {
specs.add(new OverlaySpec(bounds, COLOR_NOTIFICATION));
}
COLOR_NOTIFICATION,
specs);
}
if (layoutEnabled && settings.debugVisualizeStatusContainer) {
Bounds bounds = unionHostChildren(
addHostContainerSpecs(
root,
parent,
OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST,
false);
if (bounds != null) {
specs.add(new OverlaySpec(bounds, COLOR_STATUS));
}
COLOR_STATUS,
specs);
}
if (layoutEnabled && settings.debugVisualizeClockContainer) {
addHostChildSpecs(root, parent, TAG_CLOCK_HOST, COLOR_CLOCK, specs);
}
if (layoutEnabled && settings.debugVisualizeChipContainer) {
Bounds bounds = unionHostChildren(
addHostContainerSpecs(
root,
parent,
OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST,
false);
if (bounds != null) {
specs.add(new OverlaySpec(bounds, COLOR_CHIP));
}
COLOR_CHIP,
specs);
}
if (specs.isEmpty()) {
removeOverlay(parent);
@@ -144,15 +140,25 @@ public final class DebugBoundsOverlayController {
return new Bounds(left, top, right - left, bottom - top);
}
private Bounds unionHostChildren(
private void addHostContainerSpecs(
View root,
ViewGroup parent,
String hostTag,
boolean stableRegularIconSlots
int color,
ArrayList<OverlaySpec> specs
) {
View host = findDirectTaggedChild(parent, hostTag);
if (!(host instanceof ViewGroup group)) {
return null;
return;
}
ArrayList<Bounds> containerBounds = taggedContainerBounds(host);
if (!containerBounds.isEmpty()) {
for (Bounds bounds : containerBounds) {
if (bounds.width > 0 && bounds.height > 0) {
specs.add(new OverlaySpec(bounds, color));
}
}
return;
}
Bounds union = null;
for (int i = 0; i < group.getChildCount(); i++) {
@@ -160,13 +166,30 @@ public final class DebugBoundsOverlayController {
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
Bounds bounds = overlayBoundsRelativeTo(root, child, stableRegularIconSlots);
Bounds bounds = overlayBoundsRelativeTo(root, child, false);
if (bounds == null || bounds.width <= 0 || bounds.height <= 0) {
continue;
}
union = union == null ? bounds : union.union(bounds);
}
return union;
if (union != null) {
specs.add(new OverlaySpec(union, color));
}
}
@SuppressWarnings("unchecked")
private ArrayList<Bounds> taggedContainerBounds(View host) {
Object tag = host != null ? host.getTag(R.id.sbt_debug_container_bounds) : null;
if (!(tag instanceof ArrayList<?> values)) {
return new ArrayList<>();
}
ArrayList<Bounds> out = new ArrayList<>();
for (Object value : values) {
if (value instanceof StatusBarLayoutSolver.Bounds bounds) {
out.add(new Bounds(bounds.left, bounds.top, bounds.width, bounds.height));
}
}
return out;
}
private boolean isOverflowDot(View view) {
@@ -577,11 +577,10 @@ final class SnapshotRenderer {
private void drawOverflowDot(Bitmap bitmap, int color) {
bitmap.eraseColor(Color.TRANSPARENT);
int base = Math.min(bitmap.getWidth(), bitmap.getHeight());
if (base <= 0) {
if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
return;
}
float radius = Math.max(1f, base * 0.16f);
float radius = Math.max(1f, Math.min(bitmap.getWidth() / 2f, bitmap.getHeight() * 0.16f));
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
Canvas canvas = new Canvas(bitmap);
@@ -164,6 +164,32 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
return true;
}
void applyPackedContinuousWidth(int solvedWidth, AnchorSide wallSide) {
if (!canClipContinuously()) {
return;
}
clipStartPx = 0;
clipEndPx = 0;
int amount = Math.max(0, naturalWidth() - Math.max(0, solvedWidth));
if (amount > 0) {
clipContinuous(wallSide, amount);
}
}
void applyPackedIconState(int firstIconIndex, int visibleIcons, boolean dot, int solvedWidth) {
if (kind != LayoutItemKind.NOTIFICATIONS && kind != LayoutItemKind.STATUS) {
return;
}
int targetVisible = Math.max(0, visibleIcons);
placements = iconSlice(rawPlacements, firstIconIndex, targetVisible);
if (dot) {
replaceOverflowDot(dotWidthForSolvedState(solvedWidth), dotHeight(placements));
}
if (targetVisible == 0 && !dot && placements.isEmpty()) {
placements.add(emptyIconContainerPlacement());
}
}
@Override
protected int wallDotSlack(AnchorSide side) {
SnapshotPlacement placement = wallPlacement(side);
@@ -238,6 +264,25 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
return true;
}
private void replaceOverflowDot(int width, int height) {
if (placements == null) {
placements = new ArrayList<>();
}
ArrayList<SnapshotPlacement> withoutDot = new ArrayList<>();
for (SnapshotPlacement placement : placements) {
if (!placement.overflowDot) {
withoutDot.add(placement);
}
}
SnapshotPlacement dotPlacement = dotPlacement(width, height);
if (kind == LayoutItemKind.STATUS) {
withoutDot.add(0, dotPlacement);
} else {
withoutDot.add(dotPlacement);
}
placements = withoutDot;
}
@Override
protected Object captureState() {
return new LayoutBandState(placements == null, new ArrayList<>(activePlacements()));
@@ -450,6 +495,46 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
&& (placement.key == null || !placement.key.startsWith(SnapshotKeys.EMPTY_ICON_CONTAINER));
}
private ArrayList<SnapshotPlacement> iconSlice(
ArrayList<SnapshotPlacement> source,
int firstIconIndex,
int count
) {
ArrayList<SnapshotPlacement> result = new ArrayList<>();
if (source == null || count <= 0) {
return result;
}
int skipped = 0;
int start = Math.max(0, firstIconIndex);
for (SnapshotPlacement placement : source) {
if (!isShrinkableIcon(placement)) {
continue;
}
if (skipped < start) {
skipped++;
continue;
}
result.add(placement);
if (result.size() >= count) {
break;
}
}
return result;
}
private int shrinkableIconCount(ArrayList<SnapshotPlacement> icons) {
int count = 0;
if (icons == null) {
return 0;
}
for (SnapshotPlacement icon : icons) {
if (isShrinkableIcon(icon)) {
count++;
}
}
return count;
}
private boolean hasOverflowDot(ArrayList<SnapshotPlacement> icons) {
for (SnapshotPlacement icon : icons) {
if (icon.overflowDot) {
@@ -476,6 +561,18 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
return 0;
}
private int dotWidthForSolvedState(int solvedWidth) {
int visibleWidth = 0;
for (SnapshotPlacement placement : activePlacements()) {
if (!placement.overflowDot) {
visibleWidth += collisionBounds(placement).width;
}
}
int solvedDotWidth = Math.max(1, solvedWidth - visibleWidth);
int fallbackWidth = Math.max(1, dotWidth(activePlacements()));
return Math.min(fallbackWidth, solvedDotWidth);
}
private int dotHeight(ArrayList<SnapshotPlacement> icons) {
if (!icons.isEmpty()) {
return icons.get(0).bounds.height;
@@ -5,19 +5,18 @@ import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Anchor;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.AnchorKind;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.AnchorSide;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.LayoutEdges;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.LayoutItemKind;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.LayoutPosition;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.PlacedBand;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Region;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.RegionPlan;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.SplitRegion;
import se.ajpanton.statusbartweak.runtime.layoutsolver.PackedStatusBarLayoutSolver;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class UnlockedLayoutPlanner {
@@ -81,106 +80,68 @@ final class UnlockedLayoutPlanner {
false));
}
}
if (settings.layoutStatusEnabledUnlocked) {
if (settings.layoutStatusUnlockedCount > 0) {
ArrayList<SnapshotPlacement> rawPlacements =
statusRenderer.rawPlacements(root, collectedIcons.statusIcons);
int representativeHeight = representativeHeight(rawPlacements);
for (int i = 0; i < settings.layoutStatusUnlockedCount; i++) {
ArrayList<SnapshotPlacement> placements =
offsetPlacements(
rawPlacements,
offsetScaler.status(
settings.layoutStatusVerticalOffsetPx,
representativeHeight(rawPlacements)));
intAt(settings.layoutStatusVerticalOffsetsPx, i, settings.layoutStatusVerticalOffsetPx),
representativeHeight));
if (!placements.isEmpty()) {
bands.add(UnlockedLayoutBand.icons(
LayoutItemKind.STATUS,
statusHost,
statusRenderer,
placements,
positionFor(settings.layoutStatusPosition),
settings.layoutStatusMiddleAutoNearestSide,
sideFor(settings.layoutStatusMiddleSide),
settings.layoutStatusShowDotIfTruncated));
positionFor(stringAt(settings.layoutStatusPositions, i, settings.layoutStatusPosition)),
booleanAt(
settings.layoutStatusMiddleAutoNearestSides,
i,
settings.layoutStatusMiddleAutoNearestSide),
sideFor(stringAt(settings.layoutStatusMiddleSides, i, settings.layoutStatusMiddleSide)),
true));
}
}
if (settings.layoutNotifEnabledUnlocked) {
}
if (settings.layoutNotifUnlockedCount > 0) {
ArrayList<SnapshotPlacement> rawPlacements =
notificationRenderer.rawPlacements(root, collectedIcons.notificationIcons);
int representativeHeight = representativeHeight(rawPlacements);
for (int i = 0; i < settings.layoutNotifUnlockedCount; i++) {
ArrayList<SnapshotPlacement> placements =
offsetPlacements(
rawPlacements,
offsetScaler.notification(
settings.layoutNotifVerticalOffsetPx,
representativeHeight(rawPlacements)));
intAt(settings.layoutNotifVerticalOffsetsPx, i, settings.layoutNotifVerticalOffsetPx),
representativeHeight));
if (!placements.isEmpty()) {
bands.add(UnlockedLayoutBand.icons(
LayoutItemKind.NOTIFICATIONS,
notificationHost,
notificationRenderer,
placements,
positionFor(settings.layoutNotifPosition),
settings.layoutNotifMiddleAutoNearestSide,
sideFor(settings.layoutNotifMiddleSide),
settings.layoutNotifShowDotIfTruncated));
positionFor(stringAt(settings.layoutNotifPositions, i, settings.layoutNotifPosition)),
booleanAt(
settings.layoutNotifMiddleAutoNearestSides,
i,
settings.layoutNotifMiddleAutoNearestSide),
sideFor(stringAt(settings.layoutNotifMiddleSides, i, settings.layoutNotifMiddleSide)),
true));
}
}
}
if (bands.isEmpty()) {
return new LayoutPlan(null, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
ArrayList<PlacedBand> placedBands = new ArrayList<>();
ArrayList<LayoutItemKind> itemOrder = orderedKinds(settings.layoutItemOrder);
splitClockIfNeeded(root, bands, itemOrder, clockCutoutBounds, settings);
LayoutEdges edges = layoutEdges(root, settings, anchors);
int itemPadding = Math.max(0, settings.layoutItemPaddingPx);
RegionPlan<UnlockedLayoutBand> regionPlan = StatusBarLayoutSolver.planRegions(
root.getWidth(),
edges,
bands,
itemOrder,
layoutCutoutBounds,
itemPadding);
for (Region<UnlockedLayoutBand> region : regionPlan.regions()) {
StatusBarLayoutSolver.truncateToFit(
region.bands,
region.width(),
regionPlan.middleSide(),
settings.layoutNotifSortOrder,
itemPadding);
}
StatusBarLayoutSolver.placeStackedBands(
StatusBarLayoutSolver.bandsForPosition(bands, LayoutPosition.LEFT, itemOrder),
new Anchor(edges.left(), AnchorSide.RIGHT, AnchorKind.WALL),
placedBands,
settings.layoutNotifSortOrder,
false,
itemPadding);
StatusBarLayoutSolver.placeStackedBands(
StatusBarLayoutSolver.bandsForPosition(bands, LayoutPosition.RIGHT, itemOrder),
new Anchor(edges.right(), AnchorSide.LEFT, AnchorKind.WALL),
placedBands,
settings.layoutNotifSortOrder,
false,
itemPadding);
ArrayList<UnlockedLayoutBand> middleBands = StatusBarLayoutSolver.middleBandsForPlacement(
bands,
itemOrder,
regionPlan.middleSide());
if (!middleBands.isEmpty()) {
Anchor anchor = StatusBarLayoutSolver.anchorForMiddle(
root.getWidth(),
middleBands,
placedBands,
layoutCutoutBounds,
regionPlan.middleSide(),
itemPadding);
StatusBarLayoutSolver.placeStackedBands(
middleBands,
anchor,
placedBands,
settings.layoutNotifSortOrder,
true,
itemPadding);
}
solveWithPackedLayout(root, edges, bands, layoutCutoutBounds, settings, itemPadding);
ArrayList<SnapshotPlacement> statusPlacements = new ArrayList<>();
ArrayList<SnapshotPlacement> notificationPlacements = new ArrayList<>();
@@ -204,6 +165,266 @@ final class UnlockedLayoutPlanner {
notificationPlacements);
}
private void solveWithPackedLayout(
View root,
LayoutEdges edges,
ArrayList<UnlockedLayoutBand> bands,
Bounds cutoutBounds,
SbtSettings settings,
int itemPadding
) {
ArrayList<PackedStatusBarLayoutSolver.LayoutItem> items = new ArrayList<>();
ArrayList<UnlockedLayoutBand> itemBands = new ArrayList<>();
for (int i = 0; i < bands.size(); i++) {
UnlockedLayoutBand band = bands.get(i);
AnchorSide side = packedAnchorSide(band);
band.prepareForSide(side, settings.layoutNotifSortOrder);
PackedStatusBarLayoutSolver.LayoutItem item = packedItemForBand(band, i);
items.add(item);
itemBands.add(band);
}
configurePackedIconGroups(items);
int dx = edges.left();
int availableWidth = Math.max(0, edges.right() - edges.left());
Bounds relativeCutout = relativeCutout(cutoutBounds, edges);
PackedStatusBarLayoutSolver.Spec spec = new PackedStatusBarLayoutSolver.Spec(
availableWidth,
relativeCutout != null ? relativeCutout.left : 0,
relativeCutout != null ? relativeCutout.width : 0,
itemPadding,
packedShrinkOrder());
PackedStatusBarLayoutSolver.solve(spec, items);
for (PackedStatusBarLayoutSolver.LayoutItem item : items) {
item.x = Math.max(0, Math.min(availableWidth, item.x)) + dx;
}
for (int i = 0; i < items.size(); i++) {
UnlockedLayoutBand band = itemBands.get(i);
PackedStatusBarLayoutSolver.LayoutItem item = items.get(i);
if (band.kind == LayoutItemKind.NOTIFICATIONS || band.kind == LayoutItemKind.STATUS) {
band.applyPackedIconState(
item.firstVisibleIconIndex,
item.visibleIcons,
item.dot,
Math.round((float) item.width()));
} else if (band.kind == LayoutItemKind.CHIP || band.kind == LayoutItemKind.CLOCK) {
band.applyPackedContinuousWidth(Math.round((float) item.width()), packedAnchorSide(band));
}
band.place(Math.round((float) item.x));
}
updateDebugContainerBounds(itemBands);
}
private void updateDebugContainerBounds(ArrayList<UnlockedLayoutBand> bands) {
LinkedHashMap<ViewGroup, ArrayList<Bounds>> boundsByHost = new LinkedHashMap<>();
for (UnlockedLayoutBand band : bands) {
if (band.host == null
|| band.kind == LayoutItemKind.CLOCK) {
continue;
}
ArrayList<Bounds> hostBounds = boundsByHost.computeIfAbsent(
band.host,
ignored -> new ArrayList<>());
if (band.placedBounds == null
|| band.placedBounds.width <= 0
|| band.placedBounds.height <= 0) {
continue;
}
hostBounds.add(band.placedBounds);
}
for (Map.Entry<ViewGroup, ArrayList<Bounds>> entry : boundsByHost.entrySet()) {
entry.getKey().setTag(R.id.sbt_debug_container_bounds, entry.getValue());
}
}
private Bounds relativeCutout(Bounds cutoutBounds, LayoutEdges edges) {
if (cutoutBounds == null || edges == null) {
return null;
}
int left = Math.max(edges.left(), cutoutBounds.left);
int right = Math.min(edges.right(), cutoutBounds.left + cutoutBounds.width);
if (right <= left) {
return null;
}
return new Bounds(left - edges.left(), cutoutBounds.top, right - left, cutoutBounds.height);
}
private PackedStatusBarLayoutSolver.LayoutItem packedItemForBand(UnlockedLayoutBand band, int index) {
ArrayList<PackedStatusBarLayoutSolver.Box> boxes = new ArrayList<>();
for (Bounds box : band.collisionBoxes()) {
boxes.add(new PackedStatusBarLayoutSolver.Box(box.left, box.top, box.width, box.height));
}
if (boxes.isEmpty()) {
boxes.add(new PackedStatusBarLayoutSolver.Box(0, band.top(), band.width(), band.height()));
}
PackedStatusBarLayoutSolver.LayoutItem item = new PackedStatusBarLayoutSolver.LayoutItem(
packedName(band, index),
packedKind(band.kind),
packedPosition(band.position),
packedFallback(band),
boxes);
if (band.kind == LayoutItemKind.NOTIFICATIONS || band.kind == LayoutItemKind.STATUS) {
item.iconGroup = packedKind(band.kind).toLowerCase(java.util.Locale.ROOT);
item.iconCount = band.rawPlacements != null ? band.rawPlacements.size() : 0;
item.visibleIcons = item.iconCount;
item.poolRemaining = item.iconCount;
item.iconWidth = representativeIconWidth(band);
item.iconWidths = iconWidths(band);
item.dotWidth = Math.max(1, Math.round(band.height() * 0.75f));
item.compactDotWidth = representativeCompactDotWidth(band);
item.allowZero = false;
item.allowDot = true;
item.direction = band.kind == LayoutItemKind.STATUS
? PackedStatusBarLayoutSolver.Direction.LEFT
: PackedStatusBarLayoutSolver.Direction.RIGHT;
item.widthLimit = sumIconWidths(item.iconWidths, item.iconCount, item.iconWidth);
}
item.y = 0;
return item;
}
private int representativeIconWidth(UnlockedLayoutBand band) {
ArrayList<SnapshotPlacement> placements = band.rawPlacements;
if (placements == null || placements.isEmpty()) {
return Math.max(1, band.width());
}
return Math.max(1, placements.get(0).bounds.width);
}
private ArrayList<Integer> iconWidths(UnlockedLayoutBand band) {
ArrayList<Integer> widths = new ArrayList<>();
ArrayList<SnapshotPlacement> placements = band.rawPlacements;
if (placements == null) {
return widths;
}
for (SnapshotPlacement placement : placements) {
Bounds bounds;
if (band.kind == LayoutItemKind.NOTIFICATIONS) {
bounds = placement != null ? placement.bounds : null;
} else {
bounds = placement != null ? placement.contentBounds : null;
if (bounds == null || bounds.width < 0) {
bounds = placement != null ? placement.bounds : null;
}
}
widths.add(Math.max(0, bounds != null ? bounds.width : 0));
}
return widths;
}
private double sumIconWidths(ArrayList<Integer> widths, int count, int fallbackWidth) {
double total = 0;
for (int i = 0; i < Math.max(0, count); i++) {
if (widths != null && i < widths.size()) {
total += Math.max(0, widths.get(i));
} else {
total += Math.max(0, fallbackWidth);
}
}
return total;
}
private int representativeCompactDotWidth(UnlockedLayoutBand band) {
ArrayList<SnapshotPlacement> placements = band.rawPlacements;
if (placements == null || placements.isEmpty()) {
return Math.max(1, band.width());
}
Bounds bounds = placements.get(0).bounds;
int slotWidth = Math.max(1, bounds.width);
int base = Math.max(1, Math.min(slotWidth, bounds.height));
int radius = Math.max(1, Math.round(base * 0.16f));
int visualWidth = Math.min(slotWidth, radius * 2 + 2);
return Math.max(visualWidth, slotWidth - Math.max(0, (slotWidth - visualWidth) / 2));
}
private String stringAt(String[] values, int index, String fallback) {
return values != null && index >= 0 && index < values.length && values[index] != null
? values[index]
: fallback;
}
private boolean booleanAt(boolean[] values, int index, boolean fallback) {
return values != null && index >= 0 && index < values.length ? values[index] : fallback;
}
private int intAt(int[] values, int index, int fallback) {
return values != null && index >= 0 && index < values.length ? values[index] : fallback;
}
private void configurePackedIconGroups(ArrayList<PackedStatusBarLayoutSolver.LayoutItem> items) {
java.util.LinkedHashMap<String, ArrayList<PackedStatusBarLayoutSolver.LayoutItem>> groups =
new java.util.LinkedHashMap<>();
for (PackedStatusBarLayoutSolver.LayoutItem item : items) {
if (item.iconGroup != null) {
groups.computeIfAbsent(item.iconGroup, ignored -> new ArrayList<>()).add(item);
}
}
for (ArrayList<PackedStatusBarLayoutSolver.LayoutItem> groupItems : groups.values()) {
for (int i = 0; i < groupItems.size(); i++) {
PackedStatusBarLayoutSolver.LayoutItem item = groupItems.get(i);
item.allowZero = i > 0;
item.allowDot = i == 0;
}
}
}
private ArrayList<String> packedShrinkOrder() {
ArrayList<String> order = new ArrayList<>();
order.add("Notifs");
order.add("Status");
order.add("Chip");
order.add("Clock");
return order;
}
private String packedName(UnlockedLayoutBand band, int index) {
return packedKind(band.kind) + index;
}
private String packedKind(LayoutItemKind kind) {
if (kind == LayoutItemKind.NOTIFICATIONS) {
return "Notifs";
}
if (kind == LayoutItemKind.STATUS) {
return "Status";
}
if (kind == LayoutItemKind.CHIP) {
return "Chip";
}
return "Clock";
}
private PackedStatusBarLayoutSolver.Position packedPosition(LayoutPosition position) {
if (position == LayoutPosition.RIGHT) {
return PackedStatusBarLayoutSolver.Position.RIGHT;
}
if (position == LayoutPosition.MIDDLE) {
return PackedStatusBarLayoutSolver.Position.MIDDLE;
}
return PackedStatusBarLayoutSolver.Position.LEFT;
}
private PackedStatusBarLayoutSolver.Fallback packedFallback(UnlockedLayoutBand band) {
return band.middleSide == AnchorSide.RIGHT
? PackedStatusBarLayoutSolver.Fallback.RIGHT
: PackedStatusBarLayoutSolver.Fallback.LEFT;
}
private AnchorSide packedAnchorSide(UnlockedLayoutBand band) {
if (band.splitRegion == SplitRegion.LEFT) {
return AnchorSide.LEFT;
}
if (band.splitRegion == SplitRegion.RIGHT) {
return AnchorSide.RIGHT;
}
if (band.position == LayoutPosition.LEFT) {
return AnchorSide.RIGHT;
}
if (band.position == LayoutPosition.RIGHT) {
return AnchorSide.LEFT;
}
return band.middleSide;
}
private ArrayList<SnapshotPlacement> offsetPlacements(ArrayList<SnapshotPlacement> placements, int verticalOffsetPx) {
if (placements == null || placements.isEmpty() || verticalOffsetPx == 0) {
return placements;
@@ -718,14 +718,22 @@ final class UnlockedStockSourceCollector {
settings.layoutChipVerticalOffsetPx,
settings.layoutNotifPosition,
settings.layoutNotifSortOrder,
settings.layoutNotifShowDotIfTruncated,
settings.layoutNotifEnabledUnlocked,
settings.layoutNotifUnlockedCount,
java.util.Arrays.hashCode(settings.layoutNotifPositions),
java.util.Arrays.hashCode(settings.layoutNotifMiddleSides),
java.util.Arrays.hashCode(settings.layoutNotifMiddleAutoNearestSides),
java.util.Arrays.hashCode(settings.layoutNotifVerticalOffsetsPx),
settings.layoutNotifMiddleSide,
settings.layoutNotifMiddleAutoNearestSide,
settings.layoutNotifVerticalOffsetPx,
settings.layoutStatusPosition,
settings.layoutStatusEnabledUnlocked,
settings.layoutStatusShowDotIfTruncated,
settings.layoutStatusUnlockedCount,
java.util.Arrays.hashCode(settings.layoutStatusPositions),
java.util.Arrays.hashCode(settings.layoutStatusMiddleSides),
java.util.Arrays.hashCode(settings.layoutStatusMiddleAutoNearestSides),
java.util.Arrays.hashCode(settings.layoutStatusVerticalOffsetsPx),
settings.layoutStatusMiddleSide,
settings.layoutStatusMiddleAutoNearestSide,
settings.layoutStatusVerticalOffsetPx,
@@ -0,0 +1,87 @@
package se.ajpanton.statusbartweak.shell.debug;
import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
import android.text.TextUtils;
public final class NotificationDisplayStyleSettings {
private static final String KEY_LOCKSCREEN_MINIMIZING_NOTIFICATION =
"lockscreen_minimizing_notification";
private NotificationDisplayStyleSettings() {
}
public static State read(Context context) {
ContentResolver resolver = context != null ? context.getContentResolver() : null;
Integer minimizing = getSystemInt(resolver, KEY_LOCKSCREEN_MINIMIZING_NOTIFICATION);
return new State(minimizing, styleFor(minimizing));
}
public static boolean write(Context context, Style style) {
if (context == null || style == null || style == Style.UNKNOWN) {
return false;
}
int minimizing = switch (style) {
case CARDS -> 0;
case ICONS -> 1;
case DOT -> 2;
case UNKNOWN -> -1;
};
if (minimizing < 0) {
return false;
}
return runRootCommand(
"settings put system " + KEY_LOCKSCREEN_MINIMIZING_NOTIFICATION + " " + minimizing);
}
private static Style styleFor(Integer minimizing) {
if (minimizing == null) {
return Style.UNKNOWN;
}
return switch (minimizing) {
case 0 -> Style.CARDS;
case 1 -> Style.ICONS;
case 2 -> Style.DOT;
default -> Style.UNKNOWN;
};
}
private static Integer getSystemInt(ContentResolver resolver, String key) {
if (resolver == null || TextUtils.isEmpty(key)) {
return null;
}
try {
return Settings.System.getInt(resolver, key);
} catch (Throwable ignored) {
return null;
}
}
private static boolean runRootCommand(String command) {
if (TextUtils.isEmpty(command)) {
return false;
}
try {
Process process = new ProcessBuilder("su", "-c", command)
.redirectErrorStream(true)
.start();
return process.waitFor() == 0;
} catch (Throwable ignored) {
return false;
}
}
public enum Style {
UNKNOWN,
CARDS,
ICONS,
DOT
}
public record State(
Integer minimizingNotification,
Style style
) {
}
}
@@ -30,24 +30,24 @@ public final class SbtDefaults {
public static final boolean ICONS_LOCK_EVEN_DISTRIBUTION_DEFAULT = false;
// Cards mode (AOD).
public static final int CARDS_AOD_MAX_ICONS_PER_ROW_DEFAULT = 8;
public static final int CARDS_AOD_MAX_ICONS_PER_ROW_DEFAULT = 5;
public static final int CARDS_AOD_MAX_ICONS_PER_ROW_MIN = 1;
public static final int CARDS_AOD_MAX_ICONS_PER_ROW_MAX = 15;
public static final int CARDS_AOD_MAX_ROWS_DEFAULT = 3;
public static final int CARDS_AOD_MAX_ROWS_MIN = 1;
public static final int CARDS_AOD_MAX_ROWS_DEFAULT = 1;
public static final int CARDS_AOD_MAX_ROWS_MIN = 0;
public static final int CARDS_AOD_MAX_ROWS_MAX = 5;
public static final boolean CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT = true;
public static final boolean CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT = false;
public static final boolean CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT = true;
// Cards mode (Lockscreen).
public static final int CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT = 8;
public static final int CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT = 5;
public static final int CARDS_LOCK_MAX_ICONS_PER_ROW_MIN = 1;
public static final int CARDS_LOCK_MAX_ICONS_PER_ROW_MAX = 15;
public static final int CARDS_LOCK_MAX_ROWS_DEFAULT = 3;
public static final int CARDS_LOCK_MAX_ROWS_MIN = 1;
public static final int CARDS_LOCK_MAX_ROWS_DEFAULT = 1;
public static final int CARDS_LOCK_MAX_ROWS_MIN = 0;
public static final int CARDS_LOCK_MAX_ROWS_MAX = 5;
public static final boolean CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT = true;
public static final boolean CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT = false;
public static final boolean CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT = true;
public static final boolean AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT = false;
public static final boolean BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT = true;
public static final boolean BATTERY_AOD_UNPLUGGED_PERCENT_DEFAULT = true;
@@ -115,6 +115,7 @@ public final class SbtDefaults {
public static final boolean LAYOUT_NOTIF_ENABLED_AOD_DEFAULT = true;
public static final boolean LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT = true;
public static final boolean LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT = true;
public static final int LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT = 1;
public static final String LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT = "left";
public static final boolean LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT = true;
public static final int LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT = 0;
@@ -122,10 +123,13 @@ public final class SbtDefaults {
public static final boolean LAYOUT_STATUS_ENABLED_AOD_DEFAULT = true;
public static final boolean LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT = true;
public static final boolean LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT = true;
public static final int LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT = 1;
public static final boolean LAYOUT_STATUS_SHOW_DOT_IF_TRUNCATED_DEFAULT = true;
public static final String LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT = "left";
public static final boolean LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT = true;
public static final int LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT = 0;
public static final int LAYOUT_ICON_CONTAINER_COUNT_MIN = 0;
public static final int LAYOUT_ICON_CONTAINER_COUNT_MAX = 3;
private SbtDefaults() {
}
@@ -13,6 +13,10 @@ import java.util.Map;
import java.util.Set;
public final class SbtSettings {
private interface IndexedKey {
String key(int index);
}
public static final String MODULE_PACKAGE = "se.ajpanton.statusbartweak";
public static final String PREFS_NAME = "sbt_settings";
public static final String ACTION_SETTINGS_CHANGED =
@@ -32,6 +36,8 @@ public final class SbtSettings {
"debug_visualize_chip_container";
public static final String KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT =
"debug_visualize_camera_cutout";
public static final String KEY_LOCKED_NOTIFICATION_MODE =
"locked_notification_mode";
public static final String KEY_UNLOCKED_MAX_ICONS_PER_ROW = "unlocked_max_icons_per_row";
public static final String KEY_ICONS_AOD_MAX_ICONS_PER_ROW = "icons_aod_max_icons_per_row";
public static final String KEY_ICONS_AOD_MAX_ROWS = "icons_aod_max_rows";
@@ -101,6 +107,7 @@ public final class SbtSettings {
public static final String KEY_LAYOUT_NOTIF_ENABLED_AOD = "layout_notif_enabled_aod";
public static final String KEY_LAYOUT_NOTIF_ENABLED_LOCKSCREEN = "layout_notif_enabled_lockscreen";
public static final String KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED = "layout_notif_enabled_unlocked";
public static final String KEY_LAYOUT_NOTIF_UNLOCKED_COUNT = "layout_notif_unlocked_count";
public static final String KEY_LAYOUT_NOTIF_MIDDLE_SIDE = "layout_notif_middle_side";
public static final String KEY_LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE = "layout_notif_middle_auto_nearest_side";
public static final String KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX = "layout_notif_vertical_offset_px";
@@ -108,6 +115,7 @@ public final class SbtSettings {
public static final String KEY_LAYOUT_STATUS_ENABLED_AOD = "layout_status_enabled_aod";
public static final String KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN = "layout_status_enabled_lockscreen";
public static final String KEY_LAYOUT_STATUS_ENABLED_UNLOCKED = "layout_status_enabled_unlocked";
public static final String KEY_LAYOUT_STATUS_UNLOCKED_COUNT = "layout_status_unlocked_count";
public static final String KEY_LAYOUT_STATUS_SHOW_DOT_IF_TRUNCATED = "layout_status_show_dot_if_truncated";
public static final String KEY_LAYOUT_STATUS_MIDDLE_SIDE = "layout_status_middle_side";
public static final String KEY_LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE = "layout_status_middle_auto_nearest_side";
@@ -116,6 +124,42 @@ public final class SbtSettings {
public static final String KEY_SYSTEM_ICON_HIDE_SLOTS = "system_icon_hide_slots";
public static final String KEY_APP_ICON_BLOCKED_MODES = AppIconRules.PREF_KEY_BLOCKED_MODES;
public static final String KEY_APP_ICON_EXCEPTION_RULES = AppIconRules.PREF_KEY_EXCEPTION_RULES;
public static String layoutNotifPositionKey(int index) {
return suffixedKey(KEY_LAYOUT_NOTIF_POSITION, index);
}
public static String layoutNotifMiddleSideKey(int index) {
return suffixedKey(KEY_LAYOUT_NOTIF_MIDDLE_SIDE, index);
}
public static String layoutNotifMiddleAutoNearestSideKey(int index) {
return suffixedKey(KEY_LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE, index);
}
public static String layoutNotifVerticalOffsetKey(int index) {
return suffixedKey(KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX, index);
}
public static String layoutStatusPositionKey(int index) {
return suffixedKey(KEY_LAYOUT_STATUS_POSITION, index);
}
public static String layoutStatusMiddleSideKey(int index) {
return suffixedKey(KEY_LAYOUT_STATUS_MIDDLE_SIDE, index);
}
public static String layoutStatusMiddleAutoNearestSideKey(int index) {
return suffixedKey(KEY_LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE, index);
}
public static String layoutStatusVerticalOffsetKey(int index) {
return suffixedKey(KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX, index);
}
private static String suffixedKey(String base, int index) {
return index <= 0 ? base : base + "_" + (index + 1);
}
public final boolean enableAodCutoutLimit;
public final boolean enableLockCutoutLimit;
public final int unlockedMaxIconsPerRow;
@@ -180,6 +224,11 @@ public final class SbtSettings {
public final boolean layoutNotifEnabledAod;
public final boolean layoutNotifEnabledLockscreen;
public final boolean layoutNotifEnabledUnlocked;
public final int layoutNotifUnlockedCount;
public final String[] layoutNotifPositions;
public final String[] layoutNotifMiddleSides;
public final boolean[] layoutNotifMiddleAutoNearestSides;
public final int[] layoutNotifVerticalOffsetsPx;
public final String layoutNotifMiddleSide;
public final boolean layoutNotifMiddleAutoNearestSide;
public final int layoutNotifVerticalOffsetPx;
@@ -187,6 +236,11 @@ public final class SbtSettings {
public final boolean layoutStatusEnabledAod;
public final boolean layoutStatusEnabledLockscreen;
public final boolean layoutStatusEnabledUnlocked;
public final int layoutStatusUnlockedCount;
public final String[] layoutStatusPositions;
public final String[] layoutStatusMiddleSides;
public final boolean[] layoutStatusMiddleAutoNearestSides;
public final int[] layoutStatusVerticalOffsetsPx;
public final boolean layoutStatusShowDotIfTruncated;
public final String layoutStatusMiddleSide;
public final boolean layoutStatusMiddleAutoNearestSide;
@@ -272,6 +326,11 @@ public final class SbtSettings {
String layoutNotifMiddleSide,
boolean layoutNotifMiddleAutoNearestSide,
int layoutNotifVerticalOffsetPx,
int layoutNotifUnlockedCount,
String[] layoutNotifPositions,
String[] layoutNotifMiddleSides,
boolean[] layoutNotifMiddleAutoNearestSides,
int[] layoutNotifVerticalOffsetsPx,
String layoutStatusPosition,
boolean layoutStatusEnabledAod,
boolean layoutStatusEnabledLockscreen,
@@ -280,6 +339,11 @@ public final class SbtSettings {
String layoutStatusMiddleSide,
boolean layoutStatusMiddleAutoNearestSide,
int layoutStatusVerticalOffsetPx,
int layoutStatusUnlockedCount,
String[] layoutStatusPositions,
String[] layoutStatusMiddleSides,
boolean[] layoutStatusMiddleAutoNearestSides,
int[] layoutStatusVerticalOffsetsPx,
Map<String, String> clockCameraTypeOverrides,
Map<String, Integer> systemIconBlockedModes,
boolean statusChipsHideAll,
@@ -424,6 +488,27 @@ public final class SbtSettings {
layoutNotifVerticalOffsetPx,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutNotifUnlockedCount = Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutNotifUnlockedCount));
this.layoutNotifPositions = sanitizeStringArray(
layoutNotifPositions,
this.layoutNotifPosition,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutNotifMiddleSides = sanitizeStringArray(
layoutNotifMiddleSides,
this.layoutNotifMiddleSide,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutNotifMiddleAutoNearestSides = sanitizeBooleanArray(
layoutNotifMiddleAutoNearestSides,
this.layoutNotifMiddleAutoNearestSide,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutNotifVerticalOffsetsPx = sanitizeIntArray(
layoutNotifVerticalOffsetsPx,
this.layoutNotifVerticalOffsetPx,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutStatusPosition = layoutStatusPosition != null
? layoutStatusPosition
: SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT;
@@ -439,6 +524,27 @@ public final class SbtSettings {
layoutStatusVerticalOffsetPx,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutStatusUnlockedCount = Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutStatusUnlockedCount));
this.layoutStatusPositions = sanitizeStringArray(
layoutStatusPositions,
this.layoutStatusPosition,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutStatusMiddleSides = sanitizeStringArray(
layoutStatusMiddleSides,
this.layoutStatusMiddleSide,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutStatusMiddleAutoNearestSides = sanitizeBooleanArray(
layoutStatusMiddleAutoNearestSides,
this.layoutStatusMiddleAutoNearestSide,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutStatusVerticalOffsetsPx = sanitizeIntArray(
layoutStatusVerticalOffsetsPx,
this.layoutStatusVerticalOffsetPx,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.clockCameraTypeOverrides = clockCameraTypeOverrides != null
? Collections.unmodifiableMap(new HashMap<>(clockCameraTypeOverrides))
: Collections.emptyMap();
@@ -540,6 +646,11 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
null,
null,
null,
null,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT,
@@ -548,6 +659,11 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
null,
null,
null,
null,
Collections.emptyMap(),
Collections.emptyMap(),
false,
@@ -787,6 +903,28 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
readIconContainerCount(
prefs,
KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT),
readStringArrayFromPrefs(
prefs,
SbtSettings::layoutNotifPositionKey,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT),
readStringArrayFromPrefs(
prefs,
SbtSettings::layoutNotifMiddleSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT),
readBooleanArrayFromPrefs(
prefs,
SbtSettings::layoutNotifMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT),
readIntArrayFromPrefs(
prefs,
SbtSettings::layoutNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
prefs.getString(KEY_LAYOUT_STATUS_POSITION, SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT),
prefs.getBoolean(KEY_LAYOUT_STATUS_ENABLED_AOD, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT),
prefs.getBoolean(KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT),
@@ -798,6 +936,28 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
readIconContainerCount(
prefs,
KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
KEY_LAYOUT_STATUS_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT),
readStringArrayFromPrefs(
prefs,
SbtSettings::layoutStatusPositionKey,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT),
readStringArrayFromPrefs(
prefs,
SbtSettings::layoutStatusMiddleSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT),
readBooleanArrayFromPrefs(
prefs,
SbtSettings::layoutStatusMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT),
readIntArrayFromPrefs(
prefs,
SbtSettings::layoutStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
ClockCameraTypeSupport.parseOverrides(copyStringSet(
prefs.getStringSet(KEY_CLOCK_CAMERA_TYPE_OVERRIDES, Collections.emptySet()))),
systemIconBlockedModes,
@@ -940,6 +1100,28 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
readIconContainerCount(
bundle,
KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT),
readStringArrayFromBundle(
bundle,
SbtSettings::layoutNotifPositionKey,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT),
readStringArrayFromBundle(
bundle,
SbtSettings::layoutNotifMiddleSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT),
readBooleanArrayFromBundle(
bundle,
SbtSettings::layoutNotifMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT),
readIntArrayFromBundle(
bundle,
SbtSettings::layoutNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
bundle.getString(KEY_LAYOUT_STATUS_POSITION, SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT),
bundle.getBoolean(KEY_LAYOUT_STATUS_ENABLED_AOD, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT),
bundle.getBoolean(KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT),
@@ -951,6 +1133,28 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
readIconContainerCount(
bundle,
KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
KEY_LAYOUT_STATUS_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT),
readStringArrayFromBundle(
bundle,
SbtSettings::layoutStatusPositionKey,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT),
readStringArrayFromBundle(
bundle,
SbtSettings::layoutStatusMiddleSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT),
readBooleanArrayFromBundle(
bundle,
SbtSettings::layoutStatusMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT),
readIntArrayFromBundle(
bundle,
SbtSettings::layoutStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
ClockCameraTypeSupport.parseOverrides(readStringSetFromBundle(bundle, KEY_CLOCK_CAMERA_TYPE_OVERRIDES)),
systemIconBlockedModes,
bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_ALL, false),
@@ -985,6 +1189,116 @@ public final class SbtSettings {
return value;
}
private static int readIconContainerCount(
SharedPreferences prefs,
String countKey,
String legacyEnabledKey,
int defaultCount,
boolean legacyEnabledDefault
) {
int fallback = prefs.getBoolean(legacyEnabledKey, legacyEnabledDefault) ? defaultCount : 0;
int value = prefs.getInt(countKey, fallback);
return Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, value));
}
private static int readIconContainerCount(
Bundle bundle,
String countKey,
String legacyEnabledKey,
int defaultCount,
boolean legacyEnabledDefault
) {
int fallback = bundle.getBoolean(legacyEnabledKey, legacyEnabledDefault) ? defaultCount : 0;
int value = bundle.getInt(countKey, fallback);
return Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, value));
}
private static String[] readStringArrayFromPrefs(
SharedPreferences prefs,
IndexedKey key,
String fallback
) {
String[] result = new String[SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX];
for (int i = 0; i < result.length; i++) {
result[i] = prefs.getString(key.key(i), fallback);
}
return result;
}
private static boolean[] readBooleanArrayFromPrefs(
SharedPreferences prefs,
IndexedKey key,
boolean fallback
) {
boolean[] result = new boolean[SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX];
for (int i = 0; i < result.length; i++) {
result[i] = prefs.getBoolean(key.key(i), fallback);
}
return result;
}
private static int[] readIntArrayFromPrefs(SharedPreferences prefs, IndexedKey key, int fallback) {
int[] result = new int[SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX];
for (int i = 0; i < result.length; i++) {
result[i] = prefs.getInt(key.key(i), fallback);
}
return result;
}
private static String[] readStringArrayFromBundle(Bundle bundle, IndexedKey key, String fallback) {
String[] result = new String[SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX];
for (int i = 0; i < result.length; i++) {
result[i] = bundle.getString(key.key(i), fallback);
}
return result;
}
private static boolean[] readBooleanArrayFromBundle(Bundle bundle, IndexedKey key, boolean fallback) {
boolean[] result = new boolean[SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX];
for (int i = 0; i < result.length; i++) {
result[i] = bundle.getBoolean(key.key(i), fallback);
}
return result;
}
private static int[] readIntArrayFromBundle(Bundle bundle, IndexedKey key, int fallback) {
int[] result = new int[SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX];
for (int i = 0; i < result.length; i++) {
result[i] = bundle.getInt(key.key(i), fallback);
}
return result;
}
private static String[] sanitizeStringArray(String[] source, String fallback, int length) {
String[] result = new String[Math.max(0, length)];
for (int i = 0; i < result.length; i++) {
String value = source != null && i < source.length ? source[i] : null;
result[i] = value != null ? value : fallback;
}
return result;
}
private static boolean[] sanitizeBooleanArray(boolean[] source, boolean fallback, int length) {
boolean[] result = new boolean[Math.max(0, length)];
for (int i = 0; i < result.length; i++) {
result[i] = source != null && i < source.length ? source[i] : fallback;
}
return result;
}
private static int[] sanitizeIntArray(int[] source, int fallback, int length, int min, int max) {
int[] result = new int[Math.max(0, length)];
for (int i = 0; i < result.length; i++) {
int value = source != null && i < source.length ? source[i] : fallback;
result[i] = clampInt(value, min, max);
}
return result;
}
private static Set<String> copyStringSet(Set<String> source) {
if (source == null || source.isEmpty()) {
return Collections.emptySet();
@@ -12,6 +12,10 @@ import java.util.ArrayList;
import java.util.Map;
public class SbtSettingsProvider extends ContentProvider {
private interface IndexedKey {
String key(int index);
}
public static final String AUTHORITY = "se.ajpanton.statusbartweak.settings";
public static final String METHOD_GET_UNLOCKED = "get_unlocked";
public static final String METHOD_GET_ALL = "get_all";
@@ -214,6 +218,13 @@ public class SbtSettingsProvider extends ContentProvider {
out.putBoolean(SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED,
prefs.getBoolean(SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT));
out.putInt(SbtSettings.KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
prefs.getInt(SbtSettings.KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
prefs.getBoolean(
SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT)
? SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT
: 0));
out.putString(SbtSettings.KEY_LAYOUT_NOTIF_MIDDLE_SIDE,
prefs.getString(SbtSettings.KEY_LAYOUT_NOTIF_MIDDLE_SIDE,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT));
@@ -223,6 +234,17 @@ public class SbtSettingsProvider extends ContentProvider {
out.putInt(SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT));
putIconCopySettings(
out,
prefs,
SbtSettings::layoutNotifPositionKey,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtSettings::layoutNotifMiddleSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtSettings::layoutNotifMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtSettings::layoutNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT);
out.putString(SbtSettings.KEY_LAYOUT_STATUS_POSITION,
prefs.getString(SbtSettings.KEY_LAYOUT_STATUS_POSITION,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT));
@@ -235,6 +257,13 @@ public class SbtSettingsProvider extends ContentProvider {
out.putBoolean(SbtSettings.KEY_LAYOUT_STATUS_ENABLED_UNLOCKED,
prefs.getBoolean(SbtSettings.KEY_LAYOUT_STATUS_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT));
out.putInt(SbtSettings.KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
prefs.getBoolean(
SbtSettings.KEY_LAYOUT_STATUS_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT)
? SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT
: 0));
out.putBoolean(SbtSettings.KEY_LAYOUT_STATUS_SHOW_DOT_IF_TRUNCATED,
prefs.getBoolean(SbtSettings.KEY_LAYOUT_STATUS_SHOW_DOT_IF_TRUNCATED,
SbtDefaults.LAYOUT_STATUS_SHOW_DOT_IF_TRUNCATED_DEFAULT));
@@ -247,6 +276,17 @@ public class SbtSettingsProvider extends ContentProvider {
out.putInt(SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT));
putIconCopySettings(
out,
prefs,
SbtSettings::layoutStatusPositionKey,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtSettings::layoutStatusMiddleSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtSettings::layoutStatusMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtSettings::layoutStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT);
out.putStringArrayList(
SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES,
new ArrayList<>(prefs.getStringSet(
@@ -307,6 +347,26 @@ public class SbtSettingsProvider extends ContentProvider {
return super.call(method, arg, extras);
}
private static void putIconCopySettings(
Bundle out,
SharedPreferences prefs,
IndexedKey positionKey,
String positionDefault,
IndexedKey middleSideKey,
String middleSideDefault,
IndexedKey autoNearestKey,
boolean autoNearestDefault,
IndexedKey verticalOffsetKey,
int verticalOffsetDefault
) {
for (int i = 0; i < SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX; i++) {
out.putString(positionKey.key(i), prefs.getString(positionKey.key(i), positionDefault));
out.putString(middleSideKey.key(i), prefs.getString(middleSideKey.key(i), middleSideDefault));
out.putBoolean(autoNearestKey.key(i), prefs.getBoolean(autoNearestKey.key(i), autoNearestDefault));
out.putInt(verticalOffsetKey.key(i), prefs.getInt(verticalOffsetKey.key(i), verticalOffsetDefault));
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
@@ -0,0 +1,7 @@
package se.ajpanton.statusbartweak.shell.ui;
public final class AodLayoutFragment extends LockedSceneLayoutFragment {
public AodLayoutFragment() {
super(true);
}
}
@@ -26,6 +26,7 @@ import java.util.Map;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.shell.debug.DebugNotifications;
import se.ajpanton.statusbartweak.shell.debug.NotificationDisplayStyleSettings;
import se.ajpanton.statusbartweak.shell.settings.ClockCameraTypeSupport;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
@@ -61,6 +62,10 @@ public class IconsDebugFragment extends Fragment {
root.findViewById(R.id.debug_visualize_camera_cutout_switch);
MaterialButton restartSystemUiButton = root.findViewById(R.id.debug_restart_systemui_button);
SwitchMaterial cycleIconsSwitch = root.findViewById(R.id.debug_cycle_icons_switch);
TextView notificationStyleCurrent = root.findViewById(R.id.debug_notification_style_current);
MaterialButton notificationStyleCards = root.findViewById(R.id.debug_notification_style_cards);
MaterialButton notificationStyleIcons = root.findViewById(R.id.debug_notification_style_icons);
MaterialButton notificationStyleDot = root.findViewById(R.id.debug_notification_style_dot);
TextView currentCameraIdentified = root.findViewById(R.id.debug_camera_current_identified);
LinearLayout currentCameraOverrideRow = root.findViewById(R.id.debug_camera_current_override_row);
TextView currentCameraOverride = root.findViewById(R.id.debug_camera_current_override);
@@ -107,6 +112,11 @@ public class IconsDebugFragment extends Fragment {
SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT);
bindRestartSystemUiButton(restartSystemUiButton);
bindNotificationStyleControls(
notificationStyleCurrent,
notificationStyleCards,
notificationStyleIcons,
notificationStyleDot);
updateVisualizerSwitchAvailability(
prefs,
visualizeNotificationContainerSwitch,
@@ -188,6 +198,76 @@ public class IconsDebugFragment extends Fragment {
restartButton.setOnClickListener(v -> restartSystemUi());
}
private void bindNotificationStyleControls(
TextView currentView,
MaterialButton cardsButton,
MaterialButton iconsButton,
MaterialButton dotButton
) {
updateNotificationStyleUi(currentView);
if (cardsButton != null) {
cardsButton.setOnClickListener(v -> writeNotificationStyle(
NotificationDisplayStyleSettings.Style.CARDS,
currentView));
}
if (iconsButton != null) {
iconsButton.setOnClickListener(v -> writeNotificationStyle(
NotificationDisplayStyleSettings.Style.ICONS,
currentView));
}
if (dotButton != null) {
dotButton.setOnClickListener(v -> writeNotificationStyle(
NotificationDisplayStyleSettings.Style.DOT,
currentView));
}
}
private void updateNotificationStyleUi(TextView currentView) {
Context context = requireContext();
NotificationDisplayStyleSettings.State state =
NotificationDisplayStyleSettings.read(context);
if (currentView != null) {
currentView.setText(getString(
R.string.debug_notification_style_current,
readableNotificationStyle(state.style())));
}
}
private void writeNotificationStyle(
NotificationDisplayStyleSettings.Style style,
TextView currentView
) {
Context appContext = requireContext().getApplicationContext();
new Thread(() -> {
boolean success = NotificationDisplayStyleSettings.write(appContext, style);
if (!isAdded()) {
return;
}
requireActivity().runOnUiThread(() -> {
updateNotificationStyleUi(currentView);
Toast.makeText(
requireContext(),
success
? R.string.debug_notification_style_write_done
: R.string.debug_notification_style_write_failed,
success ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
});
}, "StatusBarTweak:NotificationStyleWrite").start();
}
private String readableNotificationStyle(NotificationDisplayStyleSettings.Style style) {
if (style == NotificationDisplayStyleSettings.Style.CARDS) {
return getString(R.string.debug_notification_style_cards);
}
if (style == NotificationDisplayStyleSettings.Style.ICONS) {
return getString(R.string.debug_notification_style_icons);
}
if (style == NotificationDisplayStyleSettings.Style.DOT) {
return getString(R.string.debug_notification_style_dot);
}
return getString(R.string.debug_notification_style_unknown);
}
private void updateVisualizerSwitchAvailability(SharedPreferences prefs,
SwitchMaterial notificationSwitch,
SwitchMaterial statusSwitch,
@@ -17,6 +17,7 @@ import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;
@@ -28,6 +29,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.switchmaterial.SwitchMaterial;
import java.util.ArrayList;
import java.util.Collections;
@@ -45,6 +47,7 @@ public final class LayoutFragment extends Fragment {
View root = inflater.inflate(R.layout.fragment_layout, container, false);
SharedPreferences prefs = requireContext()
.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE);
hideUnlockedSubpageObsoleteControls(root);
SwitchMaterial enabledSwitch = root.findViewById(R.id.clock_enabled_switch);
enabledSwitch.setChecked(prefs.getBoolean(
@@ -198,19 +201,400 @@ public final class LayoutFragment extends Fragment {
bindCheckBoxPref(prefs, root.findViewById(R.id.status_position_mode_unlocked),
SbtSettings.KEY_LAYOUT_STATUS_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT);
bindCheckBoxPref(prefs, root.findViewById(R.id.notif_show_dot_if_truncated),
SbtSettings.KEY_LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED,
SbtDefaults.LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED_DEFAULT);
bindCheckBoxPref(prefs, root.findViewById(R.id.status_show_dot_if_truncated),
SbtSettings.KEY_LAYOUT_STATUS_SHOW_DOT_IF_TRUNCATED,
SbtDefaults.LAYOUT_STATUS_SHOW_DOT_IF_TRUNCATED_DEFAULT);
bindSortOrderGroup(prefs, root.findViewById(R.id.layout_sort_icons_group));
setupOrderingList(root, prefs);
setupUnlockedIconContainerCount(
root,
prefs,
R.id.notif_position_mode_unlocked,
R.string.layout_notification_position_title,
SbtSettings.KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT,
SbtSettings::layoutNotifPositionKey,
SbtSettings::layoutNotifMiddleAutoNearestSideKey,
SbtSettings::layoutNotifMiddleSideKey,
SbtSettings::layoutNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT);
setupUnlockedIconContainerCount(
root,
prefs,
R.id.status_position_mode_unlocked,
R.string.layout_status_position_title,
SbtSettings.KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtSettings.KEY_LAYOUT_STATUS_ENABLED_UNLOCKED,
SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT,
SbtSettings::layoutStatusPositionKey,
SbtSettings::layoutStatusMiddleAutoNearestSideKey,
SbtSettings::layoutStatusMiddleSideKey,
SbtSettings::layoutStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT);
return root;
}
private interface IndexedKey {
String key(int index);
}
private void setupUnlockedIconContainerCount(
View root,
SharedPreferences prefs,
int checkboxId,
int sectionTitleId,
String countKey,
int countDefault,
String legacyEnabledKey,
boolean legacyEnabledDefault,
IndexedKey positionKey,
IndexedKey autoNearestKey,
IndexedKey middleSideKey,
IndexedKey verticalOffsetKey,
String positionDefault,
boolean autoNearestDefault,
String middleSideDefault,
int verticalOffsetDefault
) {
CheckBox checkbox = root.findViewById(checkboxId);
if (checkbox == null || !(checkbox.getParent() instanceof ViewGroup modeRow)
|| !(modeRow.getParent() instanceof LinearLayout sectionContent)) {
return;
}
int rowIndex = sectionContent.indexOfChild(modeRow);
sectionContent.removeView(modeRow);
MaterialCardView sourceCard = findAncestorCard(sectionContent);
LinearLayout cardsParent = sourceCard != null && sourceCard.getParent() instanceof LinearLayout parent
? parent
: null;
int sourceCardIndex = cardsParent != null ? cardsParent.indexOfChild(sourceCard) : -1;
String copyTag = "layout-copy-" + countKey;
LinearLayout countContainer = new LinearLayout(requireContext());
countContainer.setOrientation(LinearLayout.VERTICAL);
sectionContent.addView(countContainer, Math.max(0, rowIndex));
Runnable[] rebuild = new Runnable[1];
rebuild[0] = () -> {
countContainer.removeAllViews();
removeGeneratedCopyCards(cardsParent, copyTag);
int count = iconContainerCount(prefs, countKey, countDefault, legacyEnabledKey, legacyEnabledDefault);
countContainer.addView(iconContainerCountControl(
prefs,
countKey,
count,
legacyEnabledKey,
() -> {
if (rebuild[0] != null) {
rebuild[0].run();
}
}));
if (cardsParent == null || sourceCardIndex < 0) {
return;
}
int insertIndex = sourceCardIndex + 1;
for (int i = 1; i < count; i++, insertIndex++) {
View copy = iconCopyCardFromLayout(
checkboxId,
sectionTitleId,
i,
positionKey,
autoNearestKey,
middleSideKey,
verticalOffsetKey,
positionDefault,
autoNearestDefault,
middleSideDefault,
verticalOffsetDefault,
copyTag);
cardsParent.addView(copy, Math.min(insertIndex, cardsParent.getChildCount()));
}
};
rebuild[0].run();
}
private View iconContainerCountControl(
SharedPreferences prefs,
String countKey,
int current,
String legacyEnabledKey,
Runnable onChanged
) {
LinearLayout outer = new LinearLayout(requireContext());
outer.setOrientation(LinearLayout.VERTICAL);
TextView label = new TextView(requireContext());
label.setText(R.string.layout_icon_container_count);
label.setPadding(0, dp(8), 0, 0);
outer.addView(label);
LinearLayout row = new LinearLayout(requireContext());
row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
MaterialButton minus = smallButton("-");
MaterialButton plus = smallButton("+");
EditText input = new EditText(requireContext());
input.setGravity(android.view.Gravity.CENTER);
input.setSingleLine(true);
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER);
input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)});
input.setText(String.valueOf(current));
row.addView(minus, compactButtonParams());
row.addView(input, compactInputParams());
row.addView(plus, compactButtonParams());
outer.addView(row);
View.OnClickListener listener = view -> {
int next = clampIconContainerCount(parseInt(input, current) + (view == minus ? -1 : 1));
persistIconContainerCount(prefs, countKey, legacyEnabledKey, next);
onChanged.run();
};
minus.setOnClickListener(listener);
plus.setOnClickListener(listener);
input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
int next = clampIconContainerCount(parseInt(input, current));
persistIconContainerCount(prefs, countKey, legacyEnabledKey, next);
onChanged.run();
}
});
return outer;
}
private View iconCopyCardFromLayout(
int sourceCheckboxId,
int titleId,
int index,
IndexedKey positionKey,
IndexedKey autoNearestKey,
IndexedKey middleSideKey,
IndexedKey verticalOffsetKey,
String positionDefault,
boolean autoNearestDefault,
String middleSideDefault,
int verticalOffsetDefault,
String copyTag
) {
int cardId = sourceCheckboxId == R.id.notif_position_mode_unlocked
? R.id.layout_notif_card
: R.id.layout_status_card;
View card = inflateLayoutCard(cardId);
card.setTag(copyTag);
TextView title = findCardTitle(card);
if (title != null) {
title.setText(getString(titleId) + " " + (index + 1));
}
removeModeContainerForCheckbox(card, sourceCheckboxId);
SharedPreferences prefs = requireContext()
.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE);
if (sourceCheckboxId == R.id.notif_position_mode_unlocked) {
setupPositionSection(card,
prefs,
R.id.notif_position_group,
R.id.notif_position_middle_options_container,
R.id.notif_middle_auto_nearest,
R.id.notif_middle_side_prompt,
positionKey.key(index),
positionDefault,
autoNearestKey.key(index),
middleSideKey.key(index));
bindStepper(prefs,
verticalOffsetKey.key(index),
card.findViewById(R.id.notif_vertical_offset_input),
card.findViewById(R.id.notif_vertical_offset_minus_fast),
card.findViewById(R.id.notif_vertical_offset_minus),
card.findViewById(R.id.notif_vertical_offset_plus),
card.findViewById(R.id.notif_vertical_offset_plus_fast),
card.findViewById(R.id.notif_vertical_offset_reset),
verticalOffsetDefault);
} else {
setupPositionSection(card,
prefs,
R.id.status_position_group,
R.id.status_position_middle_options_container,
R.id.status_middle_auto_nearest,
R.id.status_middle_side_prompt,
positionKey.key(index),
positionDefault,
autoNearestKey.key(index),
middleSideKey.key(index));
bindStepper(prefs,
verticalOffsetKey.key(index),
card.findViewById(R.id.status_vertical_offset_input),
card.findViewById(R.id.status_vertical_offset_minus_fast),
card.findViewById(R.id.status_vertical_offset_minus),
card.findViewById(R.id.status_vertical_offset_plus),
card.findViewById(R.id.status_vertical_offset_plus_fast),
card.findViewById(R.id.status_vertical_offset_reset),
verticalOffsetDefault);
}
return card;
}
private MaterialButton smallButton(String text) {
MaterialButton button = new MaterialButton(requireContext(), null, com.google.android.material.R.attr.materialButtonOutlinedStyle);
button.setText(text);
button.setMinWidth(dp(40));
button.setMinHeight(dp(40));
button.setPadding(0, button.getPaddingTop(), 0, button.getPaddingBottom());
return button;
}
private LinearLayout.LayoutParams compactButtonParams() {
return new LinearLayout.LayoutParams(dp(40), ViewGroup.LayoutParams.WRAP_CONTENT);
}
private LinearLayout.LayoutParams compactInputParams() {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
dp(48),
ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = dp(8);
params.rightMargin = dp(8);
return params;
}
@Nullable
private MaterialCardView findAncestorCard(View view) {
View current = view;
while (current != null) {
if (current instanceof MaterialCardView card) {
return card;
}
current = current.getParent() instanceof View parent ? parent : null;
}
return null;
}
private void removeGeneratedCopyCards(@Nullable LinearLayout parent, String tag) {
if (parent == null) {
return;
}
for (int i = parent.getChildCount() - 1; i >= 0; i--) {
View child = parent.getChildAt(i);
if (tag.equals(child.getTag())) {
parent.removeViewAt(i);
}
}
}
private View inflateLayoutCard(int cardId) {
View fullLayout = LayoutInflater.from(requireContext()).inflate(R.layout.fragment_layout, null, false);
View card = fullLayout.findViewById(cardId);
if (card == null) {
return new View(requireContext());
}
if (card.getParent() instanceof ViewGroup parent) {
parent.removeView(card);
}
return card;
}
@Nullable
private TextView findCardTitle(View card) {
if (!(card instanceof ViewGroup cardGroup) || cardGroup.getChildCount() <= 0) {
return null;
}
View inner = cardGroup.getChildAt(0);
if (!(inner instanceof ViewGroup innerGroup) || innerGroup.getChildCount() <= 0) {
return null;
}
View title = innerGroup.getChildAt(0);
return title instanceof TextView textView ? textView : null;
}
@Nullable
private LinearLayout removeModeContainerForCheckbox(View root, int checkboxId) {
CheckBox checkbox = root.findViewById(checkboxId);
View modeContainer = directSectionChild(checkbox);
if (modeContainer == null || !(modeContainer.getParent() instanceof LinearLayout sectionContent)) {
return null;
}
sectionContent.removeView(modeContainer);
return sectionContent;
}
@Nullable
private View directSectionChild(@Nullable View child) {
View current = child;
while (current != null && current.getParent() instanceof View parent) {
if (parent.getParent() instanceof MaterialCardView) {
return current;
}
current = parent;
}
return null;
}
private int iconContainerCount(
SharedPreferences prefs,
String countKey,
int countDefault,
String legacyEnabledKey,
boolean legacyEnabledDefault
) {
int fallback = prefs.getBoolean(legacyEnabledKey, legacyEnabledDefault) ? countDefault : 0;
return clampIconContainerCount(prefs.getInt(countKey, fallback));
}
private int clampIconContainerCount(int value) {
return Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, value));
}
private void persistIconContainerCount(
SharedPreferences prefs,
String countKey,
String legacyEnabledKey,
int count
) {
prefs.edit()
.putInt(countKey, clampIconContainerCount(count))
.putBoolean(legacyEnabledKey, count > 0)
.apply();
SbtSettings.ensureReadable(requireContext());
}
private int dp(int value) {
return Math.round(value * getResources().getDisplayMetrics().density);
}
private void hideUnlockedSubpageObsoleteControls(View root) {
hide(root, R.id.layout_master_card);
hide(root, R.id.layout_padding_card);
hide(root, R.id.layout_ordering_card);
hide(root, R.id.clock_position_mode_lockscreen);
hide(root, R.id.notif_position_mode_aod);
hide(root, R.id.notif_position_mode_lockscreen);
hide(root, R.id.chip_position_mode_lockscreen);
hide(root, R.id.status_position_mode_aod);
hide(root, R.id.status_position_mode_lockscreen);
setText(root, R.id.clock_position_mode_unlocked, R.string.layout_item_enabled);
setText(root, R.id.notif_position_mode_unlocked, R.string.layout_item_enabled);
setText(root, R.id.chip_position_mode_unlocked, R.string.layout_item_enabled);
setText(root, R.id.status_position_mode_unlocked, R.string.layout_item_enabled);
}
private void hide(View root, int id) {
View view = root.findViewById(id);
if (view != null) {
view.setVisibility(View.GONE);
}
}
private void setText(View root, int id, int stringId) {
TextView view = root.findViewById(id);
if (view != null) {
view.setText(stringId);
}
}
private void setupPositionSection(View root,
SharedPreferences prefs,
int groupId,
@@ -0,0 +1,351 @@
package se.ajpanton.statusbartweak.shell.ui;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.InputFilter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.switchmaterial.SwitchMaterial;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
public final class LayoutHomeFragment extends Fragment {
private LinearLayout layoutOnContainer;
private LinearLayout miscLayoutOnContainer;
private LinearLayout miscUnlockedOffOnlyContainer;
private RadioGroup lockedModeGroup;
private TextView lockedModeHint;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Context context = requireContext();
SharedPreferences prefs = context.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE);
ScrollView scroll = new ScrollView(context);
LinearLayout root = new LinearLayout(context);
root.setOrientation(LinearLayout.VERTICAL);
root.setPadding(dp(20), dp(20), dp(20), dp(20));
scroll.addView(root);
TextView title = title(context, R.string.layout_home_title);
root.addView(title);
SwitchMaterial master = new SwitchMaterial(context);
master.setText(R.string.layout_enabled);
master.setChecked(prefs.getBoolean(SbtSettings.KEY_CLOCK_ENABLED, SbtDefaults.CLOCK_ENABLED_DEFAULT));
root.addView(card(context, R.string.layout_master_toggle_title, master));
lockedModeGroup = new RadioGroup(context);
lockedModeGroup.setOrientation(RadioGroup.VERTICAL);
lockedModeHint = body(context, R.string.layout_locked_notifications_hint);
LinearLayout lockedContent = vertical(context);
lockedContent.addView(lockedModeHint);
lockedContent.addView(lockedModeGroup);
root.addView(card(context, R.string.layout_locked_notifications_title, lockedContent));
layoutOnContainer = vertical(context);
layoutOnContainer.addView(paddingCard(context, prefs));
layoutOnContainer.addView(LayoutOrderingUi.createCard(context, prefs));
root.addView(layoutOnContainer);
root.addView(miscCard(context, prefs));
Runnable refreshAvailability = () -> refreshAvailability(prefs, master.isChecked());
master.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean(SbtSettings.KEY_CLOCK_ENABLED, isChecked).apply();
SbtSettings.ensureReadable(context);
refreshAvailability.run();
});
refreshAvailability.run();
return scroll;
}
private void refreshAvailability(SharedPreferences prefs, boolean layoutEnabled) {
setViewTreeEnabled(layoutOnContainer, layoutEnabled);
layoutOnContainer.setAlpha(layoutEnabled ? 1f : 0.45f);
setViewTreeEnabled(miscLayoutOnContainer, layoutEnabled);
miscLayoutOnContainer.setAlpha(layoutEnabled ? 1f : 0.45f);
setViewTreeEnabled(miscUnlockedOffOnlyContainer, !layoutEnabled);
miscUnlockedOffOnlyContainer.setAlpha(layoutEnabled ? 0.45f : 1f);
LockedNotificationModeUi.bind(
requireContext(),
prefs,
lockedModeGroup,
lockedModeHint,
layoutEnabled);
}
private View paddingCard(Context context, SharedPreferences prefs) {
LinearLayout content = vertical(context);
content.addView(stepper(
context,
prefs,
R.string.layout_cutout_gap_label,
SbtSettings.KEY_LAYOUT_PADDING_CUTOUT_PX,
SbtDefaults.LAYOUT_PADDING_CUTOUT_PX_DEFAULT,
-999,
999));
content.addView(stepper(
context,
prefs,
R.string.layout_left_padding_label,
SbtSettings.KEY_LAYOUT_PADDING_LEFT_PX,
SbtDefaults.LAYOUT_PADDING_LEFT_PX_DEFAULT,
-999,
999,
checkBox(
context,
prefs,
R.string.layout_padding_add_stock,
SbtSettings.KEY_LAYOUT_PADDING_LEFT_ADD_STOCK,
SbtDefaults.LAYOUT_PADDING_LEFT_ADD_STOCK_DEFAULT)));
content.addView(stepper(
context,
prefs,
R.string.layout_right_padding_label,
SbtSettings.KEY_LAYOUT_PADDING_RIGHT_PX,
SbtDefaults.LAYOUT_PADDING_RIGHT_PX_DEFAULT,
-999,
999,
checkBox(
context,
prefs,
R.string.layout_padding_add_stock,
SbtSettings.KEY_LAYOUT_PADDING_RIGHT_ADD_STOCK,
SbtDefaults.LAYOUT_PADDING_RIGHT_ADD_STOCK_DEFAULT)));
content.addView(stepper(
context,
prefs,
R.string.layout_item_padding_label,
SbtSettings.KEY_LAYOUT_ITEM_PADDING_PX,
SbtDefaults.LAYOUT_ITEM_PADDING_PX_DEFAULT,
SbtDefaults.LAYOUT_ITEM_PADDING_PX_MIN,
SbtDefaults.LAYOUT_ITEM_PADDING_PX_MAX));
return card(context, R.string.layout_padding_title, content);
}
private View miscCard(Context context, SharedPreferences prefs) {
LinearLayout content = vertical(context);
miscLayoutOnContainer = vertical(context);
miscLayoutOnContainer.addView(checkBox(
context,
prefs,
R.string.layout_ignore_under_display_cameras,
SbtSettings.KEY_LAYOUT_IGNORE_UNDER_DISPLAY_CAMERAS,
SbtDefaults.LAYOUT_IGNORE_UNDER_DISPLAY_CAMERAS_DEFAULT));
content.addView(miscLayoutOnContainer);
miscUnlockedOffOnlyContainer = vertical(context);
miscUnlockedOffOnlyContainer.addView(stepper(
context,
prefs,
R.string.label_max_unlocked_icons_per_row,
SbtSettings.KEY_UNLOCKED_MAX_ICONS_PER_ROW,
SbtDefaults.UNLOCKED_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.UNLOCKED_MAX_ICONS_PER_ROW_MIN,
SbtDefaults.UNLOCKED_MAX_ICONS_PER_ROW_MAX));
content.addView(miscUnlockedOffOnlyContainer);
return card(context, R.string.layout_misc_title, content);
}
private View stepper(
Context context,
SharedPreferences prefs,
int labelId,
String key,
int defaultValue,
int min,
int max
) {
return stepper(context, prefs, labelId, key, defaultValue, min, max, null);
}
private View stepper(
Context context,
SharedPreferences prefs,
int labelId,
String key,
int defaultValue,
int min,
int max,
@Nullable View trailingView
) {
LinearLayout outer = vertical(context);
TextView label = body(context, labelId);
outer.addView(label);
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
row.setPadding(0, dp(6), 0, dp(8));
MaterialButton minus = button(context, "-");
MaterialButton plus = button(context, "+");
EditText input = new EditText(context);
input.setGravity(android.view.Gravity.CENTER);
input.setSingleLine(true);
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED);
input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(5)});
input.setText(String.valueOf(clamp(prefs.getInt(key, defaultValue), min, max)));
row.addView(minus, buttonParams());
row.addView(input, inputParams());
row.addView(plus, buttonParams());
if (trailingView != null) {
LinearLayout.LayoutParams trailingParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
trailingParams.leftMargin = dp(8);
row.addView(trailingView, trailingParams);
}
outer.addView(row);
View.OnClickListener listener = v -> {
int delta = v == minus ? -1 : 1;
int next = clamp(parseInt(input, defaultValue) + delta, min, max);
input.setText(String.valueOf(next));
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(context);
};
minus.setOnClickListener(listener);
plus.setOnClickListener(listener);
input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
int next = clamp(parseInt(input, defaultValue), min, max);
input.setText(String.valueOf(next));
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(context);
}
});
return outer;
}
private CheckBox checkBox(
Context context,
SharedPreferences prefs,
int label,
String key,
boolean defaultValue
) {
CheckBox checkBox = new CheckBox(context);
checkBox.setText(label);
checkBox.setChecked(prefs.getBoolean(key, defaultValue));
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean(key, isChecked).apply();
SbtSettings.ensureReadable(context);
});
return checkBox;
}
private MaterialButton button(Context context, String text) {
MaterialButton button = new MaterialButton(context, null, com.google.android.material.R.attr.materialButtonOutlinedStyle);
button.setText(text);
button.setMinWidth(dp(40));
button.setMinHeight(dp(40));
button.setPadding(0, button.getPaddingTop(), 0, button.getPaddingBottom());
return button;
}
private LinearLayout.LayoutParams buttonParams() {
return new LinearLayout.LayoutParams(dp(40), ViewGroup.LayoutParams.WRAP_CONTENT);
}
private LinearLayout.LayoutParams inputParams() {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
dp(48),
ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = dp(8);
params.rightMargin = dp(8);
return params;
}
private MaterialCardView card(Context context, int titleId, View content) {
MaterialCardView card = new MaterialCardView(context);
LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
cardParams.topMargin = dp(16);
card.setLayoutParams(cardParams);
card.setUseCompatPadding(true);
card.setStrokeColor(context.getColor(R.color.sbt_card_outline));
card.setStrokeWidth(dp(1));
LinearLayout inner = vertical(context);
inner.setPadding(dp(16), dp(16), dp(16), dp(16));
TextView title = sectionTitle(context, titleId);
inner.addView(title);
inner.addView(content);
card.addView(inner);
return card;
}
private LinearLayout vertical(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
return layout;
}
private TextView title(Context context, int stringId) {
TextView view = new TextView(context);
view.setText(stringId);
view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline5);
return view;
}
private TextView sectionTitle(Context context, int stringId) {
TextView view = new TextView(context);
view.setText(stringId);
view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1);
view.setAllCaps(true);
view.setTypeface(view.getTypeface(), android.graphics.Typeface.BOLD);
return view;
}
private TextView body(Context context, int stringId) {
TextView view = new TextView(context);
view.setText(stringId);
view.setPadding(0, dp(8), 0, 0);
return view;
}
private void setViewTreeEnabled(View view, boolean enabled) {
if (view == null) {
return;
}
view.setEnabled(enabled);
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
setViewTreeEnabled(group.getChildAt(i), enabled);
}
}
}
private int parseInt(EditText input, int fallback) {
try {
return Integer.parseInt(input.getText().toString().trim());
} catch (Throwable ignored) {
return fallback;
}
}
private int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
private int dp(int value) {
return Math.round(value * getResources().getDisplayMetrics().density);
}
}
@@ -0,0 +1,324 @@
package se.ajpanton.statusbartweak.shell.ui;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class LayoutOrderingUi {
private LayoutOrderingUi() {
}
static View createCard(Context context, SharedPreferences prefs) {
LinearLayout content = vertical(context);
TextView hint = body(context, R.string.layout_ordering_hint);
content.addView(hint);
RecyclerView list = new RecyclerView(context);
list.setNestedScrollingEnabled(false);
LinearLayout.LayoutParams listParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
listParams.topMargin = dp(context, 12);
content.addView(list, listParams);
bindOrderingList(context, prefs, list);
TextView sortTitle = body(context, R.string.layout_sort_icons_title);
sortTitle.setTypeface(sortTitle.getTypeface(), android.graphics.Typeface.BOLD);
LinearLayout.LayoutParams sortTitleParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
sortTitleParams.topMargin = dp(context, 16);
content.addView(sortTitle, sortTitleParams);
RadioGroup sortGroup = new RadioGroup(context);
sortGroup.setOrientation(RadioGroup.HORIZONTAL);
addSortButton(context, sortGroup, R.string.layout_sort_icons_stock, "stock");
addSortButton(context, sortGroup, R.string.layout_sort_icons_reversed, "reversed");
addSortButton(context, sortGroup, R.string.layout_sort_icons_automatic, "automatic");
sortGroup.check(idForTag(sortGroup, prefs.getString(
SbtSettings.KEY_LAYOUT_NOTIF_SORT_ORDER,
SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_DEFAULT)));
sortGroup.setOnCheckedChangeListener((group, checkedId) -> {
prefs.edit()
.putString(SbtSettings.KEY_LAYOUT_NOTIF_SORT_ORDER, tagForId(group, checkedId))
.apply();
SbtSettings.ensureReadable(context);
});
LinearLayout.LayoutParams sortParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
sortParams.topMargin = dp(context, 8);
content.addView(sortGroup, sortParams);
return card(context, R.string.layout_ordering_title, content);
}
private static void bindOrderingList(Context context, SharedPreferences prefs, RecyclerView list) {
ArrayList<OrderingItem> items = parseOrderingItems(
context,
prefs.getString(SbtSettings.KEY_LAYOUT_ITEM_ORDER, SbtDefaults.LAYOUT_ITEM_ORDER_DEFAULT));
list.setLayoutManager(new LinearLayoutManager(context));
OrderingAdapter adapter = new OrderingAdapter(items, order -> {
prefs.edit().putString(SbtSettings.KEY_LAYOUT_ITEM_ORDER, order).apply();
SbtSettings.ensureReadable(context);
});
list.setAdapter(adapter);
final ItemTouchHelper[] helperHolder = new ItemTouchHelper[1];
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
@Override
public boolean onMove(
@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target
) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
if (from == RecyclerView.NO_POSITION || to == RecyclerView.NO_POSITION) {
return false;
}
adapter.moveItem(from, to);
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
});
helper.attachToRecyclerView(list);
helperHolder[0] = helper;
adapter.setDragStarter(holder -> {
ItemTouchHelper current = helperHolder[0];
if (current != null) {
current.startDrag(holder);
}
});
}
private static ArrayList<OrderingItem> parseOrderingItems(Context context, @Nullable String encoded) {
if (encoded == null || encoded.isEmpty() || "clock,notifications,status".equals(encoded)) {
encoded = SbtDefaults.LAYOUT_ITEM_ORDER_DEFAULT;
}
ArrayList<OrderingItem> items = new ArrayList<>();
for (String token : encoded.split(",")) {
addOrderingItemIfValid(context, items, token != null ? token.trim() : "");
}
addOrderingItemIfValid(context, items, "clock");
addOrderingItemIfValid(context, items, "chip");
addOrderingItemIfValid(context, items, "status");
addOrderingItemIfValid(context, items, "notifications");
return items;
}
private static void addOrderingItemIfValid(Context context, ArrayList<OrderingItem> items, String key) {
if (key == null || key.isEmpty()) {
return;
}
for (OrderingItem item : items) {
if (item.key.equals(key)) {
return;
}
}
int labelRes;
if ("clock".equals(key)) {
labelRes = R.string.layout_ordering_clock;
} else if ("chip".equals(key)) {
labelRes = R.string.layout_ordering_chip;
} else if ("notifications".equals(key)) {
labelRes = R.string.layout_ordering_notif_icons;
} else if ("status".equals(key)) {
labelRes = R.string.layout_ordering_status_icons;
} else {
return;
}
items.add(new OrderingItem(key, context.getString(labelRes)));
}
private static void addSortButton(Context context, RadioGroup group, int labelId, String tag) {
RadioButton button = new RadioButton(context);
button.setId(View.generateViewId());
button.setTag(tag);
button.setText(labelId);
button.setSingleLine(true);
button.setMaxLines(1);
group.addView(button, new RadioGroup.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f));
}
private static int idForTag(RadioGroup group, @Nullable String tag) {
String normalized = tag != null ? tag : "automatic";
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (normalized.equals(child.getTag())) {
return child.getId();
}
}
return group.getChildCount() > 0 ? group.getChildAt(group.getChildCount() - 1).getId() : View.NO_ID;
}
private static String tagForId(RadioGroup group, int id) {
View child = group.findViewById(id);
Object tag = child != null ? child.getTag() : null;
return tag instanceof String value ? value : "automatic";
}
private static MaterialCardView card(Context context, int titleId, View content) {
MaterialCardView card = new MaterialCardView(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.topMargin = dp(context, 16);
card.setLayoutParams(params);
card.setUseCompatPadding(true);
card.setStrokeColor(context.getColor(R.color.sbt_card_outline));
card.setStrokeWidth(dp(context, 1));
LinearLayout inner = vertical(context);
inner.setPadding(dp(context, 16), dp(context, 16), dp(context, 16), dp(context, 16));
inner.addView(sectionTitle(context, titleId));
inner.addView(content);
card.addView(inner);
return card;
}
private static LinearLayout vertical(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
return layout;
}
private static TextView sectionTitle(Context context, int stringId) {
TextView view = new TextView(context);
view.setText(stringId);
view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1);
view.setAllCaps(true);
view.setTypeface(view.getTypeface(), android.graphics.Typeface.BOLD);
return view;
}
private static TextView body(Context context, int stringId) {
TextView view = new TextView(context);
view.setText(stringId);
view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body2);
view.setPadding(0, dp(context, 8), 0, 0);
return view;
}
private static int dp(Context context, int value) {
return Math.round(value * context.getResources().getDisplayMetrics().density);
}
private record OrderingItem(String key, String label) {
}
private static final class OrderingAdapter extends RecyclerView.Adapter<OrderingViewHolder> {
interface DragStarter {
void startDrag(OrderingViewHolder holder);
}
interface OrderChangedListener {
void onOrderChanged(String encodedOrder);
}
private final List<OrderingItem> items;
private final OrderChangedListener orderChangedListener;
private DragStarter dragStarter;
OrderingAdapter(List<OrderingItem> items, OrderChangedListener orderChangedListener) {
this.items = items;
this.orderChangedListener = orderChangedListener;
}
void setDragStarter(DragStarter dragStarter) {
this.dragStarter = dragStarter;
}
void moveItem(int from, int to) {
if (from == to || from < 0 || to < 0 || from >= items.size() || to >= items.size()) {
return;
}
Collections.swap(items, from, to);
notifyItemMoved(from, to);
orderChangedListener.onOrderChanged(encodeOrder());
}
private String encodeOrder() {
StringBuilder builder = new StringBuilder();
for (OrderingItem item : items) {
if (builder.length() > 0) {
builder.append(',');
}
builder.append(item.key);
}
return builder.toString();
}
@NonNull
@Override
public OrderingViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_layout_ordering, parent, false);
return new OrderingViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull OrderingViewHolder holder, int position) {
holder.label.setText(items.get(position).label);
holder.handle.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && dragStarter != null) {
dragStarter.startDrag(holder);
return true;
}
return false;
});
}
@Override
public int getItemCount() {
return items.size();
}
}
private static final class OrderingViewHolder extends RecyclerView.ViewHolder {
final TextView label;
final TextView handle;
OrderingViewHolder(@NonNull View itemView) {
super(itemView);
label = itemView.findViewById(R.id.layout_ordering_item_label);
handle = itemView.findViewById(R.id.layout_ordering_item_handle);
}
}
}
@@ -0,0 +1,157 @@
package se.ajpanton.statusbartweak.shell.ui;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.View;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.shell.debug.NotificationDisplayStyleSettings;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class LockedNotificationModeUi {
static final String MODE_CARDS = "cards";
static final String MODE_ICONS = "icons";
static final String MODE_DOT = "dot";
static final String MODE_NONE = "none";
private LockedNotificationModeUi() {
}
static void bind(
Context context,
SharedPreferences prefs,
RadioGroup group,
TextView hint,
boolean layoutEnabled
) {
bind(context, prefs, group, hint, layoutEnabled, null);
}
static void bind(
Context context,
SharedPreferences prefs,
RadioGroup group,
TextView hint,
boolean layoutEnabled,
Runnable onChanged
) {
if (context == null || prefs == null || group == null) {
return;
}
group.setOnCheckedChangeListener(null);
RadioButton cards = button(context, R.string.debug_notification_style_cards, MODE_CARDS);
RadioButton icons = button(context, R.string.debug_notification_style_icons, MODE_ICONS);
RadioButton dot = button(context, R.string.debug_notification_style_dot, MODE_DOT);
RadioButton none = button(context, R.string.layout_locked_notifications_none, MODE_NONE);
group.removeAllViews();
group.addView(cards);
group.addView(icons);
group.addView(dot);
group.addView(none);
String stored = prefs.getString(SbtSettings.KEY_LOCKED_NOTIFICATION_MODE, MODE_DOT);
NotificationDisplayStyleSettings.Style androidStyle =
NotificationDisplayStyleSettings.read(context).style();
String current = currentMode(androidStyle, stored, layoutEnabled);
if (layoutEnabled && MODE_NONE.equals(stored)
&& androidStyle != NotificationDisplayStyleSettings.Style.DOT) {
writeMode(context, MODE_NONE);
}
group.check(idForMode(group, current));
none.setEnabled(layoutEnabled);
none.setAlpha(layoutEnabled ? 1f : 0.45f);
if (hint != null) {
boolean rememberedNone = MODE_NONE.equals(stored) && !layoutEnabled;
hint.setText(rememberedNone
? R.string.layout_locked_notifications_none_disabled
: R.string.layout_locked_notifications_hint);
}
group.setOnCheckedChangeListener((radioGroup, checkedId) -> {
String mode = modeForId(radioGroup, checkedId);
if (MODE_NONE.equals(mode) && !layoutEnabled) {
radioGroup.check(idForMode(radioGroup, MODE_DOT));
return;
}
boolean ok = writeMode(context, mode);
if (!ok) {
Toast.makeText(
context,
R.string.debug_notification_style_write_failed,
Toast.LENGTH_SHORT).show();
bind(context, prefs, group, hint, layoutEnabled);
return;
}
prefs.edit().putString(SbtSettings.KEY_LOCKED_NOTIFICATION_MODE, mode).apply();
SbtSettings.ensureReadable(context);
if (onChanged != null) {
onChanged.run();
}
});
}
static String currentMode(Context context, SharedPreferences prefs, boolean layoutEnabled) {
String stored = prefs.getString(SbtSettings.KEY_LOCKED_NOTIFICATION_MODE, MODE_DOT);
NotificationDisplayStyleSettings.Style style =
NotificationDisplayStyleSettings.read(context).style();
return currentMode(style, stored, layoutEnabled);
}
private static String currentMode(
NotificationDisplayStyleSettings.Style androidStyle,
String stored,
boolean layoutEnabled
) {
if (MODE_NONE.equals(stored) && layoutEnabled) {
return MODE_NONE;
}
if (androidStyle == NotificationDisplayStyleSettings.Style.CARDS) {
return MODE_CARDS;
}
if (androidStyle == NotificationDisplayStyleSettings.Style.ICONS) {
return MODE_ICONS;
}
return MODE_DOT;
}
private static boolean writeMode(Context context, String mode) {
NotificationDisplayStyleSettings.Style style;
if (MODE_CARDS.equals(mode)) {
style = NotificationDisplayStyleSettings.Style.CARDS;
} else if (MODE_ICONS.equals(mode)) {
style = NotificationDisplayStyleSettings.Style.ICONS;
} else {
style = NotificationDisplayStyleSettings.Style.DOT;
}
return NotificationDisplayStyleSettings.write(context, style);
}
private static RadioButton button(Context context, int text, String mode) {
RadioButton button = new RadioButton(context);
button.setId(View.generateViewId());
button.setTag(mode);
button.setText(text);
return button;
}
private static int idForMode(RadioGroup group, String mode) {
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (mode.equals(child.getTag())) {
return child.getId();
}
}
return group.getChildCount() > 0 ? group.getChildAt(0).getId() : View.NO_ID;
}
private static String modeForId(RadioGroup group, int id) {
View view = group.findViewById(id);
Object tag = view != null ? view.getTag() : null;
return tag instanceof String value ? value : MODE_DOT;
}
}
@@ -0,0 +1,949 @@
package se.ajpanton.statusbartweak.shell.ui;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.InputFilter;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
abstract class LockedSceneLayoutFragment extends Fragment {
private final boolean aod;
private RadioGroup modeGroup;
private TextView modeHint;
private LinearLayout dynamicContent;
LockedSceneLayoutFragment(boolean aod) {
this.aod = aod;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Context context = requireContext();
SharedPreferences prefs = context.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE);
ScrollView scroll = new ScrollView(context);
LinearLayout root = vertical(context);
root.setPadding(dp(20), dp(20), dp(20), dp(20));
scroll.addView(root);
root.addView(title(context, aod ? R.string.section_aod : R.string.section_lockscreen));
modeGroup = new RadioGroup(context);
modeGroup.setOrientation(RadioGroup.VERTICAL);
modeHint = body(context, R.string.layout_locked_notifications_hint);
LinearLayout modeContent = vertical(context);
modeContent.addView(modeHint);
modeContent.addView(modeGroup);
root.addView(card(context, R.string.layout_locked_notifications_title, modeContent));
dynamicContent = vertical(context);
root.addView(dynamicContent);
bindMode(context, prefs);
return scroll;
}
private void bindMode(Context context, SharedPreferences prefs) {
boolean layoutEnabled = prefs.getBoolean(SbtSettings.KEY_CLOCK_ENABLED, SbtDefaults.CLOCK_ENABLED_DEFAULT);
LockedNotificationModeUi.bind(
context,
prefs,
modeGroup,
modeHint,
layoutEnabled,
() -> rebuildDynamic(
context,
prefs,
LockedNotificationModeUi.currentMode(context, prefs, layoutEnabled)));
rebuildDynamic(context, prefs, LockedNotificationModeUi.currentMode(context, prefs, layoutEnabled));
}
private void rebuildDynamic(Context context, SharedPreferences prefs, String mode) {
dynamicContent.removeAllViews();
if (LockedNotificationModeUi.MODE_DOT.equals(mode)
|| LockedNotificationModeUi.MODE_NONE.equals(mode)) {
return;
}
if (!aod) {
View clockCard = configuredPositionCardFromLayout(
context,
prefs,
R.id.layout_clock_card,
R.id.clock_position_mode_lockscreen,
SbtSettings.KEY_CLOCK_ENABLED_LOCKSCREEN,
SbtDefaults.CLOCK_ENABLED_LOCKSCREEN_DEFAULT,
R.id.clock_position_group,
R.id.clock_position_middle_options_container,
R.id.clock_middle_auto_nearest,
R.id.clock_middle_side_prompt,
R.id.clock_middle_side_group,
R.id.clock_middle_side_right,
R.id.clock_vertical_offset_input,
R.id.clock_vertical_offset_minus_fast,
R.id.clock_vertical_offset_minus,
R.id.clock_vertical_offset_plus,
R.id.clock_vertical_offset_plus_fast,
R.id.clock_vertical_offset_reset,
sceneKey("clock_position"),
SbtDefaults.CLOCK_POSITION_DEFAULT,
sceneKey("clock_middle_auto_nearest_side"),
SbtDefaults.CLOCK_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
sceneKey("clock_middle_side"),
SbtDefaults.CLOCK_MIDDLE_CUTOUT_SIDE_DEFAULT,
sceneKey("clock_vertical_offset_px"),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_DEFAULT);
dynamicContent.addView(clockCard);
}
dynamicContent.addView(configuredIconContainersCardFromLayout(
context,
prefs,
R.id.layout_status_card,
"status",
aod ? R.id.status_position_mode_aod : R.id.status_position_mode_lockscreen,
R.id.status_position_group,
R.id.status_position_middle_options_container,
R.id.status_middle_auto_nearest,
R.id.status_middle_side_prompt,
R.id.status_middle_side_group,
R.id.status_middle_side_right,
R.id.status_vertical_offset_input,
R.id.status_vertical_offset_minus_fast,
R.id.status_vertical_offset_minus,
R.id.status_vertical_offset_plus,
R.id.status_vertical_offset_plus_fast,
R.id.status_vertical_offset_reset,
R.string.layout_status_position_title,
aod ? SbtSettings.KEY_LAYOUT_STATUS_ENABLED_AOD : SbtSettings.KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN,
aod ? SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT : SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT));
if (LockedNotificationModeUi.MODE_CARDS.equals(mode)) {
dynamicContent.addView(cardsNotificationCard(context, prefs));
} else {
dynamicContent.addView(configuredIconContainersCardFromLayout(
context,
prefs,
R.id.layout_notif_card,
"notifications",
aod ? R.id.notif_position_mode_aod : R.id.notif_position_mode_lockscreen,
R.id.notif_position_group,
R.id.notif_position_middle_options_container,
R.id.notif_middle_auto_nearest,
R.id.notif_middle_side_prompt,
R.id.notif_middle_side_group,
R.id.notif_middle_side_right,
R.id.notif_vertical_offset_input,
R.id.notif_vertical_offset_minus_fast,
R.id.notif_vertical_offset_minus,
R.id.notif_vertical_offset_plus,
R.id.notif_vertical_offset_plus_fast,
R.id.notif_vertical_offset_reset,
R.string.layout_notification_position_title,
aod ? SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_AOD : SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_LOCKSCREEN,
aod ? SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT : SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT));
}
}
private View configuredPositionCardFromLayout(
Context context,
SharedPreferences prefs,
int cardId,
int enabledCheckboxId,
String enabledKey,
boolean enabledDefault,
int positionGroupId,
int middleOptionsId,
int autoNearestId,
int promptId,
int middleSideGroupId,
int middleSideRightId,
int verticalInputId,
int verticalMinusFastId,
int verticalMinusId,
int verticalPlusId,
int verticalPlusFastId,
int verticalResetId,
String positionKey,
String positionDefault,
String autoNearestKey,
boolean autoNearestDefault,
String middleSideKey,
String middleSideDefault,
String verticalOffsetKey,
int verticalOffsetDefault
) {
View card = inflateLayoutCard(context, cardId);
configureSingleEnabledCheckbox(card, prefs, enabledCheckboxId, enabledKey, enabledDefault);
bindXmlPositionSection(
card,
prefs,
positionGroupId,
middleOptionsId,
autoNearestId,
promptId,
middleSideGroupId,
middleSideRightId,
positionKey,
positionDefault,
autoNearestKey,
autoNearestDefault,
middleSideKey,
middleSideDefault);
bindXmlStepper(
prefs,
verticalOffsetKey,
card.findViewById(verticalInputId),
card.findViewById(verticalMinusFastId),
card.findViewById(verticalMinusId),
card.findViewById(verticalPlusId),
card.findViewById(verticalPlusFastId),
card.findViewById(verticalResetId),
verticalOffsetDefault,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
return card;
}
private View configuredIconContainersCardFromLayout(
Context context,
SharedPreferences prefs,
int cardId,
String base,
int modeCheckboxId,
int positionGroupId,
int middleOptionsId,
int autoNearestId,
int promptId,
int middleSideGroupId,
int middleSideRightId,
int verticalInputId,
int verticalMinusFastId,
int verticalMinusId,
int verticalPlusId,
int verticalPlusFastId,
int verticalResetId,
int titleId,
String legacyEnabledKey,
boolean legacyEnabledDefault,
String positionDefault,
boolean autoNearestDefault,
String middleSideDefault,
int verticalOffsetDefault
) {
LinearLayout wrapper = vertical(context);
String countKey = sceneKey(base + "_count");
Runnable[] rebuild = new Runnable[1];
rebuild[0] = () -> {
wrapper.removeAllViews();
int count = iconContainerCount(prefs, countKey, legacyEnabledKey, legacyEnabledDefault);
View primary = configuredIconContainerCard(
context,
prefs,
cardId,
modeCheckboxId,
positionGroupId,
middleOptionsId,
autoNearestId,
promptId,
middleSideGroupId,
middleSideRightId,
verticalInputId,
verticalMinusFastId,
verticalMinusId,
verticalPlusId,
verticalPlusFastId,
verticalResetId,
titleId,
0,
indexedSceneKey(base + "_position", 0),
positionDefault,
indexedSceneKey(base + "_middle_auto_nearest_side", 0),
autoNearestDefault,
indexedSceneKey(base + "_middle_side", 0),
middleSideDefault,
indexedSceneKey(base + "_vertical_offset_px", 0),
verticalOffsetDefault,
legacyEnabledKey,
countKey,
count,
rebuild[0]);
wrapper.addView(primary);
for (int i = 1; i < count; i++) {
wrapper.addView(configuredIconContainerCard(
context,
prefs,
cardId,
modeCheckboxId,
positionGroupId,
middleOptionsId,
autoNearestId,
promptId,
middleSideGroupId,
middleSideRightId,
verticalInputId,
verticalMinusFastId,
verticalMinusId,
verticalPlusId,
verticalPlusFastId,
verticalResetId,
titleId,
i,
indexedSceneKey(base + "_position", i),
positionDefault,
indexedSceneKey(base + "_middle_auto_nearest_side", i),
autoNearestDefault,
indexedSceneKey(base + "_middle_side", i),
middleSideDefault,
indexedSceneKey(base + "_vertical_offset_px", i),
verticalOffsetDefault,
null,
null,
count,
null));
}
};
rebuild[0].run();
return wrapper;
}
private View configuredIconContainerCard(
Context context,
SharedPreferences prefs,
int cardId,
int modeCheckboxId,
int positionGroupId,
int middleOptionsId,
int autoNearestId,
int promptId,
int middleSideGroupId,
int middleSideRightId,
int verticalInputId,
int verticalMinusFastId,
int verticalMinusId,
int verticalPlusId,
int verticalPlusFastId,
int verticalResetId,
int titleId,
int index,
String positionKey,
String positionDefault,
String autoNearestKey,
boolean autoNearestDefault,
String middleSideKey,
String middleSideDefault,
String verticalOffsetKey,
int verticalOffsetDefault,
@Nullable String legacyEnabledKey,
@Nullable String countKey,
int count,
@Nullable Runnable onCountChanged
) {
View card = inflateLayoutCard(context, cardId);
removeSiblingModeCheckboxes(card, modeCheckboxId);
LinearLayout sectionContent = removeModeContainerForCheckbox(card, modeCheckboxId);
if (sectionContent != null && countKey != null && onCountChanged != null) {
int insertIndex = Math.min(1, sectionContent.getChildCount());
sectionContent.addView(
iconContainerCountControl(context, prefs, countKey, legacyEnabledKey, count, onCountChanged),
insertIndex);
}
if (index > 0) {
TextView title = findCardTitle(card);
if (title != null) {
title.setText(getString(titleId) + " " + (index + 1));
}
}
bindXmlPositionSection(
card,
prefs,
positionGroupId,
middleOptionsId,
autoNearestId,
promptId,
middleSideGroupId,
middleSideRightId,
positionKey,
positionDefault,
autoNearestKey,
autoNearestDefault,
middleSideKey,
middleSideDefault);
bindXmlStepper(
prefs,
verticalOffsetKey,
card.findViewById(verticalInputId),
card.findViewById(verticalMinusFastId),
card.findViewById(verticalMinusId),
card.findViewById(verticalPlusId),
card.findViewById(verticalPlusFastId),
card.findViewById(verticalResetId),
verticalOffsetDefault,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
return card;
}
private View inflateLayoutCard(Context context, int cardId) {
View fullLayout = LayoutInflater.from(context).inflate(R.layout.fragment_layout, dynamicContent, false);
View card = fullLayout.findViewById(cardId);
if (card == null) {
return new View(context);
}
if (card.getParent() instanceof ViewGroup parent) {
parent.removeView(card);
}
return card;
}
@Nullable
private TextView findCardTitle(View card) {
if (!(card instanceof ViewGroup cardGroup) || cardGroup.getChildCount() <= 0) {
return null;
}
View inner = cardGroup.getChildAt(0);
if (!(inner instanceof ViewGroup innerGroup) || innerGroup.getChildCount() <= 0) {
return null;
}
View title = innerGroup.getChildAt(0);
return title instanceof TextView textView ? textView : null;
}
private void configureSingleEnabledCheckbox(
View root,
SharedPreferences prefs,
int enabledCheckboxId,
String key,
boolean defaultValue
) {
CheckBox enabled = root.findViewById(enabledCheckboxId);
if (enabled == null) {
return;
}
hideSiblingCheckBoxes(enabled);
enabled.setText(R.string.layout_item_enabled);
enabled.setChecked(prefs.getBoolean(key, defaultValue));
enabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean(key, isChecked).apply();
SbtSettings.ensureReadable(requireContext());
});
}
private void removeSiblingModeCheckboxes(View root, int keptCheckboxId) {
CheckBox kept = root.findViewById(keptCheckboxId);
hideSiblingCheckBoxes(kept);
}
private void hideSiblingCheckBoxes(@Nullable CheckBox kept) {
if (kept == null) {
return;
}
View modeContainer = directSectionChild(kept);
if (modeContainer instanceof ViewGroup group) {
hideOtherCheckBoxes(group, kept);
}
}
private void hideOtherCheckBoxes(View view, CheckBox kept) {
if (view instanceof CheckBox checkBox && checkBox != kept) {
checkBox.setVisibility(View.GONE);
return;
}
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
hideOtherCheckBoxes(group.getChildAt(i), kept);
}
}
}
@Nullable
private LinearLayout removeModeContainerForCheckbox(View root, int checkboxId) {
CheckBox checkbox = root.findViewById(checkboxId);
View modeContainer = directSectionChild(checkbox);
if (modeContainer == null || !(modeContainer.getParent() instanceof LinearLayout sectionContent)) {
return null;
}
sectionContent.removeView(modeContainer);
return sectionContent;
}
@Nullable
private View directSectionChild(@Nullable View child) {
View current = child;
while (current != null && current.getParent() instanceof View parent) {
if (parent.getParent() instanceof MaterialCardView) {
return current;
}
current = parent;
}
return null;
}
private View cardsNotificationCard(Context context, SharedPreferences prefs) {
LinearLayout content = vertical(context);
content.addView(stepper(
context,
prefs,
R.string.label_max_rows,
aod ? SbtSettings.KEY_CARDS_AOD_MAX_ROWS : SbtSettings.KEY_CARDS_LOCK_MAX_ROWS,
aod ? SbtDefaults.CARDS_AOD_MAX_ROWS_DEFAULT : SbtDefaults.CARDS_LOCK_MAX_ROWS_DEFAULT,
aod ? SbtDefaults.CARDS_AOD_MAX_ROWS_MIN : SbtDefaults.CARDS_LOCK_MAX_ROWS_MIN,
aod ? SbtDefaults.CARDS_AOD_MAX_ROWS_MAX : SbtDefaults.CARDS_LOCK_MAX_ROWS_MAX));
content.addView(stepper(
context,
prefs,
R.string.label_max_icons_per_row,
aod ? SbtSettings.KEY_CARDS_AOD_MAX_ICONS_PER_ROW : SbtSettings.KEY_CARDS_LOCK_MAX_ICONS_PER_ROW,
aod ? SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_DEFAULT : SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT,
aod ? SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_MIN : SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MIN,
aod ? SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_MAX : SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX));
content.addView(checkBox(
context,
prefs,
R.string.label_even_distribution,
aod ? SbtSettings.KEY_CARDS_AOD_EVEN_DISTRIBUTION : SbtSettings.KEY_CARDS_LOCK_EVEN_DISTRIBUTION,
aod ? SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT : SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT));
content.addView(checkBox(
context,
prefs,
R.string.label_limit_screen_width,
aod ? SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH : SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH,
aod ? SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT : SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT));
return card(context, R.string.layout_notification_position_title, content);
}
private View iconContainerCountControl(
Context context,
SharedPreferences prefs,
String countKey,
String legacyEnabledKey,
int current,
Runnable onChanged
) {
LinearLayout outer = vertical(context);
outer.addView(body(context, R.string.layout_icon_container_count));
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(Gravity.CENTER_VERTICAL);
MaterialButton minus = button(context, "-");
MaterialButton plus = button(context, "+");
EditText input = numericInput(context, false);
input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)});
input.setText(String.valueOf(current));
row.addView(minus, buttonParams());
row.addView(input, inputParams());
row.addView(plus, buttonParams());
outer.addView(row);
View.OnClickListener listener = view -> {
int next = clampIconContainerCount(parseInt(input, current) + (view == minus ? -1 : 1));
persistIconContainerCount(prefs, countKey, legacyEnabledKey, next);
onChanged.run();
};
minus.setOnClickListener(listener);
plus.setOnClickListener(listener);
input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
int next = clampIconContainerCount(parseInt(input, current));
persistIconContainerCount(prefs, countKey, legacyEnabledKey, next);
onChanged.run();
}
});
return outer;
}
private void bindXmlPositionSection(
View root,
SharedPreferences prefs,
int groupId,
int middleOptionsId,
int autoNearestId,
int promptId,
int middleSideGroupId,
int middleSideRightId,
String positionKey,
String positionDefault,
String autoNearestKey,
boolean autoNearestDefault,
String middleSideKey,
String middleSideDefault
) {
RadioGroup position = root.findViewById(groupId);
View middleOptions = root.findViewById(middleOptionsId);
CheckBox autoNearest = root.findViewById(autoNearestId);
TextView prompt = root.findViewById(promptId);
RadioGroup side = root.findViewById(middleSideGroupId);
if (position == null || middleOptions == null || autoNearest == null || prompt == null || side == null) {
return;
}
position.check(positionIdForValue(groupId, prefs.getString(positionKey, positionDefault)));
autoNearest.setChecked(prefs.getBoolean(autoNearestKey, autoNearestDefault));
side.check("right".equals(prefs.getString(middleSideKey, middleSideDefault))
? middleSideRightId
: firstSideId(side, middleSideRightId));
updateMiddleOptions(middleOptions, "middle".equals(positionValueForId(position.getCheckedRadioButtonId())));
prompt.setText(autoNearest.isChecked()
? R.string.layout_middle_side_prompt_centred
: R.string.layout_middle_side_prompt_attach_only);
position.setOnCheckedChangeListener((group, checkedId) -> {
prefs.edit().putString(positionKey, positionValueForId(checkedId)).apply();
updateMiddleOptions(middleOptions, "middle".equals(positionValueForId(checkedId)));
SbtSettings.ensureReadable(requireContext());
});
autoNearest.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean(autoNearestKey, isChecked).apply();
prompt.setText(isChecked
? R.string.layout_middle_side_prompt_centred
: R.string.layout_middle_side_prompt_attach_only);
SbtSettings.ensureReadable(requireContext());
});
side.setOnCheckedChangeListener((group, checkedId) -> {
prefs.edit().putString(middleSideKey, checkedId == middleSideRightId ? "right" : "left").apply();
SbtSettings.ensureReadable(requireContext());
});
}
private void bindXmlStepper(
SharedPreferences prefs,
String key,
@Nullable EditText input,
@Nullable MaterialButton minusFast,
@Nullable MaterialButton minus,
@Nullable MaterialButton plus,
@Nullable MaterialButton plusFast,
@Nullable MaterialButton reset,
int defaultValue,
int min,
int max
) {
if (input == null) {
return;
}
int start = clamp(prefs.getInt(key, defaultValue), min, max);
input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(4)});
input.setText(String.valueOf(start));
bindXmlStepperButton(prefs, key, input, minusFast, -5, min, max);
bindXmlStepperButton(prefs, key, input, minus, -1, min, max);
bindXmlStepperButton(prefs, key, input, plus, 1, min, max);
bindXmlStepperButton(prefs, key, input, plusFast, 5, min, max);
if (reset != null) {
reset.setOnClickListener(v -> {
input.setText(String.valueOf(defaultValue));
persistIntPref(prefs, key, defaultValue);
});
}
input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
int next = clamp(parseInt(input, defaultValue), min, max);
input.setText(String.valueOf(next));
persistIntPref(prefs, key, next);
}
});
}
private void bindXmlStepperButton(
SharedPreferences prefs,
String key,
EditText input,
@Nullable MaterialButton button,
int delta,
int min,
int max
) {
if (button == null) {
return;
}
button.setOnClickListener(v -> {
int next = clamp(parseInt(input, 0) + delta, min, max);
input.setText(String.valueOf(next));
persistIntPref(prefs, key, next);
});
}
private int positionIdForValue(int groupId, @Nullable String value) {
String normalized = value != null ? value : "left";
if (groupId == R.id.clock_position_group) {
if ("right".equals(normalized)) {
return R.id.clock_position_right;
}
if ("middle".equals(normalized)) {
return R.id.clock_position_middle;
}
return R.id.clock_position_left;
}
if (groupId == R.id.notif_position_group) {
if ("right".equals(normalized)) {
return R.id.notif_position_right;
}
if ("middle".equals(normalized)) {
return R.id.notif_position_middle;
}
return R.id.notif_position_left;
}
if (groupId == R.id.status_position_group) {
if ("right".equals(normalized)) {
return R.id.status_position_right;
}
if ("middle".equals(normalized)) {
return R.id.status_position_middle;
}
return R.id.status_position_left;
}
return View.NO_ID;
}
private String positionValueForId(int id) {
if (id == R.id.clock_position_right
|| id == R.id.notif_position_right
|| id == R.id.status_position_right) {
return "right";
}
if (id == R.id.clock_position_middle
|| id == R.id.notif_position_middle
|| id == R.id.status_position_middle) {
return "middle";
}
return "left";
}
private int firstSideId(RadioGroup group, int rightId) {
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child.getId() != rightId) {
return child.getId();
}
}
return View.NO_ID;
}
private void updateMiddleOptions(View view, boolean enabled) {
if (view.getVisibility() != View.VISIBLE) {
view.setVisibility(View.VISIBLE);
}
view.setEnabled(enabled);
view.setAlpha(enabled ? 1f : 0.45f);
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
updateMiddleOptionChild(group.getChildAt(i), enabled);
}
}
}
private void updateMiddleOptionChild(View view, boolean enabled) {
view.setEnabled(enabled);
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
updateMiddleOptionChild(group.getChildAt(i), enabled);
}
}
}
private View stepper(Context context, SharedPreferences prefs, int label, String key, int def, int min, int max) {
LinearLayout outer = vertical(context);
outer.addView(body(context, label));
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(Gravity.CENTER_VERTICAL);
MaterialButton minus = button(context, "-");
MaterialButton plus = button(context, "+");
EditText input = numericInput(context, true);
input.setText(String.valueOf(clamp(prefs.getInt(key, def), min, max)));
row.addView(minus, buttonParams());
row.addView(input, inputParams());
row.addView(plus, buttonParams());
outer.addView(row);
View.OnClickListener listener = view -> {
int delta = view == minus ? -1 : 1;
int next = clamp(parseInt(input, def) + delta, min, max);
input.setText(String.valueOf(next));
persistIntPref(prefs, key, next);
};
minus.setOnClickListener(listener);
plus.setOnClickListener(listener);
input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
int next = clamp(parseInt(input, def), min, max);
input.setText(String.valueOf(next));
persistIntPref(prefs, key, next);
}
});
return outer;
}
private EditText numericInput(Context context, boolean signed) {
EditText input = new EditText(context);
input.setSingleLine(true);
input.setGravity(Gravity.CENTER);
input.setInputType(signed
? android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED
: android.text.InputType.TYPE_CLASS_NUMBER);
input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(4)});
return input;
}
private CheckBox checkBox(Context context, SharedPreferences prefs, int text, String key, boolean def) {
CheckBox checkBox = new CheckBox(context);
checkBox.setText(text);
checkBox.setChecked(prefs.getBoolean(key, def));
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean(key, isChecked).apply();
SbtSettings.ensureReadable(context);
});
return checkBox;
}
private void persistIntPref(SharedPreferences prefs, String key, int value) {
prefs.edit().putInt(key, value).apply();
SbtSettings.ensureReadable(requireContext());
}
private int iconContainerCount(
SharedPreferences prefs,
String countKey,
String legacyEnabledKey,
boolean legacyEnabledDefault
) {
int fallback = prefs.getBoolean(legacyEnabledKey, legacyEnabledDefault) ? 1 : 0;
return clampIconContainerCount(prefs.getInt(countKey, fallback));
}
private void persistIconContainerCount(
SharedPreferences prefs,
String countKey,
String legacyEnabledKey,
int count
) {
int clamped = clampIconContainerCount(count);
prefs.edit()
.putInt(countKey, clamped)
.putBoolean(legacyEnabledKey, clamped > 0)
.apply();
SbtSettings.ensureReadable(requireContext());
}
private int clampIconContainerCount(int value) {
return clamp(
value,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
}
private String sceneKey(String suffix) {
return (aod ? "layout_aod_" : "layout_lock_") + suffix;
}
private String indexedSceneKey(String suffix, int index) {
return index <= 0 ? sceneKey(suffix) : sceneKey(suffix) + "_" + (index + 1);
}
private MaterialButton button(Context context, String text) {
MaterialButton button = new MaterialButton(context, null, com.google.android.material.R.attr.materialButtonOutlinedStyle);
button.setText(text);
button.setMinWidth(dp(40));
button.setMinHeight(dp(40));
button.setPadding(0, button.getPaddingTop(), 0, button.getPaddingBottom());
return button;
}
private LinearLayout.LayoutParams buttonParams() {
return new LinearLayout.LayoutParams(dp(40), ViewGroup.LayoutParams.WRAP_CONTENT);
}
private LinearLayout.LayoutParams inputParams() {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
dp(48),
ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = dp(8);
params.rightMargin = dp(8);
return params;
}
private MaterialCardView card(Context context, int titleId, View content) {
MaterialCardView card = new MaterialCardView(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.topMargin = dp(16);
card.setLayoutParams(params);
card.setUseCompatPadding(true);
card.setStrokeColor(context.getColor(R.color.sbt_card_outline));
card.setStrokeWidth(dp(1));
LinearLayout inner = vertical(context);
inner.setPadding(dp(16), dp(16), dp(16), dp(16));
inner.addView(sectionTitle(context, titleId));
inner.addView(content);
card.addView(inner);
return card;
}
private LinearLayout vertical(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
return layout;
}
private TextView title(Context context, int text) {
TextView view = new TextView(context);
view.setText(text);
view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline5);
return view;
}
private TextView sectionTitle(Context context, int text) {
TextView view = new TextView(context);
view.setText(text);
view.setAllCaps(true);
view.setTypeface(view.getTypeface(), android.graphics.Typeface.BOLD);
return view;
}
private TextView body(Context context, int text) {
TextView view = new TextView(context);
view.setText(text);
view.setPadding(0, dp(8), 0, dp(6));
return view;
}
private int parseInt(EditText input, int fallback) {
try {
return Integer.parseInt(input.getText().toString().trim());
} catch (Throwable ignored) {
return fallback;
}
}
private int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
private int dp(int value) {
return Math.round(value * getResources().getDisplayMetrics().density);
}
}
@@ -0,0 +1,7 @@
package se.ajpanton.statusbartweak.shell.ui;
public final class LockscreenLayoutFragment extends LockedSceneLayoutFragment {
public LockscreenLayoutFragment() {
super(false);
}
}
@@ -85,13 +85,17 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
if (itemId == R.id.nav_system_icons) {
fragment = new SystemIconsFragment();
} else if (itemId == R.id.nav_layout) {
fragment = new LayoutHomeFragment();
} else if (itemId == R.id.nav_layout_unlocked) {
fragment = new LayoutFragment();
} else if (itemId == R.id.nav_layout_lockscreen) {
fragment = new LockscreenLayoutFragment();
} else if (itemId == R.id.nav_layout_aod) {
fragment = new AodLayoutFragment();
} else if (itemId == R.id.nav_battery_bar) {
fragment = new BatteryBarFragment();
} else if (itemId == R.id.nav_clock) {
fragment = new ClockFragment();
} else if (itemId == R.id.nav_notification_icons) {
fragment = new NotificationIconsFragment();
} else if (itemId == R.id.nav_block_app_notification_icons) {
fragment = new BlockAppNotificationIconsFragment();
} else if (itemId == R.id.nav_status_chips) {
@@ -113,8 +117,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
return R.string.nav_system_icons;
} else if (itemId == R.id.nav_layout) {
return R.string.nav_layout;
} else if (itemId == R.id.nav_notification_icons) {
return R.string.nav_notification_icons;
} else if (itemId == R.id.nav_layout_unlocked) {
return R.string.section_unlocked;
} else if (itemId == R.id.nav_layout_lockscreen) {
return R.string.section_lockscreen;
} else if (itemId == R.id.nav_layout_aod) {
return R.string.section_aod;
} else if (itemId == R.id.nav_block_app_notification_icons) {
return R.string.hidden_apps_page_title;
} else if (itemId == R.id.nav_clock) {
@@ -1,489 +0,0 @@
package se.ajpanton.statusbartweak.shell.ui;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import java.util.Arrays;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.slider.Slider;
import com.google.android.material.switchmaterial.SwitchMaterial;
public class NotificationIconsFragment extends Fragment {
private static final int INF_VALUE = Integer.MAX_VALUE;
private static final String INF_LABEL = "\u221e";
private static final int SLIDER_STEPS = 200;
private static final int INF_GAP_STEPS = 20;
private static final double SLIDER_EXPONENT = 2.0;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_notification_icons, container, false);
SharedPreferences prefs = requireContext()
.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE);
SbtSettings.ensureReadable(requireContext());
bindNumberControl(root, prefs,
R.id.unlocked_max_icons_slider,
R.id.unlocked_max_icons_input,
R.id.unlocked_max_icons_minus,
R.id.unlocked_max_icons_plus,
SbtSettings.KEY_UNLOCKED_MAX_ICONS_PER_ROW,
SbtDefaults.UNLOCKED_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.UNLOCKED_MAX_ICONS_PER_ROW_MIN,
SbtDefaults.UNLOCKED_MAX_ICONS_PER_ROW_MAX,
true);
bindNumberControl(root, prefs,
R.id.icons_aod_max_icons_slider,
R.id.icons_aod_max_icons_input,
R.id.icons_aod_max_icons_minus,
R.id.icons_aod_max_icons_plus,
SbtSettings.KEY_ICONS_AOD_MAX_ICONS_PER_ROW,
SbtDefaults.ICONS_AOD_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.ICONS_AOD_MAX_ICONS_PER_ROW_MIN,
SbtDefaults.ICONS_AOD_MAX_ICONS_PER_ROW_MAX,
true);
bindNumberControl(root, prefs,
R.id.icons_aod_max_rows_slider,
R.id.icons_aod_max_rows_input,
R.id.icons_aod_max_rows_minus,
R.id.icons_aod_max_rows_plus,
SbtSettings.KEY_ICONS_AOD_MAX_ROWS,
SbtDefaults.ICONS_AOD_MAX_ROWS_DEFAULT,
SbtDefaults.ICONS_AOD_MAX_ROWS_MIN,
SbtDefaults.ICONS_AOD_MAX_ROWS_MAX,
true);
bindSwitch(root, prefs,
R.id.icons_aod_even_switch,
SbtSettings.KEY_ICONS_AOD_EVEN_DISTRIBUTION,
SbtDefaults.ICONS_AOD_EVEN_DISTRIBUTION_DEFAULT);
bindSwitch(root, prefs,
R.id.icons_aod_cutout_switch,
SbtSettings.KEY_ICONS_AOD_CUTOUT_LIMIT,
SbtDefaults.ICONS_AOD_CUTOUT_LIMIT_DEFAULT);
bindNumberControl(root, prefs,
R.id.icons_lock_max_icons_slider,
R.id.icons_lock_max_icons_input,
R.id.icons_lock_max_icons_minus,
R.id.icons_lock_max_icons_plus,
SbtSettings.KEY_ICONS_LOCK_MAX_ICONS_PER_ROW,
SbtDefaults.ICONS_LOCK_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.ICONS_LOCK_MAX_ICONS_PER_ROW_MIN,
SbtDefaults.ICONS_LOCK_MAX_ICONS_PER_ROW_MAX,
true);
bindNumberControl(root, prefs,
R.id.icons_lock_max_rows_slider,
R.id.icons_lock_max_rows_input,
R.id.icons_lock_max_rows_minus,
R.id.icons_lock_max_rows_plus,
SbtSettings.KEY_ICONS_LOCK_MAX_ROWS,
SbtDefaults.ICONS_LOCK_MAX_ROWS_DEFAULT,
SbtDefaults.ICONS_LOCK_MAX_ROWS_MIN,
SbtDefaults.ICONS_LOCK_MAX_ROWS_MAX,
true);
bindSwitch(root, prefs,
R.id.icons_lock_even_switch,
SbtSettings.KEY_ICONS_LOCK_EVEN_DISTRIBUTION,
SbtDefaults.ICONS_LOCK_EVEN_DISTRIBUTION_DEFAULT);
bindSwitch(root, prefs,
R.id.icons_lock_cutout_switch,
SbtSettings.KEY_ICONS_LOCK_CUTOUT_LIMIT,
SbtDefaults.ICONS_LOCK_CUTOUT_LIMIT_DEFAULT);
bindNumberControl(root, prefs,
R.id.cards_aod_max_icons_slider,
R.id.cards_aod_max_icons_input,
R.id.cards_aod_max_icons_minus,
R.id.cards_aod_max_icons_plus,
SbtSettings.KEY_CARDS_AOD_MAX_ICONS_PER_ROW,
SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_MIN,
SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_MAX,
true);
bindNumberControl(root, prefs,
R.id.cards_aod_max_rows_slider,
R.id.cards_aod_max_rows_input,
R.id.cards_aod_max_rows_minus,
R.id.cards_aod_max_rows_plus,
SbtSettings.KEY_CARDS_AOD_MAX_ROWS,
SbtDefaults.CARDS_AOD_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_AOD_MAX_ROWS_MIN,
SbtDefaults.CARDS_AOD_MAX_ROWS_MAX,
true);
bindSwitch(root, prefs,
R.id.cards_aod_even_switch,
SbtSettings.KEY_CARDS_AOD_EVEN_DISTRIBUTION,
SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT);
bindSwitch(root, prefs,
R.id.cards_aod_limit_width_switch,
SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH,
SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT);
bindNumberControl(root, prefs,
R.id.cards_lock_max_icons_slider,
R.id.cards_lock_max_icons_input,
R.id.cards_lock_max_icons_minus,
R.id.cards_lock_max_icons_plus,
SbtSettings.KEY_CARDS_LOCK_MAX_ICONS_PER_ROW,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MIN,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX,
true);
bindNumberControl(root, prefs,
R.id.cards_lock_max_rows_slider,
R.id.cards_lock_max_rows_input,
R.id.cards_lock_max_rows_minus,
R.id.cards_lock_max_rows_plus,
SbtSettings.KEY_CARDS_LOCK_MAX_ROWS,
SbtDefaults.CARDS_LOCK_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_LOCK_MAX_ROWS_MIN,
SbtDefaults.CARDS_LOCK_MAX_ROWS_MAX,
true);
bindSwitch(root, prefs,
R.id.cards_lock_even_switch,
SbtSettings.KEY_CARDS_LOCK_EVEN_DISTRIBUTION,
SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT);
bindSwitch(root, prefs,
R.id.cards_lock_limit_width_switch,
SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH,
SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT);
applyLayoutAvailability(root, prefs);
return root;
}
private void applyLayoutAvailability(View root, SharedPreferences prefs) {
if (root == null || prefs == null) {
return;
}
boolean layoutEnabled = prefs.getBoolean(
SbtSettings.KEY_CLOCK_ENABLED,
SbtDefaults.CLOCK_ENABLED_DEFAULT);
View unlockedSection = root.findViewById(R.id.notification_icons_unlocked_section);
View layoutSections = root.findViewById(R.id.notification_icons_layout_sections);
View layoutRequiredHint = root.findViewById(R.id.notification_icons_layout_required_hint);
View unlockedObsoleteHint = root.findViewById(R.id.notification_icons_unlocked_obsolete_hint);
if (unlockedSection != null) {
setViewTreeEnabled(unlockedSection, !layoutEnabled);
unlockedSection.setAlpha(layoutEnabled ? 0.45f : 1f);
}
if (layoutSections != null) {
setViewTreeEnabled(layoutSections, layoutEnabled);
layoutSections.setAlpha(layoutEnabled ? 1f : 0.45f);
}
if (layoutRequiredHint != null) {
layoutRequiredHint.setVisibility(layoutEnabled ? View.GONE : View.VISIBLE);
}
if (unlockedObsoleteHint != null) {
unlockedObsoleteHint.setVisibility(layoutEnabled ? View.VISIBLE : View.GONE);
}
}
private void setViewTreeEnabled(View view, boolean enabled) {
if (view == null) {
return;
}
view.setEnabled(enabled);
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
setViewTreeEnabled(group.getChildAt(i), enabled);
}
}
}
private void bindNumberControl(View root,
SharedPreferences prefs,
int sliderId,
int inputId,
int minusId,
int plusId,
String key,
int defaultValue,
int min,
int max,
boolean allowInf) {
Slider slider = root.findViewById(sliderId);
EditText input = root.findViewById(inputId);
MaterialButton minus = root.findViewById(minusId);
MaterialButton plus = root.findViewById(plusId);
int[] positions = buildPositions(min, max);
int maxPos = SLIDER_STEPS - INF_GAP_STEPS;
slider.setValueFrom(0);
slider.setValueTo(SLIDER_STEPS);
slider.setStepSize(1f);
slider.setTickVisible(false);
slider.setLabelFormatter(value -> labelForSliderValue(Math.round(value), min, max, allowInf, positions));
final boolean[] updating = new boolean[]{false};
int current = clampInt(prefs.getInt(key, defaultValue), min, Integer.MAX_VALUE);
applyValueToUi(slider, input, min, max, allowInf, positions, current, updating);
input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(6)});
slider.addOnChangeListener((s, v, fromUser) -> {
if (updating[0]) {
return;
}
int pos = Math.round(v);
int snapped = snapPosition(pos, positions, allowInf, maxPos);
if (snapped != pos) {
updating[0] = true;
slider.setValue(snapped);
updating[0] = false;
}
int mapped = positionToValue(snapped, min, max, allowInf, positions, maxPos);
int nextValue = mapped;
updating[0] = true;
if (mapped == INF_VALUE) {
input.setText(INF_LABEL);
} else {
input.setText(String.valueOf(mapped));
}
updating[0] = false;
if (fromUser) {
prefs.edit().putInt(key, nextValue).apply();
SbtSettings.ensureReadable(requireContext());
}
});
minus.setOnClickListener(v -> {
int parsed = parseInput(input, current);
int next;
if (parsed == INF_VALUE) {
next = max;
} else {
next = clampInt(parsed - 1, min, Integer.MAX_VALUE);
}
applyValueToUi(slider, input, min, max, allowInf, positions, next, updating);
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(requireContext());
});
plus.setOnClickListener(v -> {
int parsed = parseInput(input, current);
int next;
if (parsed == INF_VALUE) {
next = INF_VALUE;
} else {
next = clampInt(parsed + 1, min, Integer.MAX_VALUE);
}
applyValueToUi(slider, input, min, max, allowInf, positions, next, updating);
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(requireContext());
});
input.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
int next = clampInt(parseInput(input, current), min, Integer.MAX_VALUE);
applyValueToUi(slider, input, min, max, allowInf, positions, next, updating);
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(requireContext());
}
return false;
});
input.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) {
if (updating[0]) {
return;
}
String text = s != null ? s.toString().trim() : "";
if (text.isEmpty()) {
return;
}
int parsed = parseInput(input, current);
int next = clampInt(parsed, min, Integer.MAX_VALUE);
if (next != parsed) {
applyValueToUi(slider, input, min, max, allowInf, positions, next, updating);
} else {
updateSliderValue(slider, min, max, allowInf, positions, next, updating);
}
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(requireContext());
}
});
}
private void bindSwitch(View root,
SharedPreferences prefs,
int switchId,
String key,
boolean defaultValue) {
SwitchMaterial toggle = root.findViewById(switchId);
boolean current = prefs.getBoolean(key, defaultValue);
toggle.setChecked(current);
toggle.setOnCheckedChangeListener((buttonView, isChecked) ->
{
prefs.edit().putBoolean(key, isChecked).apply();
SbtSettings.ensureReadable(requireContext());
});
}
private int clampInt(int value, int min, int max) {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
private int parseInput(EditText input, int fallback) {
if (input == null) {
return fallback;
}
String text = input.getText().toString().trim();
if (text.isEmpty()) {
return fallback;
}
if (INF_LABEL.equals(text) || "inf".equalsIgnoreCase(text)) {
return INF_VALUE;
}
return Integer.parseInt(text);
}
private void applyValueToUi(Slider slider,
EditText input,
int min,
int max,
boolean allowInf,
int[] positions,
int value,
boolean[] updating) {
updateSliderValue(slider, min, max, allowInf, positions, value, updating);
updating[0] = true;
if (value == INF_VALUE) {
input.setText(INF_LABEL);
} else {
input.setText(String.valueOf(value));
}
updating[0] = false;
}
private void updateSliderValue(Slider slider,
int min,
int max,
boolean allowInf,
int[] positions,
int value,
boolean[] updating) {
int idx = valueToPosition(value, min, max, allowInf, positions);
updating[0] = true;
slider.setValue(idx);
updating[0] = false;
}
private int[] buildPositions(int min, int max) {
int count = max - min + 1;
int maxPos = SLIDER_STEPS - INF_GAP_STEPS;
int[] positions = new int[count];
if (count <= 1) {
positions[0] = 0;
return positions;
}
for (int i = 0; i < count; i++) {
double t = (double) i / (double) (count - 1);
double shaped = Math.pow(t, 1.0 / SLIDER_EXPONENT);
positions[i] = (int) Math.round(shaped * maxPos);
}
positions[0] = 0;
positions[count - 1] = maxPos;
for (int i = 1; i < count; i++) {
if (positions[i] <= positions[i - 1]) {
positions[i] = positions[i - 1] + 1;
}
}
positions[count - 1] = maxPos;
for (int i = count - 2; i >= 0; i--) {
if (positions[i] >= positions[i + 1]) {
positions[i] = positions[i + 1] - 1;
}
}
return positions;
}
private int valueToPosition(int value, int min, int max, boolean allowInf, int[] positions) {
if (allowInf && value == INF_VALUE) {
return SLIDER_STEPS;
}
if (allowInf && value > max) {
return SLIDER_STEPS;
}
int clamped = clampInt(value, min, max);
return positions[clamped - min];
}
private int positionToValue(int pos,
int min,
int max,
boolean allowInf,
int[] positions,
int maxPos) {
if (allowInf && pos >= maxPos + (INF_GAP_STEPS / 2)) {
return INF_VALUE;
}
int idx = nearestIndex(pos, positions);
int value = min + idx;
return clampInt(value, min, max);
}
private int nearestIndex(int pos, int[] positions) {
int idx = Arrays.binarySearch(positions, pos);
if (idx >= 0) {
return idx;
}
int insert = -idx - 1;
if (insert <= 0) {
return 0;
}
if (insert >= positions.length) {
return positions.length - 1;
}
int left = positions[insert - 1];
int right = positions[insert];
return (pos - left <= right - pos) ? (insert - 1) : insert;
}
private int snapPosition(int pos, int[] positions, boolean allowInf, int maxPos) {
if (allowInf && pos >= maxPos + (INF_GAP_STEPS / 2)) {
return SLIDER_STEPS;
}
int idx = nearestIndex(pos, positions);
return positions[idx];
}
private String labelForSliderValue(int pos, int min, int max, boolean allowInf, int[] positions) {
int maxPos = SLIDER_STEPS - INF_GAP_STEPS;
int value = positionToValue(pos, min, max, allowInf, positions, maxPos);
return value == INF_VALUE ? INF_LABEL : String.valueOf(value);
}
}
@@ -73,6 +73,81 @@
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardUseCompatPadding="true"
app:strokeColor="@color/sbt_card_outline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:letterSpacing="0.03"
android:text="@string/debug_section_android_notification_style"
android:textAllCaps="true"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textStyle="bold" />
<TextView
android:id="@+id/debug_notification_style_current"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<TextView
android:id="@+id/debug_notification_style_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/debug_notification_style_hint"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/debug_notification_style_cards"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/debug_notification_style_cards" />
<com.google.android.material.button.MaterialButton
android:id="@+id/debug_notification_style_icons"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/debug_notification_style_icons" />
<com.google.android.material.button.MaterialButton
android:id="@+id/debug_notification_style_dot"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/debug_notification_style_dot" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
+7 -16
View File
@@ -17,6 +17,7 @@
android:textAppearance="?attr/textAppearanceHeadline5" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_master_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -49,6 +50,7 @@
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_clock_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -261,6 +263,7 @@
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_chip_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -474,6 +477,7 @@
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_notif_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -698,18 +702,11 @@
android:paddingEnd="0dp"
android:text="0" />
</LinearLayout>
<CheckBox
android:id="@+id/notif_show_dot_if_truncated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="true"
android:text="@string/layout_show_dot_if_truncated" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_status_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -934,18 +931,11 @@
android:paddingEnd="0dp"
android:text="0" />
</LinearLayout>
<CheckBox
android:id="@+id/status_show_dot_if_truncated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="true"
android:text="@string/layout_show_dot_if_truncated" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_ordering_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -1031,6 +1021,7 @@
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_padding_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -1,650 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/notification_icons_title"
android:textAppearance="?attr/textAppearanceHeadline5" />
<TextView
android:id="@+id/notification_icons_unlocked_obsolete_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/notification_icons_unlocked_obsolete_hint"
android:textAppearance="?attr/textAppearanceBody2"
android:visibility="gone" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/notification_icons_unlocked_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardUseCompatPadding="true"
app:strokeColor="@color/sbt_card_outline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/section_unlocked"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textAllCaps="true"
android:letterSpacing="0.03"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/label_max_icons_per_row"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/unlocked_max_icons_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/unlocked_max_icons_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/unlocked_max_icons_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/unlocked_max_icons_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/notification_icons_layout_required_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/notification_icons_layout_required_hint"
android:textAppearance="?attr/textAppearanceBody2"
android:visibility="gone" />
<LinearLayout
android:id="@+id/notification_icons_layout_sections"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardUseCompatPadding="true"
app:strokeColor="@color/sbt_card_outline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/section_cards_mode"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textAllCaps="true"
android:letterSpacing="0.03"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/section_aod"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/label_max_icons_per_row"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/cards_aod_max_icons_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/cards_aod_max_icons_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/cards_aod_max_icons_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/cards_aod_max_icons_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/label_max_rows"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/cards_aod_max_rows_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/cards_aod_max_rows_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/cards_aod_max_rows_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/cards_aod_max_rows_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/cards_aod_even_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/label_even_distribution" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/cards_aod_limit_width_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/label_limit_screen_width" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/section_lockscreen"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/label_max_icons_per_row"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/cards_lock_max_icons_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/cards_lock_max_icons_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/cards_lock_max_icons_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/cards_lock_max_icons_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/label_max_rows"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/cards_lock_max_rows_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/cards_lock_max_rows_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/cards_lock_max_rows_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/cards_lock_max_rows_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/cards_lock_even_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/label_even_distribution" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/cards_lock_limit_width_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/label_limit_screen_width" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardUseCompatPadding="true"
app:strokeColor="@color/sbt_card_outline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/section_icons_mode"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textAllCaps="true"
android:letterSpacing="0.03"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/section_aod"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/label_max_icons_per_row"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/icons_aod_max_icons_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/icons_aod_max_icons_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/icons_aod_max_icons_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/icons_aod_max_icons_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/label_max_rows"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/icons_aod_max_rows_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/icons_aod_max_rows_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/icons_aod_max_rows_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/icons_aod_max_rows_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/icons_aod_even_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/label_even_distribution" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/icons_aod_cutout_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/label_cutout_aware" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/section_lockscreen"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/label_max_icons_per_row"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/icons_lock_max_icons_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/icons_lock_max_icons_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/icons_lock_max_icons_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/icons_lock_max_icons_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/label_max_rows"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/icons_lock_max_rows_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/icons_lock_max_rows_input"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="3"
android:gravity="center"
android:importantForAutofill="no"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLines="1"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceSubtitle2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/icons_lock_max_rows_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<com.google.android.material.slider.Slider
android:id="@+id/icons_lock_max_rows_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/icons_lock_even_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/label_even_distribution" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/icons_lock_cutout_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/label_cutout_aware" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>
</ScrollView>
+10 -2
View File
@@ -6,9 +6,17 @@
android:checkable="true"
android:title="@string/nav_layout" />
<item
android:id="@+id/nav_notification_icons"
android:id="@+id/nav_layout_unlocked"
android:checkable="true"
android:title="@string/nav_layout_notification_icons" />
android:title="@string/nav_layout_unlocked" />
<item
android:id="@+id/nav_layout_lockscreen"
android:checkable="true"
android:title="@string/nav_layout_lockscreen" />
<item
android:id="@+id/nav_layout_aod"
android:checkable="true"
android:title="@string/nav_layout_aod" />
<item
android:id="@+id/nav_block_app_notification_icons"
android:checkable="true"
+3
View File
@@ -0,0 +1,3 @@
<resources>
<item name="sbt_debug_container_bounds" type="id" />
</resources>
+26 -10
View File
@@ -5,13 +5,14 @@
<string name="nav_close">Close navigation</string>
<string name="nav_icons_debug">Debugging</string>
<string name="nav_system_icons">Hide status icons</string>
<string name="nav_layout_notification_icons">&#160;&#160;&#160;&#160;Notification icons</string>
<string name="nav_layout_hide_notification_icons">&#160;&#160;&#160;&#160;Hide notification icons</string>
<string name="nav_layout_hide_status_icons">&#160;&#160;&#160;&#160;Hide status icons</string>
<string name="nav_clock">Clock</string>
<string name="nav_layout">Layout</string>
<string name="nav_layout_unlocked">&#160;&#160;&#160;&#160;Unlocked</string>
<string name="nav_layout_lockscreen">&#160;&#160;&#160;&#160;Lockscreen</string>
<string name="nav_layout_aod">&#160;&#160;&#160;&#160;AOD</string>
<string name="nav_battery_bar">Battery bar</string>
<string name="nav_notification_icons">Notification icons layout</string>
<string name="nav_block_app_notification_icons">Hide notification icons</string>
<string name="nav_status_chips">Status chips</string>
<string name="nav_misc">Misc</string>
@@ -29,6 +30,15 @@
<string name="debug_restart_systemui">Restart SystemUI</string>
<string name="debug_restart_systemui_started">SystemUI restart requested.</string>
<string name="debug_restart_systemui_failed">Could not restart SystemUI. Root shell failed.</string>
<string name="debug_section_android_notification_style">Android notification style</string>
<string name="debug_notification_style_current">Current: %1$s</string>
<string name="debug_notification_style_hint">Temporary control for Samsung lockscreen/AOD notification style. Writes use root shell.</string>
<string name="debug_notification_style_cards">Cards</string>
<string name="debug_notification_style_icons">Icons</string>
<string name="debug_notification_style_dot">Dot</string>
<string name="debug_notification_style_unknown">unknown</string>
<string name="debug_notification_style_write_failed">Could not write notification style. Root shell failed.</string>
<string name="debug_notification_style_write_done">Notification style changed.</string>
<string name="debug_section_notification_icons">Notification icons</string>
<string name="debug_cycle_icons_label">Cycle debug icons</string>
<string name="debug_cycle_icons_hint">Cycles visible debug icons between 0 and the configured count every 2 seconds.</string>
@@ -151,21 +161,27 @@
<string name="battery_bar_threshold_reordered">That threshold moved to a new position in the list.</string>
<string name="battery_bar_threshold_charge_note">Leave the field empty and press OK for \"Same as discharge\", or use the button below for \"Same as default\".</string>
<string name="clock_title">Clock</string>
<string name="layout_title">Layout</string>
<string name="layout_home_title">Layout</string>
<string name="layout_title">Unlocked</string>
<string name="clock_master_toggles_title">Master toggles</string>
<string name="layout_master_toggle_title">Master toggle</string>
<string name="clock_enabled">Use custom clock</string>
<string name="layout_enabled">Use custom layout</string>
<string name="clock_position_title">Clock position</string>
<string name="layout_clock_position_title">Clock position</string>
<string name="layout_chip_position_title">Status chip position</string>
<string name="layout_notification_position_title">Notification icons position</string>
<string name="layout_status_position_title">Status icons position</string>
<string name="layout_show_dot_if_truncated">Show dot if truncated</string>
<string name="layout_clock_position_title">Clock</string>
<string name="layout_chip_position_title">Status chip</string>
<string name="layout_notification_position_title">Notification icons</string>
<string name="layout_status_position_title">Status icons</string>
<string name="layout_item_enabled">Enabled</string>
<string name="layout_icon_container_count">Number of containers</string>
<string name="layout_locked_notifications_title">Locked notifications mode</string>
<string name="layout_locked_notifications_hint">Mirrors the Samsung notification display style for AOD and lockscreen.</string>
<string name="layout_locked_notifications_none">None</string>
<string name="layout_locked_notifications_none_disabled">None is remembered, but Layout OFF uses Dot.</string>
<string name="layout_misc_title">Misc</string>
<string name="label_max_unlocked_icons_per_row">Max unlocked icons per row</string>
<string name="layout_padding_title">Padding</string>
<string name="layout_subpage_disabled_hint">Requires Layout to be ON</string>
<string name="notification_icons_layout_required_hint">Requires Layout to be ON</string>
<string name="notification_icons_unlocked_obsolete_hint">Obsolete when Layout is ON</string>
<string name="layout_cutout_gap_label">Gap from cutout (px)</string>
<string name="layout_left_padding_label">Left padding (px)</string>
<string name="layout_right_padding_label">Right padding (px)</string>
@@ -0,0 +1,296 @@
package se.ajpanton.statusbartweak.runtime.layoutsolver;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import se.ajpanton.statusbartweak.runtime.layoutsolver.PackedStatusBarLayoutSolver.Box;
import se.ajpanton.statusbartweak.runtime.layoutsolver.PackedStatusBarLayoutSolver.Fallback;
import se.ajpanton.statusbartweak.runtime.layoutsolver.PackedStatusBarLayoutSolver.LayoutItem;
import se.ajpanton.statusbartweak.runtime.layoutsolver.PackedStatusBarLayoutSolver.Position;
import se.ajpanton.statusbartweak.runtime.layoutsolver.PackedStatusBarLayoutSolver.Spec;
public final class PackedStatusBarLayoutSolverTest {
@Test
public void sharedStatusPoolDoesNotExpandAcrossCutout() {
ArrayList<LayoutItem> items = baseItems(30, 2);
PackedStatusBarLayoutSolver.solve(cutoutSpec(), items);
LayoutItem status0 = find(items, "Status0");
LayoutItem status1 = find(items, "Status1");
assertFalse(overlapsCutout(status0));
assertFalse(overlapsCutout(status1));
assertTrue(status0.visibleIcons > 0);
assertTrue(status1.visibleIcons > 0);
assertTrue(status1.dot);
}
@Test
public void sharedNotificationPoolDoesNotExpandAcrossCutout() {
ArrayList<LayoutItem> items = baseItems(3, 40);
PackedStatusBarLayoutSolver.solve(cutoutSpec(), items);
LayoutItem notifs0 = find(items, "Notifs0");
LayoutItem notifs1 = find(items, "Notifs1");
LayoutItem notifs2 = find(items, "Notifs2");
assertFalse(overlapsCutout(notifs0));
assertFalse(overlapsCutout(notifs1));
assertFalse(overlapsCutout(notifs2));
}
@Test
public void zeroTruncationDoesNotExpandMultiBoxClock() {
LayoutItem clock = new LayoutItem(
"Clock",
"Clock",
Position.LEFT,
Fallback.LEFT,
LayoutItem.boxes(
new Box(102, 14, 33, 38),
new Box(0, 32, 134, 47)));
double beforeWidth = clock.width();
double actual = clock.truncateAtLeast(0);
assertTrue(actual == 0);
assertTrue(clock.width() == beforeWidth);
assertTrue(clock.boxes.get(0).width == 33);
assertTrue(clock.boxes.get(1).width == 134);
}
@Test
public void discreteTruncationUsesRealIconWidthsAndWallCompactDot() {
ArrayList<LayoutItem> items = new ArrayList<>();
items.add(new LayoutItem(
"Clock",
"Clock",
Position.LEFT,
Fallback.LEFT,
LayoutItem.boxes(
new Box(102, 14, 33, 38),
new Box(0, 32, 134, 47))));
LayoutItem status = iconItem("Status", "Status", "status", 5, 35, 197, 18, 35);
status.iconWidths = new ArrayList<>(Arrays.asList(35, 35, 35, 35, 57));
status.widthLimit = 197.0;
LayoutItem notifications = iconItem("Notifs", "Notifs", "notifications", 5, 39, 195, 36, 39);
notifications.compactDotWidth = 27;
items.add(status);
items.add(notifications);
PackedStatusBarLayoutSolver.solve(productionSpec(834, 387, 59, 11), items);
assertTrue(status.visibleIcons == 5);
assertFalse(status.dot);
assertTrue(notifications.visibleIcons == 0);
assertTrue(notifications.dot);
assertTrue(notifications.right() <= 387);
assertTrue(notifications.left() - status.right() >= 11);
}
@Test
public void middleLeftItemStacksAgainstCutoutWallInSplitRegion() {
ArrayList<LayoutItem> items = new ArrayList<>();
items.add(new LayoutItem(
"Clock",
"Clock",
Position.LEFT,
Fallback.LEFT,
LayoutItem.boxes(
new Box(102, 14, 33, 38),
new Box(0, 32, 134, 47))));
LayoutItem status = iconItem("Status", "Status", "status", 5, 35, 197, 18, 35);
status.iconWidths = new ArrayList<>(Arrays.asList(35, 35, 40, 34, 53));
status.widthLimit = 197.0;
LayoutItem notifications = new LayoutItem(
"Notifs",
"Notifs",
Position.MIDDLE,
Fallback.LEFT,
LayoutItem.boxes(new Box(0, 36, 156, 39)));
notifications.iconGroup = "notifications";
notifications.iconCount = 4;
notifications.visibleIcons = 4;
notifications.poolRemaining = 4;
notifications.iconWidth = 39;
notifications.iconWidths = new ArrayList<>(Arrays.asList(39, 39, 39, 39));
notifications.dotWidth = 39;
notifications.compactDotWidth = 27;
notifications.allowDot = true;
notifications.allowZero = false;
notifications.widthLimit = 156.0;
items.add(status);
items.add(notifications);
PackedStatusBarLayoutSolver.solve(productionSpec(834, 387, 59, 11), items);
assertTrue(notifications.right() <= 387);
assertTrue(notifications.left() >= status.right() + 11);
}
@Test
public void truncatedDotUsesAvailableIconSlotAndWallCroppedDot() {
LayoutItem notifications = iconItem(
"Notifs",
"Notifs",
"notifications",
2,
39,
78,
36,
39);
notifications.direction = PackedStatusBarLayoutSolver.Direction.RIGHT;
notifications.widthLimit = 70.0;
notifications.recomputeIconState();
assertTrue(notifications.visibleIcons == 1);
assertTrue(notifications.dot);
assertTrue(notifications.width() == 65.0);
}
private static Spec cutoutSpec() {
return new Spec(
1200,
850,
100,
new ArrayList<>(Arrays.asList("Notifs", "Status", "Chip")));
}
private static Spec productionSpec(int screenWidth, int cutoutLeft, int cutoutWidth, int padding) {
return new Spec(
screenWidth,
cutoutLeft,
cutoutWidth,
padding,
new ArrayList<>(Arrays.asList("Notifs", "Status", "Chip", "Clock")));
}
private static LayoutItem iconItem(
String name,
String kind,
String group,
int count,
int iconWidth,
int naturalWidth,
int top,
int height
) {
LayoutItem item = new LayoutItem(
name,
kind,
Position.LEFT,
Fallback.LEFT,
LayoutItem.boxes(new Box(0, top, naturalWidth, height)));
item.iconGroup = group;
item.iconCount = count;
item.visibleIcons = count;
item.poolRemaining = count;
item.iconWidth = iconWidth;
item.dotWidth = iconWidth;
item.compactDotWidth = iconWidth;
item.allowDot = true;
item.allowZero = false;
item.widthLimit = count * (double) iconWidth;
return item;
}
private static ArrayList<LayoutItem> baseItems(int statusIcons, int notificationIcons) {
ArrayList<LayoutItem> items = new ArrayList<>();
LayoutItem clock = new LayoutItem(
"Clock",
"Clock",
Position.LEFT,
Fallback.LEFT,
LayoutItem.boxes(
new Box(0, 0, 240, 40),
new Box(80, -20, 30, 30)));
clock.y = 100;
items.add(clock);
items.add(LayoutItem.fixed(
"Chip",
"Chip",
Position.MIDDLE,
Fallback.RIGHT,
400,
40,
90));
addIcon(items, "Notifs0", "Notifs", Position.LEFT, Fallback.LEFT, "notifications", notificationIcons, 0);
addIcon(items, "Notifs1", "Notifs", Position.LEFT, Fallback.LEFT, "notifications", notificationIcons, 50);
addIcon(items, "Notifs2", "Notifs", Position.LEFT, Fallback.LEFT, "notifications", notificationIcons, 30);
addIcon(items, "Status0", "Status", Position.RIGHT, Fallback.RIGHT, "status", statusIcons, 50);
addIcon(items, "Status1", "Status", Position.RIGHT, Fallback.RIGHT, "status", statusIcons, 100);
items.add(LayoutItem.fixed("Dummy0", "Dummy", Position.MIDDLE, Fallback.LEFT, 100, 30, 50));
items.add(LayoutItem.fixed("Dummy1", "Dummy", Position.MIDDLE, Fallback.LEFT, 100, 30, 0));
items.add(LayoutItem.fixed("Dummy2", "Dummy", Position.MIDDLE, Fallback.LEFT, 100, 30, 20));
configureIconGroups(items);
return sortByKindOrder(items, Arrays.asList("Clock", "Chip", "Status", "Notifs", "Dummy"));
}
private static void addIcon(
ArrayList<LayoutItem> items,
String name,
String kind,
Position position,
Fallback fallback,
String group,
int count,
int y
) {
items.add(LayoutItem.iconStrip(name, kind, position, fallback, group, count, 50, y));
}
private static void configureIconGroups(ArrayList<LayoutItem> items) {
ArrayList<LayoutItem> status = new ArrayList<>();
ArrayList<LayoutItem> notifications = new ArrayList<>();
for (LayoutItem item : items) {
if ("status".equals(item.iconGroup)) {
status.add(item);
} else if ("notifications".equals(item.iconGroup)) {
notifications.add(item);
}
}
configureGroup(status);
configureGroup(notifications);
}
private static void configureGroup(ArrayList<LayoutItem> items) {
for (int i = 0; i < items.size(); i++) {
LayoutItem item = items.get(i);
item.allowZero = i < items.size() - 1;
item.allowDot = i == items.size() - 1;
}
}
private static ArrayList<LayoutItem> sortByKindOrder(ArrayList<LayoutItem> items, List<String> order) {
ArrayList<LayoutItem> sorted = new ArrayList<>(items);
sorted.sort((a, b) -> {
int kind = Integer.compare(order.indexOf(a.kind), order.indexOf(b.kind));
return kind != 0 ? kind : a.name.compareTo(b.name);
});
return sorted;
}
private static LayoutItem find(ArrayList<LayoutItem> items, String name) {
for (LayoutItem item : items) {
if (name.equals(item.name)) {
return item;
}
}
throw new AssertionError("Missing item " + name);
}
private static boolean overlapsCutout(LayoutItem item) {
return Math.max(item.left(), 850) < Math.min(item.right(), 950);
}
}
@@ -0,0 +1,85 @@
package se.ajpanton.statusbartweak.runtime.render;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.util.ArrayList;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.AnchorSide;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.LayoutItemKind;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.LayoutPosition;
public final class UnlockedLayoutBandTest {
@Test
public void compactNotificationDotUsesSolverWidthAfterIconShrinking() {
UnlockedLayoutBand band = UnlockedLayoutBand.icons(
LayoutItemKind.NOTIFICATIONS,
null,
null,
placements(6, 39, 39),
LayoutPosition.LEFT,
false,
AnchorSide.LEFT,
true);
band.applyPackedIconState(0, 0, true, 14);
band.place(388);
assertEquals(1, band.placements.size());
SnapshotPlacement dot = band.placements.get(0);
assertTrue(dot.overflowDot);
assertEquals(14, dot.bounds.width);
assertEquals(388, dot.bounds.left);
assertEquals(14, band.placedBounds.width);
}
@Test
public void packedIconStateUsesRequestedShrinkableIconSlice() {
ArrayList<SnapshotPlacement> placements = placements(6, 39, 39);
placements.add(1, new SnapshotPlacement(
null,
new Bounds(0, 36, 39, 39),
"existing-dot",
false,
true,
null,
new Bounds(0, 0, 39, 39),
0));
UnlockedLayoutBand band = UnlockedLayoutBand.icons(
LayoutItemKind.NOTIFICATIONS,
null,
null,
placements,
LayoutPosition.LEFT,
false,
AnchorSide.LEFT,
true);
band.applyPackedIconState(2, 3, false, 117);
assertEquals(3, band.placements.size());
assertEquals("icon2", band.placements.get(0).key);
assertEquals("icon3", band.placements.get(1).key);
assertEquals("icon4", band.placements.get(2).key);
}
private static ArrayList<SnapshotPlacement> placements(int count, int width, int height) {
ArrayList<SnapshotPlacement> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
Bounds bounds = new Bounds(0, 36, width, height);
result.add(new SnapshotPlacement(
null,
bounds,
"icon" + i,
false,
false,
null,
new Bounds(0, 0, width, height),
0));
}
return result;
}
}