diff options
author | Scott Murray <scott.murray@konsulko.com> | 2023-12-21 17:22:52 -0500 |
---|---|---|
committer | Scott Murray <scott.murray@konsulko.com> | 2023-12-21 22:48:36 +0000 |
commit | a445ffb0d847b8d1e44213b95bfbe60ecdf19952 (patch) | |
tree | 8854ab9d843a983f6b43c3cd6eb7df34765a167c /lib | |
parent | 4ae68f5be11d110f2df10d54377d970921e30a21 (diff) |
Add application launcher support
Changes:
- Add required agl-shell and applauncher gRPC API source and
generated files.
- Remove unused deprecated databroker gRPC API files as cleanup.
- Add app launcher helper object and associated RiverPod provider.
The implementation is based on code from the old Flutter
homescreen. There will likely be follow up work to use the
recent changes to the agl-shell gRPC API to handle display
setting for applications needing remote displays.
- Wire up application enumeration and starting in the app page.
A placeholder generic application icon has been added that will
be used for the external applications for now, as the SVG icons
being used with the old homescreens are not really suitable.
- Wire up activating the homescreen again if the bottom panel is
touched after an external application has been started.
Bug-AGL: SPEC-5026
Change-Id: I01de4760cfd098d3b5f2e6843ad9103a8ea87935
Signed-off-by: Scott Murray <scott.murray@konsulko.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/data/data_providers/app_launcher.dart | 108 | ||||
-rw-r--r-- | lib/data/data_providers/app_launcher_info.dart | 21 | ||||
-rw-r--r-- | lib/data/data_providers/app_provider.dart | 7 | ||||
-rw-r--r-- | lib/export.dart | 1 | ||||
-rw-r--r-- | lib/presentation/common_widget/custom_bottom_bar.dart | 1 | ||||
-rw-r--r-- | lib/presentation/screens/apps/apps_content.dart | 58 | ||||
-rw-r--r-- | lib/presentation/screens/home/home.dart | 1 |
7 files changed, 171 insertions, 26 deletions
diff --git a/lib/data/data_providers/app_launcher.dart b/lib/data/data_providers/app_launcher.dart new file mode 100644 index 0000000..b0199d3 --- /dev/null +++ b/lib/data/data_providers/app_launcher.dart @@ -0,0 +1,108 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:protos/protos.dart'; + +class AppLauncher { + final Ref ref; + + late ClientChannel aglShellChannel; + late AglShellManagerServiceClient aglShell; + late ClientChannel appLauncherChannel; + late AppLauncherClient appLauncher; + + List<String> appStack = [ 'homescreen' ]; + + AppLauncher({required this.ref}) { + aglShellChannel = + ClientChannel('localhost', + port: 14005, + options: ChannelOptions(credentials: ChannelCredentials.insecure())); + + aglShell = AglShellManagerServiceClient(aglShellChannel); + + appLauncherChannel = + ClientChannel('localhost', + port: 50052, + options: ChannelOptions(credentials: ChannelCredentials.insecure())); + appLauncher = AppLauncherClient(appLauncherChannel); + } + + run() async { + getAppList(); + + try { + var response = appLauncher.getStatusEvents(StatusRequest()); + await for (var event in response) { + if (event.hasApp()) { + AppStatus app_status = event.app; + debugPrint("Got app status:"); + debugPrint("$app_status"); + if (app_status.hasId() && app_status.hasStatus()) { + if (app_status.status == "started") { + activateApp(app_status.id); + } else if (app_status.status == "terminated") { + deactivateApp(app_status.id); + } + } + } + } + } catch (e) { + print(e); + } + } + + getAppList() async { + try { + var response = await appLauncher.listApplications(ListRequest()); + List<AppLauncherInfo> apps = []; + for (AppInfo info in response.apps) { + debugPrint("Got app:"); + debugPrint("$info"); + // Existing icons are currently not usable, so leave blank for now + apps.add(AppLauncherInfo(id: info.id, name: info.name, icon: "", internal: false)); + } + apps.sort((a, b) => a.name.compareTo(b.name)); + + // Add built-in app widgets + apps.insert(0, AppLauncherInfo(id: "clock", name: "Clock", icon: "clock.svg", internal: true)); + apps.insert(0, AppLauncherInfo(id: "weather", name: "Weather", icon: "weather.svg", internal: true)); + + ref.read(appLauncherListProvider.notifier).update(apps); + } catch (e) { + print(e); + } + } + + void startApp(String id) async { + await appLauncher.startApplication(StartRequest(id: id)); + } + + addAppToStack(String id) { + if (!appStack.contains(id)) { + appStack.add(id); + } else { + int current = appStack.indexOf(id); + if (current != (appStack.length - 1)) { + appStack.removeAt(current); + appStack.add(id); + } + } + } + + activateApp(String id) async { + if (appStack.last != id) { + var req = ActivateRequest(appId: id); + var response = aglShell.activateApp(req); + addAppToStack(id); + } + } + + deactivateApp(String id) async { + if (appStack.contains(id)) { + appStack.remove(id); + if (appStack.isNotEmpty) { + activateApp(appStack.last); + } + } + } + +} diff --git a/lib/data/data_providers/app_launcher_info.dart b/lib/data/data_providers/app_launcher_info.dart new file mode 100644 index 0000000..4d79cfe --- /dev/null +++ b/lib/data/data_providers/app_launcher_info.dart @@ -0,0 +1,21 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class AppLauncherInfo { + final String id; + final String name; + final String icon; + final bool internal; + + AppLauncherInfo({required this.id, required this.name, required this.icon, required this.internal}); +} + +class AppLauncherList extends Notifier<List<AppLauncherInfo>> { + @override + List<AppLauncherInfo> build() { + return []; + } + + void update(List<AppLauncherInfo> newAppList) { + state = newAppList; + } +} diff --git a/lib/data/data_providers/app_provider.dart b/lib/data/data_providers/app_provider.dart index cfda370..1670eba 100644 --- a/lib/data/data_providers/app_provider.dart +++ b/lib/data/data_providers/app_provider.dart @@ -5,6 +5,7 @@ import 'package:flutter_ics_homescreen/data/data_providers/units_notifier.dart'; import 'package:flutter_ics_homescreen/data/data_providers/audio_notifier.dart'; import 'package:flutter_ics_homescreen/data/data_providers/users_notifier.dart'; import 'package:flutter_ics_homescreen/data/data_providers/val_client.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/app_launcher.dart'; import 'package:flutter_ics_homescreen/export.dart'; import '../models/users.dart'; @@ -43,6 +44,12 @@ final valClientProvider = Provider((ref) { return ValClient(config: config, ref: ref); }); +final appLauncherProvider = Provider((ref) { + return AppLauncher(ref: ref); +}); + +final appLauncherListProvider = NotifierProvider<AppLauncherList, List<AppLauncherInfo>>(AppLauncherList.new); + final vehicleProvider = NotifierProvider<VehicleNotifier, Vehicle>(VehicleNotifier.new); diff --git a/lib/export.dart b/lib/export.dart index 17cab07..1e07f3f 100644 --- a/lib/export.dart +++ b/lib/export.dart @@ -1,6 +1,7 @@ export 'data/data_providers/app.dart'; export 'data/data_providers/app_config_provider.dart'; export 'data/data_providers/app_provider.dart'; +export 'data/data_providers/app_launcher_info.dart'; export 'presentation/router/routes/routes.dart'; export 'data/theme/theme.dart'; diff --git a/lib/presentation/common_widget/custom_bottom_bar.dart b/lib/presentation/common_widget/custom_bottom_bar.dart index 64684e9..61a7e20 100644 --- a/lib/presentation/common_widget/custom_bottom_bar.dart +++ b/lib/presentation/common_widget/custom_bottom_bar.dart @@ -44,6 +44,7 @@ class CustomBottomBarState extends ConsumerState<CustomBottomBar> { setState(() { selectedNav = title; }); + ref.read(appLauncherProvider).activateApp("homescreen"); ref.read(currentTimeProvider.notifier).isYearChanged = false; ref.read(appProvider.notifier).update((state) => state = status); } diff --git a/lib/presentation/screens/apps/apps_content.dart b/lib/presentation/screens/apps/apps_content.dart index fe6e3b0..b0afda1 100644 --- a/lib/presentation/screens/apps/apps_content.dart +++ b/lib/presentation/screens/apps/apps_content.dart @@ -1,48 +1,54 @@ import 'package:flutter_ics_homescreen/export.dart'; import 'package:flutter_ics_homescreen/presentation/screens/apps/widgets/app_button.dart'; -class Apps extends StatefulWidget { +class Apps extends ConsumerStatefulWidget { const Apps({super.key}); @override - State<Apps> createState() => _AppsState(); + ConsumerState<Apps> createState() => _AppsState(); } -class _AppsState extends State<Apps> { - onPressed({required String type}) { - if (type == "weather") { - context.flow<AppState>().update((next) => AppState.weather); - } else if (type == "clock") { - context.flow<AppState>().update((next) => AppState.clock); +class _AppsState extends ConsumerState<Apps> { + onPressed({required bool internal, required String id}) { + if (internal) { + if (id == "weather") { + context.flow<AppState>().update((next) => AppState.weather); + } else if (id == "clock") { + context.flow<AppState>().update((next) => AppState.clock); + } + } else { + ref.read(appLauncherProvider).startApp(id); } } @override Widget build(BuildContext context) { + var apps = ref.watch(appLauncherListProvider); + return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const CommonTitle(title: "Applications"), Padding( padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 148), - child: Wrap( - children: [ - AppButton( - image: "weather.svg", - title: "Weather", - onPressed: () { - onPressed(type: "weather"); - }, - ), - AppButton( - image: "clock.svg", - title: "Clock", - onPressed: () { - onPressed(type: "clock"); - }, - ) - ], - ), + child: GridView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + itemCount: apps.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3), + itemBuilder: (context, index) { + return GridTile( + child: Container( + alignment: Alignment.center, + child: AppButton( + title: apps[index].name, + image: apps[index].icon.isNotEmpty ? apps[index].icon : "app-generic.svg", + onPressed: () { + onPressed(internal: apps[index].internal, id: apps[index].id); + }, + ))); + }) ), // Center( // child: SizedBox( diff --git a/lib/presentation/screens/home/home.dart b/lib/presentation/screens/home/home.dart index 86da46f..3d80f92 100644 --- a/lib/presentation/screens/home/home.dart +++ b/lib/presentation/screens/home/home.dart @@ -13,6 +13,7 @@ class HomeScreen extends ConsumerStatefulWidget { class HomeScreenState extends ConsumerState<HomeScreen> { @override void initState() { + ref.read(appLauncherProvider).run(); super.initState(); } |