Skip to content

Commit 1d1a2f3

Browse files
committed
UI/UX improvements: focus indicators, icon cache fix, layout changes
- Added drop shadow to app card selection for visibility on light backgrounds - Created consistent focus indicators for settings and network widgets - Fixed icon cache not clearing on app uninstall (PACKAGE_REMOVED event) - Moved settings icon to left of network indicator - Fixed date/time dialog autofocus to start on first item - Changed GestureDetector to InkWell for TV remote compatibility - Matched settings icon padding with network indicator size
1 parent 6a3ac65 commit 1d1a2f3

4 files changed

Lines changed: 125 additions & 38 deletions

File tree

lib/providers/apps_service.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ class AppsService extends ChangeNotifier
9898
String packageName = event['packageName'];
9999
await _database.deleteApps([packageName]);
100100

101+
// Clear icon cache for removed app
102+
_iconCache.remove(packageName);
103+
_bannerCache.remove(packageName);
104+
101105
App? application = _applications.remove(packageName);
102106

103107
if (application != null) {

lib/widgets/app_card.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ class _AppCardState extends State<AppCard> with SingleTickerProviderStateMixin {
152152
color: Colors.white.withAlpha(_animation.value.round()),
153153
width: 3
154154
),
155+
boxShadow: [
156+
BoxShadow(
157+
color: Colors.black.withAlpha((_animation.value * 0.5).round()),
158+
blurRadius: 8,
159+
spreadRadius: 1,
160+
),
161+
],
155162
),
156163
),
157164
),
@@ -167,6 +174,13 @@ class _AppCardState extends State<AppCard> with SingleTickerProviderStateMixin {
167174
color: Colors.white,
168175
width: 3
169176
),
177+
boxShadow: const [
178+
BoxShadow(
179+
color: Colors.black54,
180+
blurRadius: 8,
181+
spreadRadius: 1,
182+
),
183+
],
170184
),
171185
),
172186
);

lib/widgets/focus_aware_app_bar.dart

Lines changed: 102 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -47,37 +47,37 @@ class _FocusAwareAppBarState extends State<FocusAwareAppBar>
4747
return widget!;
4848
},
4949
child: AppBar(
50-
title: Selector<SettingsService, bool>(
51-
selector: (_, settings) => settings.showNetworkIndicatorInStatusBar,
52-
builder: (context, showNetwork, _) => Selector<SettingsService, bool>(
53-
selector: (_, settings) => settings.showWifiWidgetInStatusBar,
54-
builder: (context, showWifi, _) => Row(
55-
mainAxisSize: MainAxisSize.min,
56-
children: [
57-
if (showNetwork)
58-
const Padding(
59-
padding: EdgeInsets.only(right: 12),
60-
child: NetworkWidget(),
61-
),
62-
if (showWifi) const DailyWifiUsageWidget(),
63-
],
50+
// Left side: Settings, Network indicator, WiFi usage
51+
title: Row(
52+
mainAxisSize: MainAxisSize.min,
53+
children: [
54+
// Settings button (moved to left side)
55+
_FocusableIconButton(
56+
icon: Icons.settings_outlined,
57+
onPressed: () => showDialog(context: context, builder: (_) => const SettingsPanel()),
6458
),
65-
),
59+
const SizedBox(width: 16),
60+
// Network indicator (conditionally shown)
61+
Selector<SettingsService, bool>(
62+
selector: (_, settings) => settings.showNetworkIndicatorInStatusBar,
63+
builder: (context, showNetwork, _) => showNetwork
64+
? Padding(
65+
padding: const EdgeInsets.only(right: 12),
66+
child: _FocusableNetworkWidget(),
67+
)
68+
: const SizedBox.shrink(),
69+
),
70+
// WiFi usage widget
71+
Selector<SettingsService, bool>(
72+
selector: (_, settings) => settings.showWifiWidgetInStatusBar,
73+
builder: (context, showWifi, _) => showWifi
74+
? const DailyWifiUsageWidget()
75+
: const SizedBox.shrink(),
76+
),
77+
],
6678
),
79+
// Right side: Date/Time only
6780
actions: [
68-
IconButton(
69-
padding: const EdgeInsets.all(2),
70-
constraints: const BoxConstraints(),
71-
splashRadius: 20,
72-
icon: const Icon(Icons.settings_outlined,
73-
shadows: [
74-
Shadow(color: Colors.black54, blurRadius: 8, offset: Offset(0, 2))
75-
],
76-
),
77-
onPressed: () => showDialog(context: context, builder: (_) => const SettingsPanel()),
78-
// sometime after Flutter 3.7.5, no later than 3.16.8, the focus highlight went away
79-
focusColor: Theme.of(context).primaryColorLight,
80-
),
8181
Padding(
8282
padding: const EdgeInsets.only(left: 16, right: 32),
8383
child: Selector<SettingsService,
@@ -92,16 +92,13 @@ class _FocusAwareAppBarState extends State<FocusAwareAppBar>
9292
dateFormat: service.dateFormat,
9393
timeFormat: service.timeFormat),
9494
builder: (context, dateTimeSettings, _) {
95-
// TODO: Disabling the "show date" option while both are enabled causes the *time* to disappear,
96-
// then re-enabling that same option causes the time to appear twice.
97-
// A restart (or just changing to the full screen clock) fixes the issue, but why does this happen?
9895
return Row(
9996
mainAxisSize: MainAxisSize.min,
10097
children: [
10198
if (dateTimeSettings.showDateInStatusBar)
10299
Flexible(
103100
child: DateTimeWidget(dateTimeSettings.dateFormat,
104-
key: const ValueKey('date'), // Unique key for date widget
101+
key: const ValueKey('date'),
105102
updateInterval: const Duration(minutes: 1),
106103
textStyle: Theme.of(context).textTheme.titleLarge!.copyWith(
107104
shadows: [
@@ -115,7 +112,7 @@ class _FocusAwareAppBarState extends State<FocusAwareAppBar>
115112
if (dateTimeSettings.showTimeInStatusBar)
116113
Flexible(
117114
child: DateTimeWidget(dateTimeSettings.timeFormat,
118-
key: const ValueKey('time'), // Unique key for time widget
115+
key: const ValueKey('time'),
119116
textStyle: Theme.of(context).textTheme.titleLarge!.copyWith(
120117
shadows: [
121118
const Shadow(color: Colors.black54, offset: Offset(0, 2), blurRadius: 8)
@@ -132,4 +129,76 @@ class _FocusAwareAppBarState extends State<FocusAwareAppBar>
132129
)
133130
);
134131
}
132+
}
133+
134+
/// Reusable focusable icon button with consistent outline focus indicator
135+
class _FocusableIconButton extends StatefulWidget {
136+
final IconData icon;
137+
final VoidCallback onPressed;
138+
139+
const _FocusableIconButton({required this.icon, required this.onPressed});
140+
141+
@override
142+
State<_FocusableIconButton> createState() => _FocusableIconButtonState();
143+
}
144+
145+
class _FocusableIconButtonState extends State<_FocusableIconButton> {
146+
bool _focused = false;
147+
148+
@override
149+
Widget build(BuildContext context) {
150+
return Focus(
151+
onFocusChange: (hasFocus) => setState(() => _focused = hasFocus),
152+
child: InkWell(
153+
onTap: widget.onPressed,
154+
borderRadius: BorderRadius.circular(8),
155+
child: Container(
156+
padding: const EdgeInsets.all(4), // Match network indicator padding
157+
decoration: BoxDecoration(
158+
borderRadius: BorderRadius.circular(8),
159+
border: _focused
160+
? Border.all(color: Colors.white, width: 2)
161+
: null,
162+
boxShadow: _focused
163+
? const [BoxShadow(color: Colors.black54, blurRadius: 8, spreadRadius: 1)]
164+
: null,
165+
),
166+
child: Icon(widget.icon,
167+
shadows: const [
168+
Shadow(color: Colors.black54, blurRadius: 8, offset: Offset(0, 2))
169+
],
170+
),
171+
),
172+
),
173+
);
174+
}
175+
}
176+
177+
/// Network widget with consistent focus indicator
178+
class _FocusableNetworkWidget extends StatefulWidget {
179+
@override
180+
State<_FocusableNetworkWidget> createState() => _FocusableNetworkWidgetState();
181+
}
182+
183+
class _FocusableNetworkWidgetState extends State<_FocusableNetworkWidget> {
184+
bool _focused = false;
185+
186+
@override
187+
Widget build(BuildContext context) {
188+
return Focus(
189+
onFocusChange: (hasFocus) => setState(() => _focused = hasFocus),
190+
child: Container(
191+
decoration: BoxDecoration(
192+
borderRadius: BorderRadius.circular(8),
193+
border: _focused
194+
? Border.all(color: Colors.white, width: 2)
195+
: null,
196+
boxShadow: _focused
197+
? const [BoxShadow(color: Colors.black54, blurRadius: 8, spreadRadius: 1)]
198+
: null,
199+
),
200+
child: const NetworkWidget(),
201+
),
202+
);
203+
}
135204
}

lib/widgets/settings/date_time_format_dialog.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ class _DateTimeFormatDialogState extends State<DateTimeFormatDialog> {
8686
),
8787
const SizedBox(height: 8),
8888

89-
...dateFormatPresets.map((preset) => RadioListTile<String>(
89+
...dateFormatPresets.asMap().entries.map((entry) => RadioListTile<String>(
90+
autofocus: entry.key == 0, // Autofocus first item
9091
dense: true,
91-
title: Text(preset.$2), // Example display
92-
subtitle: Text(preset.$1, style: TextStyle(fontSize: 12, color: Colors.grey)),
93-
value: preset.$1,
92+
title: Text(entry.value.$2), // Example display
93+
subtitle: Text(entry.value.$1, style: TextStyle(fontSize: 12, color: Colors.grey)),
94+
value: entry.value.$1,
9495
groupValue: _selectedDateFormat,
9596
onChanged: (value) {
9697
setState(() => _selectedDateFormat = value!);
@@ -124,7 +125,6 @@ class _DateTimeFormatDialogState extends State<DateTimeFormatDialog> {
124125
Align(
125126
alignment: Alignment.centerRight,
126127
child: TextButton(
127-
autofocus: true,
128128
onPressed: () {
129129
Navigator.pop(context, (_selectedDateFormat, _selectedTimeFormat));
130130
},

0 commit comments

Comments
 (0)