Skip to content

Commit 4a8e9f1

Browse files
committed
feat: Signal Camera settings tab + UiPrefs centralization
1 parent c7edd05 commit 4a8e9f1

17 files changed

Lines changed: 426 additions & 173 deletions

‎app/src/main/java/com/drivehub/kamera/MainActivity.java‎

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal
4444
private static volatile boolean sMainVisible = false;
4545
private static volatile boolean sSettingsDialogOpen = false;
4646
private final SettingsAppearanceController appearanceController = new SettingsAppearanceController(this);
47+
private final SignalCameraSettingsController signalCameraSettingsController =
48+
new SignalCameraSettingsController(this);
4749

4850
private final OtaController otaController = new OtaController(this);
4951

@@ -192,74 +194,86 @@ private void showSettingsDialog() {
192194
SharedPreferences avmPrefs = getSharedPreferences(AVM_PREFS_NAME, MODE_PRIVATE);
193195

194196
Switch swOverlay = dialog.findViewById(R.id.switchOverlayOnSignal);
195-
swOverlay.setChecked(prefs.getBoolean("overlayOnSignal", false));
196-
swOverlay.setOnCheckedChangeListener((btn, checked) -> {
197-
prefs.edit().putBoolean("overlayOnSignal", checked).apply();
198-
if (!checked) OverlayService.hideOverlay(this);
199-
});
200-
201197
Switch swSafetyWarning = dialog.findViewById(R.id.switchSafetyWarning);
202198
swSafetyWarning.setChecked(avmPrefs.getBoolean(KEY_SAFETY_WARNING, true));
203199
swSafetyWarning.setOnCheckedChangeListener((btn, checked) -> {
204200
avmPrefs.edit().putBoolean(KEY_SAFETY_WARNING, checked).apply();
205201
applyWarningVisibility();
206202
});
207203
Switch swAllowBetaUpdates = dialog.findViewById(R.id.switchAllowBetaUpdates);
204+
SeekBar seekOverlayHideDelay = dialog.findViewById(R.id.seekOverlayHideDelay);
205+
EditText etOverlayHideDelayValue = dialog.findViewById(R.id.etOverlayHideDelayValue);
208206

209207
SeekBar seekCorner = dialog.findViewById(R.id.seekCornerRadius);
210208
EditText etCorner = dialog.findViewById(R.id.etCornerRadius);
211209
ImageButton dialogClose = dialog.findViewById(R.id.btnClose);
212210
TextView tabUpdate = dialog.findViewById(R.id.tabUpdate);
213211
TextView tabSettings = dialog.findViewById(R.id.tabSettings);
212+
TextView tabSignalCamera = dialog.findViewById(R.id.tabSignalCamera);
214213
TextView tabOptik = dialog.findViewById(R.id.tabOptik);
215214
TextView tabCredits = dialog.findViewById(R.id.tabCredits);
216215
View sectionUpdate = dialog.findViewById(R.id.sectionUpdate);
217216
View sectionSettings = dialog.findViewById(R.id.sectionSettings);
217+
View sectionSignalCamera = dialog.findViewById(R.id.sectionSignalCamera);
218218
View sectionOptik = dialog.findViewById(R.id.sectionOptik);
219219
View sectionCredits = dialog.findViewById(R.id.sectionCredits);
220220
View accentRow = dialog.findViewById(R.id.rowAccentColor);
221221
View accentPreview = dialog.findViewById(R.id.viewAccentPreview);
222222
EditText etAccentColor = dialog.findViewById(R.id.etAccentColor);
223+
signalCameraSettingsController.bind(
224+
prefs,
225+
swOverlay,
226+
seekOverlayHideDelay,
227+
etOverlayHideDelayValue
228+
);
229+
223230
appearanceController.bindSettingsAppearance(
224231
prefs,
225232
swOverlay,
226233
swSafetyWarning,
227234
swAllowBetaUpdates,
228235
dialogClose,
236+
seekOverlayHideDelay,
229237
seekCorner,
230238
etCorner,
231239
accentRow,
232240
accentPreview,
233241
etAccentColor,
234242
tabUpdate,
235243
tabSettings,
244+
tabSignalCamera,
236245
tabOptik,
237246
tabCredits
238247
);
239248

240-
bindSettingsTab(tabUpdate, tabSettings, tabOptik, tabCredits,
241-
sectionUpdate, sectionSettings, sectionOptik, sectionCredits, 1);
249+
bindSettingsTab(tabUpdate, tabSettings, tabSignalCamera, tabOptik, tabCredits,
250+
sectionUpdate, sectionSettings, sectionSignalCamera, sectionOptik, sectionCredits, 1);
242251
appearanceController.reapplyForActiveTab(1);
243252
tabUpdate.setOnClickListener(v -> {
244-
bindSettingsTab(tabUpdate, tabSettings, tabOptik, tabCredits,
245-
sectionUpdate, sectionSettings, sectionOptik, sectionCredits, 0);
253+
bindSettingsTab(tabUpdate, tabSettings, tabSignalCamera, tabOptik, tabCredits,
254+
sectionUpdate, sectionSettings, sectionSignalCamera, sectionOptik, sectionCredits, 0);
246255
appearanceController.reapplyForActiveTab(0);
247256
});
248257
tabSettings.setOnClickListener(v -> {
249-
bindSettingsTab(tabUpdate, tabSettings, tabOptik, tabCredits,
250-
sectionUpdate, sectionSettings, sectionOptik, sectionCredits, 1);
258+
bindSettingsTab(tabUpdate, tabSettings, tabSignalCamera, tabOptik, tabCredits,
259+
sectionUpdate, sectionSettings, sectionSignalCamera, sectionOptik, sectionCredits, 1);
251260
appearanceController.reapplyForActiveTab(1);
252261
});
253-
tabOptik.setOnClickListener(v -> {
254-
bindSettingsTab(tabUpdate, tabSettings, tabOptik, tabCredits,
255-
sectionUpdate, sectionSettings, sectionOptik, sectionCredits, 2);
262+
tabSignalCamera.setOnClickListener(v -> {
263+
bindSettingsTab(tabUpdate, tabSettings, tabSignalCamera, tabOptik, tabCredits,
264+
sectionUpdate, sectionSettings, sectionSignalCamera, sectionOptik, sectionCredits, 2);
256265
appearanceController.reapplyForActiveTab(2);
257266
});
258-
tabCredits.setOnClickListener(v -> {
259-
bindSettingsTab(tabUpdate, tabSettings, tabOptik, tabCredits,
260-
sectionUpdate, sectionSettings, sectionOptik, sectionCredits, 3);
267+
tabOptik.setOnClickListener(v -> {
268+
bindSettingsTab(tabUpdate, tabSettings, tabSignalCamera, tabOptik, tabCredits,
269+
sectionUpdate, sectionSettings, sectionSignalCamera, sectionOptik, sectionCredits, 3);
261270
appearanceController.reapplyForActiveTab(3);
262271
});
272+
tabCredits.setOnClickListener(v -> {
273+
bindSettingsTab(tabUpdate, tabSettings, tabSignalCamera, tabOptik, tabCredits,
274+
sectionUpdate, sectionSettings, sectionSignalCamera, sectionOptik, sectionCredits, 4);
275+
appearanceController.reapplyForActiveTab(4);
276+
});
263277

264278
TextView tvVersion = dialog.findViewById(R.id.tvDialogVersion);
265279
TextView tvBeta = dialog.findViewById(R.id.tvDialogVersionBeta);
@@ -298,18 +312,20 @@ private void showSettingsDialog() {
298312
}
299313

300314
private void bindSettingsTab(
301-
TextView tabUpdate, TextView tabSettings, TextView tabOptik, TextView tabCredits,
302-
View sectionUpdate, View sectionSettings, View sectionOptik, View sectionCredits,
315+
TextView tabUpdate, TextView tabSettings, TextView tabSignalCamera, TextView tabOptik, TextView tabCredits,
316+
View sectionUpdate, View sectionSettings, View sectionSignalCamera, View sectionOptik, View sectionCredits,
303317
int active
304318
) {
305319
sectionUpdate.setVisibility(active == 0 ? View.VISIBLE : View.GONE);
306320
sectionSettings.setVisibility(active == 1 ? View.VISIBLE : View.GONE);
307-
sectionOptik.setVisibility(active == 2 ? View.VISIBLE : View.GONE);
308-
sectionCredits.setVisibility(active == 3 ? View.VISIBLE : View.GONE);
321+
sectionSignalCamera.setVisibility(active == 2 ? View.VISIBLE : View.GONE);
322+
sectionOptik.setVisibility(active == 3 ? View.VISIBLE : View.GONE);
323+
sectionCredits.setVisibility(active == 4 ? View.VISIBLE : View.GONE);
309324
styleSettingsTab(tabUpdate, active == 0);
310325
styleSettingsTab(tabSettings, active == 1);
311-
styleSettingsTab(tabOptik, active == 2);
312-
styleSettingsTab(tabCredits, active == 3);
326+
styleSettingsTab(tabSignalCamera, active == 2);
327+
styleSettingsTab(tabOptik, active == 3);
328+
styleSettingsTab(tabCredits, active == 4);
313329
}
314330

315331
private void styleSettingsTab(TextView tab, boolean active) {

‎app/src/main/java/com/drivehub/kamera/SettingsAppearanceController.java‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ final class SettingsAppearanceController {
2929
private Switch swSafetyWarning;
3030
private Switch swAllowBetaUpdates;
3131
private ImageButton dialogClose;
32+
private SeekBar seekOverlayHideDelay;
3233
private SeekBar seekCorner;
3334
private EditText etCorner;
3435
private View accentRow;
3536
private View accentPreview;
3637
private EditText etAccentColor;
3738
private TextView tabUpdate;
3839
private TextView tabSettings;
40+
private TextView tabSignalCamera;
3941
private TextView tabOptik;
4042
private TextView tabCredits;
4143
private int activeTab;
@@ -57,13 +59,15 @@ void bindSettingsAppearance(
5759
Switch swSafetyWarning,
5860
Switch swAllowBetaUpdates,
5961
ImageButton dialogClose,
62+
SeekBar seekOverlayHideDelay,
6063
SeekBar seekCorner,
6164
EditText etCorner,
6265
View accentRow,
6366
View accentPreview,
6467
EditText etAccentColor,
6568
TextView tabUpdate,
6669
TextView tabSettings,
70+
TextView tabSignalCamera,
6771
TextView tabOptik,
6872
TextView tabCredits
6973
) {
@@ -72,13 +76,15 @@ void bindSettingsAppearance(
7276
this.swSafetyWarning = swSafetyWarning;
7377
this.swAllowBetaUpdates = swAllowBetaUpdates;
7478
this.dialogClose = dialogClose;
79+
this.seekOverlayHideDelay = seekOverlayHideDelay;
7580
this.seekCorner = seekCorner;
7681
this.etCorner = etCorner;
7782
this.accentRow = accentRow;
7883
this.accentPreview = accentPreview;
7984
this.etAccentColor = etAccentColor;
8085
this.tabUpdate = tabUpdate;
8186
this.tabSettings = tabSettings;
87+
this.tabSignalCamera = tabSignalCamera;
8288
this.tabOptik = tabOptik;
8389
this.tabCredits = tabCredits;
8490
this.activeTab = 1;
@@ -268,6 +274,10 @@ private void applyAccentColorToSettingsDialog(int accentColor) {
268274
seekCorner.setProgressTintList(ColorStateList.valueOf(accentColor));
269275
seekCorner.setThumbTintList(ColorStateList.valueOf(accentColor));
270276
}
277+
if (seekOverlayHideDelay != null) {
278+
seekOverlayHideDelay.setProgressTintList(ColorStateList.valueOf(accentColor));
279+
seekOverlayHideDelay.setThumbTintList(ColorStateList.valueOf(accentColor));
280+
}
271281
if (swOverlay != null) {
272282
swOverlay.setTrackTintList(buildToggleTrackTint(accentColor));
273283
}
@@ -285,8 +295,9 @@ private void applyAccentColorToSettingsDialog(int accentColor) {
285295
}
286296
if (tabUpdate != null) tabUpdate.setTextColor(activeTab == 0 ? accentColor : 0xFF777777);
287297
if (tabSettings != null) tabSettings.setTextColor(activeTab == 1 ? accentColor : 0xFF777777);
288-
if (tabOptik != null) tabOptik.setTextColor(activeTab == 2 ? accentColor : 0xFF777777);
289-
if (tabCredits != null) tabCredits.setTextColor(activeTab == 3 ? accentColor : 0xFF777777);
298+
if (tabSignalCamera != null) tabSignalCamera.setTextColor(activeTab == 2 ? accentColor : 0xFF777777);
299+
if (tabOptik != null) tabOptik.setTextColor(activeTab == 3 ? accentColor : 0xFF777777);
300+
if (tabCredits != null) tabCredits.setTextColor(activeTab == 4 ? accentColor : 0xFF777777);
290301
}
291302

292303
private ColorStateList buildToggleTrackTint(int accentColor) {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.drivehub.kamera;
2+
3+
import android.content.SharedPreferences;
4+
import android.widget.EditText;
5+
import android.widget.SeekBar;
6+
import android.widget.Switch;
7+
8+
final class SignalCameraSettingsController {
9+
10+
private final MainActivity activity;
11+
12+
SignalCameraSettingsController(MainActivity activity) {
13+
this.activity = activity;
14+
}
15+
16+
void bind(
17+
SharedPreferences prefs,
18+
Switch swOverlay,
19+
SeekBar seekOverlayHideDelay,
20+
EditText etOverlayHideDelayValue
21+
) {
22+
if (swOverlay != null) {
23+
swOverlay.setChecked(prefs.getBoolean(UiPrefs.KEY_OVERLAY_ON_SIGNAL, false));
24+
swOverlay.setOnCheckedChangeListener((btn, checked) -> {
25+
prefs.edit().putBoolean(UiPrefs.KEY_OVERLAY_ON_SIGNAL, checked).apply();
26+
if (!checked) {
27+
OverlayService.hideOverlay(activity);
28+
}
29+
});
30+
}
31+
32+
if (seekOverlayHideDelay == null) return;
33+
34+
// Slider and text input update each other, so guard programmatic changes to avoid loops.
35+
final boolean[] syncingOverlayHideDelay = {false};
36+
int savedOverlayHideDelayMs = UiPrefs.getOverlayHideDelayMs(prefs);
37+
seekOverlayHideDelay.setMax(UiPrefs.MAX_OVERLAY_HIDE_DELAY_MS / UiPrefs.OVERLAY_HIDE_DELAY_STEP_MS);
38+
seekOverlayHideDelay.setProgress(savedOverlayHideDelayMs / UiPrefs.OVERLAY_HIDE_DELAY_STEP_MS);
39+
40+
if (etOverlayHideDelayValue != null) {
41+
etOverlayHideDelayValue.setText(String.valueOf(savedOverlayHideDelayMs));
42+
etOverlayHideDelayValue.setSelection(etOverlayHideDelayValue.getText().length());
43+
etOverlayHideDelayValue.addTextChangedListener(new SimpleTextWatcher() {
44+
@Override
45+
public void afterTextChanged(android.text.Editable s) {
46+
if (syncingOverlayHideDelay[0] || s == null) return;
47+
String text = s.toString().trim();
48+
if (text.isEmpty()) return;
49+
try {
50+
int delayMs = UiPrefs.clampOverlayHideDelayMs(Integer.parseInt(text));
51+
prefs.edit().putLong(UiPrefs.KEY_OVERLAY_HIDE_DELAY_MS, delayMs).apply();
52+
int progress = delayMs / UiPrefs.OVERLAY_HIDE_DELAY_STEP_MS;
53+
if (seekOverlayHideDelay.getProgress() != progress) {
54+
seekOverlayHideDelay.setProgress(progress);
55+
}
56+
} catch (NumberFormatException ignored) {
57+
}
58+
}
59+
});
60+
etOverlayHideDelayValue.setOnFocusChangeListener((v, hasFocus) -> {
61+
if (hasFocus) return;
62+
// Normalize free-form keyboard input on blur so the stored value always stays valid.
63+
String text = etOverlayHideDelayValue.getText() == null
64+
? ""
65+
: etOverlayHideDelayValue.getText().toString().trim();
66+
int delayMs;
67+
try {
68+
delayMs = text.isEmpty() ? UiPrefs.getOverlayHideDelayMs(prefs) : Integer.parseInt(text);
69+
} catch (NumberFormatException ignored) {
70+
delayMs = UiPrefs.getOverlayHideDelayMs(prefs);
71+
}
72+
delayMs = UiPrefs.clampOverlayHideDelayMs(delayMs);
73+
prefs.edit().putLong(UiPrefs.KEY_OVERLAY_HIDE_DELAY_MS, delayMs).apply();
74+
syncingOverlayHideDelay[0] = true;
75+
etOverlayHideDelayValue.setText(String.valueOf(delayMs));
76+
etOverlayHideDelayValue.setSelection(etOverlayHideDelayValue.getText().length());
77+
syncingOverlayHideDelay[0] = false;
78+
int progress = delayMs / UiPrefs.OVERLAY_HIDE_DELAY_STEP_MS;
79+
if (seekOverlayHideDelay.getProgress() != progress) {
80+
seekOverlayHideDelay.setProgress(progress);
81+
}
82+
});
83+
}
84+
85+
seekOverlayHideDelay.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
86+
@Override
87+
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
88+
int delayMs = UiPrefs.clampOverlayHideDelayMs(progress * UiPrefs.OVERLAY_HIDE_DELAY_STEP_MS);
89+
prefs.edit().putLong(UiPrefs.KEY_OVERLAY_HIDE_DELAY_MS, delayMs).apply();
90+
if (etOverlayHideDelayValue == null) return;
91+
String currentValue = etOverlayHideDelayValue.getText() == null
92+
? ""
93+
: etOverlayHideDelayValue.getText().toString();
94+
String nextValue = String.valueOf(delayMs);
95+
if (!nextValue.equals(currentValue)) {
96+
syncingOverlayHideDelay[0] = true;
97+
etOverlayHideDelayValue.setText(nextValue);
98+
etOverlayHideDelayValue.setSelection(etOverlayHideDelayValue.getText().length());
99+
syncingOverlayHideDelay[0] = false;
100+
}
101+
}
102+
103+
@Override
104+
public void onStartTrackingTouch(SeekBar seekBar) {
105+
}
106+
107+
@Override
108+
public void onStopTrackingTouch(SeekBar seekBar) {
109+
}
110+
});
111+
}
112+
113+
// Helper base so the controller only overrides the callback it actually needs.
114+
private abstract static class SimpleTextWatcher implements android.text.TextWatcher {
115+
@Override
116+
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
117+
}
118+
119+
@Override
120+
public void onTextChanged(CharSequence s, int start, int before, int count) {
121+
}
122+
}
123+
}

‎app/src/main/java/com/drivehub/kamera/SignalService.java‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public class SignalService extends Service {
5757
private int currentMode = -1; // -1:init, 0:none, 1:left, 2:right, 3:reverse
5858

5959
private static final String PREFS_NAME = "rec_prefs";
60-
private static final String KEY_OVERLAY_HIDE_DELAY_MS = "overlayHideDelayMs";
6160
private final Handler mainHandler = new Handler();
6261
private final Runnable hideRunnable = new Runnable() {
6362
@Override
@@ -301,7 +300,7 @@ private void cacheGear(int g) {
301300
private boolean isOverlayEnabled() {
302301
try {
303302
SharedPreferences sp = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
304-
return sp.getBoolean("overlayOnSignal", false);
303+
return sp.getBoolean(UiPrefs.KEY_OVERLAY_ON_SIGNAL, false);
305304
} catch (Throwable t) {
306305
return false;
307306
}
@@ -310,10 +309,7 @@ private boolean isOverlayEnabled() {
310309
private long readOverlayHideDelayMs() {
311310
try {
312311
SharedPreferences sp = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
313-
long v = sp.getLong(KEY_OVERLAY_HIDE_DELAY_MS, 1500L);
314-
if (v < 0) v = 0;
315-
if (v > 30000L) v = 30000L;
316-
return v;
312+
return UiPrefs.getOverlayHideDelayMs(sp);
317313
} catch (Throwable ignored) {
318314
return 1500L;
319315
}

‎app/src/main/java/com/drivehub/kamera/UiPrefs.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ final class UiPrefs {
1010
static final String KEY_TILE_CORNER_RADIUS = "tileCornerRadius";
1111
static final String KEY_ACCENT_COLOR = "accentColor";
1212
static final String KEY_ALLOW_BETA_UPDATES = "allowBetaUpdates";
13+
static final String KEY_OVERLAY_ON_SIGNAL = "overlayOnSignal";
14+
static final String KEY_OVERLAY_HIDE_DELAY_MS = "overlayHideDelayMs";
1315
static final int MAX_TILE_CORNER_RADIUS = 35;
16+
static final int MAX_OVERLAY_HIDE_DELAY_MS = 3000;
17+
static final int OVERLAY_HIDE_DELAY_STEP_MS = 100;
1418
private static final int DEFAULT_TILE_CORNER_RADIUS = 16;
19+
private static final int DEFAULT_OVERLAY_HIDE_DELAY_MS = 1500;
1520
private static final String DEFAULT_ACCENT_COLOR = "#E7E7E7";
1621

1722
private UiPrefs() {
@@ -29,6 +34,11 @@ static boolean isBetaUpdatesEnabled(SharedPreferences prefs) {
2934
return prefs.getBoolean(KEY_ALLOW_BETA_UPDATES, false);
3035
}
3136

37+
static int getOverlayHideDelayMs(SharedPreferences prefs) {
38+
long value = prefs.getLong(KEY_OVERLAY_HIDE_DELAY_MS, DEFAULT_OVERLAY_HIDE_DELAY_MS);
39+
return clampOverlayHideDelayMs((int) value);
40+
}
41+
3242
static float getCornerRadiusFraction(SharedPreferences prefs) {
3343
return clampRadiusSetting(getTileCornerRadiusSetting(prefs)) / (float) MAX_TILE_CORNER_RADIUS;
3444
}
@@ -95,4 +105,8 @@ static boolean isLightColor(int color) {
95105
private static int clampRadiusSetting(int value) {
96106
return Math.max(0, Math.min(MAX_TILE_CORNER_RADIUS, value));
97107
}
108+
109+
static int clampOverlayHideDelayMs(int value) {
110+
return Math.max(0, Math.min(MAX_OVERLAY_HIDE_DELAY_MS, value));
111+
}
98112
}

0 commit comments

Comments
 (0)