Skip to content

Commit d6fb323

Browse files
committed
Release: v2026.01.001 - Focus Enhancements & Settings Refinement
1 parent c70b771 commit d6fb323

39 files changed

+2885
-1505
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ app.*.map.json
4444
/android/app/debug
4545
/android/app/profile
4646
/android/app/release
47+
/android/app/build
4748
/android/build
4849

4950
android/app/google-services.json

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"java.configuration.updateBuildConfiguration": "automatic"
2+
"java.configuration.updateBuildConfiguration": "automatic",
3+
"java.compile.nullAnalysis.mode": "automatic"
34
}

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ This customized version introduces usability enhancements and some UX improvemen
1919
- **Easy WiFi Access** - Network indicator doubles as a shortcut to system WiFi settings.
2020
- **Quick Presets** - Select Time/Date formats and Category names from a list (No keyboard required).
2121
- **Pitch Black Wallpaper** - Added a true black gradient background option.
22-
- **Easier D-Pad Navigation** - High-contrast focus outlines for better visibility on TV.
22+
- **Enhanced Focus Indicator** - New double-border design ensures perfect visibility on any background.
23+
- **Smart Navigation** - Fixed "bounce back" issues and optimized focus traversal for a smoother experience.
24+
- **Refined Settings** - Reorganized menus with a new "Miscellaneous" section and unified focus styles.
2325
- **New Categories** - Added "All Apps" and "Favorites" with auto-population support.
24-
- **UI Refinement** - Polished interface with cleaner typography and unified terminology.
2526
- **Optimizations** - Improved performance with aggressive icon caching and code cleanups.
2627

2728

android/app/src/main/java/me/efesser/flauncher/MainActivity.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,14 @@ private boolean openScreensaverSettings() {
590590
return true;
591591
}
592592

593-
// 3. Final fallback - open main settings
593+
// 3. FALLBACK: Try Display Settings (often contains screensaver on newer
594+
// Android TV/Google TV)
595+
Intent displayIntent = new Intent(Settings.ACTION_DISPLAY_SETTINGS);
596+
if (tryStartActivity(displayIntent)) {
597+
return true;
598+
}
599+
600+
// 4. Final fallback - open main settings
594601
return launchActivityFromAction(Settings.ACTION_SETTINGS);
595602
}
596603

android/settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pluginManagement {
3737
plugins {
3838
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
3939
id "com.android.application" version '8.2.1' apply false
40+
id "org.jetbrains.kotlin.android" version "1.9.22" apply false
4041
}
4142

4243
include ":app"

lib/actions.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ class BackAction extends Action<BackIntent> {
6565
}
6666
}
6767

68+
class MoveFocusToSettingsIntent extends Intent {
69+
const MoveFocusToSettingsIntent();
70+
}
71+
6872
class BackIntent extends Intent {
6973
const BackIntent();
7074
}

lib/flauncher.dart

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919

20+
import 'package:flauncher/actions.dart';
2021
import 'package:flauncher/custom_traversal_policy.dart';
2122
import 'package:flauncher/providers/apps_service.dart';
2223
import 'package:flauncher/providers/launcher_state.dart';
@@ -31,78 +32,107 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3132

3233
import 'models/category.dart';
3334

34-
class FLauncher extends StatelessWidget {
35-
const FLauncher();
35+
class FLauncher extends StatefulWidget {
36+
const FLauncher({super.key});
3637

3738
@override
38-
Widget build(BuildContext context) => FocusTraversalGroup(
39-
policy: RowByRowTraversalPolicy(),
40-
child: Stack(
41-
children: [
42-
Consumer<WallpaperService>(
43-
builder: (_, wallpaperService, __) => _wallpaper(context, wallpaperService)
44-
),
45-
Consumer<LauncherState>(
46-
builder: (_, state, child) => Visibility(
47-
child: child!,
48-
replacement: const Center(
49-
child: AlternativeLauncherView()
50-
),
51-
visible: state.launcherVisible
39+
State<FLauncher> createState() => _FLauncherState();
40+
}
41+
42+
class _FLauncherState extends State<FLauncher> {
43+
final GlobalKey<FocusAwareAppBarState> _appBarKey = GlobalKey();
44+
45+
@override
46+
Widget build(BuildContext context) => Actions(
47+
actions: <Type, Action<Intent>>{
48+
MoveFocusToSettingsIntent: CallbackAction<MoveFocusToSettingsIntent>(
49+
onInvoke: (_) => _appBarKey.currentState?.focusSettings(),
50+
),
51+
},
52+
child: FocusTraversalGroup(
53+
policy: RowByRowTraversalPolicy(),
54+
child: Stack(
55+
children: [
56+
Consumer<WallpaperService>(
57+
builder: (_, wallpaperService, __) => _wallpaper(context, wallpaperService)
5258
),
53-
child: Scaffold(
54-
backgroundColor: Colors.transparent,
55-
appBar: FocusAwareAppBar(),
56-
body: Padding(
57-
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
58-
child: Consumer<AppsService>(
59-
builder: (context, appsService, _) {
60-
if (appsService.initialized) {
61-
return SingleChildScrollView(child: _sections(appsService.launcherSections));
62-
}
63-
else {
64-
return _emptyState(context);
59+
Consumer<LauncherState>(
60+
builder: (_, state, child) => Visibility(
61+
child: child!,
62+
replacement: const Center(
63+
child: AlternativeLauncherView()
64+
),
65+
visible: state.launcherVisible
66+
),
67+
child: Scaffold(
68+
backgroundColor: Colors.transparent,
69+
appBar: FocusAwareAppBar(key: _appBarKey),
70+
body: Padding(
71+
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
72+
child: Consumer<AppsService>(
73+
builder: (context, appsService, _) {
74+
if (appsService.initialized) {
75+
return SingleChildScrollView(child: _sections(appsService.launcherSections));
76+
}
77+
else {
78+
return _emptyState(context);
79+
}
6580
}
66-
}
81+
)
6782
)
6883
)
6984
)
70-
)
71-
]
72-
)
85+
]
86+
)
87+
),
7388
);
7489

75-
Widget _sections(List<LauncherSection> sections) => Column(
76-
children: sections.map((section) {
90+
Widget _sections(List<LauncherSection> sections) {
91+
List<Widget> children = [];
92+
bool firstCategoryFound = false;
93+
94+
for (var section in sections) {
7795
final Key sectionKey = Key(section.id.toString());
78-
final Widget categoryWidget;
7996

8097
if (section is LauncherSpacer) {
81-
return SizedBox(key: sectionKey, height: section.height.toDouble());
98+
children.add(SizedBox(key: sectionKey, height: section.height.toDouble()));
99+
continue;
82100
}
83101

84102
Category category = section as Category;
103+
Widget categoryWidget;
104+
105+
// Pass isFirstSection only to the first category found
106+
bool isFirstSection = !firstCategoryFound;
107+
if (isFirstSection) firstCategoryFound = true;
108+
85109
switch (category.type) {
86110
case CategoryType.row:
87111
categoryWidget = CategoryRow(
88112
key: sectionKey,
89113
category: category,
90-
applications: category.applications
114+
applications: category.applications,
115+
isFirstSection: isFirstSection
91116
);
117+
break; // Added break
92118
case CategoryType.grid:
93119
categoryWidget = AppsGrid(
94120
key: sectionKey,
95121
category: category,
96-
applications: category.applications
122+
applications: category.applications,
123+
isFirstSection: isFirstSection
97124
);
125+
break; // Added break
98126
}
99127

100-
return Padding(
101-
padding: const EdgeInsets.symmetric(vertical: 8),
102-
child: categoryWidget
103-
);
104-
}).toList(),
105-
);
128+
children.add(Padding(
129+
padding: const EdgeInsets.symmetric(vertical: 8),
130+
child: categoryWidget
131+
));
132+
}
133+
134+
return Column(children: children);
135+
}
106136

107137
Widget _wallpaper(BuildContext context, WallpaperService wallpaperService) {
108138
if (wallpaperService.wallpaper != null) {

lib/flauncher_app.dart

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,22 @@ class FLauncherApp extends StatelessWidget
7979
useMaterial3: true,
8080
brightness: Brightness.dark,
8181
primarySwatch: _swatch,
82-
cardColor: _swatch[300],
83-
canvasColor: _swatch[300],
84-
dialogBackgroundColor: _swatch[400],
85-
scaffoldBackgroundColor: _swatch[400],
86-
textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(foregroundColor: Colors.white)),
82+
cardColor: const Color(0xFF1E1E1E), // Dark surface color
83+
canvasColor: const Color(0xFF121212), // Dark background
84+
dialogBackgroundColor: const Color(0xFF1E1E1E),
85+
scaffoldBackgroundColor: const Color(0xFF121212), // Dark background
86+
textButtonTheme: TextButtonThemeData(
87+
style: TextButton.styleFrom(
88+
foregroundColor: Colors.white, // Revert to white for settings list
89+
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
90+
)
91+
),
92+
dialogTheme: DialogTheme(
93+
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
94+
backgroundColor: const Color(0xFF1E1E1E),
95+
titleTextStyle: Typography.material2018().white.titleLarge,
96+
contentTextStyle: Typography.material2018().white.bodyMedium,
97+
),
8798
appBarTheme: const AppBarTheme(elevation: 0, backgroundColor: Colors.transparent),
8899
typography: Typography.material2018(),
89100
inputDecorationTheme: InputDecorationTheme(

lib/l10n/app_en.arb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"showCategoryTitles": "Show category titles",
8484
"sort": "Sort",
8585
"systemSettings": "System settings",
86-
"textAboutDialog": "FlauncherL is a customized fork of FLauncher, developed by LeanBitLab.\n\nFLauncher is an open-source alternative launcher for Android TV.\nSource code available at {repoUrl}.\n\nLogo by Katie (@fureturoe), design by @FXCostanzo.",
86+
"textAboutDialog": "FlauncherL is a customized open-source launcher for Android TV, based on FLauncher.\n\nDeveloped by LeanBitLab.\nSource code available at {repoUrl}.\n\nLogo by Katie (@fureturoe), design by @FXCostanzo.",
8787
"@textAboutDialog": {
8888
"placeholders": {
8989
"repoUrl": {

lib/l10n/app_es.arb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"showCategoryTitles": "Mostrar títulos de categorías",
8787
"sort": "Orden",
8888
"systemSettings": "Ajustes del sistema",
89-
"textAboutDialog": "FLauncher es un lanzador de aplicaciones alternativo de código abierto para Android TV.\nCódigo fuente disponible en {repoUrl}.\n\nLogo creado por Katie (@fureturoe) y diseño por @FXCostanzo.",
89+
"textAboutDialog": "FlauncherL es un lanzador de código abierto personalizado para Android TV, basado en FLauncher.\n\nDesarrollado por LeanBitLab.\nCódigo fuente disponible en {repoUrl}.\n\nLogo creado por Katie (@fureturoe) y diseño por @FXCostanzo.",
9090
"@textAboutDialog": {
9191
"placeholders": {
9292
"repoUrl": {

0 commit comments

Comments
 (0)