From 220f953a12e9e7b24c9d178952b062970ca24f1e Mon Sep 17 00:00:00 2001 From: HenriqueSFernandes Date: Mon, 16 Mar 2026 13:03:52 +0000 Subject: [PATCH 1/5] feat: added parking lot information --- .gitignore | 3 +- flake.lock | 6 +- flake.nix | 2 +- .../fetchers/parking_lot_fetcher.dart | 50 +++++++++++ .../lib/generated/intl/messages_en.dart | 5 ++ .../lib/generated/intl/messages_pt_PT.dart | 7 ++ packages/uni_app/lib/generated/l10n.dart | 25 ++++++ packages/uni_app/lib/l10n/intl_en.arb | 6 ++ packages/uni_app/lib/l10n/intl_pt_PT.arb | 6 ++ .../entities/parking_lot_occupation.dart | 23 +++++ .../riverpod/parking_lot_provider.dart | 32 +++++++ .../lib/utils/favorite_widget_type.dart | 1 + .../uni_app/lib/view/faculty/faculty.dart | 11 ++- packages/uni_app/lib/view/home/home.dart | 4 + .../home/widgets/edit/draggable_utils.dart | 2 + .../widgets/parking/parking_card_shimmer.dart | 21 +++++ .../widgets/parking/parking_home_card.dart | 57 ++++++++++++ .../uni_ui/lib/cards/parking_lot_card.dart | 88 +++++++++++++++++++ packages/uni_ui/lib/icons.dart | 2 + 19 files changed, 345 insertions(+), 6 deletions(-) create mode 100644 packages/uni_app/lib/controller/fetchers/parking_lot_fetcher.dart create mode 100644 packages/uni_app/lib/model/entities/parking_lot_occupation.dart create mode 100644 packages/uni_app/lib/model/providers/riverpod/parking_lot_provider.dart create mode 100644 packages/uni_app/lib/view/home/widgets/parking/parking_card_shimmer.dart create mode 100644 packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart create mode 100644 packages/uni_ui/lib/cards/parking_lot_card.dart diff --git a/.gitignore b/.gitignore index e2039378a..a69a7ad65 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ uni/android/key.properties **.jks # Custom Lint log -custom_lint.log \ No newline at end of file +custom_lint.log +.direnv diff --git a/flake.lock b/flake.lock index 2f3c7570e..a57a81f83 100644 --- a/flake.lock +++ b/flake.lock @@ -54,11 +54,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1770562336, - "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", + "lastModified": 1773579282, + "narHash": "sha256-LWvZj9Bvm1EuoO6zbX4yjZebwnZNfeTbmCJGS7RGQ3Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "rev": "5a88de74db0e948139be4b46f9a94d64aa11391c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e36417391..6c5e9099f 100644 --- a/flake.nix +++ b/flake.nix @@ -48,7 +48,7 @@ ndkVersions = ["28.2.13676358"]; }; - flutter = pkgs.flutter338; + flutter = pkgs.flutter; jdks = with pkgs; [jdk21 jdk17]; }; } { diff --git a/packages/uni_app/lib/controller/fetchers/parking_lot_fetcher.dart b/packages/uni_app/lib/controller/fetchers/parking_lot_fetcher.dart new file mode 100644 index 000000000..3c8fcc15b --- /dev/null +++ b/packages/uni_app/lib/controller/fetchers/parking_lot_fetcher.dart @@ -0,0 +1,50 @@ +import 'dart:convert'; + +import 'package:uni/controller/networking/network_router.dart'; +import 'package:uni/model/entities/parking_lot_occupation.dart'; +import 'package:uni/session/flows/base/session.dart'; + +class ParkingLotFetcher { + static const _endpoint = 'instalacs_geral.ocupacao_parques'; + + Future getParkingLotOccupation(Session session) async { + final url = '${NetworkRouter.getBaseUrl('feup')}$_endpoint'; + + final response = await NetworkRouter.getWithCookies(url, {}, session); + return _parse(response.body); + } + + ParkingLotOccupation _parse(String body) { + final json = jsonDecode(body) as Map; + final itdc = json['itdc'] as List; + final resposta = + (itdc.first as Map)['resposta'] + as Map; + + final lots = [ + ParkingLot( + id: 'P1', + name: 'Pessoal Permanente', + capacity: resposta['p1lotacao'] as int, + occupied: resposta['p1ocupados'] as int, + free: resposta['p1livres'] as int, + ), + ParkingLot( + id: 'P3', + name: 'Estudantes', + capacity: resposta['p3lotacao'] as int, + occupied: resposta['p3ocupados'] as int, + free: resposta['p3livres'] as int, + ), + ParkingLot( + id: 'P4', + name: 'Pessoal Não Permanente', + capacity: resposta['p4lotacao'] as int, + occupied: resposta['p4ocupados'] as int, + free: resposta['p4livres'] as int, + ), + ]; + + return ParkingLotOccupation(lots); + } +} diff --git a/packages/uni_app/lib/generated/intl/messages_en.dart b/packages/uni_app/lib/generated/intl/messages_en.dart index 015247fa8..d5fe80a98 100644 --- a/packages/uni_app/lib/generated/intl/messages_en.dart +++ b/packages/uni_app/lib/generated/intl/messages_en.dart @@ -334,6 +334,9 @@ class MessageLookup extends MessageLookupByLibrary { "no_library_info": MessageLookupByLibrary.simpleMessage( "No library occupation information available", ), + "no_parking_info": MessageLookupByLibrary.simpleMessage( + "No parking lot information available", + ), "no_link": MessageLookupByLibrary.simpleMessage( "We couldn\'t open the link", ), @@ -383,6 +386,8 @@ class MessageLookup extends MessageLookupByLibrary { "Error opening the file", ), "other_links": MessageLookupByLibrary.simpleMessage("Other links"), + "parking": MessageLookupByLibrary.simpleMessage("Parking"), + "parking_lots": MessageLookupByLibrary.simpleMessage("Parking Lots"), "pass_change_request": MessageLookupByLibrary.simpleMessage( "For security reasons, passwords must be changed periodically.", ), diff --git a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart index bedaae31e..bd36d43d6 100644 --- a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart +++ b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart @@ -354,6 +354,9 @@ class MessageLookup extends MessageLookupByLibrary { "no_library_info": MessageLookupByLibrary.simpleMessage( "Sem informação de ocupação", ), + "no_parking_info": MessageLookupByLibrary.simpleMessage( + "Sem informação de estacionamento disponível", + ), "no_link": MessageLookupByLibrary.simpleMessage( "Não conseguimos abrir o link", ), @@ -405,6 +408,10 @@ class MessageLookup extends MessageLookupByLibrary { "Erro ao abrir o ficheiro", ), "other_links": MessageLookupByLibrary.simpleMessage("Outros links"), + "parking": MessageLookupByLibrary.simpleMessage("Estacionamento"), + "parking_lots": MessageLookupByLibrary.simpleMessage( + "Parques de Estacionamento", + ), "pass_change_request": MessageLookupByLibrary.simpleMessage( "Por razões de segurança, as palavras-passe têm de ser alteradas periodicamente.", ), diff --git a/packages/uni_app/lib/generated/l10n.dart b/packages/uni_app/lib/generated/l10n.dart index 8928735f9..0acf75d7f 100644 --- a/packages/uni_app/lib/generated/l10n.dart +++ b/packages/uni_app/lib/generated/l10n.dart @@ -722,6 +722,31 @@ class S { ); } + /// `Parking Lots` + String get parking_lots { + return Intl.message( + 'Parking Lots', + name: 'parking_lots', + desc: '', + args: [], + ); + } + + /// `Parking` + String get parking { + return Intl.message('Parking', name: 'parking', desc: '', args: []); + } + + /// `No parking lot information available` + String get no_parking_info { + return Intl.message( + 'No parking lot information available', + name: 'no_parking_info', + desc: '', + args: [], + ); + } + /// `Lunch` String get lunch { return Intl.message('Lunch', name: 'lunch', desc: '', args: []); diff --git a/packages/uni_app/lib/l10n/intl_en.arb b/packages/uni_app/lib/l10n/intl_en.arb index b6e687820..d575c286e 100644 --- a/packages/uni_app/lib/l10n/intl_en.arb +++ b/packages/uni_app/lib/l10n/intl_en.arb @@ -172,6 +172,12 @@ "@leave_feedback": {}, "library_occupation": "Library Occupation", "@library_occupation": {}, + "parking_lots": "Parking Lots", + "@parking_lots": {}, + "parking": "Parking", + "@parking": {}, + "no_parking_info": "No parking lot information available", + "@no_parking_info": {}, "lunch": "Lunch", "@lunch": {}, "download_error": "Error downloading the file", diff --git a/packages/uni_app/lib/l10n/intl_pt_PT.arb b/packages/uni_app/lib/l10n/intl_pt_PT.arb index d08c7f8a4..5044dc982 100644 --- a/packages/uni_app/lib/l10n/intl_pt_PT.arb +++ b/packages/uni_app/lib/l10n/intl_pt_PT.arb @@ -176,6 +176,12 @@ "@load_error": {}, "library_occupation": "Ocupação da Biblioteca", "@library_occupation": {}, + "parking_lots": "Parques de Estacionamento", + "@parking_lots": {}, + "parking": "Estacionamento", + "@parking": {}, + "no_parking_info": "Sem informação de estacionamento disponível", + "@no_parking_info": {}, "lunch": "Almoço", "@lunch": {}, "download_error": "Erro ao descarregar o ficheiro", diff --git a/packages/uni_app/lib/model/entities/parking_lot_occupation.dart b/packages/uni_app/lib/model/entities/parking_lot_occupation.dart new file mode 100644 index 000000000..326e0b55f --- /dev/null +++ b/packages/uni_app/lib/model/entities/parking_lot_occupation.dart @@ -0,0 +1,23 @@ +class ParkingLot { + ParkingLot({ + required this.id, + required this.name, + required this.capacity, + required this.occupied, + required this.free, + }); + + final String id; + final String name; + final int capacity; + final int occupied; + final int free; + + double get occupancyRatio => capacity > 0 ? occupied / capacity : 0.0; +} + +class ParkingLotOccupation { + ParkingLotOccupation(this.lots); + + final List lots; +} diff --git a/packages/uni_app/lib/model/providers/riverpod/parking_lot_provider.dart b/packages/uni_app/lib/model/providers/riverpod/parking_lot_provider.dart new file mode 100644 index 000000000..7834bae6c --- /dev/null +++ b/packages/uni_app/lib/model/providers/riverpod/parking_lot_provider.dart @@ -0,0 +1,32 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:uni/controller/fetchers/parking_lot_fetcher.dart'; +import 'package:uni/model/entities/parking_lot_occupation.dart'; +import 'package:uni/model/providers/riverpod/cached_async_notifier.dart'; +import 'package:uni/model/providers/riverpod/session_provider.dart'; + +final parkingLotProvider = + AsyncNotifierProvider( + ParkingLotNotifier.new, + ); + +final class ParkingLotNotifier + extends CachedAsyncNotifier { + @override + Duration? get cacheDuration => const Duration(minutes: 15); + + @override + Future loadFromStorage() async { + // No disk persistence — data is real-time occupancy + return null; + } + + @override + Future loadFromRemote() async { + final session = await ref.read(sessionProvider.future); + if (session == null) { + return null; + } + + return ParkingLotFetcher().getParkingLotOccupation(session); + } +} diff --git a/packages/uni_app/lib/utils/favorite_widget_type.dart b/packages/uni_app/lib/utils/favorite_widget_type.dart index 743d8891d..a28658de4 100644 --- a/packages/uni_app/lib/utils/favorite_widget_type.dart +++ b/packages/uni_app/lib/utils/favorite_widget_type.dart @@ -5,4 +5,5 @@ enum FavoriteWidgetType { restaurants, calendar, news, + parking, } diff --git a/packages/uni_app/lib/view/faculty/faculty.dart b/packages/uni_app/lib/view/faculty/faculty.dart index d427ab790..5520faab7 100644 --- a/packages/uni_app/lib/view/faculty/faculty.dart +++ b/packages/uni_app/lib/view/faculty/faculty.dart @@ -2,10 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/model/providers/riverpod/library_occupation_provider.dart'; +import 'package:uni/model/providers/riverpod/parking_lot_provider.dart'; import 'package:uni/utils/navigation_items.dart'; import 'package:uni/view/faculty/widgets/service_cards.dart'; import 'package:uni/view/home/widgets/calendar/calendar_home_card.dart'; import 'package:uni/view/home/widgets/library/library_home_card.dart'; +import 'package:uni/view/home/widgets/parking/parking_home_card.dart'; import 'package:uni/view/widgets/pages_layouts/general/general.dart'; class FacultyPageView extends ConsumerStatefulWidget { @@ -25,6 +27,10 @@ class FacultyPageViewState extends GeneralPageViewState { return ListView( children: const [ LibraryHomeCard(), + Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: ParkingLotHomeCard(), + ), Padding( padding: EdgeInsets.symmetric(vertical: 10), child: CalendarHomeCard(), @@ -39,6 +45,9 @@ class FacultyPageViewState extends GeneralPageViewState { @override Future onRefresh() async { - await ref.read(libraryProvider.notifier).refreshRemote(); + await Future.wait([ + ref.read(libraryProvider.notifier).refreshRemote(), + ref.read(parkingLotProvider.notifier).refreshRemote(), + ]); } } diff --git a/packages/uni_app/lib/view/home/home.dart b/packages/uni_app/lib/view/home/home.dart index 83e34c822..f7cf6b1b9 100644 --- a/packages/uni_app/lib/view/home/home.dart +++ b/packages/uni_app/lib/view/home/home.dart @@ -11,6 +11,7 @@ import 'package:uni/model/providers/riverpod/exam_provider.dart'; import 'package:uni/model/providers/riverpod/lecture_provider.dart'; import 'package:uni/model/providers/riverpod/library_occupation_provider.dart'; import 'package:uni/model/providers/riverpod/news_provider.dart'; +import 'package:uni/model/providers/riverpod/parking_lot_provider.dart'; import 'package:uni/model/providers/riverpod/pedagogical_surveys_provider.dart'; import 'package:uni/model/providers/riverpod/profile_provider.dart'; import 'package:uni/model/providers/riverpod/restaurant_provider.dart'; @@ -23,6 +24,7 @@ import 'package:uni/view/home/widgets/connectivity_warning.dart'; import 'package:uni/view/home/widgets/exams/exam_home_card.dart'; import 'package:uni/view/home/widgets/library/library_home_card.dart'; import 'package:uni/view/home/widgets/news/news_home_card.dart'; +import 'package:uni/view/home/widgets/parking/parking_home_card.dart'; import 'package:uni/view/home/widgets/pedagogical_surveys_info.dart'; import 'package:uni/view/home/widgets/restaurants/restaurant_home_card.dart'; import 'package:uni/view/home/widgets/schedule/schedule_home_card.dart'; @@ -60,6 +62,7 @@ class HomePageViewState extends ConsumerState { FavoriteWidgetType.library: libraryProvider, FavoriteWidgetType.restaurants: restaurantProvider, FavoriteWidgetType.news: newsProvider, + FavoriteWidgetType.parking: parkingLotProvider, }; @override @@ -104,6 +107,7 @@ class HomePageViewState extends ConsumerState { FavoriteWidgetType.restaurants: const RestaurantHomeCard(), FavoriteWidgetType.calendar: const CalendarHomeCard(), FavoriteWidgetType.news: const NewsHomeCard(), + FavoriteWidgetType.parking: const ParkingLotHomeCard(), }; return AnnotatedRegion( diff --git a/packages/uni_app/lib/view/home/widgets/edit/draggable_utils.dart b/packages/uni_app/lib/view/home/widgets/edit/draggable_utils.dart index db4b669a5..b5c64eea1 100644 --- a/packages/uni_app/lib/view/home/widgets/edit/draggable_utils.dart +++ b/packages/uni_app/lib/view/home/widgets/edit/draggable_utils.dart @@ -20,6 +20,8 @@ import 'package:uni_ui/icons.dart'; return (S.of(context).calendar, const UniIcon(UniIcons.calendar)); case FavoriteWidgetType.news: return (S.of(context).news, const UniIcon(UniIcons.news)); + case FavoriteWidgetType.parking: + return (S.of(context).parking, const UniIcon(UniIcons.parking)); // case 'ucs': // title = 'UCS'; // icon = const UniIcon(UniIcons.graduationCap); diff --git a/packages/uni_app/lib/view/home/widgets/parking/parking_card_shimmer.dart b/packages/uni_app/lib/view/home/widgets/parking/parking_card_shimmer.dart new file mode 100644 index 000000000..00b032177 --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/parking/parking_card_shimmer.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:uni_ui/common/generic_squircle.dart'; + +class ShimmerParkingHomeCard extends StatelessWidget { + const ShimmerParkingHomeCard({super.key}); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: GenericSquircle( + child: Container( + height: 160, + decoration: const BoxDecoration(color: Colors.white), + ), + ), + ); + } +} diff --git a/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart b/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart new file mode 100644 index 000000000..07cb10f5a --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/parking_lot_occupation.dart'; +import 'package:uni/model/providers/riverpod/default_consumer.dart'; +import 'package:uni/model/providers/riverpod/parking_lot_provider.dart'; +import 'package:uni/view/home/widgets/generic_home_card.dart'; +import 'package:uni/view/home/widgets/parking/parking_card_shimmer.dart'; +import 'package:uni/view/widgets/icon_label.dart'; +import 'package:uni_ui/cards/parking_lot_card.dart'; +import 'package:uni_ui/icons.dart'; + +class ParkingLotHomeCard extends GenericHomecard { + const ParkingLotHomeCard({super.key}) + : super( + titlePadding: const EdgeInsets.symmetric(horizontal: 20), + bodyPadding: const EdgeInsets.symmetric(horizontal: 20), + ); + + @override + String getTitle(BuildContext context) { + return S.of(context).parking_lots; + } + + @override + void onCardClick(BuildContext context) => {}; + + @override + Widget buildCardContent(BuildContext context) { + return DefaultConsumer( + provider: parkingLotProvider, + builder: (context, ref, occupation) => ParkingLotCard( + lots: occupation.lots + .map( + (lot) => ParkingLotRowWidget( + lotId: lot.id, + lotName: lot.name, + free: lot.free, + capacity: lot.capacity, + ), + ) + .toList(), + ), + hasContent: (occupation) => occupation.lots.isNotEmpty, + nullContentWidget: Center( + child: IconLabel( + icon: Icon(UniIcons.parking, size: 45), + label: S.of(context).no_parking_info, + labelTextStyle: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + loadingWidget: const ShimmerParkingHomeCard(), + ); + } +} diff --git a/packages/uni_ui/lib/cards/parking_lot_card.dart b/packages/uni_ui/lib/cards/parking_lot_card.dart new file mode 100644 index 000000000..549994e99 --- /dev/null +++ b/packages/uni_ui/lib/cards/parking_lot_card.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:uni_ui/cards/generic_card.dart'; + +class ParkingLotRowWidget extends StatelessWidget { + const ParkingLotRowWidget({ + super.key, + required this.lotId, + required this.lotName, + required this.free, + required this.capacity, + }); + + final String lotId; + final String lotName; + final int free; + final int capacity; + + @override + Widget build(BuildContext context) { + final occupied = capacity - free; + final ratio = capacity > 0 ? (occupied / capacity).clamp(0.0, 1.0) : 0.0; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '$lotId ', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: lotName, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + Text( + '$free livre${free == 1 ? '' : 's'}', + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), + const SizedBox(height: 4), + LinearPercentIndicator( + lineHeight: 8.0, + percent: ratio, + backgroundColor: const Color.fromRGBO(177, 77, 84, 0.25), + progressColor: Theme.of(context).primaryColor, + barRadius: const Radius.circular(10), + padding: EdgeInsets.zero, + ), + ], + ), + ); + } +} + +class ParkingLotCard extends StatelessWidget { + const ParkingLotCard({super.key, required this.lots}); + + /// Each entry: (lotId, lotName, free, capacity) + final List lots; + + @override + Widget build(BuildContext context) { + return GenericCard( + key: key, + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), + margin: EdgeInsets.zero, + tooltip: '', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: lots, + ), + ); + } +} diff --git a/packages/uni_ui/lib/icons.dart b/packages/uni_ui/lib/icons.dart index 662e5c79a..a02cda90d 100644 --- a/packages/uni_ui/lib/icons.dart +++ b/packages/uni_ui/lib/icons.dart @@ -94,6 +94,8 @@ class UniIcons { static const courseUnit = PhosphorIconsDuotone.chalkboardTeacher; static const warning = PhosphorIconsDuotone.warningOctagon; + + static const parking = PhosphorIconsDuotone.garage; } // The same as default Icon class from material.dart but allowing to use PhosphorIcons duotone icons From 4c51d86f5af8006b0e2d6679ac58eb050c8e3f44 Mon Sep 17 00:00:00 2001 From: HenriqueSFernandes Date: Mon, 16 Mar 2026 13:15:37 +0000 Subject: [PATCH 2/5] fix: missing translations --- .../fetchers/parking_lot_fetcher.dart | 6 ++-- .../lib/generated/intl/messages_en.dart | 8 +++++ .../lib/generated/intl/messages_pt_PT.dart | 8 +++++ packages/uni_app/lib/generated/l10n.dart | 35 +++++++++++++++++++ packages/uni_app/lib/l10n/intl_en.arb | 8 +++++ packages/uni_app/lib/l10n/intl_pt_PT.arb | 8 +++++ .../entities/parking_lot_occupation.dart | 6 ++-- .../riverpod/parking_lot_provider.dart | 1 - .../widgets/parking/parking_home_card.dart | 12 ++++++- .../uni_ui/lib/cards/parking_lot_card.dart | 5 +-- 10 files changed, 88 insertions(+), 9 deletions(-) diff --git a/packages/uni_app/lib/controller/fetchers/parking_lot_fetcher.dart b/packages/uni_app/lib/controller/fetchers/parking_lot_fetcher.dart index 3c8fcc15b..ec2f062ae 100644 --- a/packages/uni_app/lib/controller/fetchers/parking_lot_fetcher.dart +++ b/packages/uni_app/lib/controller/fetchers/parking_lot_fetcher.dart @@ -24,21 +24,21 @@ class ParkingLotFetcher { final lots = [ ParkingLot( id: 'P1', - name: 'Pessoal Permanente', + type: ParkingLotType.permanentStaff, capacity: resposta['p1lotacao'] as int, occupied: resposta['p1ocupados'] as int, free: resposta['p1livres'] as int, ), ParkingLot( id: 'P3', - name: 'Estudantes', + type: ParkingLotType.students, capacity: resposta['p3lotacao'] as int, occupied: resposta['p3ocupados'] as int, free: resposta['p3livres'] as int, ), ParkingLot( id: 'P4', - name: 'Pessoal Não Permanente', + type: ParkingLotType.nonPermanentStaff, capacity: resposta['p4lotacao'] as int, occupied: resposta['p4ocupados'] as int, free: resposta['p4livres'] as int, diff --git a/packages/uni_app/lib/generated/intl/messages_en.dart b/packages/uni_app/lib/generated/intl/messages_en.dart index d5fe80a98..b3a25d615 100644 --- a/packages/uni_app/lib/generated/intl/messages_en.dart +++ b/packages/uni_app/lib/generated/intl/messages_en.dart @@ -337,6 +337,14 @@ class MessageLookup extends MessageLookupByLibrary { "no_parking_info": MessageLookupByLibrary.simpleMessage( "No parking lot information available", ), + "parking_lot_free": MessageLookupByLibrary.simpleMessage("free"), + "parking_lot_non_permanent_staff": MessageLookupByLibrary.simpleMessage( + "Non-Permanent Staff", + ), + "parking_lot_permanent_staff": MessageLookupByLibrary.simpleMessage( + "Permanent Staff", + ), + "parking_lot_students": MessageLookupByLibrary.simpleMessage("Students"), "no_link": MessageLookupByLibrary.simpleMessage( "We couldn\'t open the link", ), diff --git a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart index bd36d43d6..9a9fb0c4f 100644 --- a/packages/uni_app/lib/generated/intl/messages_pt_PT.dart +++ b/packages/uni_app/lib/generated/intl/messages_pt_PT.dart @@ -357,6 +357,14 @@ class MessageLookup extends MessageLookupByLibrary { "no_parking_info": MessageLookupByLibrary.simpleMessage( "Sem informação de estacionamento disponível", ), + "parking_lot_free": MessageLookupByLibrary.simpleMessage("livre"), + "parking_lot_non_permanent_staff": MessageLookupByLibrary.simpleMessage( + "Pessoal Não Permanente", + ), + "parking_lot_permanent_staff": MessageLookupByLibrary.simpleMessage( + "Pessoal Permanente", + ), + "parking_lot_students": MessageLookupByLibrary.simpleMessage("Estudantes"), "no_link": MessageLookupByLibrary.simpleMessage( "Não conseguimos abrir o link", ), diff --git a/packages/uni_app/lib/generated/l10n.dart b/packages/uni_app/lib/generated/l10n.dart index 0acf75d7f..c89eaf22a 100644 --- a/packages/uni_app/lib/generated/l10n.dart +++ b/packages/uni_app/lib/generated/l10n.dart @@ -747,6 +747,41 @@ class S { ); } + /// `Permanent Staff` + String get parking_lot_permanent_staff { + return Intl.message( + 'Permanent Staff', + name: 'parking_lot_permanent_staff', + desc: '', + args: [], + ); + } + + /// `Students` + String get parking_lot_students { + return Intl.message( + 'Students', + name: 'parking_lot_students', + desc: '', + args: [], + ); + } + + /// `Non-Permanent Staff` + String get parking_lot_non_permanent_staff { + return Intl.message( + 'Non-Permanent Staff', + name: 'parking_lot_non_permanent_staff', + desc: '', + args: [], + ); + } + + /// `free` + String get parking_lot_free { + return Intl.message('free', name: 'parking_lot_free', desc: '', args: []); + } + /// `Lunch` String get lunch { return Intl.message('Lunch', name: 'lunch', desc: '', args: []); diff --git a/packages/uni_app/lib/l10n/intl_en.arb b/packages/uni_app/lib/l10n/intl_en.arb index d575c286e..a5539a656 100644 --- a/packages/uni_app/lib/l10n/intl_en.arb +++ b/packages/uni_app/lib/l10n/intl_en.arb @@ -178,6 +178,14 @@ "@parking": {}, "no_parking_info": "No parking lot information available", "@no_parking_info": {}, + "parking_lot_permanent_staff": "Permanent Staff", + "@parking_lot_permanent_staff": {}, + "parking_lot_students": "Students", + "@parking_lot_students": {}, + "parking_lot_non_permanent_staff": "Non-Permanent Staff", + "@parking_lot_non_permanent_staff": {}, + "parking_lot_free": "free", + "@parking_lot_free": {}, "lunch": "Lunch", "@lunch": {}, "download_error": "Error downloading the file", diff --git a/packages/uni_app/lib/l10n/intl_pt_PT.arb b/packages/uni_app/lib/l10n/intl_pt_PT.arb index 5044dc982..71dd8454c 100644 --- a/packages/uni_app/lib/l10n/intl_pt_PT.arb +++ b/packages/uni_app/lib/l10n/intl_pt_PT.arb @@ -182,6 +182,14 @@ "@parking": {}, "no_parking_info": "Sem informação de estacionamento disponível", "@no_parking_info": {}, + "parking_lot_permanent_staff": "Pessoal Permanente", + "@parking_lot_permanent_staff": {}, + "parking_lot_students": "Estudantes", + "@parking_lot_students": {}, + "parking_lot_non_permanent_staff": "Pessoal Não Permanente", + "@parking_lot_non_permanent_staff": {}, + "parking_lot_free": "livre", + "@parking_lot_free": {}, "lunch": "Almoço", "@lunch": {}, "download_error": "Erro ao descarregar o ficheiro", diff --git a/packages/uni_app/lib/model/entities/parking_lot_occupation.dart b/packages/uni_app/lib/model/entities/parking_lot_occupation.dart index 326e0b55f..b3edb27bd 100644 --- a/packages/uni_app/lib/model/entities/parking_lot_occupation.dart +++ b/packages/uni_app/lib/model/entities/parking_lot_occupation.dart @@ -1,14 +1,16 @@ +enum ParkingLotType { permanentStaff, students, nonPermanentStaff } + class ParkingLot { ParkingLot({ required this.id, - required this.name, + required this.type, required this.capacity, required this.occupied, required this.free, }); final String id; - final String name; + final ParkingLotType type; final int capacity; final int occupied; final int free; diff --git a/packages/uni_app/lib/model/providers/riverpod/parking_lot_provider.dart b/packages/uni_app/lib/model/providers/riverpod/parking_lot_provider.dart index 7834bae6c..ceda350dc 100644 --- a/packages/uni_app/lib/model/providers/riverpod/parking_lot_provider.dart +++ b/packages/uni_app/lib/model/providers/riverpod/parking_lot_provider.dart @@ -16,7 +16,6 @@ final class ParkingLotNotifier @override Future loadFromStorage() async { - // No disk persistence — data is real-time occupancy return null; } diff --git a/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart b/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart index 07cb10f5a..0f9cea5aa 100644 --- a/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart +++ b/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart @@ -9,6 +9,15 @@ import 'package:uni/view/widgets/icon_label.dart'; import 'package:uni_ui/cards/parking_lot_card.dart'; import 'package:uni_ui/icons.dart'; +String _lotName(BuildContext context, ParkingLotType type) { + final s = S.of(context); + return switch (type) { + ParkingLotType.permanentStaff => s.parking_lot_permanent_staff, + ParkingLotType.students => s.parking_lot_students, + ParkingLotType.nonPermanentStaff => s.parking_lot_non_permanent_staff, + }; +} + class ParkingLotHomeCard extends GenericHomecard { const ParkingLotHomeCard({super.key}) : super( @@ -33,9 +42,10 @@ class ParkingLotHomeCard extends GenericHomecard { .map( (lot) => ParkingLotRowWidget( lotId: lot.id, - lotName: lot.name, + lotName: _lotName(context, lot.type), free: lot.free, capacity: lot.capacity, + freeLabel: S.of(context).parking_lot_free, ), ) .toList(), diff --git a/packages/uni_ui/lib/cards/parking_lot_card.dart b/packages/uni_ui/lib/cards/parking_lot_card.dart index 549994e99..68fc70837 100644 --- a/packages/uni_ui/lib/cards/parking_lot_card.dart +++ b/packages/uni_ui/lib/cards/parking_lot_card.dart @@ -9,12 +9,14 @@ class ParkingLotRowWidget extends StatelessWidget { required this.lotName, required this.free, required this.capacity, + required this.freeLabel, }); final String lotId; final String lotName; final int free; final int capacity; + final String freeLabel; @override Widget build(BuildContext context) { @@ -46,7 +48,7 @@ class ParkingLotRowWidget extends StatelessWidget { ), ), Text( - '$free livre${free == 1 ? '' : 's'}', + '$free $freeLabel', style: Theme.of(context).textTheme.titleMedium, ), ], @@ -69,7 +71,6 @@ class ParkingLotRowWidget extends StatelessWidget { class ParkingLotCard extends StatelessWidget { const ParkingLotCard({super.key, required this.lots}); - /// Each entry: (lotId, lotName, free, capacity) final List lots; @override From 2845c691c5bd786892ed7840a16ff1663588e64d Mon Sep 17 00:00:00 2001 From: HenriqueSFernandes Date: Mon, 16 Mar 2026 13:27:39 +0000 Subject: [PATCH 3/5] fix: lint --- .../lib/view/home/widgets/parking/parking_home_card.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart b/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart index 0f9cea5aa..62b63b2d3 100644 --- a/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart +++ b/packages/uni_app/lib/view/home/widgets/parking/parking_home_card.dart @@ -53,7 +53,7 @@ class ParkingLotHomeCard extends GenericHomecard { hasContent: (occupation) => occupation.lots.isNotEmpty, nullContentWidget: Center( child: IconLabel( - icon: Icon(UniIcons.parking, size: 45), + icon: const Icon(UniIcons.parking, size: 45), label: S.of(context).no_parking_info, labelTextStyle: TextStyle( fontSize: 14, From 674882f740ef83622db0c68b6834e15d70f9be35 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:36:37 +0000 Subject: [PATCH 4/5] feat: added android auto support for parking information --- .../android/app/src/main/AndroidManifest.xml | 23 +++++ .../src/main/res/xml/automotive_app_desc.xml | 4 + .../lib/controller/car_controller.dart | 91 +++++++++++++++++++ packages/uni_app/lib/main.dart | 3 + packages/uni_app/pubspec.yaml | 1 + pubspec.lock | 8 ++ 6 files changed, 130 insertions(+) create mode 100644 packages/uni_app/android/app/src/main/res/xml/automotive_app_desc.xml create mode 100644 packages/uni_app/lib/controller/car_controller.dart diff --git a/packages/uni_app/android/app/src/main/AndroidManifest.xml b/packages/uni_app/android/app/src/main/AndroidManifest.xml index 618033b6e..57a58da12 100644 --- a/packages/uni_app/android/app/src/main/AndroidManifest.xml +++ b/packages/uni_app/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,23 @@ android:usesCleartextTraffic="true" android:allowBackup="false" > + + + + + + + + + + + + + diff --git a/packages/uni_app/android/app/src/main/res/xml/automotive_app_desc.xml b/packages/uni_app/android/app/src/main/res/xml/automotive_app_desc.xml new file mode 100644 index 000000000..8dc476fe0 --- /dev/null +++ b/packages/uni_app/android/app/src/main/res/xml/automotive_app_desc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/uni_app/lib/controller/car_controller.dart b/packages/uni_app/lib/controller/car_controller.dart new file mode 100644 index 000000000..549c7421f --- /dev/null +++ b/packages/uni_app/lib/controller/car_controller.dart @@ -0,0 +1,91 @@ +import 'dart:io'; +import 'package:flutter_carplay/flutter_carplay.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/parking_lot_occupation.dart'; +import 'package:uni/model/providers/riverpod/parking_lot_provider.dart'; + +class CarController { + CarController(this.ref); + + final Ref ref; + bool _initialized = false; + + void init() { + if (_initialized) { + return; + } + _initialized = true; + + ref.listen(parkingLotProvider, (previous, next) { + next.whenData((occupation) { + if (occupation != null) { + _updateCarTemplates(occupation); + } + }); + }, fireImmediately: true); + } + + void _updateCarTemplates(ParkingLotOccupation occupation) { + S s; + try { + s = S.current; + } catch (_) { + return; + } + + if (Platform.isIOS) { + final cpItems = occupation.lots.map((lot) { + final name = _lotName(lot.type, s); + final free = lot.free; + final capacity = lot.capacity; + return CPListItem( + text: name, + detailText: '$free / $capacity ${s.parking_lot_free}', + onPress: (complete, self) => complete(), + ); + }).toList(); + + FlutterCarplay.setRootTemplate( + rootTemplate: CPListTemplate( + sections: [CPListSection(items: cpItems, header: s.parking_lots)], + title: 'uni', + systemIcon: 'car', + ), + ); + } + + if (Platform.isAndroid) { + final aaItems = occupation.lots.map((lot) { + final name = _lotName(lot.type, s); + final free = lot.free; + final capacity = lot.capacity; + return AAListItem( + title: name, + subtitle: '$free / $capacity ${s.parking_lot_free}', + onPress: (complete, item) => complete(), + ); + }).toList(); + + FlutterAndroidAuto.setRootTemplate( + template: AAListTemplate( + sections: [AAListSection(items: aaItems, title: s.parking_lots)], + title: 'uni', + ), + ); + } + } + + String _lotName(ParkingLotType type, S s) { + switch (type) { + case ParkingLotType.permanentStaff: + return s.parking_lot_permanent_staff; + case ParkingLotType.students: + return s.parking_lot_students; + case ParkingLotType.nonPermanentStaff: + return s.parking_lot_non_permanent_staff; + } + } +} + +final carControllerProvider = Provider(CarController.new); diff --git a/packages/uni_app/lib/main.dart b/packages/uni_app/lib/main.dart index fbefafce8..d89ba94a7 100644 --- a/packages/uni_app/lib/main.dart +++ b/packages/uni_app/lib/main.dart @@ -13,6 +13,7 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:ua_client_hints/ua_client_hints.dart'; import 'package:uni/controller/background_workers/background_callback.dart'; +import 'package:uni/controller/car_controller.dart'; import 'package:uni/controller/cleanup.dart'; import 'package:uni/controller/fetchers/terms_and_conditions_fetcher.dart'; import 'package:uni/controller/local_storage/migrations/migration_controller.dart'; @@ -148,6 +149,8 @@ class ApplicationState extends ConsumerState { if (plausible != null) { navigatorObservers.add(PlausibleNavigatorObserver(plausible)); } + + ref.read(carControllerProvider).init(); } @override diff --git a/packages/uni_app/pubspec.yaml b/packages/uni_app/pubspec.yaml index 50949efb8..d4b4b4b0e 100644 --- a/packages/uni_app/pubspec.yaml +++ b/packages/uni_app/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: flutter: sdk: flutter flutter_cache_manager: ^3.4.1 + flutter_carplay: ^1.2.0 flutter_dotenv: ^6.0.0 flutter_local_notifications: ^20.0.0 flutter_localizations: diff --git a/pubspec.lock b/pubspec.lock index a0b0d29aa..c92449fc0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -567,6 +567,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" + flutter_carplay: + dependency: transitive + description: + name: flutter_carplay + sha256: "134d5448a648573122af9d5a9e86c326f711150309eb518333a8bfad284604af" + url: "https://pub.dev" + source: hosted + version: "1.2.11" flutter_dotenv: dependency: transitive description: From 03c0220aad9b48c0aafadf0f3ecc4deb578acb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=A3o=20Barbosa?= Date: Mon, 16 Mar 2026 21:57:12 +0000 Subject: [PATCH 5/5] added necessary files to support carplay on ios --- .../ios/Runner.xcodeproj/project.pbxproj | 9 +++++++ packages/uni_app/ios/Runner/AppDelegate.swift | 6 ++++- packages/uni_app/ios/Runner/Info.plist | 25 ++++++++++++++++++- .../uni_app/ios/Runner/Runner.entitlements | 8 ++++++ .../uni_app/ios/Runner/SceneDelegate.swift | 25 +++++++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 packages/uni_app/ios/Runner/Runner.entitlements create mode 100644 packages/uni_app/ios/Runner/SceneDelegate.swift diff --git a/packages/uni_app/ios/Runner.xcodeproj/project.pbxproj b/packages/uni_app/ios/Runner.xcodeproj/project.pbxproj index 9f321be0b..d5a117f42 100644 --- a/packages/uni_app/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/uni_app/ios/Runner.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; A19789621A76D32B94690770 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65A7FEFFF94135D00ABAE9B9 /* Pods_Runner.framework */; }; B0BA8183595B62BB34BA7067 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D20E49018C2538464D8BC7C /* Pods_RunnerTests.framework */; }; + C14713CF2F68ABEF00644B7E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14713CE2F68ABEF00644B7E /* SceneDelegate.swift */; }; C183B2E32ED222610052FE14 /* uni.icon in Resources */ = {isa = PBXBuildFile; fileRef = C183B2E22ED222610052FE14 /* uni.icon */; }; C183B2E52ED222700052FE14 /* uni_dev.icon in Resources */ = {isa = PBXBuildFile; fileRef = C183B2E42ED222700052FE14 /* uni_dev.icon */; }; /* End PBXBuildFile section */ @@ -67,6 +68,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C14713CE2F68ABEF00644B7E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + C14713D02F68AE1E00644B7E /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; C183B2E22ED222610052FE14 /* uni.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = uni.icon; sourceTree = ""; }; C183B2E42ED222700052FE14 /* uni_dev.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = uni_dev.icon; sourceTree = ""; }; /* End PBXFileReference section */ @@ -147,6 +150,8 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + C14713D02F68AE1E00644B7E /* Runner.entitlements */, + C14713CE2F68ABEF00644B7E /* SceneDelegate.swift */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -386,6 +391,7 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + C14713CF2F68ABEF00644B7E /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -478,6 +484,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = uni; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -701,6 +708,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = uni_dev; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -724,6 +732,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = uni; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/packages/uni_app/ios/Runner/AppDelegate.swift b/packages/uni_app/ios/Runner/AppDelegate.swift index df8182fe6..5e496a71b 100644 --- a/packages/uni_app/ios/Runner/AppDelegate.swift +++ b/packages/uni_app/ios/Runner/AppDelegate.swift @@ -4,13 +4,17 @@ import workmanager import flutter_local_notifications import app_links +// Global Flutter engine for sharing between app and CarPlay +let flutterEngine = FlutterEngine(name: "SharedEngine", project: nil, allowHeadlessExecution: true) + @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) + flutterEngine.run() + GeneratedPluginRegistrant.register(with: flutterEngine) // Notifications WorkmanagerPlugin.registerTask(withIdentifier:"pt.up.fe.ni.uni.notificationworker") diff --git a/packages/uni_app/ios/Runner/Info.plist b/packages/uni_app/ios/Runner/Info.plist index e3c79a662..d8261ff6e 100644 --- a/packages/uni_app/ios/Runner/Info.plist +++ b/packages/uni_app/ios/Runner/Info.plist @@ -61,8 +61,31 @@ Possibilidade de adicionar prints a um bug report UIApplicationSceneManifest + UIApplicationSupportsMultipleScenes + UISceneConfigurations - + + CPTemplateApplicationSceneSessionRoleApplication + + + UISceneConfigurationName + CarPlay Configuration + UISceneDelegateClassName + flutter_carplay.FlutterCarPlaySceneDelegate + + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + UIApplicationSupportsIndirectInputEvents diff --git a/packages/uni_app/ios/Runner/Runner.entitlements b/packages/uni_app/ios/Runner/Runner.entitlements new file mode 100644 index 000000000..aba04fc97 --- /dev/null +++ b/packages/uni_app/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.carplay-parking + + + \ No newline at end of file diff --git a/packages/uni_app/ios/Runner/SceneDelegate.swift b/packages/uni_app/ios/Runner/SceneDelegate.swift new file mode 100644 index 000000000..8e41f9548 --- /dev/null +++ b/packages/uni_app/ios/Runner/SceneDelegate.swift @@ -0,0 +1,25 @@ +import UIKit +import Flutter + +@available(iOS 13.0, *) +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = scene as? UIWindowScene else { return } + + window = UIWindow(windowScene: windowScene) + + let controller = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) + + // Attach the native Launch Screen to hide the black screen transition + if let storyboardName = Bundle.main.object(forInfoDictionaryKey: "UILaunchStoryboardName") as? String, + let launchScreen = UIStoryboard(name: storyboardName, bundle: nil).instantiateInitialViewController()?.view { + launchScreen.frame = UIScreen.main.bounds + controller.splashScreenView = launchScreen + } + + window?.rootViewController = controller + window?.makeKeyAndVisible() + } +} \ No newline at end of file