diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/app.dart b/lib/app.dart index ab7d8a2..06109f2 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,5 @@ import 'package:fire_scribe/app_scaffold.dart'; import 'package:fire_scribe/app_theme.dart'; -import 'package:fire_scribe/dashboard/dashboard_route.dart'; import 'package:fire_scribe/l10n/app_localizations.dart'; import 'package:fire_scribe/l10n/cubit/localization_cubit.dart'; import 'package:fire_scribe/l10n/supported_locales.dart'; @@ -12,9 +11,7 @@ import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; class App extends StatelessWidget { - const App({ - super.key, - }); + const App({super.key}); @override Widget build(BuildContext context) { @@ -24,35 +21,33 @@ class App extends StatelessWidget { navigatorKey: GetIt.instance>(), routes: $appRoutes, errorBuilder: (context, state) { - return AppScaffold( - child: _AppErrorPage(), - ); + return AppScaffold(child: _AppErrorPage()); }, ); return BlocProvider( - create: (final context) => LocalizationCubit( - sharedPreferences: GetIt.instance(), - ), + create: + (final context) => + LocalizationCubit(sharedPreferences: GetIt.instance()), child: BlocBuilder( - buildWhen: (final _, final current) => current.when( - initial: () => false, - locale: (final _) => true, - ), + buildWhen: + (final _, final current) => + current.when(initial: () => false, locale: (final _) => true), builder: (final context, final state) { return state.when( initial: () => const SizedBox(), - locale: (final locale) => MaterialApp.router( - title: S.of(context).appTitle, - theme: appTheme, - debugShowCheckedModeBanner: false, - supportedLocales: supportedLocales, - locale: locale, - localizationsDelegates: S.delegates, - routeInformationProvider: router.routeInformationProvider, - routeInformationParser: router.routeInformationParser, - routerDelegate: router.routerDelegate, - ), + locale: + (final locale) => MaterialApp.router( + title: S.of(context).appTitle, + theme: appTheme, + debugShowCheckedModeBanner: false, + supportedLocales: supportedLocales, + locale: locale, + localizationsDelegates: S.delegates, + routeInformationProvider: router.routeInformationProvider, + routeInformationParser: router.routeInformationParser, + routerDelegate: router.routerDelegate, + ), ); }, ), @@ -80,9 +75,7 @@ class _AppErrorPage extends StatelessWidget { SizedBox(height: 10), ElevatedButton( onPressed: () => GoRouter.of(context).refresh(), - child: Text( - S.of(context).refresh, - ), + child: Text(S.of(context).refresh), ), ], ), diff --git a/lib/app_drawer_menu.dart b/lib/app_drawer_menu.dart index ce2f07f..b18f58a 100644 --- a/lib/app_drawer_menu.dart +++ b/lib/app_drawer_menu.dart @@ -1,6 +1,4 @@ -import 'package:fire_scribe/dashboard/dashboard_route.dart'; import 'package:fire_scribe/l10n/app_localizations.dart'; -import 'package:fire_scribe/license/license_routes.dart'; import 'package:fire_scribe/routes.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -14,18 +12,14 @@ class AppDrawerMenu extends StatelessWidget { elevation: 2, shadowColor: Theme.of(context).colorScheme.shadow, child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 22, - ), + padding: const EdgeInsets.symmetric(vertical: 22), child: ListView( physics: ClampingScrollPhysics(), children: [ InkWell( onTap: () => DashboardRoute().go(context), child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), + padding: const EdgeInsets.symmetric(horizontal: 8), child: Image.asset( 'assets/app/logo.png', width: 52, @@ -40,16 +34,17 @@ class AppDrawerMenu extends StatelessWidget { title: S.of(context).server, route: DashboardRoute().location, ), + AppDrawerMenuItemData( + icon: Symbols.import_export, + title: S.of(context).importExport, + route: ImportExportRoute().location, + ), AppDrawerMenuItemData( icon: Symbols.license, title: S.of(context).thirdPartyLicenses, route: ThirdPartyLicensesRoute().location, ), - ].map( - (item) => AppDrawerMenuItem( - data: item, - ), - ), + ].map((item) => AppDrawerMenuItem(data: item)), ], ), ), @@ -60,10 +55,7 @@ class AppDrawerMenu extends StatelessWidget { class AppDrawerMenuItem extends StatelessWidget { final AppDrawerMenuItemData data; - const AppDrawerMenuItem({ - super.key, - required this.data, - }); + const AppDrawerMenuItem({super.key, required this.data}); @override Widget build(BuildContext context) { final location = GoRouterState.of(context).uri; @@ -78,32 +70,24 @@ class AppDrawerMenuItem extends StatelessWidget { child: Column( children: [ Padding( - padding: EdgeInsets.symmetric( - vertical: 4.0, - ), + padding: EdgeInsets.symmetric(vertical: 4.0), child: Container( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), - decoration: isSelected - ? BoxDecoration( - color: Theme.of(context) - .colorScheme - .primaryContainer - .withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(24), - ) - : null, - child: Icon( - data.icon, - fill: 1, - size: 24, - ), + decoration: + isSelected + ? BoxDecoration( + color: Theme.of( + context, + ).colorScheme.primaryContainer.withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(24), + ) + : null, + child: Icon(data.icon, fill: 1, size: 24), ), ), SizedBox(width: 4), Padding( - padding: EdgeInsets.symmetric( - horizontal: 8.0, - ), + padding: EdgeInsets.symmetric(horizontal: 8.0), child: Text( data.title, textAlign: TextAlign.center, diff --git a/lib/dashboard/dashboard_route.dart b/lib/dashboard/dashboard_route.dart deleted file mode 100644 index ad6423c..0000000 --- a/lib/dashboard/dashboard_route.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:fire_scribe/app_scaffold.dart'; -import 'package:fire_scribe/dashboard/dashboard_page.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -@immutable -class DashboardRoute extends GoRouteData { - @override - Page buildPage(BuildContext context, GoRouterState state) { - return NoTransitionPage( - child: AppScaffold( - child: DashboardPage(), - ), - ); - } -} diff --git a/lib/dashboard/widgets/resource_paginated_list.dart b/lib/dashboard/widgets/resource_paginated_list.dart index 39dfccf..f78c197 100644 --- a/lib/dashboard/widgets/resource_paginated_list.dart +++ b/lib/dashboard/widgets/resource_paginated_list.dart @@ -13,87 +13,82 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:intl/intl.dart'; class ResourcePaginatedList extends StatefulWidget { - const ResourcePaginatedList({ - super.key, - }); + const ResourcePaginatedList({super.key}); @override State createState() => _ResourcePaginatedListState(); } class _ResourcePaginatedListState extends State { - final pagingController = PagingController(firstPageKey: 0); + // final pagingController = PagingController(firstPageKey: 0); + late final pagingController = PagingController( + fetchPage: (pageKey) => fetchPage(pageKey), + getNextPageKey: + // The infinite scroller doesn't have appendLastPage anymore, so we + // need to determine when the last page is reached. The logic here is + // to check if the last page was empty (or never returned any results), + // or alternatively see if we already retrieved items (items.isNotEmpty + // is true) and the "next" searchCursor is null. In that case the + // server doesn't offer more data after the last page. + (state) => + (state.lastPageIsEmpty || + ((state.items?.isNotEmpty ?? false) && + searchCursor == null)) + ? null + : state.nextIntPageKey, + ); + String? entitySelected; String? searchCursor; - @override - void initState() { - super.initState(); - pagingController.addPageRequestListener(fetchPage); - } - @override void dispose() { - pagingController.removePageRequestListener(fetchPage); pagingController.dispose(); super.dispose(); } - Future fetchPage(final int pageOffset) async { - final currenEntity = entitySelected; - if (currenEntity == null || currenEntity.isEmpty) { - pagingController.appendLastPage([]); - return; + Future> fetchPage(final int pageOffset) async { + final currentEntity = entitySelected; + if (currentEntity == null || currentEntity.isEmpty) { + return []; } - final parameters = { - '_sort': '-_lastUpdated', - '_count': '25', - }; + final parameters = {'_sort': '-_lastUpdated', '_count': '25'}; if (searchCursor != null) { parameters['ct'] = searchCursor!; } - final rawBundle = - await BlocProvider.of(context).request( + final rawBundle = await BlocProvider.of( + context, + ).request( request: FhirRequest( operation: FhirRequestOperation.search, - entityName: currenEntity, + entityName: currentEntity, parameters: parameters, ), ); if (rawBundle == null) { - pagingController.appendLastPage([]); - return; + return []; } final bundle = Bundle.fromJson(rawBundle); final nextUrl = bundle.link?.firstWhereOrNull((item) => item.relation == 'next')?.url; searchCursor = nextUrl?.value?.queryParameters['ct']; - final entries = (bundle.entry ?? []) - .map((entry) => entry.resource) - .nonNulls - .toList(); + final entries = + (bundle.entry ?? []) + .map((entry) => entry.resource) + .nonNulls + .toList(); - if (searchCursor == null) { - pagingController.appendLastPage(entries); - } else { - pagingController.appendPage( - entries, - (pagingController.itemList?.length ?? 0) + entries.length, - ); - } + return entries; } void updateExistingResource(final int index, final Resource resource) { setState(() { - pagingController.itemList?.removeAt(index); - pagingController.itemList?.insert( - 0, - resource, - ); + pagingController.items?.removeAt(index); + pagingController.items?.insert(0, resource); }); } @@ -101,17 +96,18 @@ class _ResourcePaginatedListState extends State { if (entitySelected == null) { return; } - final resource = await FhirResourceEditorBottomSheet.show(context, - resource: ResourceX.factoryCreation(entitySelected!)); + final resource = await FhirResourceEditorBottomSheet.show( + context, + resource: ResourceX.factoryCreation(entitySelected!), + ); if (resource != null) { context.popAndPushSnackbar( - message: S.of(context).resourceCreationSuccessful( - entitySelected!, - resource.fhirId ?? '', - ), + message: S + .of(context) + .resourceCreationSuccessful(entitySelected!, resource.fhirId ?? ''), ); setState(() { - pagingController.itemList?.insert(0, resource); + pagingController.items?.insert(0, resource); }); } } @@ -127,7 +123,7 @@ class _ResourcePaginatedListState extends State { ), ); setState(() { - pagingController.itemList?.removeAt(index); + pagingController.items?.removeAt(index); }); } catch (e) { context.popAndPushSnackbar( @@ -152,12 +148,13 @@ class _ResourcePaginatedListState extends State { }, builder: (context, state) { return state.when( - noselected: () => Center( - child: Text( - S.of(context).selectAnEntityFromList, - style: Theme.of(context).textTheme.labelLarge, - ), - ), + noselected: + () => Center( + child: Text( + S.of(context).selectAnEntityFromList, + style: Theme.of(context).textTheme.labelLarge, + ), + ), selected: (entitySelected) { return Column( children: [ @@ -186,40 +183,50 @@ class _ResourcePaginatedListState extends State { ), ), Expanded( - child: PagedListView.separated( - pagingController: pagingController, - physics: const AlwaysScrollableScrollPhysics(), - padding: const EdgeInsets.symmetric(horizontal: 24), - itemExtent: 80, - builderDelegate: PagedChildBuilderDelegate( - newPageProgressIndicatorBuilder: (context) => - const Center( - child: CircularProgressIndicator(), - ), - noItemsFoundIndicatorBuilder: (context) => Center( - child: Text( - S.of(context).noDataAssociatedToAnEntity, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyLarge, - ), - ), - noMoreItemsIndicatorBuilder: (context) => - const SizedBox(), - itemBuilder: (context, item, index) { - return ResourcePaginatedListCard( - resource: item, - resourceWasUpdated: (resource) => - updateExistingResource( - index, - resource, + child: PagingListener( + controller: pagingController, + builder: + ( + context, + state, + fetchNextPage, + ) => PagedListView.separated( + state: state, + fetchNextPage: fetchNextPage, + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 24), + itemExtent: 80, + builderDelegate: PagedChildBuilderDelegate( + newPageProgressIndicatorBuilder: + (context) => const Center( + child: CircularProgressIndicator(), + ), + noItemsFoundIndicatorBuilder: + (context) => Center( + child: Text( + S.of(context).noDataAssociatedToAnEntity, + textAlign: TextAlign.center, + style: + Theme.of(context).textTheme.bodyLarge, + ), + ), + noMoreItemsIndicatorBuilder: + (context) => const SizedBox(), + itemBuilder: (context, item, index) { + return ResourcePaginatedListCard( + resource: item, + resourceWasUpdated: + (resource) => + updateExistingResource(index, resource), + resourceDelete: + (resource) => + deleteResource(index, resource), + ); + }, ), - resourceDelete: (resource) => - deleteResource(index, resource), - ); - }, - ), - separatorBuilder: (context, index) => - const SizedBox(height: 2), + separatorBuilder: + (context, index) => const SizedBox(height: 2), + ), ), ), ], @@ -234,10 +241,7 @@ class _ResourcePaginatedListState extends State { class ResourcePaginatedListHeader extends StatelessWidget { final String entityType; - const ResourcePaginatedListHeader({ - super.key, - required this.entityType, - }); + const ResourcePaginatedListHeader({super.key, required this.entityType}); @override Widget build(BuildContext context) { return Row( @@ -288,10 +292,7 @@ class ResourcePaginatedListCard extends StatelessWidget { return InkWell( onTap: () => _showCodeEditor(context), child: Container( - padding: EdgeInsets.symmetric( - horizontal: 24, - vertical: 12, - ), + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerLowest, borderRadius: BorderRadius.circular(12), @@ -312,7 +313,7 @@ class ResourcePaginatedListCard extends StatelessWidget { IconButton( onPressed: () => {resourceDelete(resource)}, icon: const Icon(Icons.delete), - ) + ), ], ), ), diff --git a/lib/import_export/import_export_page.dart b/lib/import_export/import_export_page.dart new file mode 100644 index 0000000..1857982 --- /dev/null +++ b/lib/import_export/import_export_page.dart @@ -0,0 +1,228 @@ +import 'package:dio/dio.dart'; +import 'package:fire_scribe/auth/cubit/fhir_server_connection_cubit.dart'; +import 'package:fire_scribe/l10n/app_localizations.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// A page for handling import and export functionality of FHIR resources. +/// +/// This page provides a user interface for importing FHIR resources from external +/// sources and exporting resources to various formats. The content will be +/// implemented in subsequent steps. +class ImportExportPage extends StatefulWidget { + const ImportExportPage({super.key}); + + @override + State createState() => _ImportExportPageState(); +} + +class _ImportExportPageState extends State { + final TextEditingController _urlController = TextEditingController(); + final FocusNode _urlFocusNode = FocusNode(); + + @override + void dispose() { + _urlController.dispose(); + _urlFocusNode.dispose(); + super.dispose(); + } + + /// Handles the import button press + Future handleImport() async { + final url = _urlController.text.trim(); + if (url.isEmpty) { + return; + } + + try { + final connectionCubit = BlocProvider.of( + context, + ); + + // Check if we're authenticated + final isAuthenticated = connectionCubit.state.maybeWhen( + authenticated: (_, __) => true, + orElse: () => false, + ); + + if (!isAuthenticated) { + // Show error message that user needs to be connected to a server + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).connectionRequired), + content: Text(S.of(context).connectionRequiredMessage), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(S.of(context).ok), + ), + ], + ); + }, + ); + return; + } + + // Get the FHIR client from the authenticated state + final fhirClient = connectionCubit.state.maybeWhen( + authenticated: (_, client) => client, + orElse: () => null, + ); + + if (fhirClient == null) { + // TODO: Show error message + return; + } + + // Make the import request using the underlying Dio client + final response = await fhirClient.dio.post( + '/\$import', + data: { + 'resourceType': 'Parameters', + 'parameter': [ + {'name': 'inputFormat', 'valueString': 'application/fhir+ndjson'}, + { + 'name': 'input', + 'part': [ + {'name': 'url', 'valueUri': url}, + ], + }, + ], + }, + options: Options( + headers: { + 'Prefer': 'respond-async', + 'Content-Type': 'application/fhir+json', + }, + ), + ); + + // Handle the response based on status code + if (response.statusCode.toString().startsWith("2")) { + // Success - import started + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).importStarted), + content: Text(S.of(context).importStartedMessage), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(S.of(context).ok), + ), + ], + ); + }, + ); + } else { + // Unexpected response - show failure + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).importFailed), + content: Text( + S + .of(context) + .importFailedMessage( + 'Status: ${response.statusCode}\nResponse: ${response.data}', + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(S.of(context).ok), + ), + ], + ); + }, + ); + } + } catch (e) { + // Show error dialog with the exception details + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).importError), + content: Text(S.of(context).importErrorMessage(e.toString())), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(S.of(context).ok), + ), + ], + ); + }, + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header section + Row( + children: [ + Icon( + Icons.import_export, + size: 32, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 12), + Text( + S.of(context).importExport, + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + const SizedBox(height: 8), + Text( + S.of(context).importExportDescription, + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: 32), + + // URL input section + Text( + S.of(context).ndjsonFileToImport, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 12), + TextField( + controller: _urlController, + focusNode: _urlFocusNode, + decoration: InputDecoration( + hintText: 'https://example.com/fhir/resources.ndjson', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + prefixIcon: const Icon(Icons.link), + ), + keyboardType: TextInputType.url, + textInputAction: TextInputAction.done, + onSubmitted: (_) => handleImport(), + ), + const SizedBox(height: 24), + + // Import button + FilledButton.icon( + onPressed: + _urlController.text.trim().isNotEmpty ? handleImport : null, + icon: const Icon(Icons.upload), + label: Text(S.of(context).import), + ), + ], + ), + ), + ); + } +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index a989488..29faa2c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -17,9 +17,10 @@ class S { static Future load(final Locale locale) async { // If we're given "en_US", we'll use it as-is. If we're // given "en", we extract it and use it. - final localeName = locale.countryCode == null || locale.countryCode!.isEmpty - ? locale.languageCode - : locale.toString(); + final localeName = + locale.countryCode == null || locale.countryCode!.isEmpty + ? locale.languageCode + : locale.toString(); // We make sure the locale name is in the right format e.g. // converting "en-US" to "en_US". @@ -44,11 +45,11 @@ class S { } static List get delegates => [ - const AppLocalizationsDelegate(), - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ]; + const AppLocalizationsDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ]; static String? getLocalizedString(final BuiltMap data) { // @@ -63,7 +64,8 @@ class S { // As a fallback, retrieve the string by language lookup. // (de, en, it, ...) // - final shortLocale = Intl.defaultLocale?.substring(0, 2) ?? + final shortLocale = + Intl.defaultLocale?.substring(0, 2) ?? supportedLocales.first.languageCode; if (data.keys.contains(shortLocale)) { return data[shortLocale]; @@ -83,134 +85,133 @@ class S { } String get appTitle => Intl.message( - 'Fire Scribe', - name: 'appTitle', - desc: 'The title of the app', - ); + 'Fire Scribe', + name: 'appTitle', + desc: 'The title of the app', + ); - String get appErrorPageMessage => - Intl.message('Internal error, refresh the page or restart the app.', - name: 'appErrorPageMessage', - desc: 'The message shown when a user accesses an incorrect route'); + String get appErrorPageMessage => Intl.message( + 'Internal error, refresh the page or restart the app.', + name: 'appErrorPageMessage', + desc: 'The message shown when a user accesses an incorrect route', + ); String get refresh => Intl.message('Refresh', name: 'refresh', desc: 'Refresh word'); String get allResources => Intl.message( - 'All resources', - name: 'allResources', - desc: 'The title of the entity list section', - ); + 'All resources', + name: 'allResources', + desc: 'The title of the entity list section', + ); String get serverUrl => Intl.message( - 'Server URL', - name: 'serverUrl', - desc: 'The label of the server connection text field', - ); + 'Server URL', + name: 'serverUrl', + desc: 'The label of the server connection text field', + ); String get introduceServerUrl => Intl.message( - 'Enter Server URL', - name: 'introduceServerUrl', - desc: 'The hint text of the server connection text field', - ); + 'Enter Server URL', + name: 'introduceServerUrl', + desc: 'The hint text of the server connection text field', + ); String get serverUrlFormEmpty => Intl.message( - 'Enter the URL of the server to connect before proceeding.', - name: 'serverUrlFormEmpty', - desc: 'The error message when user connect with an empty server url', - ); + 'Enter the URL of the server to connect before proceeding.', + name: 'serverUrlFormEmpty', + desc: 'The error message when user connect with an empty server url', + ); String get bearerToken => Intl.message( - 'Bearer Token', - name: 'bearerToken', - desc: 'The label of the bearer token form text field', - ); + 'Bearer Token', + name: 'bearerToken', + desc: 'The label of the bearer token form text field', + ); String get introduceBearerToken => Intl.message( - 'Enter Bearer Token', - name: 'introduceBearerToken', - desc: 'The hint text of the bearer token form text field', - ); + 'Enter Bearer Token', + name: 'introduceBearerToken', + desc: 'The hint text of the bearer token form text field', + ); String get bearerTokenFormEmpty => Intl.message( - 'Enter a Bearer token to connect', - name: 'bearerTokenFormEmpty', - desc: 'The error message when user connect with an empty bearer token', - ); + 'Enter a Bearer token to connect', + name: 'bearerTokenFormEmpty', + desc: 'The error message when user connect with an empty bearer token', + ); String get loginWithAzureIdentity => Intl.message( - 'Login with Azure Identity', - name: 'loginWithAzureIdentity', - desc: 'The title of the button to change to azure identity login form', - ); + 'Login with Azure Identity', + name: 'loginWithAzureIdentity', + desc: 'The title of the button to change to azure identity login form', + ); String get loginWithBearerToken => Intl.message( - 'Login with Bearer token', - name: 'loginWithBearerToken', - desc: 'The title of the button to change to bearer token login form', - ); + 'Login with Bearer token', + name: 'loginWithBearerToken', + desc: 'The title of the button to change to bearer token login form', + ); String get serverConnectedWarning => Intl.message( - 'Connected to server', - name: 'serverConnectedWarning', - desc: 'Message for showing in a snackbar after a successful connection', - ); + 'Connected to server', + name: 'serverConnectedWarning', + desc: 'Message for showing in a snackbar after a successful connection', + ); String get serverNotConnectedWarning => Intl.message( - 'Cannot connect to specified URL', - name: 'serverNotConnectedWarning', - desc: - 'Message for showing in a snackbar after an unsuccessful connection', - ); + 'Cannot connect to specified URL', + name: 'serverNotConnectedWarning', + desc: 'Message for showing in a snackbar after an unsuccessful connection', + ); String get connectToServerHelpText => Intl.message( - '''Connect to the server to view all resources. + '''Connect to the server to view all resources. Copy and paste the link to your server's URL above.''', - name: 'connectToServerHelpText', - desc: - 'Message showed when the user does not have a valid connection to a server', - ); + name: 'connectToServerHelpText', + desc: + 'Message showed when the user does not have a valid connection to a server', + ); String get noDataAssociatedToAnEntity => Intl.message( - '''There's no data associated + '''There's no data associated with this entity''', - name: 'noDataAssociatedToAnEntity', - desc: - 'Message showed when no data retrieved from backend for a certcain entity', - ); + name: 'noDataAssociatedToAnEntity', + desc: + 'Message showed when no data retrieved from backend for a certcain entity', + ); String entityTypeIdHeader(final String entityType) => Intl.message( - '$entityType ID', - name: 'entityTypeIdHeader', - args: [entityType], - desc: 'ID column for the data viewer', - ); + '$entityType ID', + name: 'entityTypeIdHeader', + args: [entityType], + desc: 'ID column for the data viewer', + ); String get lastUpdate => Intl.message( - 'Last update', - name: 'lastUpdate', - desc: 'Last update column for the data viewer', - ); + 'Last update', + name: 'lastUpdate', + desc: 'Last update column for the data viewer', + ); String get selectAnEntityFromList => Intl.message( - 'Select an entity to see entries of that type', - name: 'selectAnEntityFromList', - desc: - 'Message showed when user is connected but not selected any entity to display data yet', - ); - - String get pageComingSoon => Intl.message('Page coming soon', - name: 'pageComingSoon', - desc: 'Message to show for pages that are not integrated yet'); - - String get server => Intl.message( - 'Server', - name: 'server', - desc: 'Server menu item label', - ); + 'Select an entity to see entries of that type', + name: 'selectAnEntityFromList', + desc: + 'Message showed when user is connected but not selected any entity to display data yet', + ); + + String get pageComingSoon => Intl.message( + 'Page coming soon', + name: 'pageComingSoon', + desc: 'Message to show for pages that are not integrated yet', + ); + + String get server => + Intl.message('Server', name: 'server', desc: 'Server menu item label'); String get license => Intl.message( - '''MIT License + '''MIT License Copyright (c) 2024 Evoleen Technology GmbH Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -219,101 +220,178 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''', - name: 'license', - desc: 'License menu item label', - ); + name: 'license', + desc: 'License menu item label', + ); String get thirdPartyLicenses => Intl.message( - 'Third-Party Licenses', - name: 'thirdPartyLicenses', - desc: 'third party licenses menu item label', - ); + 'Third-Party Licenses', + name: 'thirdPartyLicenses', + desc: 'third party licenses menu item label', + ); String get saveChanges => Intl.message( - 'Save changes', - name: 'saveChanges', - desc: 'Save changes button title in json code editor', - ); + 'Save changes', + name: 'saveChanges', + desc: 'Save changes button title in json code editor', + ); String get invalidFhirJsonFormat => Intl.message( - 'Invalid FHIR JSON Format', - name: 'invalidFhirJsonFormat', - desc: - 'Error message when json code editor does not fhir FHIR structure', - ); + 'Invalid FHIR JSON Format', + name: 'invalidFhirJsonFormat', + desc: 'Error message when json code editor does not fhir FHIR structure', + ); String invalidJsonFormat(final String error) => Intl.message( - 'Invalid JSON Format: $error', - name: 'invalidJsonFormat', - desc: 'Error message when json code editor does not JSON structure', - args: [error], - ); + 'Invalid JSON Format: $error', + name: 'invalidJsonFormat', + desc: 'Error message when json code editor does not JSON structure', + args: [error], + ); String get errorWhenUpdatingResource => Intl.message( - 'Error when updating resource, please try again', - name: 'errorWhenUpdatingResource', - desc: - 'Error message when json is syntax correct but cannot be updated on backend', - ); + 'Error when updating resource, please try again', + name: 'errorWhenUpdatingResource', + desc: + 'Error message when json is syntax correct but cannot be updated on backend', + ); String get errorWhenDeletingResource => Intl.message( - 'Error when deleting resource, please try again', - name: 'errorWhenDeletingResource', - desc: 'Error message when a deletion request is rejected by the server', - ); + 'Error when deleting resource, please try again', + name: 'errorWhenDeletingResource', + desc: 'Error message when a deletion request is rejected by the server', + ); String get warningResourceIdChanged => Intl.message( - 'The resource\'s id was modified, the update will be saved as new resource or be rejected by the server.\nDo you want to continue?', - name: 'warningResourceIdChanged', - desc: - 'Warning message to show when user try to publish an entity with a different ID than original', - ); + 'The resource\'s id was modified, the update will be saved as new resource or be rejected by the server.\nDo you want to continue?', + name: 'warningResourceIdChanged', + desc: + 'Warning message to show when user try to publish an entity with a different ID than original', + ); String get warningResourceTypeChanged => Intl.message( - 'The resource\'s type was modified, the update will be saved as new resource or be rejected by the server.\nDo you want to continue?', - name: 'warningResourceTypeChanged', - desc: - 'Warning message to show when user try to publish an entity with a different resource type than original', - ); + 'The resource\'s type was modified, the update will be saved as new resource or be rejected by the server.\nDo you want to continue?', + name: 'warningResourceTypeChanged', + desc: + 'Warning message to show when user try to publish an entity with a different resource type than original', + ); String get noData => Intl.message( - '--', - name: 'noData', - desc: - 'Text to display in entity list view when no data available for any of its columns', - ); + '--', + name: 'noData', + desc: + 'Text to display in entity list view when no data available for any of its columns', + ); String get warning => Intl.message( - 'Warning', - name: 'warning', - desc: 'Title of the warning dialog', - ); + 'Warning', + name: 'warning', + desc: 'Title of the warning dialog', + ); String get cancel => Intl.message( - 'Cancel', - name: 'cancel', - desc: 'Title of the cancel button in the warning dialog', - ); + 'Cancel', + name: 'cancel', + desc: 'Title of the cancel button in the warning dialog', + ); String get proceed => Intl.message( - 'Continue', - name: 'proceed', - desc: 'Title of the continue button in the warning dialog', - ); + 'Continue', + name: 'proceed', + desc: 'Title of the continue button in the warning dialog', + ); String createNewResource(final String resourceType) => Intl.message( - 'Create new $resourceType', - name: 'createNewResource', - desc: 'Title of the create resource button', - args: [resourceType], - ); + 'Create new $resourceType', + name: 'createNewResource', + desc: 'Title of the create resource button', + args: [resourceType], + ); String resourceCreationSuccessful( - final String resourceType, final String resourceId) => - Intl.message( - '$resourceType $resourceId created succesfuly', - name: 'resourceCreationSuccessful', - desc: 'Success message to show in after create a new resource', - args: [resourceType, resourceId], - ); + final String resourceType, + final String resourceId, + ) => Intl.message( + '$resourceType $resourceId created succesfuly', + name: 'resourceCreationSuccessful', + desc: 'Success message to show in after create a new resource', + args: [resourceType, resourceId], + ); + + String get importExport => Intl.message( + 'Bulk Import', + name: 'importExport', + desc: 'Import/Export menu item label', + ); + + String get importExportDescription => Intl.message( + 'Bulk import FHIR resources to the server', + name: 'importExportDescription', + desc: 'Description text for the Import/Export page', + ); + + String get ndjsonFileToImport => Intl.message( + 'NDJSON file to import', + name: 'ndjsonFileToImport', + desc: 'Label for the file picker input field for NDJSON import', + ); + + String get import => Intl.message( + 'Import', + name: 'import', + desc: 'Button text for importing the selected file', + ); + + String get connectionRequired => Intl.message( + 'Connection Required', + name: 'connectionRequired', + desc: 'Title for the dialog shown when user needs to connect to a server', + ); + + String get connectionRequiredMessage => Intl.message( + 'You need to be connected to a FHIR server before being able to import or export data.', + name: 'connectionRequiredMessage', + desc: 'Message shown in the dialog when user needs to connect to a server', + ); + + String get ok => + Intl.message('OK', name: 'ok', desc: 'OK button text for dialogs'); + + String get importError => Intl.message( + 'Import Error', + name: 'importError', + desc: 'Title for the dialog shown when import fails', + ); + + String importErrorMessage(final String error) => Intl.message( + 'An error occurred while importing the data:\n\n$error', + name: 'importErrorMessage', + desc: 'Message shown in the dialog when import fails', + args: [error], + ); + + String get importStarted => Intl.message( + 'Import Started', + name: 'importStarted', + desc: 'Title for the dialog shown when import is successfully initiated', + ); + + String get importStartedMessage => Intl.message( + 'The import process has been started successfully. You can monitor the progress in your FHIR server.', + name: 'importStartedMessage', + desc: 'Message shown in the dialog when import is successfully initiated', + ); + + String get importFailed => Intl.message( + 'Import Failed', + name: 'importFailed', + desc: 'Title for the dialog shown when import request fails', + ); + + String importFailedMessage(final String details) => Intl.message( + 'The import request failed:\n\n$details', + name: 'importFailedMessage', + desc: 'Message shown in the dialog when import request fails', + args: [details], + ); } diff --git a/lib/l10n/l10n/intl_messages.arb b/lib/l10n/l10n/intl_messages.arb index d0651db..64acb28 100644 --- a/lib/l10n/l10n/intl_messages.arb +++ b/lib/l10n/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-11-29T13:30:33.994568", + "@@last_modified": "2025-06-27T15:08:59.528239", "appTitle": "Fire Scribe", "@appTitle": { "description": "The title of the app", @@ -166,6 +166,12 @@ "type": "text", "placeholders": {} }, + "errorWhenDeletingResource": "Error when deleting resource, please try again", + "@errorWhenDeletingResource": { + "description": "Error message when a deletion request is rejected by the server", + "type": "text", + "placeholders": {} + }, "warningResourceIdChanged": "The resource's id was modified, the update will be saved as new resource or be rejected by the server.\nDo you want to continue?", "@warningResourceIdChanged": { "description": "Warning message to show when user try to publish an entity with a different ID than original", @@ -218,5 +224,87 @@ "resourceType": {}, "resourceId": {} } + }, + "importExport": "Bulk Import", + "@importExport": { + "description": "Import/Export menu item label", + "type": "text", + "placeholders": {} + }, + "importExportDescription": "Bulk import FHIR resources to the server", + "@importExportDescription": { + "description": "Description text for the Import/Export page", + "type": "text", + "placeholders": {} + }, + "ndjsonFileToImport": "NDJSON file to import", + "@ndjsonFileToImport": { + "description": "Label for the file picker input field for NDJSON import", + "type": "text", + "placeholders": {} + }, + "import": "Import", + "@import": { + "description": "Button text for importing the selected file", + "type": "text", + "placeholders": {} + }, + "connectionRequired": "Connection Required", + "@connectionRequired": { + "description": "Title for the dialog shown when user needs to connect to a server", + "type": "text", + "placeholders": {} + }, + "connectionRequiredMessage": "You need to be connected to a FHIR server before being able to import or export data.", + "@connectionRequiredMessage": { + "description": "Message shown in the dialog when user needs to connect to a server", + "type": "text", + "placeholders": {} + }, + "ok": "OK", + "@ok": { + "description": "OK button text for dialogs", + "type": "text", + "placeholders": {} + }, + "importError": "Import Error", + "@importError": { + "description": "Title for the dialog shown when import fails", + "type": "text", + "placeholders": {} + }, + "importErrorMessage": "An error occurred while importing the data:\n\n{error}", + "@importErrorMessage": { + "description": "Message shown in the dialog when import fails", + "type": "text", + "placeholders": { + "error": {} + } + }, + "importStarted": "Import Started", + "@importStarted": { + "description": "Title for the dialog shown when import is successfully initiated", + "type": "text", + "placeholders": {} + }, + "importStartedMessage": "The import process has been started successfully. You can monitor the progress in your FHIR server.", + "@importStartedMessage": { + "description": "Message shown in the dialog when import is successfully initiated", + "type": "text", + "placeholders": {} + }, + "importFailed": "Import Failed", + "@importFailed": { + "description": "Title for the dialog shown when import request fails", + "type": "text", + "placeholders": {} + }, + "importFailedMessage": "The import request failed:\n\n{details}", + "@importFailedMessage": { + "description": "Message shown in the dialog when import request fails", + "type": "text", + "placeholders": { + "details": {} + } } } \ No newline at end of file diff --git a/lib/license/license_routes.dart b/lib/license/license_routes.dart deleted file mode 100644 index d65359c..0000000 --- a/lib/license/license_routes.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:fire_scribe/app_scaffold.dart'; -import 'package:fire_scribe/license/pages/mic_oss_license_single_page.dart'; -import 'package:fire_scribe/license/pages/third_party_licenses_page.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -@immutable -class ThirdPartyLicensesRoute extends GoRouteData { - @override - Page buildPage(final BuildContext context, final GoRouterState state) { - return NoTransitionPage( - child: AppScaffold( - child: ThirdPartyLicensesPage(), - ), - ); - } -} - -@immutable -class ThirdPartyLicensesDetailRoute extends GoRouteData { - final String thirdPartyLicensePackageName; - const ThirdPartyLicensesDetailRoute({ - required this.thirdPartyLicensePackageName, - }); - - @override - Page buildPage(final BuildContext context, final GoRouterState state) { - return NoTransitionPage( - child: AppScaffold( - child: MiscOssLicenseSinglePage( - packageName: thirdPartyLicensePackageName, - ), - ), - ); - } -} diff --git a/lib/license/pages/third_party_licenses_page.dart b/lib/license/pages/third_party_licenses_page.dart index ac5063c..4d2d842 100644 --- a/lib/license/pages/third_party_licenses_page.dart +++ b/lib/license/pages/third_party_licenses_page.dart @@ -1,6 +1,5 @@ import 'package:collection/collection.dart'; import 'package:fire_scribe/l10n/app_localizations.dart'; -import 'package:fire_scribe/license/license_routes.dart'; import 'package:fire_scribe/oss_licenses.dart'; import 'package:fire_scribe/routes.dart'; import 'package:flutter/foundation.dart'; diff --git a/lib/routes.dart b/lib/routes.dart index cc473a1..3ff3bab 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,5 +1,8 @@ -import 'package:fire_scribe/dashboard/dashboard_route.dart'; -import 'package:fire_scribe/license/license_routes.dart'; +import 'package:fire_scribe/app_scaffold.dart'; +import 'package:fire_scribe/dashboard/dashboard_page.dart'; +import 'package:fire_scribe/import_export/import_export_page.dart'; +import 'package:fire_scribe/license/pages/mic_oss_license_single_page.dart'; +import 'package:fire_scribe/license/pages/third_party_licenses_page.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -7,15 +10,14 @@ part 'routes.g.dart'; @TypedShellRoute( routes: [ - TypedGoRoute( - path: '/dashboard', - ), + TypedGoRoute(path: '/dashboard'), + TypedGoRoute(path: '/import-export'), TypedGoRoute( path: '/third-party-licenses', routes: [ TypedGoRoute( path: ':thirdPartyLicensePackageName', - ) + ), ], ), ], @@ -27,3 +29,50 @@ class HomeRoute extends ShellRouteData { return navigator; } } + +@immutable +class DashboardRoute extends GoRouteData with _$DashboardRoute { + @override + Page buildPage(BuildContext context, GoRouterState state) { + return NoTransitionPage(child: AppScaffold(child: DashboardPage())); + } +} + +@immutable +class ImportExportRoute extends GoRouteData with _$ImportExportRoute { + @override + Page buildPage(BuildContext context, GoRouterState state) { + return NoTransitionPage(child: AppScaffold(child: ImportExportPage())); + } +} + +@immutable +class ThirdPartyLicensesRoute extends GoRouteData + with _$ThirdPartyLicensesRoute { + @override + Page buildPage(final BuildContext context, final GoRouterState state) { + return NoTransitionPage( + child: AppScaffold(child: ThirdPartyLicensesPage()), + ); + } +} + +@immutable +class ThirdPartyLicensesDetailRoute extends GoRouteData + with _$ThirdPartyLicensesDetailRoute { + final String thirdPartyLicensePackageName; + const ThirdPartyLicensesDetailRoute({ + required this.thirdPartyLicensePackageName, + }); + + @override + Page buildPage(final BuildContext context, final GoRouterState state) { + return NoTransitionPage( + child: AppScaffold( + child: MiscOssLicenseSinglePage( + packageName: thirdPartyLicensePackageName, + ), + ), + ); + } +} diff --git a/lib/routes.g.dart b/lib/routes.g.dart index 356e24b..898e9dc 100644 --- a/lib/routes.g.dart +++ b/lib/routes.g.dart @@ -15,15 +15,19 @@ RouteBase get $homeRoute => ShellRouteData.$route( routes: [ GoRouteData.$route( path: '/dashboard', - factory: $DashboardRouteExtension._fromState, + factory: _$DashboardRoute._fromState, + ), + GoRouteData.$route( + path: '/import-export', + factory: _$ImportExportRoute._fromState, ), GoRouteData.$route( path: '/third-party-licenses', - factory: $ThirdPartyLicensesRouteExtension._fromState, + factory: _$ThirdPartyLicensesRoute._fromState, routes: [ GoRouteData.$route( path: ':thirdPartyLicensePackageName', - factory: $ThirdPartyLicensesDetailRouteExtension._fromState, + factory: _$ThirdPartyLicensesDetailRoute._fromState, ), ], ), @@ -34,59 +38,99 @@ extension $HomeRouteExtension on HomeRoute { static HomeRoute _fromState(GoRouterState state) => HomeRoute(); } -extension $DashboardRouteExtension on DashboardRoute { +mixin _$DashboardRoute on GoRouteData { static DashboardRoute _fromState(GoRouterState state) => DashboardRoute(); + @override String get location => GoRouteData.$location( '/dashboard', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $ThirdPartyLicensesRouteExtension on ThirdPartyLicensesRoute { +mixin _$ImportExportRoute on GoRouteData { + static ImportExportRoute _fromState(GoRouterState state) => + ImportExportRoute(); + + @override + String get location => GoRouteData.$location( + '/import-export', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin _$ThirdPartyLicensesRoute on GoRouteData { static ThirdPartyLicensesRoute _fromState(GoRouterState state) => ThirdPartyLicensesRoute(); + @override String get location => GoRouteData.$location( '/third-party-licenses', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $ThirdPartyLicensesDetailRouteExtension - on ThirdPartyLicensesDetailRoute { +mixin _$ThirdPartyLicensesDetailRoute on GoRouteData { static ThirdPartyLicensesDetailRoute _fromState(GoRouterState state) => ThirdPartyLicensesDetailRoute( thirdPartyLicensePackageName: state.pathParameters['thirdPartyLicensePackageName']!, ); + ThirdPartyLicensesDetailRoute get _self => + this as ThirdPartyLicensesDetailRoute; + + @override String get location => GoRouteData.$location( - '/third-party-licenses/${Uri.encodeComponent(thirdPartyLicensePackageName)}', + '/third-party-licenses/${Uri.encodeComponent(_self.thirdPartyLicensePackageName)}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c61cb1a..a35ca25 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import file_picker import package_info_plus import path_provider_foundation import share_plus @@ -12,6 +13,7 @@ import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 1574ec7..9c4e167 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - file_picker (0.0.1): + - FlutterMacOS - FlutterMacOS (1.0.0) - package_info_plus (0.0.1): - FlutterMacOS @@ -14,6 +16,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) @@ -22,6 +25,8 @@ DEPENDENCIES: - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + file_picker: + :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos FlutterMacOS: :path: Flutter/ephemeral package_info_plus: @@ -36,6 +41,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + file_picker: a041d1192928187f68dfeee136d593da11a480f2 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 package_info_plus: f0052d280d17aa382b932f399edf32507174e870 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 39ea4a5..edc88c4 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -411,14 +411,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; diff --git a/pubspec.lock b/pubspec.lock index 46a37ea..37759a7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -34,10 +34,10 @@ packages: dependency: transitive description: name: archive - sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.0.7" args: dependency: "direct main" description: @@ -142,6 +142,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.0.0" + build_verify: + dependency: "direct dev" + description: + name: build_verify + sha256: b136572561f0b70c2e60c2a1afe5104027af533de65508e5f421922a28d8a74a + url: "https://pub.dev" + source: hosted + version: "2.0.0" built_collection: dependency: "direct main" description: @@ -154,18 +162,18 @@ packages: dependency: transitive description: name: built_value - sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27" url: "https://pub.dev" source: hosted - version: "8.9.5" + version: "8.10.1" chalkdart: dependency: transitive description: name: chalkdart - sha256: e7cfcc9a9d9546843304c1ff87fe0696c7eb82ee70e6df63f555f321b15a40d8 + sha256: "7ffc6bd39c81453fb9ba8dbce042a9c960219b75ea1c07196a7fa41c2fab9e86" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "3.0.5" characters: dependency: transitive description: @@ -190,6 +198,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -234,10 +250,10 @@ packages: dependency: transitive description: name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + sha256: aa07dbe5f2294c827b7edb9a87bba44a9c15a3cc81bc8da2ca19b37322d30080 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.14.1" cross_file: dependency: transitive description: @@ -351,6 +367,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "3ddd0838d4fd028e23771a9e5f83f4ec6b3d73919cd1d72ae02709a47c1fcf7d" + url: "https://pub.dev" + source: hosted + version: "8.2.0" fixnum: dependency: transitive description: @@ -368,10 +392,10 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "1046d719fbdf230330d3443187cc33cc11963d15c9089f6cc56faa42a4c5f0cc" + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "9.1.1" flutter_code_editor: dependency: "direct main" description: @@ -392,10 +416,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.14.3" + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -417,6 +441,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.4" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" flutter_staggered_grid_view: dependency: transitive description: @@ -439,10 +471,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: "62b248b2dfb06ded10c84b713215b25aea020a5b08c32e801a974361557ebc3f" url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "3.0.0-0.0.dev" freezed_annotation: dependency: "direct main" description: @@ -479,18 +511,18 @@ packages: dependency: "direct main" description: name: go_router - sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 + sha256: ac294be30ba841830cfa146e5a3b22bb09f8dc5a0fdd9ca9332b04b0bde99ebf url: "https://pub.dev" source: hosted - version: "14.8.1" + version: "15.2.4" go_router_builder: dependency: "direct dev" description: name: go_router_builder - sha256: a79845a602f9ce3d837408c408915ea034eb2957fe0b527cfa3fc50e2e9bcb93 + sha256: "2357997b3419219ee7096c861e65506475202a49cf38153efab0e82370925eaa" url: "https://pub.dev" source: hosted - version: "2.8.2" + version: "3.0.1" google_fonts: dependency: "direct main" description: @@ -535,10 +567,10 @@ packages: dependency: transitive description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_multi_server: dependency: transitive description: @@ -559,18 +591,18 @@ packages: dependency: transitive description: name: image - sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.5.4" infinite_scroll_pagination: dependency: "direct main" description: name: infinite_scroll_pagination - sha256: "4047eb8191e8b33573690922a9e995af64c3949dc87efc844f936b039ea279df" + sha256: "9b8f95362928a1de835658835194b435c40f2bfc386f6545c1fd706203df0cd4" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "5.1.0" intl: dependency: "direct main" description: @@ -583,10 +615,10 @@ packages: dependency: "direct dev" description: name: intl_translation - sha256: b858d88b569f3c529e992ba7186aa495f3e862897df60edb932563c619943610 + sha256: b3f1ebfab4109d1a946b45c57523628da92a0e2a2df5f2d9981ef4334fd24a26 url: "https://pub.dev" source: hosted - version: "0.20.0" + version: "0.20.1" io: dependency: transitive description: @@ -687,10 +719,10 @@ packages: dependency: "direct main" description: name: material_symbols_icons - sha256: db745002d0323c32097f5bc23711c02b0fb36ef616011a38c8c1798d5684d368 + sha256: "7c50901b39d1ad645ee25d920aed008061e1fd541a897b4ebf2c01d966dbf16b" url: "https://pub.dev" source: hosted - version: "4.2810.0" + version: "4.2815.1" meta: dependency: transitive description: @@ -775,10 +807,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -847,18 +879,18 @@ packages: dependency: transitive description: name: posix - sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" provider: dependency: transitive description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5" pub_semver: dependency: "direct main" description: @@ -895,18 +927,18 @@ packages: dependency: transitive description: name: share_plus - sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da + sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0 url: "https://pub.dev" source: hosted - version: "10.1.4" + version: "11.0.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "6.0.0" shared_preferences: dependency: "direct main" description: @@ -919,10 +951,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: @@ -1012,10 +1044,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_helper: dependency: transitive description: @@ -1092,42 +1124,42 @@ packages: dependency: "direct main" description: name: talker - sha256: "45abef5b92f9b9bd42c3f20133ad4b20ab12e1da2aa206fc0a40ea874bed7c5d" + sha256: cf02a0d294701c76022f32bc8eb7e6f943953eb17fa1f8aaee56af210848134b url: "https://pub.dev" source: hosted - version: "4.7.1" + version: "4.9.1" talker_bloc_logger: dependency: "direct main" description: name: talker_bloc_logger - sha256: "2214a5f6ef9ff33494dc6149321c270356962725cc8fc1a485d44b1d9b812ddd" + sha256: fce90b694728bb1e5cba831047eb051b38e99900256a66725b8057b3452e6730 url: "https://pub.dev" source: hosted - version: "4.7.1" + version: "4.9.1" talker_dio_logger: dependency: "direct main" description: name: talker_dio_logger - sha256: "52c1b554cccedec6073637a6d4f6a3e267dd4451c1545fe57e1b26897a560ccb" + sha256: "926688b8ee3d4328d16a978d2d400145afec5448b32014bbb255904bfa648c6e" url: "https://pub.dev" source: hosted - version: "4.7.1" + version: "4.9.1" talker_flutter: dependency: "direct main" description: name: talker_flutter - sha256: "77458ca11638dfefb651e898a26101ee54e60dc0b168ad7481a05b1c97ce2680" + sha256: "9573cca369b964c8a8d34f422b74d4b934ac64341dd31560352fe458ba6b7894" url: "https://pub.dev" source: hosted - version: "4.7.1" + version: "4.9.1" talker_logger: dependency: transitive description: name: talker_logger - sha256: ed9b20b8c09efff9f6b7c63fc6630ee2f84aa92661ae09e5ba04e77272bf2ad2 + sha256: f1755d517e5ca8b119b65ad2fc1079746a8d03bd565e75d6b9d5aedf5c1d5b15 url: "https://pub.dev" source: hosted - version: "4.7.1" + version: "4.9.1" term_glyph: dependency: transitive description: @@ -1196,18 +1228,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" url: "https://pub.dev" source: hosted - version: "6.3.15" + version: "6.3.16" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" url_launcher_linux: dependency: transitive description: @@ -1236,10 +1268,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: @@ -1276,10 +1308,10 @@ packages: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" web: dependency: transitive description: @@ -1292,18 +1324,18 @@ packages: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" webkit_inspection_protocol: dependency: transitive description: @@ -1316,10 +1348,10 @@ packages: dependency: transitive description: name: win32 - sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.dev" source: hosted - version: "5.12.0" + version: "5.13.0" xdg_directories: dependency: transitive description: @@ -1340,10 +1372,10 @@ packages: dependency: transitive description: name: xml2json - sha256: "9ff65c144a48f7f73d2bd6d919700c7697beced3b51216b982f78c68bc0da142" + sha256: "8a7ae63b76676f083d81287b61f03222952609c0507893bc62e60a4a588a9702" url: "https://pub.dev" source: hosted - version: "6.2.6" + version: "6.2.7" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 695aa71..046dbfc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,9 +22,10 @@ dependencies: url: https://github.com/evoleen/fhir_rest_client.git ref: add_docs fhir: 0.12.0 + file_picker: 8.2.0 flutter: sdk: flutter - flutter_bloc: 9.1.0 + flutter_bloc: 9.1.1 flutter_code_editor: 0.3.3 flutter_highlight: 0.7.0 flutter_localizations: @@ -32,33 +33,34 @@ dependencies: freezed_annotation: 2.4.4 get_it: 8.0.3 google_fonts: 6.2.1 - go_router: 14.8.1 + go_router: 15.2.4 highlight: 0.7.0 - infinite_scroll_pagination: 4.1.0 + infinite_scroll_pagination: 5.1.0 intl: 0.19.0 - material_symbols_icons: 4.2810.0 + material_symbols_icons: 4.2815.1 package_info_plus: 8.3.0 path: 1.9.1 pub_semver: 2.2.0 shared_preferences: 2.5.3 stack_trace: 1.12.1 - talker: 4.7.1 - talker_bloc_logger: 4.7.1 - talker_dio_logger: 4.7.1 - talker_flutter: 4.7.1 + talker: 4.9.1 + talker_bloc_logger: 4.9.1 + talker_dio_logger: 4.9.1 + talker_flutter: 4.9.1 dev_dependencies: bloc_test: 10.0.0 build_runner: 2.4.15 + build_verify: 2.0.0 dartz: 0.10.1 - flutter_launcher_icons: 0.14.3 + flutter_launcher_icons: 0.14.4 flutter_lints: 5.0.0 flutter_oss_licenses: 3.0.4 flutter_test: sdk: flutter - freezed: 2.5.7 - go_router_builder: 2.8.2 - intl_translation: 0.20.0 + freezed: 3.0.0-0.0.dev + go_router_builder: 3.0.1 + intl_translation: 0.20.1 mocktail: 1.0.4 remove_from_coverage: 2.0.0