|
|
@@ -1,9 +1,12 @@
|
|
|
+import 'dart:async';
|
|
|
+
|
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
|
|
import '../../core/theme/app_colors.dart';
|
|
|
import '../../core/widgets/app_icon.dart';
|
|
|
import '../../core/widgets/surface_card.dart';
|
|
|
+import '../../data/models/slot_assignment.dart';
|
|
|
import '../../data/models/slot_type.dart';
|
|
|
import '../apps/all_apps_screen.dart';
|
|
|
import '../apps/app_picker_sheet.dart';
|
|
|
@@ -16,7 +19,7 @@ import 'widgets/status_cluster.dart';
|
|
|
import 'widgets/volume_card.dart';
|
|
|
|
|
|
/// The only home page. No app bar, no dock, no pages: information, the main
|
|
|
-/// volume control, and five configurable shortcuts.
|
|
|
+/// volume control, and up to 16 configurable shortcuts.
|
|
|
class HomeScreen extends StatelessWidget {
|
|
|
const HomeScreen({super.key});
|
|
|
|
|
|
@@ -29,39 +32,25 @@ class HomeScreen extends StatelessWidget {
|
|
|
final screen = MediaQuery.sizeOf(context);
|
|
|
final compact = screen.height < 700 || screen.width < 1100;
|
|
|
final horizontalPadding = compact ? 30.0 : 44.0;
|
|
|
- final leftWidth = (screen.width * 0.37).clamp(300.0, 440.0);
|
|
|
+ final denseHome = launcher.homeButtonCount > 8;
|
|
|
+ final leftWidth = denseHome
|
|
|
+ ? (screen.width * 0.31).clamp(300.0, 400.0)
|
|
|
+ : (screen.width * 0.37).clamp(300.0, 440.0);
|
|
|
final gap = compact ? 26.0 : 40.0;
|
|
|
final showHomeBanner = launcher.defaultHomeKnown && !launcher.isDefaultHome;
|
|
|
|
|
|
- final navigation = launcher.assignmentFor(SlotType.navigation);
|
|
|
- final music = launcher.assignmentFor(SlotType.music);
|
|
|
- final radio = launcher.assignmentFor(SlotType.radio);
|
|
|
-
|
|
|
final shortcuts = <ShortcutSpec>[
|
|
|
- ShortcutSpec(
|
|
|
- title: 'Navigation',
|
|
|
- subtitle: navigation?.label ?? 'Select app',
|
|
|
- iconAsset: SlotType.navigation.iconAsset,
|
|
|
- assigned: navigation != null,
|
|
|
- onTap: () => _openSlot(context, SlotType.navigation),
|
|
|
- onLongPress: () => showAppPicker(context, SlotType.navigation),
|
|
|
- ),
|
|
|
- ShortcutSpec(
|
|
|
- title: 'Music',
|
|
|
- subtitle: music?.label ?? 'Select app',
|
|
|
- iconAsset: SlotType.music.iconAsset,
|
|
|
- assigned: music != null,
|
|
|
- onTap: () => _openSlot(context, SlotType.music),
|
|
|
- onLongPress: () => showAppPicker(context, SlotType.music),
|
|
|
- ),
|
|
|
- ShortcutSpec(
|
|
|
- title: 'Radio',
|
|
|
- subtitle: radio?.label ?? 'Select app',
|
|
|
- iconAsset: SlotType.radio.iconAsset,
|
|
|
- assigned: radio != null,
|
|
|
- onTap: () => _openSlot(context, SlotType.radio),
|
|
|
- onLongPress: () => showAppPicker(context, SlotType.radio),
|
|
|
- ),
|
|
|
+ for (final slot in SlotType.values)
|
|
|
+ if (launcher.isSlotEnabled(slot))
|
|
|
+ ShortcutSpec(
|
|
|
+ title: slot.label,
|
|
|
+ subtitle: launcher.assignmentFor(slot)?.label ?? 'Select app',
|
|
|
+ iconAsset: slot.iconAsset,
|
|
|
+ assigned: launcher.assignmentFor(slot) != null,
|
|
|
+ onTap: () => _openSlot(context, slot),
|
|
|
+ onLongPress: () => showAppPicker(context, slot),
|
|
|
+ longPressHint: 'change the app',
|
|
|
+ ),
|
|
|
ShortcutSpec(
|
|
|
title: 'Settings',
|
|
|
subtitle: 'Theme & PIN',
|
|
|
@@ -76,6 +65,16 @@ class HomeScreen extends StatelessWidget {
|
|
|
assigned: true,
|
|
|
onTap: () => _openAllApps(context),
|
|
|
),
|
|
|
+ for (final pinned in launcher.pinnedApps)
|
|
|
+ ShortcutSpec(
|
|
|
+ title: pinned.label.isEmpty ? pinned.packageName : pinned.label,
|
|
|
+ subtitle: null,
|
|
|
+ iconAsset: 'assets/icons/app-window.svg',
|
|
|
+ assigned: true,
|
|
|
+ onTap: () => _openPinned(context, pinned),
|
|
|
+ onLongPress: () => _removePinned(context, pinned),
|
|
|
+ longPressHint: 'remove from Home',
|
|
|
+ ),
|
|
|
];
|
|
|
|
|
|
return PopScope(
|
|
|
@@ -174,7 +173,9 @@ class HomeScreen extends StatelessWidget {
|
|
|
if (!compact) ...[
|
|
|
const SizedBox(height: 4),
|
|
|
Text(
|
|
|
- 'Hold a shortcut to change the app',
|
|
|
+ launcher.pinnedApps.isEmpty
|
|
|
+ ? 'Hold a shortcut to change the app'
|
|
|
+ : 'Hold a slot to change · hold an added app to remove',
|
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
|
color: colors.inkFaint,
|
|
|
),
|
|
|
@@ -196,6 +197,26 @@ class HomeScreen extends StatelessWidget {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ Future<void> _openPinned(
|
|
|
+ BuildContext context,
|
|
|
+ SlotAssignment assignment,
|
|
|
+ ) async {
|
|
|
+ final error = await context.read<LauncherController>().launchAssignment(
|
|
|
+ assignment,
|
|
|
+ );
|
|
|
+ if (error != null && context.mounted) {
|
|
|
+ _showMessage(context, error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void _removePinned(BuildContext context, SlotAssignment assignment) {
|
|
|
+ unawaited(context.read<LauncherController>().removePinnedApp(assignment));
|
|
|
+ _showMessage(
|
|
|
+ context,
|
|
|
+ '${assignment.label.isEmpty ? 'App' : assignment.label} removed from Home',
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
Future<void> _openSlot(BuildContext context, SlotType slot) async {
|
|
|
final launcher = context.read<LauncherController>();
|
|
|
if (launcher.assignmentFor(slot) == null) {
|
|
|
@@ -227,8 +248,9 @@ class HomeScreen extends StatelessWidget {
|
|
|
}
|
|
|
|
|
|
void _showMessage(BuildContext context, String message) {
|
|
|
- ScaffoldMessenger.of(context)
|
|
|
- .showSnackBar(SnackBar(content: Text(message)));
|
|
|
+ final messenger = ScaffoldMessenger.of(context);
|
|
|
+ messenger.hideCurrentSnackBar();
|
|
|
+ messenger.showSnackBar(SnackBar(content: Text(message)));
|
|
|
}
|
|
|
}
|
|
|
|