diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index fa41eac..e796a3a 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -38,7 +38,7 @@ jobs: # Get packages that have changes using melos diff - packages=$(melos list --diff="$BASE_REF" --json | jq -c "[.[] | select(.name != \"biomedict_workspace\") | .name]" 2>/dev/null || echo '[]') + packages=$(melos list --diff="$BASE_REF" --json | jq -c "[.[] | select(.name != \"flutter_forge_workspace\") | .name]" 2>/dev/null || echo '[]') if [ "$packages" = "" ] || [ "$packages" = "null" ]; then packages="[]" fi @@ -66,7 +66,12 @@ jobs: melos bootstrap --scope="${{ matrix.package }}" - name: Format package - run: melos exec --scope="${{ matrix.package }}" -- dart format . --set-exit-if-changed + # Brick templates are mustache, not parseable Dart, and `dart format` + # has no exclude of its own - so filter the paths before it sees them. + run: | + melos exec --scope="${{ matrix.package }}" -- \ + "find . -name '*.dart' -not -path '*/__brick__/*' -not -path '*/.dart_tool/*' \ + -exec dart format --set-exit-if-changed {} +" - name: Analyze package run: melos exec --scope="${{ matrix.package }}" -- dart analyze --fatal-infos diff --git a/melos.yaml b/melos.yaml index e4822fc..0e84cb6 100644 --- a/melos.yaml +++ b/melos.yaml @@ -23,8 +23,11 @@ command: scripts: lint: description: "Run format and analyze all packages" + # Brick sources are mustache templates, not valid Dart, so the formatter + # has to skip them - `dart format` has no exclude of its own. run: | - dart format --set-exit-if-changed . && + find . -name '*.dart' -not -path '*/__brick__/*' -not -path '*/.dart_tool/*' \ + -exec dart format --set-exit-if-changed {} + && dart analyze --fatal-infos . exec: concurrency: 5 diff --git a/packages/nonstop_cli/README.md b/packages/nonstop_cli/README.md index 5eb87ef..b66e0b6 100644 --- a/packages/nonstop_cli/README.md +++ b/packages/nonstop_cli/README.md @@ -68,6 +68,7 @@ nonstop create [arguments] | `--output-directory` | `-o` | Output directory for the new project | Current directory | | `--description` | `--desc` | Description for the new project | "A Melos-managed project for mono-repo, created using NonStop CLI." | | `--org-name` | `--org` | Organization name for the new project | `com.example` | +| `--defaults` | `-y` | Skip the module picker and take the recommended set | Prompt | #### ๐Ÿ“ฆ Template Options @@ -75,7 +76,7 @@ nonstop create [arguments] | Template | ๐ŸŽฏ Description | ๐Ÿ“ Structure Created | |-----------|------------------------------------------------------------------|-------------------------------------------------------------------------| -| `mono` | ๐Ÿข Generate a Flutter application along with mono-repo (default) | Complete mono-repo structure with apps, features, packages, and plugins | +| `mono` | ๐Ÿข Generate a full app skeleton in a mono-repo (default) | App shell, design system, localization and the modules you pick | | `package` | ๐Ÿ“ฆ Generate a Flutter package for a Melos-managed mono-repo | Flutter package compatible with mono-repo structure | | `app` | ๐Ÿ“ฑ Generate a Flutter application for a Melos-managed mono-repo | Flutter application configured for mono-repo structure | | `plugin` | ๐Ÿ”Œ Generate a Flutter plugin for a Melos-managed mono-repo | Flutter plugin compatible with mono-repo structure | @@ -84,6 +85,38 @@ nonstop create [arguments] #### ๐Ÿ’ก Examples +#### ๐Ÿงฉ Modules + +`mono` does not stop at empty folders. It asks which platform modules the app +needs and generates them wired together - bootstrap order, DI registration, +route table and all. + +``` +? Which modules should youtube start with? +โฏ โ—‰ Network - Dio client, auth + logging interceptors, typed errors + โ—‰ Authentication - Firebase Auth (email, Google, Apple) with route guards + โ—ฏ Cloud Firestore - Firestore + Cloud Functions wiring and timestamp converters + โ—‰ Push notifications + โ—‰ Analytics + โ—‰ Crash reporting + โ—‰ Feature flags + โ—‰ Developer tools + โ—‰ Dashboard shell +``` + +Answer non-interactively with `-y` (recommended set) or per module: + +```bash +nonstop create youtube -y --no-notifications --firestore +``` + +Two rules are applied for you, because the code will not compile otherwise: +push notifications pull in `network`, and developer tools pull in +`feature_flags`. + +Every project also gets `core`, `di`, `design_system`, `localization` and +`utils` - the pieces every app needs. +
๐Ÿข Create a mono-repo project @@ -96,9 +129,22 @@ This creates the following structure: ``` youtube/ โ”œโ”€โ”€ ๐Ÿ“ฑ apps/ -โ”‚ โ””โ”€โ”€ youtube/ +โ”‚ โ””โ”€โ”€ youtube/ # bootstrap, router, splash - no business logic โ”œโ”€โ”€ ๐Ÿงฉ features/ +โ”‚ โ”œโ”€โ”€ auth/ # Firebase Auth + route guards +โ”‚ โ””โ”€โ”€ dashboard/ # bottom-navigation shell โ”œโ”€โ”€ ๐Ÿ“ฆ packages/ +โ”‚ โ”œโ”€โ”€ core/ # logger, DI bootstrap, observers, error types +โ”‚ โ”œโ”€โ”€ design_system/ # Material 3 theme, components, toasts +โ”‚ โ”œโ”€โ”€ di/ # service locator +โ”‚ โ”œโ”€โ”€ localization/ # type-safe strings (i69n) +โ”‚ โ”œโ”€โ”€ network/ # Dio client and interceptors +โ”‚ โ”œโ”€โ”€ analytics/ +โ”‚ โ”œโ”€โ”€ crashlytics/ +โ”‚ โ”œโ”€โ”€ feature_flags/ +โ”‚ โ”œโ”€โ”€ notifications/ +โ”‚ โ”œโ”€โ”€ developer/ +โ”‚ โ””โ”€โ”€ utils/ โ”œโ”€โ”€ ๐Ÿ”Œ plugins/ โ”œโ”€โ”€ ๐Ÿ“‹ analysis_options.yaml โ”œโ”€โ”€ ๐Ÿ“– README.md @@ -106,6 +152,11 @@ youtube/ โ””โ”€โ”€ ๐Ÿ“„ pubspec.yaml ``` +The generated workspace is bootstrapped, formatted and passes its own +`melos lint` and `melos test` before you touch it. If you picked any Firebase +module, run `flutterfire configure` in the app directory - the generated +`firebase_options.dart` throws until you do. +
diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/.gitignore b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/.gitignore index 34a1c19..1f60ad4 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/.gitignore +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/.gitignore @@ -12,5 +12,7 @@ pubspec_overrides.yaml .flutter-plugins-dependencies *.iml *.lock +# Apps ship a binary; pin their resolution. +!apps/*/pubspec.lock mason-lock.json .mason diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/README.md index 1225a1b..4c9021d 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/README.md +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/README.md @@ -1,20 +1,95 @@ # {{ name.titleCase() }} -{{ description }} +{{{description}}} [![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) [![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) +## Layout -## Technologies at play +``` +apps/{{name.snakeCase()}} the application shell: bootstrap, router, splash +features/ vertical slices (ui + state + data) +packages/ shared capabilities +plugins/ native integrations +``` -- Dart -- Flutter -- Melos +Dependencies point inward: apps depend on features, features depend on +packages, packages depend on nothing above them. -## Getting Started +### What is already wired -- All apps are in the `/apps` directory. -- All features are in the `/features` directory -- All shared packages are in the `/packages` directory -- All plugins are in the `/plugins` directory \ No newline at end of file +| Module | Role | +| --- | --- | +| `packages/core` | logger, DI bootstrap, route observers, error types, BLoC observer | +| `packages/di` | service locator (GetIt) behind a small interface | +| `packages/design_system` | Material 3 theme, shared components, toasts, loaders | +| `packages/localization` | type-safe strings generated from YAML (i69n) | +| `packages/utils` | dependency-light helpers | +{{#network}}| `packages/network` | Dio client, auth + logging interceptors, typed errors | +{{/network}}{{#analytics}}| `packages/analytics` | event tracking behind a swappable client | +{{/analytics}}{{#crashlytics}}| `packages/crashlytics` | crash and non-fatal reporting | +{{/crashlytics}}{{#notifications}}| `packages/notifications` | FCM, permissions, device-token registration | +{{/notifications}}{{#feature_flags}}| `packages/feature_flags` | Remote Config flags with a widget wrapper | +{{/feature_flags}}{{#developer}}| `packages/developer` | in-app dev tools, reachable by a hidden 5-tap gesture | +{{/developer}}{{#auth}}| `features/auth` | Firebase Auth (email, Google, Apple) + route guards | +{{/auth}}{{#dashboard}}| `features/dashboard` | bottom-navigation shell with starter tabs | +{{/dashboard}} + +## Getting started + +```sh +dart pub global activate melos +melos bootstrap +``` +{{#firebase}} +### Firebase + +This project uses Firebase, so configure it before the first run - the +generated `lib/firebase_options.dart` deliberately throws until you do: + +```sh +dart pub global activate flutterfire_cli +cd apps/{{name.snakeCase()}} +flutterfire configure +``` +{{/firebase}} +### Run + +```sh +cd apps/{{name.snakeCase()}} +flutter run --dart-define=BASE_URL=https://api.example.com +``` + +Every environment value is a `--dart-define` read in +`packages/core/lib/constants/environment.dart`. Add new ones there rather than +scattering `String.fromEnvironment` around the codebase. +{{#firebase}} +To run against the Firebase emulator suite, add +`--dart-define=USE_EMULATORS=true`. +{{/firebase}} +## Day-to-day + +```sh +melos lint # format + analyze everything +melos test # run every package's tests +melos run generate # build_runner where it is needed +melos run generate:i69n # regenerate strings after editing messages.i69n.yaml +``` + +## Adding to the monorepo + +```sh +nonstop create package my_feature -o features +nonstop create package my_package -o packages +nonstop create app my_second_app -o apps +nonstop create plugin my_plugin -o plugins +``` + +## Where to start + +1. Replace the placeholder tabs in `features/dashboard`. +2. Put your product strings in `packages/localization/lib/messages.i69n.yaml`. +3. Set the palette in `packages/design_system/lib/generated/theme.dart`. +4. Add feature modules and spread their routes into + `apps/{{name.snakeCase()}}/lib/router/router.dart`. diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/analysis_options.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/analysis_options.yaml index d09b221..4100bd8 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/analysis_options.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/analysis_options.yaml @@ -1,5 +1,16 @@ include: package:flutter_lints/flutter.yaml +analyzer: + exclude: + # Generated code - regenerate it, do not lint it. + - "**/*.g.dart" + - "**/*.freezed.dart" + - "**/*.i69n.dart" + # Build output and the plugin sources CocoaPods symlinks into the app. + - "**/build/**" + - "**/ios/.symlinks/**" + - "**/macos/.symlinks/**" + linter: rules: - require_trailing_commas: true \ No newline at end of file + require_trailing_commas: true diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/README.md index 6673234..b323ea6 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/README.md +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/README.md @@ -1 +1,11 @@ -# Put all your apps in this folder +# Apps + +Runnable Flutter applications. An app wires modules together in +`lib/bootstrap.dart` and composes feature routes in `lib/router/router.dart` - +it should hold almost no business logic of its own. + +Add another (an admin build, a white-label variant) with: + +```sh +nonstop create app my_other_app -o apps +``` diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/app.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/app.dart new file mode 100644 index 0000000..12b4796 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/app.dart @@ -0,0 +1,66 @@ +import 'package:core/core.dart'; +import 'package:design_system/design_system.dart'; +{{#notifications}}import 'package:di/di.dart'; +{{/notifications}}import 'package:flutter/material.dart'; +import 'package:localization/localization.dart'; +{{#notifications}}import 'package:notifications/notifications.dart'; +{{/notifications}}import 'package:{{name.snakeCase()}}/router/router.dart'; + +class App extends StatefulWidget { + const App({super.key}); + + @override + State createState() => _AppState(); +} + +class _AppState extends State { +{{#notifications}} late final AppLifecycleListener _lifecycleListener; + +{{/notifications}} @override + void initState() { + super.initState(); +{{#notifications}} // Asking for notification permission needs a foreground surface, so this + // runs from the first frame rather than from bootstrap. Move it behind + // sign-in once you would rather ask a user who has committed to the app. + _lifecycleListener = AppLifecycleListener(onResume: _clearBadge); + WidgetsBinding.instance.addPostFrameCallback((_) async { + final logger = di.get(); + try { + await di.get().init(); + } catch (e, s) { + logger.e('Failed to initialize NotificationClient', e, s); + } + }); +{{/notifications}} } +{{#notifications}} + void _clearBadge() { + final logger = di.get(); + try { + di.get().clearBadge(); + } catch (e, s) { + logger.e('Failed to clear badge on app resume', e, s); + } + } + + @override + void dispose() { + _lifecycleListener.dispose(); + super.dispose(); + } +{{/notifications}} + @override + Widget build(BuildContext context) { + return GlobalEventChannelProvider( + child: DesignSystemWrapper( + builder: (context, theme) => MaterialApp.router( + debugShowCheckedModeBanner: false, + title: strings.app.name, + theme: theme, + routeInformationParser: AppRouter.routeInformationParser, + routeInformationProvider: AppRouter.routeInformationProvider, + routerDelegate: AppRouter.routerDelegate, + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/bootstrap.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/bootstrap.dart new file mode 100644 index 0000000..1fe9563 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/bootstrap.dart @@ -0,0 +1,59 @@ +{{#analytics}}import 'package:analytics/analytics.dart' as analytics; +{{/analytics}}{{#auth}}import 'package:auth/auth.dart' as auth; +{{/auth}}import 'package:bloc/bloc.dart'; +import 'package:core/core.dart' as core; +{{#firebase}}import 'package:core/developer/emulators.dart' as emulators; +{{/firebase}}import 'package:core/logger/logger.dart'; +{{#crashlytics}}import 'package:crashlytics/crashlytics.dart' as crashlytics; +{{/crashlytics}}import 'package:di/di.dart'; +{{#feature_flags}}import 'package:feature_flags/feature_flags.dart' as feature_flags; +{{/feature_flags}}{{#firebase}}import 'package:firebase_core/firebase_core.dart'; +{{/firebase}}import 'package:flutter/widgets.dart'; +{{#network}}import 'package:network/network.dart' as network; +{{/network}}{{#notifications}}import 'package:notifications/notifications.dart' as notifications; +{{/notifications}}{{#firebase}}import 'package:{{name.snakeCase()}}/firebase_options.dart'; +{{/firebase}} +/// Brings every module up, in dependency order, before the first frame. +/// +/// Order matters: the logger is registered first so everything after it can +/// report failures, and Firebase must be live before any Firebase-backed +/// module initialises. +Future init() async { + WidgetsFlutterBinding.ensureInitialized(); + final stopwatch = Stopwatch()..start(); + + await core.init(); + final logger = di.get(); + logger.i('Core ready in ${stopwatch.elapsedMilliseconds} ms'); + + Bloc.observer = core.CoreBlocObserver(); +{{#firebase}} + final options = DefaultFirebaseOptions.currentPlatform; + await Firebase.initializeApp(options: options); + + if (core.Environment.useEmulators) { + await emulators.init(); + } +{{/firebase}} +{{#crashlytics}} await crashlytics.init(); +{{/crashlytics}}{{#analytics}} await analytics.init(); +{{/analytics}}{{#feature_flags}} await feature_flags.init(); +{{/feature_flags}}{{#auth}} + final clientId = options.iosClientId ?? options.androidClientId ?? ''; + await auth.init(auth.DefaultAuthConfig(clientId: clientId)); +{{/auth}}{{#network}} + await network.init( + config: network.DefaultNetworkConfig(baseUrl: core.Environment.baseUrl), + ); +{{/network}}{{#notifications}} + await notifications.init(); +{{/notifications}} + // TODO: initialise your own feature modules here. + + stopwatch.stop(); + logger.i('Bootstrap completed in ${stopwatch.elapsedMilliseconds} ms'); +{{#analytics}} + await analytics.AnalyticsHelper.logAppOpen( + parameters: {'bootstrap_duration': stopwatch.elapsedMilliseconds}, + ); +{{/analytics}}} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/firebase_options.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/firebase_options.dart new file mode 100644 index 0000000..9a50531 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/firebase_options.dart @@ -0,0 +1,17 @@ +{{#firebase}}// This file is a placeholder. Generate the real one with: +// +// cd apps/{{name.snakeCase()}} && flutterfire configure +// +// FlutterFire overwrites this file with your project's options and also drops +// google-services.json / GoogleService-Info.plist into the native folders. +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; + +class DefaultFirebaseOptions { + const DefaultFirebaseOptions._(); + + static FirebaseOptions get currentPlatform => throw UnsupportedError( + 'Firebase has not been configured for {{name.titleCase()}}.\n' + 'Run `flutterfire configure` inside apps/{{name.snakeCase()}} ' + 'to replace lib/firebase_options.dart.', + ); +}{{/firebase}} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/main.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/main.dart new file mode 100644 index 0000000..7178b59 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/main.dart @@ -0,0 +1,8 @@ +import 'package:flutter/material.dart'; +import 'package:{{name.snakeCase()}}/app.dart'; +import 'package:{{name.snakeCase()}}/bootstrap.dart' as bootstrap; + +void main() async { + await bootstrap.init(); + runApp(const App()); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/router/router.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/router/router.dart new file mode 100644 index 0000000..69f3586 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/router/router.dart @@ -0,0 +1,114 @@ +{{#analytics}}import 'package:analytics/analytics.dart'; +{{/analytics}}{{#auth}}import 'package:auth/auth.dart' as auth; +{{/auth}}import 'package:core/core.dart' as core; +{{#dashboard}}import 'package:dashboard/dashboard.dart'; +{{/dashboard}}import 'package:design_system/design_system.dart'; +{{#developer}}import 'package:developer/developer.dart' as developer; +{{/developer}}import 'package:di/di.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:localization/localization.dart'; +import 'package:navigation_history_observer/navigation_history_observer.dart'; +import 'package:{{name.snakeCase()}}/ui/splash_screen.dart'; + +/// The app's single [GoRouter]. +/// +/// Feature packages expose their own `routes` list and are spliced in here, so +/// adding a feature is one import plus one spread - no route table to merge. +class AppRouter { + const AppRouter._(); + + static final GoRouter _router = createRouter(); + + static RouterDelegate get routerDelegate => _router.routerDelegate; + + static RouteInformationParser get routeInformationParser => + _router.routeInformationParser; + + static RouteInformationProvider? get routeInformationProvider => + _router.routeInformationProvider; + + static GoRouter createRouter() { + final router = GoRouter( + debugLogDiagnostics: kDebugMode, + initialLocation: core.CoreRoutes.root, + observers: [ + NavigationHistoryObserver(), + core.CoreRouteObserver(), +{{#analytics}} AnalyticsRouteObserver(), +{{/analytics}} ], + errorBuilder: (context, state) => ErrorScreen( + title: strings.errors.page_not_found, + message: strings.errors.page_not_found_description, + onGoHome: () => context.go(core.CoreRoutes.root), + error: state.uri.toString(), + ), + routes: [ + GoRoute( + path: core.CoreRoutes.root, + builder: (context, state) => const SplashScreen(), + ), +{{#dashboard}} GoRoute( + path: core.CoreRoutes.home, + redirect: (context, state) => DashboardRouter.home, + ), + DashboardRouter.createShellRoute(), +{{/dashboard}}{{^dashboard}} GoRoute( + path: core.CoreRoutes.home, + builder: (context, state) => const _HomeScreen(), + ), +{{/dashboard}}{{#auth}} ...auth.AuthRouter( + onSignedIn: _onAuthenticated, + onSignedUp: _onAuthenticated, + ).routes, +{{/auth}}{{#developer}} ...developer.DeveloperRouter().routes, +{{/developer}} GoRoute( + path: core.CoreRoutes.error, + builder: (context, state) => ErrorScreen( + title: + state.uri.queryParameters[core.Keys.title] ?? + strings.generic.error, + message: + state.uri.queryParameters[core.Keys.message] ?? + strings.errors.unexpected_error, + error: state.uri.queryParameters[core.Keys.error], + onGoHome: () => context.go(core.CoreRoutes.root), + ), + ), + ], + ); + + di.register(router); + return router; + } +{{#auth}} + /// Where a user lands once sign-in or sign-up succeeds. + /// + /// Load the profile / entitlements you need here before routing on. + static Future _onAuthenticated(BuildContext context) async { +{{#analytics}} await AnalyticsHelper.logEvent(AnalyticsEvents.user.authenticatedRedirect); +{{/analytics}} if (context.mounted) { + context.go({{#dashboard}}DashboardRouter.home{{/dashboard}}{{^dashboard}}core.CoreRoutes.home{{/dashboard}}); + } + } +{{/auth}}} +{{^dashboard}} +/// Placeholder landing screen - replace it with your first feature. +class _HomeScreen extends StatelessWidget { + const _HomeScreen(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(strings.app.name)), + body: Center( + child: {{#developer}}developer.OpenDevToolsWrapper( + // Tap 5x to reach the developer tools. + child: Text(strings.app.welcome_to_app), + ){{/developer}}{{^developer}}Text(strings.app.welcome_to_app){{/developer}}, + ), + ); + } +} +{{/dashboard}} \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/ui/splash_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/ui/splash_screen.dart new file mode 100644 index 0000000..11997e2 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/ui/splash_screen.dart @@ -0,0 +1,61 @@ +{{#auth}}import 'package:auth/auth.dart' as auth; +{{/auth}}{{^dashboard}}import 'package:core/core.dart' as core; +{{/dashboard}}{{#dashboard}}import 'package:dashboard/dashboard.dart'; +{{/dashboard}}import 'package:design_system/design_system.dart'; +{{#auth}}import 'package:di/di.dart'; +{{/auth}}import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:localization/localization.dart'; + +/// First frame the user sees. +/// +/// It exists to give async startup work (session restore, profile fetch) a +/// place to happen before the app commits to a destination. +class SplashScreen extends StatefulWidget { + const SplashScreen({super.key}); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) => _redirect()); + } + + Future _redirect() async { + if (!mounted) return; +{{#auth}} + if (!di.get().isSignedIn) { + context.go(auth.AuthRoutes.signIn); + return; + } +{{/auth}} + // TODO: load whatever the first screen needs before navigating. + context.go({{#dashboard}}DashboardRouter.home{{/dashboard}}{{^dashboard}}core.CoreRoutes.home{{/dashboard}}); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + strings.app.name, + style: theme.textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 32), + const DefaultLoader(), + ], + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml new file mode 100644 index 0000000..b60cd50 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml @@ -0,0 +1,61 @@ +name: {{name.snakeCase()}} +description: "{{{description}}}" +publish_to: 'none' +version: 0.1.0+1 + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.8 + go_router: ^15.1.3 + bloc: ^9.0.0 + navigation_history_observer: ^1.1.1 + core: + path: ../../packages/core + design_system: + path: ../../packages/design_system + di: + path: ../../packages/di + localization: + path: ../../packages/localization + utils: + path: ../../packages/utils +{{#firebase}} firebase_core: ^3.14.0 +{{/firebase}}{{#network}} network: + path: ../../packages/network +{{/network}}{{#analytics}} analytics: + path: ../../packages/analytics +{{/analytics}}{{#crashlytics}} crashlytics: + path: ../../packages/crashlytics +{{/crashlytics}}{{#feature_flags}} feature_flags: + path: ../../packages/feature_flags +{{/feature_flags}}{{#notifications}} notifications: + path: ../../packages/notifications +{{/notifications}}{{#developer}} developer: + path: ../../packages/developer +{{/developer}}{{#auth}} auth: + path: ../../features/auth +{{/auth}}{{#dashboard}} dashboard: + path: ../../features/dashboard +{{/dashboard}} +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +dependency_overrides: + intl: ^0.20.2 + +flutter: + uses-material-design: true +{{#auth}} fonts: + # firebase_ui_auth ships this font but never registers it, so the Google + # and Apple buttons render as tofu unless the app declares it here. + - family: SocialIcons + fonts: + - asset: packages/firebase_ui_auth/fonts/SocialIcons.ttf +{{/auth}} \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/test/{{smoke_test_file}} b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/test/{{smoke_test_file}} new file mode 100644 index 0000000..a87dd87 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/test/{{smoke_test_file}} @@ -0,0 +1,17 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:{{name.snakeCase()}}/app.dart'; +import 'package:{{name.snakeCase()}}/bootstrap.dart' as bootstrap; + +void main() { + testWidgets('the app boots and reaches its first screen', (tester) async { + // Exercises the whole graph: module init order, DI registration and the + // splash redirect. If a module forgets to register something, this fails + // here rather than on a device. + await bootstrap.init(); + await tester.pumpWidget(const App()); + await tester.pumpAndSettle(); + + expect(tester.takeException(), isNull); +{{#dashboard}} expect(find.text('Explore'), findsWidgets); +{{/dashboard}} }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/README.md index 66a1979..2f493c7 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/README.md +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/README.md @@ -1 +1,15 @@ -# Put all your features in this folder \ No newline at end of file +# Features + +Vertical slices of the product. A feature owns its UI, state and data access, +and exposes two things to the app: + +- an `init()` that registers its services with the locator +- a `Router` exposing the routes the app splices into its route table + +Features may depend on `packages/`, and should avoid depending on each other. + +Add one with: + +```sh +nonstop create package my_feature -o features +``` diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/README.md new file mode 100644 index 0000000..de195f3 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/README.md @@ -0,0 +1,18 @@ +# Auth + +A Melos-managed project for mono-repo, created using NonStop CLI. + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) + +## Installation ๐Ÿ’ป + +**โ— In order to start using Auth you must have the [Flutter SDK][flutter_install_link] installed on your machine.** + +Install via `dart pub add`: + +```sh +dart pub add auth +``` + +[flutter_install_link]: https://docs.flutter.dev/get-started/install \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/analytics/analytics.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/analytics/analytics.dart new file mode 100644 index 0000000..e19d0ba --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/analytics/analytics.dart @@ -0,0 +1,101 @@ +{{#analytics}}import 'package:analytics/analytics.dart'; +{{/analytics}}import 'package:firebase_auth/firebase_auth.dart'; + +/// Every auth-related analytics event in one place. +/// +/// Screens call these instead of `AnalyticsHelper` directly so the event names +/// and parameter shapes stay consistent across sign-in, sign-up and recovery. +{{^analytics}}/// +/// Analytics is not enabled in this project, so the log methods are no-ops. +/// Wire them up by adding the `analytics` package back to `pubspec.yaml`. +{{/analytics}}class AuthAnalytics { + const AuthAnalytics._(); + + /// 'google', 'apple' or 'email', derived from the signed-in provider. + static String getAuthMethod(List? providerData) { + if (providerData?.isNotEmpty ?? false) { + final providerId = providerData!.first.providerId; + if (providerId.contains('google')) return 'google'; + if (providerId.contains('apple')) return 'apple'; + } + return 'email'; + } + + static Future logSignInSuccess({ + required String method, + String? userEmail, + }) async { +{{#analytics}} await AnalyticsHelper.logSignIn( + method: method, + parameters: _successParams(userEmail), + ); +{{/analytics}} } + + static Future logSignUpSuccess({ + required String method, + String? userEmail, + }) async { +{{#analytics}} await AnalyticsHelper.logSignUp( + method: method, + parameters: _successParams(userEmail), + ); +{{/analytics}} } + + static Future logUserCreationSuccess({ + required String method, + String? userEmail, + }) async { +{{#analytics}} await AnalyticsHelper.logEvent( + AnalyticsEvents.user.authenticatedRedirect, + parameters: { + 'auth_method': method, + 'flow_type': 'registration', + ..._successParams(userEmail), + }, + ); +{{/analytics}} } + + static Future logSignOutSuccess() async { +{{#analytics}} await AnalyticsHelper.logEvent( + AnalyticsEvents.auth.signOut, + parameters: _successParams(null), + ); +{{/analytics}} } + + /// [flowType] is one of 'sign_in', 'sign_up', 'forgot_password'. + static Future logAuthError({ + required String errorType, + required String flowType, + String? errorMessage, + String? method, + }) async { +{{#analytics}} await AnalyticsHelper.logEvent( + AnalyticsEvents.auth.failed, + parameters: { + 'error_type': errorType, + 'flow_type': flowType, + 'auth_method': method, + 'error_message': errorMessage, + 'timestamp': DateTime.now().toIso8601String(), + }, + ); +{{/analytics}} } + + static Future logSignOutError({required String errorMessage}) async { +{{#analytics}} await AnalyticsHelper.logEvent( + AnalyticsEvents.error.appError, + parameters: { + 'error_type': 'sign_out_failed', + 'error_message': errorMessage, + 'feature': 'auth', + 'timestamp': DateTime.now().toIso8601String(), + }, + ); +{{/analytics}} } +{{#analytics}} + static Map _successParams(String? userEmail) => { + 'success': 'true', + 'user_email_domain': userEmail?.split('@').last, + 'timestamp': DateTime.now().toIso8601String(), + }; +{{/analytics}}} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/auth.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/auth.dart new file mode 100644 index 0000000..520383f --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/auth.dart @@ -0,0 +1,40 @@ +import 'dart:io'; + +import 'package:auth/config/config.dart'; +import 'package:auth/data/index.dart'; +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; +import 'package:firebase_ui_auth/firebase_ui_auth.dart'; +import 'package:firebase_ui_oauth_apple/firebase_ui_oauth_apple.dart'; +import 'package:firebase_ui_oauth_google/firebase_ui_oauth_google.dart'; +{{#network}}import 'package:network/network.dart'; +{{/network}} +export 'analytics/analytics.dart'; +export 'config/index.dart'; +export 'constants/index.dart'; +export 'data/index.dart'; +export 'router/router.dart'; +export 'ui/screens/index.dart'; + +/// Registers the auth providers and services. +/// +/// Call this after `Firebase.initializeApp` and before the router is built - +/// route guards resolve [AuthService] from the locator on the first frame. +Future init(AuthConfig config) async { + final logger = di.get(); + logger.i('Initializing Auth...'); + + FirebaseUIAuth.configureProviders([ + EmailAuthProvider(), + GoogleProvider(clientId: config.clientId, iOSPreferPlist: true), + if (Platform.isIOS) AppleProvider(), + ]); + + di.register(AuthServiceImp()); +{{#network}} + // The network layer reads bearer tokens through this provider, so auth has + // to own it - registering it anywhere else risks a stale token on refresh. + di.register(FirebaseAuthTokenProvider()); +{{/network}} + logger.i('Auth initialized'); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/config.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/config.dart new file mode 100644 index 0000000..a6a04a0 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/config.dart @@ -0,0 +1,3 @@ +abstract class AuthConfig { + String get clientId; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/config_imp.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/config_imp.dart new file mode 100644 index 0000000..693191f --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/config_imp.dart @@ -0,0 +1,8 @@ +import 'package:auth/config/config.dart'; + +class DefaultAuthConfig implements AuthConfig { + @override + String clientId; + + DefaultAuthConfig({required this.clientId}); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/index.dart new file mode 100644 index 0000000..5a25c0b --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/index.dart @@ -0,0 +1,2 @@ +export 'config.dart'; +export 'config_imp.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/constants/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/constants/index.dart new file mode 100644 index 0000000..a935bb9 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/constants/index.dart @@ -0,0 +1 @@ +export 'routes.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/constants/routes.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/constants/routes.dart new file mode 100644 index 0000000..9289a7a --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/constants/routes.dart @@ -0,0 +1,10 @@ +class AuthRoutes { + AuthRoutes._(); + + static const String auth = '/auth'; + + static const String signIn = '$auth/sign-in'; + static const String signUp = '$auth/sign-up'; + static const String forgotPassword = '$auth/forgot-password'; + static const String resetPassword = '$auth/reset-password'; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/index.dart new file mode 100644 index 0000000..cfb42d9 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/index.dart @@ -0,0 +1,3 @@ +export './services/index.dart'; +{{#network}}export './token/firebase_auth_token_provider.dart'; +{{/network}} \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/auth_service.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/auth_service.dart new file mode 100644 index 0000000..1c351b4 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/auth_service.dart @@ -0,0 +1,7 @@ +abstract class AuthService { + String get uid; + + bool get isSignedIn; + + Future signOut(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/auth_service_imp.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/auth_service_imp.dart new file mode 100644 index 0000000..9c407ff --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/auth_service_imp.dart @@ -0,0 +1,35 @@ +import 'package:auth/analytics/analytics.dart'; +import 'package:auth/data/services/auth_service.dart'; +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +class AuthServiceImp implements AuthService { + final _logger = di.get(); + final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; + + @override + String get uid => _firebaseAuth.currentUser?.uid ?? ''; + + @override + bool get isSignedIn => _firebaseAuth.currentUser != null; + + @override + Future signOut() async { + try { + _logger.d('Attempting to sign out user'); + await _firebaseAuth.signOut(); + _logger.d('User signed out successfully'); + + // Log successful sign out + AuthAnalytics.logSignOutSuccess(); + } catch (e, s) { + _logger.e('Error signing out user: $e', e, s); + + // Log sign out error + AuthAnalytics.logSignOutError(errorMessage: e.toString()); + + rethrow; + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/index.dart new file mode 100644 index 0000000..67ce986 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/index.dart @@ -0,0 +1,3 @@ +//GENERATED BARREL FILE +export 'auth_service.dart'; +export 'auth_service_imp.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/token/firebase_auth_token_provider.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/token/firebase_auth_token_provider.dart new file mode 100644 index 0000000..321743f --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/token/firebase_auth_token_provider.dart @@ -0,0 +1,163 @@ +{{#network}}import 'dart:async'; + +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:network/network.dart'; + +final _tag = 'FirebaseAuthTokenProvider'; + +class FirebaseAuthTokenProvider implements AuthTokenProvider { + FirebaseAuthTokenProvider() : _logger = di.get() { + _initializeTokenStream(); + } + + final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; + final Logger _logger; + + late final StreamController _tokenController; + StreamSubscription? _authStateSubscription; + String? _cachedToken; + DateTime? _tokenExpiry; + + void _initializeTokenStream() { + _tokenController = StreamController.broadcast(); + + // Listen to auth state changes + _authStateSubscription = _firebaseAuth.authStateChanges().listen( + (User? user) async { + if (user != null) { + try { + final token = await user.getIdToken(); + _cachedToken = token; + _setTokenExpiry(token!); + _tokenController.add(token); + _logger.d('๐Ÿ”$_tag: Auth token updated for user: ${user.uid}'); + } catch (e, s) { + _logger.e( + '๐Ÿšจ$_tag: Error getting token for user: ${user.uid}', + e, + s, + ); + _cachedToken = null; + _tokenExpiry = null; + _tokenController.add(null); + } + } else { + _cachedToken = null; + _tokenExpiry = null; + _tokenController.add(null); + _logger.d('๐Ÿ”“$_tag: User signed out, token cleared'); + } + }, + onError: (e, s) { + _logger.e('๐Ÿšจ $_tag: Auth state change error', e, s); + _tokenController.add(null); + }, + ); + } + + void _setTokenExpiry(String token) { + try { + // Firebase ID tokens are typically valid for 1 hour + // We'll refresh 5 minutes before expiry + _tokenExpiry = DateTime.now().add(const Duration(minutes: 55)); + } catch (e) { + _logger.w('โš ๏ธ$_tag: Could not determine token expiry, using default $e'); + _tokenExpiry = DateTime.now().add(const Duration(minutes: 55)); + } + } + + @override + Future getValidToken() async { + final user = _firebaseAuth.currentUser; + if (user == null) { + _logger.d('๐Ÿ”“$_tag: No authenticated user available'); + return null; + } + + // Check if cached token is still valid + if (_cachedToken != null && !isTokenExpired) { + _logger.d('๐Ÿ”$_tag: Using cached token'); + return _cachedToken; + } + + // Get fresh token + try { + _logger.d('๐Ÿ”„$_tag: Fetching fresh token for user: ${user.uid}'); + final token = await user.getIdToken(true); // Force refresh + _cachedToken = token; + _setTokenExpiry(token!); + _tokenController.add(token); + return token; + } catch (e, s) { + _logger.e('๐Ÿšจ$_tag: Error getting fresh token', e, s); + _cachedToken = null; + _tokenExpiry = null; + return null; + } + } + + @override + String? getCurrentToken() { + return _cachedToken; + } + + @override + Stream get tokenStream => _tokenController.stream; + + @override + Future refreshToken() async { + final user = _firebaseAuth.currentUser; + if (user == null) { + _logger.w('๐Ÿ”“$_tag: Cannot refresh token - no authenticated user'); + return null; + } + + try { + _logger.d('๐Ÿ”„$_tag: Refreshing token for user: ${user.uid}'); + final token = await user.getIdToken(true); // Force refresh + _cachedToken = token; + _setTokenExpiry(token!); + _tokenController.add(token); + _logger.i('โœ…$_tag: Token refreshed successfully'); + return token; + } catch (e, s) { + _logger.e('๐Ÿšจ$_tag: Token refresh failed', e, s); + _cachedToken = null; + _tokenExpiry = null; + _tokenController.add(null); + return null; + } + } + + @override + bool get isAuthenticated => _firebaseAuth.currentUser != null; + + @override + bool get isTokenExpired { + if (_tokenExpiry == null) return true; + return DateTime.now().isAfter(_tokenExpiry!); + } + + @override + Future signOut() async { + try { + _logger.d('๐Ÿ”“$_tag: Signing out user'); + await _firebaseAuth.signOut(); + _cachedToken = null; + _tokenExpiry = null; + _tokenController.add(null); + _logger.i('โœ…$_tag: User signed out successfully'); + } catch (e, s) { + _logger.e('๐Ÿšจ$_tag: Error signing out user', e, s); + rethrow; + } + } + + void dispose() { + _authStateSubscription?.cancel(); + _tokenController.close(); + _logger.d('๐Ÿงน$_tag: disposed'); + } +}{{/network}} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/router/router.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/router/router.dart new file mode 100644 index 0000000..0b4c742 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/router/router.dart @@ -0,0 +1,53 @@ +import 'package:auth/constants/index.dart'; +import 'package:auth/data/services/auth_service.dart'; +import 'package:auth/ui/screens/index.dart'; +import 'package:core/core.dart' as core; +import 'package:di/di.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class AuthRouter extends core.CoreRouter { + final Function(BuildContext context) onSignedIn; + final Function(BuildContext context) onSignedUp; + + AuthRouter({required this.onSignedIn, required this.onSignedUp}); + + @override + List get routes => [ + GoRoute( + path: AuthRoutes.signIn, + builder: (context, state) => SignInScreen(onSignedIn: onSignedIn), + ), + GoRoute( + path: AuthRoutes.signUp, + builder: (context, state) => RegisterScreen(onSignedUp: onSignedUp), + ), + GoRoute( + path: AuthRoutes.forgotPassword, + builder: (context, state) { + final email = state.uri.queryParameters[core.Keys.email]; + return ForgotPasswordScreen(email: email); + }, + ), + ]; +} + +final class GoAuthRoute extends GoRoute { + /// Create a secure route that requires authentication + GoAuthRoute({ + required super.path, + required super.builder, + List super.routes = const [], + super.pageBuilder, + }) : super( + redirect: (context, state) async { + final authService = di.get(); + + if (!authService.isSignedIn) { + return AuthRoutes.signIn; + } + + return null; + }, + ); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/components/footer_builder.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/components/footer_builder.dart new file mode 100644 index 0000000..234ede7 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/components/footer_builder.dart @@ -0,0 +1,70 @@ +import 'package:auth/constants/index.dart'; +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; +import 'package:firebase_ui_auth/firebase_ui_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:localization/localization.dart'; + +enum FooterType { signIn, register } + +Widget footerBuilder(BuildContext context, AuthAction action, FooterType type) { + final logger = di.get(); + switch (type) { + case FooterType.signIn: + return _AuthFooter( + title: strings.auth.dont_have_account, + linkText: strings.auth.register, + onTap: () { + logger.d('Navigating to register screen'); + + context.go(AuthRoutes.signUp); + }, + ); + case FooterType.register: + return _AuthFooter( + title: strings.auth.already_have_account, + linkText: strings.auth.sign_in, + onTap: () { + logger.d('Navigating to sign in screen'); + + context.go(AuthRoutes.signIn); + }, + ); + } +} + +class _AuthFooter extends StatelessWidget { + final String title; + final String linkText; + final VoidCallback onTap; + + const _AuthFooter({ + required this.title, + required this.linkText, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(title), + GestureDetector( + onTap: onTap, + child: Text( + linkText, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/components/header_builder.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/components/header_builder.dart new file mode 100644 index 0000000..80eed4c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/components/header_builder.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:localization/localization.dart'; + +/// Banner shown above the sign-in and register forms. +/// +/// Deliberately drawn from the theme rather than an image, so a fresh project +/// has no asset to ship and nothing to fetch. Swap it for your own artwork: +/// add the file under the app's `assets:` and use `design_system`'s `Header` +/// with `HeaderType.asset`. +Widget headerBuilder(BuildContext context) { + final theme = Theme.of(context); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 24), + child: Column( + children: [ + Container( + height: 72, + width: 72, + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(20), + ), + child: Icon( + Icons.bolt_outlined, + size: 40, + color: theme.colorScheme.onPrimaryContainer, + ), + ), + const SizedBox(height: 16), + Text( + strings.app.name, + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/forgot_password_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/forgot_password_screen.dart new file mode 100644 index 0000000..265c87f --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/forgot_password_screen.dart @@ -0,0 +1,19 @@ +import 'package:auth/ui/components/header_builder.dart'; +import 'package:firebase_ui_auth/firebase_ui_auth.dart' as ui_auth; +import 'package:flutter/material.dart'; + +class ForgotPasswordScreen extends StatelessWidget { + final String? email; + + const ForgotPasswordScreen({super.key, this.email}); + + @override + Widget build(BuildContext context) { + return ui_auth.ForgotPasswordScreen( + email: email, + headerBuilder: (context, constraints, shrinkOffset) { + return headerBuilder(context); + }, + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/index.dart new file mode 100644 index 0000000..2fe2af8 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/index.dart @@ -0,0 +1,4 @@ +//GENERATED BARREL FILE +export 'forgot_password_screen.dart'; +export 'register_screen.dart'; +export 'sign_in_screen.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/register_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/register_screen.dart new file mode 100644 index 0000000..67688ea --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/register_screen.dart @@ -0,0 +1,83 @@ +import 'package:auth/analytics/analytics.dart'; +import 'package:auth/ui/components/footer_builder.dart'; +import 'package:auth/ui/components/header_builder.dart'; +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; +import 'package:firebase_ui_auth/firebase_ui_auth.dart' as ui_auth; +import 'package:flutter/material.dart'; + +class RegisterScreen extends StatelessWidget { + const RegisterScreen({super.key, required this.onSignedUp}); + + final void Function(BuildContext context) onSignedUp; + + @override + Widget build(BuildContext context) { + final logger = di.get(); + + return ui_auth.RegisterScreen( + showAuthActionSwitch: false, + actions: [ + ui_auth.AuthStateChangeAction((context, state) async { + logger.d('User registered successfully: ${state.user?.email}'); + + final method = AuthAnalytics.getAuthMethod(state.user?.providerData); + + // Log successful sign in (when user already exists) + AuthAnalytics.logSignInSuccess( + method: method, + userEmail: state.user?.email, + ); + + onSignedUp(context); + }), + ui_auth.AuthStateChangeAction(( + context, + state, + ) async { + logger.d( + 'User created successfully: ${state.credential.user?.email}', + ); + + final method = AuthAnalytics.getAuthMethod( + state.credential.user?.providerData, + ); + + // Log successful sign up + AuthAnalytics.logSignUpSuccess( + method: method, + userEmail: state.credential.user?.email, + ); + + // Log user creation success + AuthAnalytics.logUserCreationSuccess( + method: method, + userEmail: state.credential.user?.email, + ); + + onSignedUp(context); + }), + ui_auth.AuthStateChangeAction(( + context, + state, + ) async { + logger.e('Registration failed: ${state.exception}'); + + // Log auth error + AuthAnalytics.logAuthError( + errorType: state.exception.runtimeType.toString(), + flowType: 'sign_up', + errorMessage: state.exception.toString(), + method: 'email', + ); + }), + ], + footerBuilder: (context, action) { + return footerBuilder(context, action, FooterType.register); + }, + headerBuilder: (context, constraints, shrinkOffset) { + return headerBuilder(context); + }, + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/sign_in_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/sign_in_screen.dart new file mode 100644 index 0000000..13f00f3 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/sign_in_screen.dart @@ -0,0 +1,94 @@ +import 'package:auth/analytics/analytics.dart'; +import 'package:auth/constants/index.dart'; +import 'package:auth/ui/components/footer_builder.dart'; +import 'package:auth/ui/components/header_builder.dart'; +import 'package:core/core.dart' as core; +import 'package:core/logger/logger.dart'; +import 'package:design_system/toast/toasts.dart'; +import 'package:di/di.dart'; +import 'package:firebase_ui_auth/firebase_ui_auth.dart' as ui_auth; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:localization/localization.dart'; + +class SignInScreen extends StatelessWidget { + const SignInScreen({super.key, required this.onSignedIn}); + + final Function(BuildContext context) onSignedIn; + + @override + Widget build(BuildContext context) { + final logger = di.get(); + + return ui_auth.SignInScreen( + showAuthActionSwitch: false, + oauthButtonVariant: ui_auth.OAuthButtonVariant.icon_and_text, + actions: [ + ui_auth.ForgotPasswordAction((context, email) { + logger.d('Navigating to forgot password screen for email: $email'); + + final uri = Uri( + path: AuthRoutes.forgotPassword, + queryParameters: {core.Keys.email: email}, + ); + context.push(uri.toString()); + }), + ui_auth.AuthStateChangeAction((context, state) async { + logger.d('User signed in successfully: ${state.user?.email}'); + + final method = AuthAnalytics.getAuthMethod(state.user?.providerData); + + // Log successful sign in + AuthAnalytics.logSignInSuccess( + method: method, + userEmail: state.user?.email, + ); + + onSignedIn(context); + }), + ui_auth.AuthStateChangeAction(( + context, + state, + ) async { + logger.d( + 'User signed in successfully: ${state.credential.user?.email}', + ); + + final method = AuthAnalytics.getAuthMethod( + state.credential.user?.providerData, + ); + + // Log successful user creation (from sign in screen) + AuthAnalytics.logUserCreationSuccess( + method: method, + userEmail: state.credential.user?.email, + ); + + onSignedIn(context); + }), + ui_auth.AuthStateChangeAction(( + context, + state, + ) async { + logger.e('Sign-in failed: ${state.exception}'); + + // Log auth error + AuthAnalytics.logAuthError( + errorType: state.exception.runtimeType.toString(), + flowType: 'sign_in', + errorMessage: state.exception.toString(), + ); + + Toast.error(context, message: strings.errors.default_error_message); + }), + //TODO: Handle other AuthState + ], + footerBuilder: (context, action) { + return footerBuilder(context, action, FooterType.signIn); + }, + headerBuilder: (context, constraints, shrinkOffset) { + return headerBuilder(context); + }, + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml new file mode 100644 index 0000000..8c82322 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml @@ -0,0 +1,40 @@ +name: auth +description: "Firebase Authentication (email, Google, Apple) with go_router integration for {{name.titleCase()}}." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + equatable: ^2.0.7 + firebase_auth: ^5.6.0 + firebase_core: ^3.14.0 + firebase_ui_auth: ^1.17.0 + firebase_ui_oauth_apple: ^1.3.2 + firebase_ui_oauth_google: ^1.4.2 + go_router: ^15.1.3 + json_annotation: ^4.9.0 + core: + path: ../../packages/core + design_system: + path: ../../packages/design_system + di: + path: ../../packages/di + localization: + path: ../../packages/localization +{{#firestore}} cloud_firestore: ^5.6.0 +{{/firestore}}{{#network}} network: + path: ../../packages/network +{{/network}}{{#analytics}} analytics: + path: ../../packages/analytics +{{/analytics}} +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/README.md new file mode 100644 index 0000000..4f80f3e --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/README.md @@ -0,0 +1,18 @@ +# Dashboard + +Dashboard feature with bottom navigation + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) + +## Installation ๐Ÿ’ป + +**โ— In order to start using Dashboard you must have the [Flutter SDK][flutter_install_link] installed on your machine.** + +Install via `dart pub add`: + +```sh +dart pub add dashboard +``` + +[flutter_install_link]: https://docs.flutter.dev/get-started/install \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/dashboard.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/dashboard.dart new file mode 100644 index 0000000..ffb156e --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/dashboard.dart @@ -0,0 +1,5 @@ +library; + +export 'router/dashboard_router.dart'; +export 'ui/screens/index.dart'; +export 'ui/widgets/index.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/router/dashboard_router.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/router/dashboard_router.dart new file mode 100644 index 0000000..1c08705 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/router/dashboard_router.dart @@ -0,0 +1,51 @@ +{{#auth}}import 'package:auth/auth.dart' as auth; +{{/auth}}import 'package:dashboard/ui/screens/index.dart'; +import 'package:go_router/go_router.dart'; + +{{#auth}}/// Every shell branch is guarded - unauthenticated users bounce to sign-in. +typedef _ShellRoute = auth.GoAuthRoute;{{/auth}}{{^auth}}typedef _ShellRoute = GoRoute;{{/auth}} + +/// Bottom-navigation shell for the signed-in part of the app. +/// +/// Add a tab by adding a [StatefulShellBranch] here and a destination in +/// [DashboardShellScreen]. +class DashboardRouter { + const DashboardRouter._(); + + static const String home = '/home/dashboard'; + static const String explore = '/home/explore'; + static const String profile = '/home/profile'; + + static StatefulShellRoute createShellRoute() { + return StatefulShellRoute.indexedStack( + builder: (context, state, navigationShell) => + DashboardShellScreen(navigationShell: navigationShell), + branches: [ + StatefulShellBranch( + routes: [ + _ShellRoute( + path: home, + builder: (context, state) => const HomeTabScreen(), + ), + ], + ), + StatefulShellBranch( + routes: [ + _ShellRoute( + path: explore, + builder: (context, state) => const ExploreTabScreen(), + ), + ], + ), + StatefulShellBranch( + routes: [ + _ShellRoute( + path: profile, + builder: (context, state) => const ProfileTabScreen(), + ), + ], + ), + ], + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/dashboard_shell_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/dashboard_shell_screen.dart new file mode 100644 index 0000000..6c890f2 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/dashboard_shell_screen.dart @@ -0,0 +1,54 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:localization/localization.dart'; + +/// Hosts the bottom navigation bar and keeps each tab's navigation stack alive. +/// +/// Add a tab by adding a [StatefulShellBranch] in `DashboardRouter` and a +/// matching destination here - the two lists are positional. +class DashboardShellScreen extends StatefulWidget { + const DashboardShellScreen({super.key, required this.navigationShell}); + + final StatefulNavigationShell navigationShell; + + @override + State createState() => _DashboardShellScreenState(); +} + +class _DashboardShellScreenState extends State { + void _onDestinationSelected(int index) { + widget.navigationShell.goBranch( + index, + initialLocation: index == widget.navigationShell.currentIndex, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: widget.navigationShell, + bottomNavigationBar: NavigationBar( + selectedIndex: widget.navigationShell.currentIndex, + onDestinationSelected: _onDestinationSelected, + destinations: [ + NavigationDestination( + icon: const Icon(NavigationIcons.home), + selectedIcon: const Icon(NavigationIcons.homeSelected), + label: strings.nav.home, + ), + const NavigationDestination( + icon: Icon(NavigationIcons.explore), + selectedIcon: Icon(NavigationIcons.exploreSelected), + label: 'Explore', + ), + NavigationDestination( + icon: const Icon(NavigationIcons.profile), + selectedIcon: const Icon(NavigationIcons.profileSelected), + label: strings.nav.profile, + ), + ], + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/explore_tab_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/explore_tab_screen.dart new file mode 100644 index 0000000..1dfa7b9 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/explore_tab_screen.dart @@ -0,0 +1,21 @@ +import 'package:dashboard/ui/widgets/index.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +class ExploreTabScreen extends StatelessWidget { + const ExploreTabScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Explore')), + body: const SafeArea( + child: PlaceholderTab( + icon: NavigationIcons.exploreSelected, + title: 'Explore', + subtitle: 'Your second tab. Replace this with a real feature.', + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/home_tab_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/home_tab_screen.dart new file mode 100644 index 0000000..340cac9 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/home_tab_screen.dart @@ -0,0 +1,22 @@ +import 'package:dashboard/ui/widgets/index.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:localization/localization.dart'; + +class HomeTabScreen extends StatelessWidget { + const HomeTabScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(strings.nav.dashboard)), + body: SafeArea( + child: PlaceholderTab( + icon: NavigationIcons.homeSelected, + title: strings.app.welcome_to_app, + subtitle: strings.app.description, + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/index.dart new file mode 100644 index 0000000..8bebb72 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/index.dart @@ -0,0 +1,4 @@ +export 'dashboard_shell_screen.dart'; +export 'explore_tab_screen.dart'; +export 'home_tab_screen.dart'; +export 'profile_tab_screen.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/profile_tab_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/profile_tab_screen.dart new file mode 100644 index 0000000..977abad --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/profile_tab_screen.dart @@ -0,0 +1,87 @@ +{{#auth}}import 'package:auth/auth.dart' as auth; +{{/auth}}import 'package:design_system/design_system.dart'; +{{#developer}}import 'package:developer/developer.dart'; +{{/developer}}{{#auth}}import 'package:di/di.dart'; +{{/auth}}import 'package:flutter/material.dart'; +{{#auth}}import 'package:go_router/go_router.dart'; +{{/auth}}import 'package:localization/localization.dart'; + +/// Account tab: identity, settings entry points, sign out. +/// +/// This is deliberately thin - hang your real profile feature off it. +class ProfileTabScreen extends StatelessWidget { + const ProfileTabScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(strings.profile.profile)), + body: SafeArea( + child: ListView( + children: [ + const SizedBox(height: 16), + {{#developer}}// Tap the avatar 5x to open developer tools. + OpenDevToolsWrapper(child: const _ProfileAvatar()),{{/developer}}{{^developer}}const _ProfileAvatar(),{{/developer}} + const SizedBox(height: 24), + ListTile( + leading: const Icon(NavigationIcons.settings), + title: Text(strings.profile.settings), + trailing: const Icon(Icons.chevron_right), + onTap: () {}, + ), + {{#auth}}ListTile( + leading: const Icon(Icons.logout), + title: Text(strings.auth.sign_out), + onTap: () => _confirmSignOut(context), + ),{{/auth}} + ], + ), + ), + ); + } +{{#auth}} + Future _confirmSignOut(BuildContext context) async { + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(strings.auth.sign_out), + content: Text(strings.auth.sign_out_confirmation), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(strings.generic.cancel), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(strings.auth.sign_out), + ), + ], + ), + ); + + if (confirmed != true || !context.mounted) return; + + await di.get().signOut(); + if (context.mounted) context.go(auth.AuthRoutes.signIn); + } +{{/auth}}} + +class _ProfileAvatar extends StatelessWidget { + const _ProfileAvatar(); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Center( + child: CircleAvatar( + radius: 44, + backgroundColor: colorScheme.primaryContainer, + child: Icon( + NavigationIcons.profileSelected, + size: 44, + color: colorScheme.onPrimaryContainer, + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/widgets/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/widgets/index.dart new file mode 100644 index 0000000..7abc376 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/widgets/index.dart @@ -0,0 +1 @@ +export 'placeholder_tab.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/widgets/placeholder_tab.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/widgets/placeholder_tab.dart new file mode 100644 index 0000000..e367fed --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/widgets/placeholder_tab.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +/// Drop-in "nothing here yet" tab body. +/// +/// Delete this the moment a tab grows real content. +class PlaceholderTab extends StatelessWidget { + const PlaceholderTab({ + super.key, + required this.icon, + required this.title, + required this.subtitle, + this.action, + }); + + final IconData icon; + final String title; + final String subtitle; + final Widget? action; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final textTheme = theme.textTheme; + + return Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: colorScheme.primary.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon(icon, size: 72, color: colorScheme.primary), + ), + const SizedBox(height: 32), + Text( + title, + style: textTheme.headlineMedium?.copyWith( + color: colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + Text( + subtitle, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + height: 1.5, + ), + textAlign: TextAlign.center, + ), + if (action != null) ...[const SizedBox(height: 32), action!], + ], + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml new file mode 100644 index 0000000..aa88eaf --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml @@ -0,0 +1,32 @@ +name: dashboard +description: "Bottom-navigation shell and starter tabs for {{name.titleCase()}}." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + go_router: ^15.1.3 + core: + path: ../../packages/core + design_system: + path: ../../packages/design_system + di: + path: ../../packages/di + localization: + path: ../../packages/localization +{{#auth}} auth: + path: ../auth +{{/auth}}{{#developer}} developer: + path: ../../packages/developer +{{/developer}} +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/melos.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/melos.yaml index 7b645ab..338f220 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/melos.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/melos.yaml @@ -3,14 +3,25 @@ name: {{ name.snakeCase() }}_workspace ############################################################################### # MONOREPO CONFIGURATION ############################################################################### -# Define the packages that make up your monorepo -# Customize these patterns to match your project structure +# apps/ - runnable Flutter applications +# features/ - vertical slices (ui + state + data) that an app composes +# packages/ - horizontal capabilities shared by features (network, design, ...) +# plugins/ - platform channels and native integrations packages: - apps/* - features/* - packages/* - plugins/* +command: + bootstrap: + dependencies: + json_annotation: ^4.9.0 + dev_dependencies: + build_runner: ^2.4.15 + flutter_lints: ^5.0.0 + json_serializable: ^6.9.5 + ############################################################################### # SCRIPTS ############################################################################### @@ -25,8 +36,11 @@ scripts: lint: description: "Run format and analyze all packages" + # `dart format` has no exclude of its own, so build output and the plugin + # sources CocoaPods symlinks into the app are filtered out here. run: | - dart format --set-exit-if-changed . && + find . -name '*.dart' -not -path '*/build/*' -not -path '*/.dart_tool/*' \ + -not -path '*/.symlinks/*' -exec dart format --set-exit-if-changed {} + && dart analyze --fatal-infos . exec: concurrency: 5 @@ -49,13 +63,23 @@ scripts: dirExists: lib generate: - description: "Generate code for Dart packages using build_runner" + description: "Run build_runner in every package that needs codegen" run: dart run build_runner build --delete-conflicting-outputs exec: concurrency: 1 packageFilters: dependsOn: build_runner + generate:i69n: + description: "Regenerate localization strings from messages.i69n.yaml" + run: | + dart run build_runner build --delete-conflicting-outputs && + dart format lib/messages.i69n.dart + exec: + concurrency: 1 + packageFilters: + scope: "localization" + test: description: "Run all tests (Dart and Flutter)" run: melos run test:select --no-select @@ -72,8 +96,7 @@ scripts: ############################################################################### # IDE CONFIGURATION ############################################################################### -# Optional: Configure IDE integration ide: intellij: enabled: true - moduleNamePrefix: '' \ No newline at end of file + moduleNamePrefix: '' diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/README.md index e880883..528e3dc 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/README.md +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/README.md @@ -1 +1,13 @@ -# Put all your shared packages in this folder +# Packages + +Horizontal capabilities shared across features: networking, design system, +logging, analytics, dependency injection. + +A package here must not depend on a `features/` package - dependencies point +inward, from features to packages. + +Add one with: + +```sh +nonstop create package my_package -o packages +``` diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/README.md new file mode 100644 index 0000000..cb34437 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/README.md @@ -0,0 +1,18 @@ +# Core + +A Melos-managed project for mono-repo, created using NonStop CLI. + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) + +## Installation ๐Ÿ’ป + +**โ— In order to start using Core you must have the [Flutter SDK][flutter_install_link] installed on your machine.** + +Install via `dart pub add`: + +```sh +dart pub add core +``` + +[flutter_install_link]: https://docs.flutter.dev/get-started/install \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event.dart new file mode 100644 index 0000000..b65f666 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; + +/// Base type for events broadcast on the app-wide event channel. +/// +/// Use this for the handful of things that genuinely cross feature +/// boundaries - "the signed-in user changed", "this list is stale" - and keep +/// everything else inside the feature that owns it. +abstract class GlobalEventType extends Equatable { + const GlobalEventType(); + + @override + List get props => []; +} + +/// Example event: something invalidated the current user's profile. +/// +/// Replace this with your own events; it is here to show the shape. +class RefreshProfile extends GlobalEventType { + const RefreshProfile(); +} + +class FireGlobalEvent extends Equatable { + final GlobalEventType eventType; + + const FireGlobalEvent({required this.eventType}); + + @override + List get props => [eventType]; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_bloc.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_bloc.dart new file mode 100644 index 0000000..128e05e --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_bloc.dart @@ -0,0 +1,41 @@ +import 'package:bloc/bloc.dart'; +import 'package:core/bloc/global_event.dart'; +import 'package:core/bloc/global_event_channel_state.dart'; +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; + +class GlobalEventChannel extends Bloc { + final Logger _logger = di.get(); + + GlobalEventChannel({required int maxRecentEvents}) + : super(GlobalEventState(maxRecentEvents: maxRecentEvents)) { + on(_onFireGlobalEvent); + } + + void _onFireGlobalEvent( + FireGlobalEvent event, + Emitter emit, + ) { + _logger.i('GlobalEventChannel: Processing ${event.eventType.runtimeType}'); + + final eventType = event.eventType; + final newHistory = List.from(state.history); + newHistory.insert(0, eventType); + + if (newHistory.length > state.maxRecentEvents) { + newHistory.removeLast(); + } + + final newEventCounts = Map.from(state.eventCounts); + final eventTypeKey = eventType.runtimeType; + newEventCounts[eventTypeKey] = (newEventCounts[eventTypeKey] ?? 0) + 1; + + emit( + state.copyWith( + history: newHistory, + eventCounts: newEventCounts, + current: eventType, + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_provider.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_provider.dart new file mode 100644 index 0000000..eb84614 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_provider.dart @@ -0,0 +1,92 @@ +import 'package:core/bloc/global_event_channel_bloc.dart'; +import 'package:core/bloc/global_event_channel_state.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class GlobalEventChannelProvider extends StatelessWidget { + final Widget child; + final int maxRecentEvents; + final bool lazy; + + const GlobalEventChannelProvider({ + super.key, + required this.child, + this.maxRecentEvents = 50, + this.lazy = true, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + lazy: lazy, + create: (context) => GlobalEventChannel(maxRecentEvents: maxRecentEvents), + child: child, + ); + } +} + +class GlobalEventChannelBuilder extends StatelessWidget { + final BlocWidgetBuilder builder; + final BlocBuilderCondition? buildWhen; + + const GlobalEventChannelBuilder({ + super.key, + required this.builder, + this.buildWhen, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: buildWhen, + builder: builder, + ); + } +} + +class GlobalEventChannelListener extends StatelessWidget { + final Widget child; + final BlocWidgetListener listener; + final BlocListenerCondition? listenWhen; + + const GlobalEventChannelListener({ + super.key, + required this.child, + required this.listener, + this.listenWhen, + }); + + @override + Widget build(BuildContext context) { + return BlocListener( + listenWhen: listenWhen, + listener: listener, + child: child, + ); + } +} + +class GlobalEventChannelConsumer extends StatelessWidget { + final BlocWidgetBuilder builder; + final BlocWidgetListener listener; + final BlocBuilderCondition? buildWhen; + final BlocListenerCondition? listenWhen; + + const GlobalEventChannelConsumer({ + super.key, + required this.builder, + required this.listener, + this.buildWhen, + this.listenWhen, + }); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + buildWhen: buildWhen, + listenWhen: listenWhen, + builder: builder, + listener: listener, + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_state.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_state.dart new file mode 100644 index 0000000..50cf330 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_state.dart @@ -0,0 +1,33 @@ +import 'package:core/bloc/global_event.dart'; +import 'package:equatable/equatable.dart'; + +class GlobalEventState extends Equatable { + final List history; + final Map eventCounts; + final GlobalEventType? current; + final int maxRecentEvents; + + const GlobalEventState({ + this.history = const [], + this.eventCounts = const {}, + this.current, + this.maxRecentEvents = 50, + }); + + GlobalEventState copyWith({ + List? history, + Map? eventCounts, + GlobalEventType? current, + int? maxRecentEvents, + }) { + return GlobalEventState( + history: history ?? this.history, + eventCounts: eventCounts ?? this.eventCounts, + current: current ?? this.current, + maxRecentEvents: maxRecentEvents ?? this.maxRecentEvents, + ); + } + + @override + List get props => [history, eventCounts, current, maxRecentEvents]; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/index.dart new file mode 100644 index 0000000..8d8d5d4 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/bloc/index.dart @@ -0,0 +1,19 @@ +//GENERATED BARREL FILE + +import 'package:core/bloc/global_event.dart'; +import 'package:core/bloc/global_event_channel_bloc.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +export 'global_event.dart'; +export 'global_event_channel_bloc.dart'; +export 'global_event_channel_provider.dart'; +export 'global_event_channel_state.dart'; + +extension GlobalEventChannelExtension on BuildContext { + /// Fires a global event with the given [eventType]. + void fire(GlobalEventType eventType) { + final bloc = read(); + bloc.add(FireGlobalEvent(eventType: eventType)); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/defaults.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/defaults.dart new file mode 100644 index 0000000..9ca3832 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/defaults.dart @@ -0,0 +1,6 @@ +/// App-wide default values. +class Defaults { + Defaults._(); + + static const int defaultPageSize = 20; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/environment.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/environment.dart new file mode 100644 index 0000000..205f857 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/environment.dart @@ -0,0 +1,18 @@ +/// Build-time configuration. +/// +/// Every value is a `--dart-define`, so the same binary can be pointed at a +/// different backend without a code change: +/// +/// ```sh +/// flutter run --dart-define=BASE_URL=https://staging.example.com +/// ``` +class Environment { + Environment._(); + + static const bool useEmulators = bool.fromEnvironment('USE_EMULATORS'); + + static const String baseUrl = String.fromEnvironment( + 'BASE_URL', + defaultValue: 'https://api.example.com', + ); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/index.dart new file mode 100644 index 0000000..4db272b --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/index.dart @@ -0,0 +1,4 @@ +export 'defaults.dart'; +export 'environment.dart'; +export 'keys.dart'; +export 'routes.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/keys.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/keys.dart new file mode 100644 index 0000000..2faa261 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/keys.dart @@ -0,0 +1,11 @@ +/// Query-parameter and `extra` keys shared between routers and screens. +/// +/// Keep these here rather than inline so a rename is a single edit. +class Keys { + Keys._(); + + static const String email = 'email'; + static const String title = 'title'; + static const String message = 'message'; + static const String error = 'error'; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/routes.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/routes.dart new file mode 100644 index 0000000..51921d5 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/constants/routes.dart @@ -0,0 +1,13 @@ +/// Paths owned by the app shell itself. +/// +/// Feature packages declare their own route constants; these are the ones +/// every app has. +class CoreRoutes { + CoreRoutes._(); + + static const String root = '/'; + static const String home = '/home'; + static const String dashboard = '/home/dashboard'; + static const String profile = '/home/profile'; + static const String error = '/error'; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/datetime_converter.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/datetime_converter.dart new file mode 100644 index 0000000..7c477d7 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/datetime_converter.dart @@ -0,0 +1,35 @@ +import 'package:intl/intl.dart'; + +final class DateTimeConverter { + const DateTimeConverter(); + + /// Converts a [DateTime] to a [String] in the format 'MMMM d, y'. + /// If the [date] is null, it returns an empty string. + /// + /// Example: + /// ```dart + /// DateTime date = DateTime.now(); + /// String formattedDate = DateTimeConverter.toViewFormat(date); + /// print(formattedDate); // Output: 'October 1, 2023' + /// ``` + static String toViewFormat(DateTime? date) { + if (date == null) return ''; + final dateFormate = DateFormat('MMMM d, y'); + + return dateFormate.format(date.toLocal()); + } + + static String toViewFormatWithTime(DateTime? date) { + if (date == null) return ''; + final dateFormate = DateFormat('MMMM d, y, h:mm a'); + + return dateFormate.format(date); + } + + static String toViewFormatTime(DateTime? date) { + if (date == null) return ''; + final dateFormate = DateFormat('h:mm:ss a'); + + return dateFormate.format(date); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/index.dart new file mode 100644 index 0000000..bac47b7 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/index.dart @@ -0,0 +1,3 @@ +//GENERATED BARREL FILE +export 'datetime_converter.dart'; +export 'timestamp_converter.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/timestamp_converter.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/timestamp_converter.dart new file mode 100644 index 0000000..3691e46 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/converters/timestamp_converter.dart @@ -0,0 +1,44 @@ +{{#firestore}}import 'package:cloud_firestore/cloud_firestore.dart'; +{{/firestore}}import 'package:json_annotation/json_annotation.dart'; + +/// Accepts the three shapes a timestamp arrives in - ISO string,{{#firestore}} +/// Firestore [Timestamp],{{/firestore}} or the `{_seconds, _nanoseconds}` map that +/// Cloud Functions emit - and always writes back UTC ISO-8601. +class TimestampConverter implements JsonConverter { + const TimestampConverter(); + + @override + DateTime fromJson(dynamic json) { + final parsed = _tryParse(json); + if (parsed == null) { + throw ArgumentError('Invalid timestamp format: $json'); + } + return parsed; + } + + @override + String? toJson(DateTime? date) => date?.toUtc().toIso8601String(); +} + +class TimestampConverterNullable implements JsonConverter { + const TimestampConverterNullable(); + + @override + DateTime? fromJson(dynamic json) => _tryParse(json); + + @override + String? toJson(DateTime? date) => date?.toUtc().toIso8601String(); +} + +DateTime? _tryParse(dynamic json) { + if (json is String && json.isNotEmpty) return DateTime.parse(json).toLocal(); +{{#firestore}} if (json is Timestamp) return json.toDate(); +{{/firestore}} if (json is Map) { + final seconds = json['_seconds'] as int; + final nanoseconds = json['_nanoseconds'] as int? ?? 0; + return DateTime.fromMillisecondsSinceEpoch( + seconds * 1000 + nanoseconds ~/ 1000000, + ); + } + return null; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/core.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/core.dart new file mode 100644 index 0000000..06b8df3 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/core.dart @@ -0,0 +1,19 @@ +library; + +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; + +export 'bloc/index.dart'; +export 'constants/index.dart'; +export 'converters/index.dart'; +export 'errors/index.dart'; +export 'extensions/index.dart'; +export 'logger/logger.dart'; +export 'observer/index.dart'; +export 'router/core_router.dart'; + +Future init() async { + registerLoggerWithDI(); + final logger = di.get(); + logger.i('Core module initialized'); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/developer/emulators.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/developer/emulators.dart new file mode 100644 index 0000000..9586507 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/developer/emulators.dart @@ -0,0 +1,25 @@ +{{#firebase}}{{#auth}}import 'package:firebase_auth/firebase_auth.dart'; +{{/auth}}{{#firestore}}import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:cloud_functions/cloud_functions.dart'; +{{/firestore}}import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; + +/// Points every Firebase SDK at the local emulator suite. +/// +/// Ports match the defaults in `firebase.json`; change both together. +Future init() async { + final logger = di.get(); + logger.i('Using Firebase emulators'); + try { +{{#auth}} await FirebaseAuth.instance.useAuthEmulator('localhost', 9099); +{{/auth}}{{#firestore}} FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080); + FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001); +{{/firestore}} logger.i('Successfully initialised the Firebase emulators'); + } catch (error, stackTrace) { + logger.e('Failed to initialise the Firebase emulators:', error, stackTrace); + } +} +{{/firebase}}{{^firebase}}/// No Firebase services are enabled in this project, so there is nothing to +/// point at an emulator. Kept as a no-op so bootstrap stays uniform. +Future init() async {} +{{/firebase}} \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/errors/exceptions.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/errors/exceptions.dart new file mode 100644 index 0000000..704f0bb --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/errors/exceptions.dart @@ -0,0 +1,11 @@ +import 'package:localization/localization.dart'; + +class CoreException implements Exception { + final String message; + + CoreException({required this.message}); +} + +class UserNotFoundException extends CoreException { + UserNotFoundException() : super(message: strings.errors.user_not_found); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/errors/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/errors/index.dart new file mode 100644 index 0000000..c6bf010 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/errors/index.dart @@ -0,0 +1,2 @@ +//GENERATED BARREL FILE +export 'exceptions.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/extensions/basic.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/extensions/basic.dart new file mode 100644 index 0000000..5d04989 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/extensions/basic.dart @@ -0,0 +1,7 @@ +extension BasicStringExtensions on String? { + bool get asBool { + if (this == null) return false; + final lower = this!.toLowerCase(); + return lower == 'true' || lower == '1'; + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/extensions/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/extensions/index.dart new file mode 100644 index 0000000..0f1b76c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/extensions/index.dart @@ -0,0 +1 @@ +export 'basic.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/index.dart new file mode 100644 index 0000000..48419fd --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/index.dart @@ -0,0 +1,3 @@ +//GENERATED BARREL FILE +export 'logger.dart'; +export 'talker_logger_impl.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/logger.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/logger.dart new file mode 100644 index 0000000..b87be14 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/logger.dart @@ -0,0 +1,22 @@ +import 'package:core/logger/talker_logger_impl.dart'; +import 'package:di/di.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +void registerLoggerWithDI() { + final talker = TalkerFlutter.init(); + final logger = TalkerLoggerImpl(talker); + + di.register(logger); +} + +abstract class Logger { + Object get logger; + + void d(String message); + + void i(String message); + + void w(String message); + + void e(String message, [Object? error, StackTrace? stackTrace]); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/talker_logger_impl.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/talker_logger_impl.dart new file mode 100644 index 0000000..2db6aee --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/logger/talker_logger_impl.dart @@ -0,0 +1,31 @@ +import 'package:core/logger/logger.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +class TalkerLoggerImpl implements Logger { + final Talker _talker; + + TalkerLoggerImpl(this._talker); + + @override + void d(String message) { + _talker.debug(message); + } + + @override + void i(String message) { + _talker.info(message); + } + + @override + void w(String message) { + _talker.warning(message); + } + + @override + void e(String message, [Object? error, StackTrace? stackTrace]) { + _talker.error(message, error, stackTrace); + } + + @override + Object get logger => _talker; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_bloc_observer.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_bloc_observer.dart new file mode 100644 index 0000000..1de8928 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_bloc_observer.dart @@ -0,0 +1,34 @@ +import 'package:bloc/bloc.dart'; +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:talker_bloc_logger/talker_bloc_logger_observer.dart'; +import 'package:talker_bloc_logger/talker_bloc_logger_settings.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +class CoreBlocObserver extends BlocObserver { + final TalkerBlocObserver _observer; + + CoreBlocObserver() + : _observer = TalkerBlocObserver( + talker: di.get().logger as Talker, + settings: TalkerBlocLoggerSettings(printEventFullData: true), + ); + + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + _observer.onEvent(bloc, event); + } + + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + _observer.onTransition(bloc, transition); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + super.onError(bloc, error, stackTrace); + _observer.onError(bloc, error, stackTrace); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart new file mode 100644 index 0000000..776e95c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart @@ -0,0 +1,89 @@ +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:flutter/material.dart'; +import 'package:navigation_history_observer/navigation_history_observer.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +class CoreRouteObserver extends NavigatorObserver { + CoreRouteObserver() : _observer = di.get().logger as Talker; + + final Talker _observer; + + @override + void didPush(Route route, Route? previousRoute) { + super.didPush(route, previousRoute); + if (route.settings.name == null) { + return; + } + _observer.logCustom(TalkerRouteLog(route: route, type: RouteLogType.push)); + } + + @override + void didPop(Route route, Route? previousRoute) { + super.didPop(route, previousRoute); + if (route.settings.name == null) { + return; + } + _observer.logCustom(TalkerRouteLog(route: route, type: RouteLogType.pop)); + } + + @override + void didRemove(Route route, Route? previousRoute) { + super.didRemove(route, previousRoute); + if (route.settings.name == null) { + return; + } + _observer.logCustom( + TalkerRouteLog(route: route, type: RouteLogType.remove), + ); + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + super.didReplace(newRoute: newRoute, oldRoute: oldRoute); + if (newRoute?.settings.name == null) { + return; + } + _observer.logCustom( + TalkerRouteLog(route: newRoute!, type: RouteLogType.replace), + ); + } +} + +enum RouteLogType { push, pop, remove, replace } + +class TalkerRouteLog extends TalkerLog { + TalkerRouteLog({required Route route, required RouteLogType type}) + : super(_createMessage(route, type)); + + @override + AnsiPen get pen => AnsiPen()..xterm(135); + + @override + String get key => TalkerLogType.route.key; + + static String _createMessage(Route route, RouteLogType type) { + final buffer = StringBuffer(); + buffer.write(type.name); + buffer.write(' route named '); + buffer.write(route.settings.name ?? 'null'); + + final args = route.settings.arguments; + if (args != null) { + buffer.write('\nArguments: $args'); + } + + final historyObserver = NavigationHistoryObserver(); + final history = historyObserver.history; + if (history.isEmpty) { + buffer.write('\nHistory: na'); + } else { + final historyAsString = historyObserver.history + .map((r) => r.settings.name) + .join(' -> '); + buffer.write('\nHistory: $historyAsString'); + } + + return buffer.toString(); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/index.dart new file mode 100644 index 0000000..1f24d43 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/index.dart @@ -0,0 +1,3 @@ +//GENERATED BARREL FILE +export 'core_bloc_observer.dart'; +export 'core_route_observer.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/router/core_router.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/router/core_router.dart new file mode 100644 index 0000000..3cd154c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/router/core_router.dart @@ -0,0 +1,5 @@ +import 'package:go_router/go_router.dart'; + +abstract class CoreRouter { + List get routes; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/pubspec.yaml new file mode 100644 index 0000000..2076f23 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/pubspec.yaml @@ -0,0 +1,39 @@ +name: core +description: "Cross-cutting foundation for {{name.titleCase()}}: logging, DI wiring, routing contracts, error types and BLoC plumbing." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + bloc: ^9.0.0 + equatable: ^2.0.5 + flutter_bloc: ^9.1.1 + go_router: ^15.1.3 + intl: ^0.20.2 + json_annotation: ^4.9.0 + navigation_history_observer: ^1.1.1 + talker_bloc_logger: ^4.9.1 + talker_flutter: ^4.4.1 + di: + path: ../di + localization: + path: ../localization +{{#firebase}} firebase_core: ^3.14.0 +{{/firebase}}{{#auth}} firebase_auth: ^5.3.4 +{{/auth}}{{#firestore}} cloud_firestore: ^5.5.3 + cloud_functions: ^5.2.3 +{{/firestore}} +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +dependency_overrides: + intl: ^0.20.2 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/README.md new file mode 100644 index 0000000..47e41eb --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/README.md @@ -0,0 +1,18 @@ +# Design System + +A Melos-managed project for mono-repo, created using NonStop CLI. + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) + +## Installation ๐Ÿ’ป + +**โ— In order to start using Design System you must have the [Flutter SDK][flutter_install_link] installed on your machine.** + +Install via `dart pub add`: + +```sh +dart pub add design_system +``` + +[flutter_install_link]: https://docs.flutter.dev/get-started/install \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/asset_image.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/asset_image.dart new file mode 100644 index 0000000..d321f7c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/asset_image.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class AssetImage extends StatelessWidget { + final String assetPath; + final double? width; + final double? height; + final BoxFit boxFit; + final String? package; + + const AssetImage({ + super.key, + required this.assetPath, + this.width, + this.height, + this.boxFit = BoxFit.cover, + this.package, + }); + @override + Widget build(BuildContext context) { + // if asset is svg, use SvgPicture.asset + if (assetPath.endsWith('.svg')) { + return SvgPicture.asset( + assetPath, + width: width, + height: height, + fit: boxFit, + package: package, + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.image, + size: 32, + color: Theme.of(context).colorScheme.onSurface, + ); + }, + ); + } + + // otherwise, use Image.asset + return Image.asset( + assetPath, + width: width, + height: height, + fit: boxFit, + package: package, + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.image, + size: 32, + color: Theme.of(context).colorScheme.onSurface, + ); + }, + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/auth_headers_builder.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/auth_headers_builder.dart new file mode 100644 index 0000000..09d09e4 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/auth_headers_builder.dart @@ -0,0 +1,116 @@ +{{#network}}import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:flutter/material.dart'; +import 'package:network/network.dart'; + +/// A widget that provides authentication headers to its child builder. +/// +/// This widget automatically fetches the current authentication token +/// using the [AuthTokenProvider] from dependency injection and constructs +/// HTTP headers with the Bearer token. +/// +/// Example usage: +/// ```dart +/// // Without token validation (default - faster, uses cached token) +/// AuthHeadersBuilder( +/// builder: (context, headers) { +/// return CachedNetworkImage( +/// imageUrl: imageUrl, +/// httpHeaders: headers, +/// ); +/// }, +/// ) +/// +/// // With token validation (ensures fresh token) +/// AuthHeadersBuilder(d +/// validateToken: true, +/// builder: (context, headers) { +/// return CachedNetworkImage( +/// imageUrl: imageUrl, +/// httpHeaders: headers, +/// ); +/// }, +/// ) +/// ``` +class AuthHeadersBuilder extends StatelessWidget { + static final _logger = di.get(); + + /// Creates an [AuthHeadersBuilder]. + /// + /// The [builder] function is called with the current [BuildContext] and + /// a map of HTTP headers. If authentication fails or no token is available, + /// an empty map is provided. + /// + /// If [validateToken] is true, the token will be validated and + /// refreshed if needed. If false (default), the current cached token will be used + /// without validation. + const AuthHeadersBuilder({ + super.key, + required this.builder, + this.validateToken = false, + }); + + /// The builder function that receives the authentication headers. + /// + /// The headers map will contain: + /// - 'Authorization': 'Bearer {token}' (if authenticated) + /// - 'Content-Type': 'application/json' + /// + /// If no authentication token is available, an empty map {} is provided. + final Widget Function(BuildContext context, Map headers) + builder; + + /// Whether to validate and potentially refresh the token. + /// + /// If true, uses [AuthTokenProvider.getValidToken] which validates + /// the token and refreshes it if needed. + /// If false (default), uses [AuthTokenProvider.getCurrentToken] which returns the + /// cached token without validation. + final bool validateToken; + + Map _getSyncAuthHeaders() { + try { + final authTokenProvider = di.get(); + final token = authTokenProvider.getCurrentToken(); + + if (token != null) { + return {'Authorization': 'Bearer $token'}; + } + } catch (e) { + _logger.e('Error getting auth headers: $e'); + } + return {}; + } + + Future> _getAsyncAuthHeaders() async { + try { + final authTokenProvider = di.get(); + final token = await authTokenProvider.getValidToken(); + + if (token != null) { + return {'Authorization': 'Bearer $token'}; + } + } catch (e) { + _logger.e('Error getting auth headers: $e'); + } + return {}; + } + + @override + Widget build(BuildContext context) { + // If validation is not needed, return synchronously without FutureBuilder + if (!validateToken) { + final headers = _getSyncAuthHeaders(); + return builder(context, headers); + } + + // If validation is needed, use FutureBuilder + return FutureBuilder>( + future: _getAsyncAuthHeaders(), + builder: (context, snapshot) { + final headers = snapshot.data ?? {}; + return builder(context, headers); + }, + ); + } +}{{/network}} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/date_filter_chips.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/date_filter_chips.dart new file mode 100644 index 0000000..0932cc1 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/date_filter_chips.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:localization/localization.dart'; + +enum DateFilterType { + week, + month; + + String get displayName { + switch (this) { + case DateFilterType.week: + return strings.common.week; + case DateFilterType.month: + return strings.common.month; + } + } +} + +class DateFilterChips extends StatelessWidget { + final DateFilterType selectedFilter; + final ValueChanged onFilterChanged; + + const DateFilterChips({ + super.key, + required this.selectedFilter, + required this.onFilterChanged, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: DateFilterType.values.map((filter) { + final isSelected = filter == selectedFilter; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: FilterChip( + label: Text(filter.displayName), + selected: isSelected, + onSelected: (selected) { + if (selected) { + onFilterChanged(filter); + } + }, + backgroundColor: colorScheme.surface, + selectedColor: colorScheme.primary, + checkmarkColor: colorScheme.onPrimary, + labelStyle: TextStyle( + color: isSelected ? colorScheme.onPrimary : colorScheme.onSurface, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: isSelected + ? colorScheme.primary + : colorScheme.outline.withValues(alpha: 0.5), + width: isSelected ? 1.5 : 1, + ), + ), + ), + ); + }).toList(), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/default_error_view.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/default_error_view.dart new file mode 100644 index 0000000..718e1b6 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/default_error_view.dart @@ -0,0 +1,127 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:localization/localization.dart'; + +class DefaultErrorView extends StatelessWidget { + const DefaultErrorView({ + super.key, + this.error, + this.stackTrace, + this.onRetry, + this.descriptionBuilder, + this.padding, + }); + + final Object? error; + final StackTrace? stackTrace; + final VoidCallback? onRetry; + final String? Function(Object?)? descriptionBuilder; + final EdgeInsetsGeometry? padding; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final textTheme = theme.textTheme; + + final title = strings.errors.default_error_message; + final description = descriptionBuilder?.call(error); + + return SafeArea( + child: Padding( + padding: padding ?? const EdgeInsets.all(40.0), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: colorScheme.errorContainer.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: colorScheme.error.withValues(alpha: 0.2), + width: 1, + ), + ), + child: Icon( + Icons.error_outline_rounded, + size: 40, + color: colorScheme.error, + ), + ), + const SizedBox(height: 24), + Text( + title, + style: textTheme.titleMedium?.copyWith( + color: colorScheme.onSurface, + ), + textAlign: TextAlign.center, + maxLines: 5, + overflow: TextOverflow.ellipsis, + ), + if (description != null) ...[ + const SizedBox(height: 8), + Text( + description, + style: textTheme.labelMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + maxLines: 20, + overflow: TextOverflow.ellipsis, + ), + ], + if (error != null && kDebugMode) ...[ + const SizedBox(height: 16), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: colorScheme.outline.withValues(alpha: 0.3), + width: 1, + ), + ), + child: SelectableText( + 'type:${error.runtimeType}:\n$error\n${stackTrace?.toString() ?? ''}', + style: textTheme.bodySmall?.copyWith( + color: colorScheme.onSurface, + fontFamily: 'monospace', + fontSize: 12, + ), + textAlign: TextAlign.left, + maxLines: 10, + ), + ), + ], + const SizedBox(height: 32), + Wrap( + spacing: 12, + runSpacing: 8, + alignment: WrapAlignment.center, + children: [ + if (onRetry != null) + OutlinedButton( + onPressed: onRetry, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + minimumSize: const Size(88, 44), + ), + child: Text(strings.generic.try_again), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/header.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/header.dart new file mode 100644 index 0000000..415c857 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/header.dart @@ -0,0 +1,47 @@ +import 'package:design_system/components/network_url_image.dart'; +import 'package:design_system/utils/index.dart'; +import 'package:flutter/material.dart'; + +enum HeaderType { + image, + asset; + + double get ratio => switch (this) { + HeaderType.image => 4 / 1, + HeaderType.asset => 1 / 1, + }; +} + +class Header extends StatelessWidget { + const Header({ + super.key, + this.type = HeaderType.image, + this.assetPath, + this.imageUrl, + }); + + final HeaderType type; + final String? assetPath; + final String? imageUrl; + + @override + Widget build(BuildContext context) { + return AspectRatio(aspectRatio: type.ratio, child: _buildImage(context)); + } + + Widget _buildImage(BuildContext context) { + switch (type) { + case HeaderType.asset: + return Hero( + tag: assetPath!, + child: Image.asset( + assetPath!, + fit: BoxFit.contain, + alignment: Alignment.center, + ), + ); + case HeaderType.image: + return NetworkUrlImage(url: imageUrl.asEmptyIfNull); + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/index.dart new file mode 100644 index 0000000..9363949 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/index.dart @@ -0,0 +1,8 @@ +//GENERATED BARREL FILE +export 'asset_image.dart'; +export 'auth_headers_builder.dart'; +export 'date_filter_chips.dart'; +export 'default_error_view.dart'; +export 'header.dart'; +export 'loaders.dart'; +export 'network_url_image.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/loaders.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/loaders.dart new file mode 100644 index 0000000..b215488 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/loaders.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class DefaultLoader extends StatelessWidget { + const DefaultLoader({super.key, this.size = 20}); + + final double size; + + @override + Widget build(BuildContext context) { + return Center( + child: SizedBox( + width: size, + height: size, + child: CircularProgressIndicator.adaptive(strokeWidth: 4), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart new file mode 100644 index 0000000..a80c2d0 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart @@ -0,0 +1,45 @@ +import 'dart:math'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +class NetworkUrlImage extends StatelessWidget { + final String url; + final double? width; + final double? height; + final BoxFit? boxFit; + + const NetworkUrlImage({ + super.key, + required this.url, + this.width, + this.height, + this.boxFit = BoxFit.cover, + }); + + @override + Widget build(final BuildContext context) => CachedNetworkImage( + imageUrl: url, + placeholder: (final context, final __) { + final theme = Theme.of(context); + + return Container( + color: theme.colorScheme.surface, + width: width, + height: height, + child: Icon( + Icons.image, + size: min(width ?? 32, height ?? 32), + color: theme.cardColor, + ), + ); + }, + width: width, + height: height, + fit: boxFit, + fadeOutDuration: const Duration(), + fadeInDuration: const Duration(), + errorWidget: (final context, final url, final error) => + Icon(Icons.image, size: 32), + ); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/constants/navigation_icons.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/constants/navigation_icons.dart new file mode 100644 index 0000000..5697b47 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/constants/navigation_icons.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +/// Icons used by the app's primary navigation. +/// +/// Swap these for your product's icon set - every navigation surface reads +/// from here so a single edit re-skins the whole shell. +class NavigationIcons { + const NavigationIcons._(); + + static const IconData home = Icons.home_outlined; + static const IconData homeSelected = Icons.home; + + static const IconData explore = Icons.explore_outlined; + static const IconData exploreSelected = Icons.explore; + + static const IconData dashboard = Icons.dashboard_outlined; + static const IconData dashboardSelected = Icons.dashboard; + + static const IconData insights = Icons.trending_up_outlined; + static const IconData insightsSelected = Icons.trending_up; + + static const IconData profile = Icons.person_outline; + static const IconData profileSelected = Icons.person; + + static const IconData settings = Icons.settings_outlined; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/design_system.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/design_system.dart new file mode 100644 index 0000000..ad047a9 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/design_system.dart @@ -0,0 +1,138 @@ +import 'package:design_system/generated/theme.dart'; +import 'package:design_system/generated/util.dart'; +import 'package:flutter/material.dart'; + +export 'components/index.dart'; +export 'constants/navigation_icons.dart'; +export 'dialogs/index.dart'; +export 'generated/theme.dart'; +export 'generated/util.dart'; +export 'loader/loader.dart'; +export 'screens/index.dart'; +export 'wrapper/wrappers.dart'; + +class DesignSystem { + final MaterialTheme theme; + + DesignSystem( + BuildContext context, { + String bodyFont = "Open Sans", + String displayFont = "Open Sans", + }) : theme = buildTheme( + context, + bodyFont: bodyFont, + displayFont: displayFont, + ); + + static MaterialTheme buildTheme( + BuildContext context, { + String bodyFont = "Open Sans", + String displayFont = "Open Sans", + }) { + final textTheme = createTextTheme(context, bodyFont, displayFont); + return MaterialTheme(textTheme); + } + + ThemeData _baseTheme({required bool isDark}) { + final base = isDark ? theme.dark() : theme.light(); + return base.copyWith( + inputDecorationTheme: base.inputDecorationTheme.copyWith( + border: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.grey.shade300), + ), + hintStyle: base.textTheme.labelSmall?.copyWith( + color: base.colorScheme.onSurface.withValues(alpha: 0.6), + ), + labelStyle: base.textTheme.labelMedium?.copyWith( + color: base.colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + appBarTheme: AppBarTheme( + backgroundColor: base.colorScheme.surface, + surfaceTintColor: base.colorScheme.surfaceTint, + foregroundColor: base.colorScheme.onSurface, + elevation: 0, + centerTitle: true, + titleTextStyle: base.textTheme.titleLarge?.copyWith( + color: base.colorScheme.onSurface, + fontWeight: FontWeight.w600, + ), + iconTheme: IconThemeData(color: base.colorScheme.onSurface), + actionsIconTheme: IconThemeData(color: base.colorScheme.onSurface), + ), + navigationBarTheme: NavigationBarThemeData( + backgroundColor: base.colorScheme.surface, + surfaceTintColor: Colors.transparent, + elevation: 0, + height: 70, + labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, + labelTextStyle: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return base.textTheme.labelSmall?.copyWith( + color: base.colorScheme.onSurface, + fontWeight: FontWeight.w600, + fontSize: 12, + ); + } + return base.textTheme.labelSmall?.copyWith( + color: base.colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w500, + fontSize: 12, + ); + }), + iconTheme: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return IconThemeData(color: base.colorScheme.onPrimary, size: 20); + } + return IconThemeData( + color: base.colorScheme.onSurfaceVariant, + size: 20, + ); + }), + indicatorColor: base.colorScheme.primary, + indicatorShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + ), + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + textStyle: base.textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: base.colorScheme.primary, + textStyle: base.textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w600, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + textStyle: base.textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: base.colorScheme.primary, + textStyle: base.textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w600, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + side: BorderSide(color: base.colorScheme.primary, width: 1.5), + ), + ), + ); + } + + ThemeData light() => _baseTheme(isDark: false); + + ThemeData dark() => _baseTheme(isDark: true); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/dialogs/file_info_dialog.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/dialogs/file_info_dialog.dart new file mode 100644 index 0000000..a2b456f --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/dialogs/file_info_dialog.dart @@ -0,0 +1,264 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class FileInfoDialog extends StatelessWidget { + final File file; + + const FileInfoDialog({super.key, required this.file}); + + static Future show(BuildContext context, File file) { + return showDialog( + context: context, + builder: (context) => FileInfoDialog(file: file), + ); + } + + String _getFileType(String extension) { + switch (extension.toLowerCase()) { + case 'jpg': + case 'jpeg': + return 'JPEG Image'; + case 'png': + return 'PNG Image'; + case 'gif': + return 'GIF Image'; + case 'webp': + return 'WebP Image'; + case 'bmp': + return 'BMP Image'; + case 'tiff': + case 'tif': + return 'TIFF Image'; + case 'heic': + case 'heif': + return 'HEIF Image'; + default: + return 'Image File'; + } + } + + String _formatFileSize(int bytes) { + if (bytes < 1024) return '$bytes B'; + if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; + return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; + } + + Widget _buildInfoRow({ + required IconData icon, + required String label, + required String value, + required ColorScheme colorScheme, + required TextTheme textTheme, + bool isPath = false, + VoidCallback? onTap, + }) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(icon, size: 18, color: colorScheme.onSurfaceVariant), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + SelectableText( + value, + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurface, + fontFamily: isPath ? 'monospace' : null, + ), + ), + ], + ), + ), + if (onTap != null) + Icon( + Icons.copy, + size: 16, + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.7), + ), + ], + ), + ), + ); + } + + void _copyToClipboard(BuildContext context, String value, String label) { + Clipboard.setData(ClipboardData(text: value)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('$label copied to clipboard'), + duration: const Duration(seconds: 2), + behavior: SnackBarBehavior.floating, + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final textTheme = theme.textTheme; + + // Get file info + final fileStat = file.statSync(); + final fileSize = fileStat.size; + final fileName = file.path.split('/').last; + final fileExtension = fileName.split('.').last.toLowerCase(); + final fileType = _getFileType(fileExtension); + + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + constraints: const BoxConstraints(maxWidth: 400, maxHeight: 600), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: colorScheme.primaryContainer, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), + ), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + color: colorScheme.onPrimaryContainer, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'File Information', + style: textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w600, + color: colorScheme.onPrimaryContainer, + ), + ), + ), + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: Icon( + Icons.close, + color: colorScheme.onPrimaryContainer, + ), + ), + ], + ), + ), + + // Content + Flexible( + child: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + _buildInfoRow( + icon: Icons.image_outlined, + label: 'File Name', + value: fileName, + colorScheme: colorScheme, + textTheme: textTheme, + onTap: () => + _copyToClipboard(context, fileName, 'File name'), + ), + _buildInfoRow( + icon: Icons.category_outlined, + label: 'File Type', + value: fileType, + colorScheme: colorScheme, + textTheme: textTheme, + onTap: () => + _copyToClipboard(context, fileType, 'File type'), + ), + _buildInfoRow( + icon: Icons.extension_outlined, + label: 'File Extension', + value: '.$fileExtension', + colorScheme: colorScheme, + textTheme: textTheme, + onTap: () => _copyToClipboard( + context, + '.$fileExtension', + 'File extension', + ), + ), + _buildInfoRow( + icon: Icons.storage_outlined, + label: 'File Size', + value: _formatFileSize(fileSize), + colorScheme: colorScheme, + textTheme: textTheme, + onTap: () => _copyToClipboard( + context, + _formatFileSize(fileSize), + 'File size', + ), + ), + _buildInfoRow( + icon: Icons.folder_outlined, + label: 'File Path', + value: file.path, + colorScheme: colorScheme, + textTheme: textTheme, + isPath: true, + onTap: () => + _copyToClipboard(context, file.path, 'File path'), + ), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest.withValues( + alpha: 0.5, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon( + Icons.touch_app, + size: 16, + color: colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Tap any field to copy to clipboard', + style: textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + fontStyle: FontStyle.italic, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/dialogs/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/dialogs/index.dart new file mode 100644 index 0000000..df0904f --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/dialogs/index.dart @@ -0,0 +1 @@ +export 'file_info_dialog.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/generated/theme.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/generated/theme.dart new file mode 100644 index 0000000..33d3a0f --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/generated/theme.dart @@ -0,0 +1,386 @@ +import "package:flutter/material.dart"; + +class MaterialTheme { + final TextTheme textTheme; + + const MaterialTheme(this.textTheme); + + static ColorScheme lightScheme() { + return const ColorScheme( + brightness: Brightness.light, + primary: Color(0xff446732), + surfaceTint: Color(0xff446732), + onPrimary: Color(0xffffffff), + primaryContainer: Color(0xffc5efab), + onPrimaryContainer: Color(0xff2d4f1c), + secondary: Color(0xff55624c), + onSecondary: Color(0xffffffff), + secondaryContainer: Color(0xffd9e7cb), + onSecondaryContainer: Color(0xff3e4a35), + tertiary: Color(0xff386666), + onTertiary: Color(0xffffffff), + tertiaryContainer: Color(0xffbbebec), + onTertiaryContainer: Color(0xff1e4e4e), + error: Color(0xffba1a1a), + onError: Color(0xffffffff), + errorContainer: Color(0xffffdad6), + onErrorContainer: Color(0xff93000a), + surface: Color(0xfff8faf0), + onSurface: Color(0xff191d16), + onSurfaceVariant: Color(0xff43483e), + outline: Color(0xff74796d), + outlineVariant: Color(0xffc3c8bb), + shadow: Color(0xff000000), + scrim: Color(0xff000000), + inverseSurface: Color(0xff2e312b), + inversePrimary: Color(0xffaad291), + primaryFixed: Color(0xffc5efab), + onPrimaryFixed: Color(0xff072100), + primaryFixedDim: Color(0xffaad291), + onPrimaryFixedVariant: Color(0xff2d4f1c), + secondaryFixed: Color(0xffd9e7cb), + onSecondaryFixed: Color(0xff131f0d), + secondaryFixedDim: Color(0xffbdcbb0), + onSecondaryFixedVariant: Color(0xff3e4a35), + tertiaryFixed: Color(0xffbbebec), + onTertiaryFixed: Color(0xff002020), + tertiaryFixedDim: Color(0xffa0cfcf), + onTertiaryFixedVariant: Color(0xff1e4e4e), + surfaceDim: Color(0xffd9dbd1), + surfaceBright: Color(0xfff8faf0), + surfaceContainerLowest: Color(0xffffffff), + surfaceContainerLow: Color(0xfff2f5ea), + surfaceContainer: Color(0xffedefe5), + surfaceContainerHigh: Color(0xffe7e9df), + surfaceContainerHighest: Color(0xffe1e4d9), + ); + } + + ThemeData light() { + return theme(lightScheme()); + } + + static ColorScheme lightMediumContrastScheme() { + return const ColorScheme( + brightness: Brightness.light, + primary: Color(0xff1d3e0c), + surfaceTint: Color(0xff446732), + onPrimary: Color(0xffffffff), + primaryContainer: Color(0xff53773f), + onPrimaryContainer: Color(0xffffffff), + secondary: Color(0xff2d3a26), + onSecondary: Color(0xffffffff), + secondaryContainer: Color(0xff64715a), + onSecondaryContainer: Color(0xffffffff), + tertiary: Color(0xff073d3e), + onTertiary: Color(0xffffffff), + tertiaryContainer: Color(0xff477575), + onTertiaryContainer: Color(0xffffffff), + error: Color(0xff740006), + onError: Color(0xffffffff), + errorContainer: Color(0xffcf2c27), + onErrorContainer: Color(0xffffffff), + surface: Color(0xfff8faf0), + onSurface: Color(0xff0f120c), + onSurfaceVariant: Color(0xff33382e), + outline: Color(0xff4f544a), + outlineVariant: Color(0xff6a6f64), + shadow: Color(0xff000000), + scrim: Color(0xff000000), + inverseSurface: Color(0xff2e312b), + inversePrimary: Color(0xffaad291), + primaryFixed: Color(0xff53773f), + onPrimaryFixed: Color(0xffffffff), + primaryFixedDim: Color(0xff3b5d29), + onPrimaryFixedVariant: Color(0xffffffff), + secondaryFixed: Color(0xff64715a), + onSecondaryFixed: Color(0xffffffff), + secondaryFixedDim: Color(0xff4c5943), + onSecondaryFixedVariant: Color(0xffffffff), + tertiaryFixed: Color(0xff477575), + onTertiaryFixed: Color(0xffffffff), + tertiaryFixedDim: Color(0xff2e5c5d), + onTertiaryFixedVariant: Color(0xffffffff), + surfaceDim: Color(0xffc5c8be), + surfaceBright: Color(0xfff8faf0), + surfaceContainerLowest: Color(0xffffffff), + surfaceContainerLow: Color(0xfff2f5ea), + surfaceContainer: Color(0xffe7e9df), + surfaceContainerHigh: Color(0xffdbded4), + surfaceContainerHighest: Color(0xffd0d3c9), + ); + } + + ThemeData lightMediumContrast() { + return theme(lightMediumContrastScheme()); + } + + static ColorScheme lightHighContrastScheme() { + return const ColorScheme( + brightness: Brightness.light, + primary: Color(0xff133304), + surfaceTint: Color(0xff446732), + onPrimary: Color(0xffffffff), + primaryContainer: Color(0xff30511e), + onPrimaryContainer: Color(0xffffffff), + secondary: Color(0xff242f1c), + onSecondary: Color(0xffffffff), + secondaryContainer: Color(0xff404d38), + onSecondaryContainer: Color(0xffffffff), + tertiary: Color(0xff003233), + onTertiary: Color(0xffffffff), + tertiaryContainer: Color(0xff215051), + onTertiaryContainer: Color(0xffffffff), + error: Color(0xff600004), + onError: Color(0xffffffff), + errorContainer: Color(0xff98000a), + onErrorContainer: Color(0xffffffff), + surface: Color(0xfff8faf0), + onSurface: Color(0xff000000), + onSurfaceVariant: Color(0xff000000), + outline: Color(0xff292e25), + outlineVariant: Color(0xff464b41), + shadow: Color(0xff000000), + scrim: Color(0xff000000), + inverseSurface: Color(0xff2e312b), + inversePrimary: Color(0xffaad291), + primaryFixed: Color(0xff30511e), + onPrimaryFixed: Color(0xffffffff), + primaryFixedDim: Color(0xff1a3a09), + onPrimaryFixedVariant: Color(0xffffffff), + secondaryFixed: Color(0xff404d38), + onSecondaryFixed: Color(0xffffffff), + secondaryFixedDim: Color(0xff2a3622), + onSecondaryFixedVariant: Color(0xffffffff), + tertiaryFixed: Color(0xff215051), + onTertiaryFixed: Color(0xffffffff), + tertiaryFixedDim: Color(0xff02393a), + onTertiaryFixedVariant: Color(0xffffffff), + surfaceDim: Color(0xffb7bab0), + surfaceBright: Color(0xfff8faf0), + surfaceContainerLowest: Color(0xffffffff), + surfaceContainerLow: Color(0xfff0f2e7), + surfaceContainer: Color(0xffe1e4d9), + surfaceContainerHigh: Color(0xffd3d5cb), + surfaceContainerHighest: Color(0xffc5c8be), + ); + } + + ThemeData lightHighContrast() { + return theme(lightHighContrastScheme()); + } + + static ColorScheme darkScheme() { + return const ColorScheme( + brightness: Brightness.dark, + primary: Color(0xffaad291), + surfaceTint: Color(0xffaad291), + onPrimary: Color(0xff173807), + primaryContainer: Color(0xff2d4f1c), + onPrimaryContainer: Color(0xffc5efab), + secondary: Color(0xffbdcbb0), + onSecondary: Color(0xff283420), + secondaryContainer: Color(0xff3e4a35), + onSecondaryContainer: Color(0xffd9e7cb), + tertiary: Color(0xffa0cfcf), + onTertiary: Color(0xff003738), + tertiaryContainer: Color(0xff1e4e4e), + onTertiaryContainer: Color(0xffbbebec), + error: Color(0xffffb4ab), + onError: Color(0xff690005), + errorContainer: Color(0xff93000a), + onErrorContainer: Color(0xffffdad6), + surface: Color(0xff11140e), + onSurface: Color(0xffe1e4d9), + onSurfaceVariant: Color(0xffc3c8bb), + outline: Color(0xff8d9286), + outlineVariant: Color(0xff43483e), + shadow: Color(0xff000000), + scrim: Color(0xff000000), + inverseSurface: Color(0xffe1e4d9), + inversePrimary: Color(0xff446732), + primaryFixed: Color(0xffc5efab), + onPrimaryFixed: Color(0xff072100), + primaryFixedDim: Color(0xffaad291), + onPrimaryFixedVariant: Color(0xff2d4f1c), + secondaryFixed: Color(0xffd9e7cb), + onSecondaryFixed: Color(0xff131f0d), + secondaryFixedDim: Color(0xffbdcbb0), + onSecondaryFixedVariant: Color(0xff3e4a35), + tertiaryFixed: Color(0xffbbebec), + onTertiaryFixed: Color(0xff002020), + tertiaryFixedDim: Color(0xffa0cfcf), + onTertiaryFixedVariant: Color(0xff1e4e4e), + surfaceDim: Color(0xff11140e), + surfaceBright: Color(0xff373a33), + surfaceContainerLowest: Color(0xff0c0f09), + surfaceContainerLow: Color(0xff191d16), + surfaceContainer: Color(0xff1d211a), + surfaceContainerHigh: Color(0xff282b24), + surfaceContainerHighest: Color(0xff32362f), + ); + } + + ThemeData dark() { + return theme(darkScheme()); + } + + static ColorScheme darkMediumContrastScheme() { + return const ColorScheme( + brightness: Brightness.dark, + primary: Color(0xffbfe8a5), + surfaceTint: Color(0xffaad291), + onPrimary: Color(0xff0c2c00), + primaryContainer: Color(0xff759b5f), + onPrimaryContainer: Color(0xff000000), + secondary: Color(0xffd2e1c5), + onSecondary: Color(0xff1d2916), + secondaryContainer: Color(0xff87957c), + onSecondaryContainer: Color(0xff000000), + tertiary: Color(0xffb5e5e5), + onTertiary: Color(0xff002b2c), + tertiaryContainer: Color(0xff6b9999), + onTertiaryContainer: Color(0xff000000), + error: Color(0xffffd2cc), + onError: Color(0xff540003), + errorContainer: Color(0xffff5449), + onErrorContainer: Color(0xff000000), + surface: Color(0xff11140e), + onSurface: Color(0xffffffff), + onSurfaceVariant: Color(0xffd9ded0), + outline: Color(0xffafb4a7), + outlineVariant: Color(0xff8d9286), + shadow: Color(0xff000000), + scrim: Color(0xff000000), + inverseSurface: Color(0xffe1e4d9), + inversePrimary: Color(0xff2f501d), + primaryFixed: Color(0xffc5efab), + onPrimaryFixed: Color(0xff041500), + primaryFixedDim: Color(0xffaad291), + onPrimaryFixedVariant: Color(0xff1d3e0c), + secondaryFixed: Color(0xffd9e7cb), + onSecondaryFixed: Color(0xff091405), + secondaryFixedDim: Color(0xffbdcbb0), + onSecondaryFixedVariant: Color(0xff2d3a26), + tertiaryFixed: Color(0xffbbebec), + onTertiaryFixed: Color(0xff001415), + tertiaryFixedDim: Color(0xffa0cfcf), + onTertiaryFixedVariant: Color(0xff073d3e), + surfaceDim: Color(0xff11140e), + surfaceBright: Color(0xff42463e), + surfaceContainerLowest: Color(0xff060804), + surfaceContainerLow: Color(0xff1b1f18), + surfaceContainer: Color(0xff262922), + surfaceContainerHigh: Color(0xff30342d), + surfaceContainerHighest: Color(0xff3b3f38), + ); + } + + ThemeData darkMediumContrast() { + return theme(darkMediumContrastScheme()); + } + + static ColorScheme darkHighContrastScheme() { + return const ColorScheme( + brightness: Brightness.dark, + primary: Color(0xffd2fcb7), + surfaceTint: Color(0xffaad291), + onPrimary: Color(0xff000000), + primaryContainer: Color(0xffa6ce8d), + onPrimaryContainer: Color(0xff020e00), + secondary: Color(0xffe6f5d8), + onSecondary: Color(0xff000000), + secondaryContainer: Color(0xffb9c7ac), + onSecondaryContainer: Color(0xff040e02), + tertiary: Color(0xffc9f9f9), + onTertiary: Color(0xff000000), + tertiaryContainer: Color(0xff9ccbcb), + onTertiaryContainer: Color(0xff000e0e), + error: Color(0xffffece9), + onError: Color(0xff000000), + errorContainer: Color(0xffffaea4), + onErrorContainer: Color(0xff220001), + surface: Color(0xff11140e), + onSurface: Color(0xffffffff), + onSurfaceVariant: Color(0xffffffff), + outline: Color(0xffedf2e4), + outlineVariant: Color(0xffbfc4b7), + shadow: Color(0xff000000), + scrim: Color(0xff000000), + inverseSurface: Color(0xffe1e4d9), + inversePrimary: Color(0xff2f501d), + primaryFixed: Color(0xffc5efab), + onPrimaryFixed: Color(0xff000000), + primaryFixedDim: Color(0xffaad291), + onPrimaryFixedVariant: Color(0xff041500), + secondaryFixed: Color(0xffd9e7cb), + onSecondaryFixed: Color(0xff000000), + secondaryFixedDim: Color(0xffbdcbb0), + onSecondaryFixedVariant: Color(0xff091405), + tertiaryFixed: Color(0xffbbebec), + onTertiaryFixed: Color(0xff000000), + tertiaryFixedDim: Color(0xffa0cfcf), + onTertiaryFixedVariant: Color(0xff001415), + surfaceDim: Color(0xff11140e), + surfaceBright: Color(0xff4e514a), + surfaceContainerLowest: Color(0xff000000), + surfaceContainerLow: Color(0xff1d211a), + surfaceContainer: Color(0xff2e312b), + surfaceContainerHigh: Color(0xff393c35), + surfaceContainerHighest: Color(0xff444840), + ); + } + + ThemeData darkHighContrast() { + return theme(darkHighContrastScheme()); + } + + ThemeData theme(ColorScheme colorScheme) => ThemeData( + useMaterial3: true, + brightness: colorScheme.brightness, + colorScheme: colorScheme, + textTheme: textTheme.apply( + bodyColor: colorScheme.onSurface, + displayColor: colorScheme.onSurface, + ), + scaffoldBackgroundColor: colorScheme.surface, + canvasColor: colorScheme.surface, + ); + + List get extendedColors => []; +} + +class ExtendedColor { + final Color seed, value; + final ColorFamily light; + final ColorFamily lightHighContrast; + final ColorFamily lightMediumContrast; + final ColorFamily dark; + final ColorFamily darkHighContrast; + final ColorFamily darkMediumContrast; + + const ExtendedColor({ + required this.seed, + required this.value, + required this.light, + required this.lightHighContrast, + required this.lightMediumContrast, + required this.dark, + required this.darkHighContrast, + required this.darkMediumContrast, + }); +} + +class ColorFamily { + const ColorFamily({ + required this.color, + required this.onColor, + required this.colorContainer, + required this.onColorContainer, + }); + + final Color color; + final Color onColor; + final Color colorContainer; + final Color onColorContainer; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/generated/util.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/generated/util.dart new file mode 100644 index 0000000..10a2d84 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/generated/util.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +TextTheme createTextTheme( + BuildContext context, + String bodyFontString, + String displayFontString, +) { + TextTheme baseTextTheme = Theme.of(context).textTheme; + TextTheme bodyTextTheme = GoogleFonts.getTextTheme( + bodyFontString, + baseTextTheme, + ); + TextTheme displayTextTheme = GoogleFonts.getTextTheme( + displayFontString, + baseTextTheme, + ); + TextTheme textTheme = displayTextTheme.copyWith( + bodyLarge: bodyTextTheme.bodyLarge, + bodyMedium: bodyTextTheme.bodyMedium, + bodySmall: bodyTextTheme.bodySmall, + labelLarge: bodyTextTheme.labelLarge, + labelMedium: bodyTextTheme.labelMedium, + labelSmall: bodyTextTheme.labelSmall, + ); + return textTheme; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/loader/loader.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/loader/loader.dart new file mode 100644 index 0000000..3e75782 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/loader/loader.dart @@ -0,0 +1,15 @@ +import 'package:design_system/components/index.dart'; +import 'package:flutter/material.dart'; +import 'package:loader_overlay/loader_overlay.dart'; + +class Loader { + static void show(BuildContext context) { + context.loaderOverlay.show( + widgetBuilder: (_) => const Center(child: DefaultLoader()), + ); + } + + static void hide(BuildContext context) { + context.loaderOverlay.hide(); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/screens/error_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/screens/error_screen.dart new file mode 100644 index 0000000..25c9f79 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/screens/error_screen.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:localization/localization.dart'; + +class ErrorScreen extends StatelessWidget { + const ErrorScreen({ + super.key, + this.title, + this.message, + this.onRetry, + this.onGoHome, + this.error, + }); + + final String? title; + final String? message; + final VoidCallback? onRetry; + final VoidCallback? onGoHome; + final String? error; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final textTheme = theme.textTheme; + + final resolvedTitle = title ?? strings.errors.default_error_message; + final resolvedMessage = message ?? strings.errors.default_error_description; + + return Scaffold( + backgroundColor: colorScheme.surface, + appBar: AppBar( + backgroundColor: colorScheme.surface, + foregroundColor: colorScheme.onSurface, + elevation: 0, + surfaceTintColor: colorScheme.surfaceTint, + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: colorScheme.errorContainer.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: colorScheme.error.withValues(alpha: 0.2), + width: 1, + ), + ), + child: Icon( + Icons.error_outline_rounded, + size: 40, + color: colorScheme.error, + ), + ), + const SizedBox(height: 24), + Text( + resolvedTitle, + style: textTheme.titleLarge?.copyWith( + color: colorScheme.onSurface, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + Text( + resolvedMessage, + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + if (error != null) ...[ + const SizedBox(height: 16), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: colorScheme.outline.withValues(alpha: 0.3), + width: 1, + ), + ), + child: SelectableText( + error!, + style: textTheme.bodySmall?.copyWith( + color: colorScheme.onSurface, + fontFamily: 'monospace', + fontSize: 12, + ), + textAlign: TextAlign.left, + maxLines: 4, + ), + ), + ], + const SizedBox(height: 32), + Wrap( + spacing: 12, + runSpacing: 8, + alignment: WrapAlignment.center, + children: [ + if (onRetry != null) + FilledButton( + onPressed: onRetry, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + minimumSize: const Size(88, 44), + ), + child: Text(strings.generic.try_again), + ), + if (onGoHome != null) + OutlinedButton( + onPressed: onGoHome, + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + minimumSize: const Size(88, 44), + side: BorderSide( + color: colorScheme.outline, + width: 1, + ), + ), + child: Text(strings.generic.retry), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/screens/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/screens/index.dart new file mode 100644 index 0000000..807a6de --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/screens/index.dart @@ -0,0 +1 @@ +export 'error_screen.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/toast/toasts.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/toast/toasts.dart new file mode 100644 index 0000000..3e06134 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/toast/toasts.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:iconsax_flutter/iconsax_flutter.dart'; +import 'package:toastification/toastification.dart'; + +class Toast { + static void notification({required String title, required String body}) => + toastification.show( + type: ToastificationType.info, + style: ToastificationStyle.fillColored, + title: Text(title), + description: Text(body), + alignment: Alignment.topCenter, + autoCloseDuration: const Duration(seconds: 3, milliseconds: 500), + animationBuilder: (context, animation, alignment, child) { + return ScaleTransition(scale: animation, child: child); + }, + icon: Icon(Iconsax.notification), + borderRadius: BorderRadius.circular(12.0), + showProgressBar: true, + dragToClose: true, + applyBlurEffect: true, + ); + + static void error( + BuildContext context, { + required String message, + String? description, + }) => toastification.show( + context: context, + type: ToastificationType.error, + style: ToastificationStyle.fillColored, + title: Text(message), + description: description != null ? Text(description) : null, + alignment: Alignment.topCenter, + autoCloseDuration: const Duration(seconds: 3, milliseconds: 500), + animationBuilder: (context, animation, alignment, child) { + return ScaleTransition(scale: animation, child: child); + }, + icon: Icon(Iconsax.warning_2), + borderRadius: BorderRadius.circular(12.0), + showProgressBar: true, + dragToClose: true, + applyBlurEffect: true, + ); + + static void success( + BuildContext context, { + required String message, + String? description, + }) => toastification.show( + context: context, + type: ToastificationType.success, + style: ToastificationStyle.fillColored, + title: Text(message), + description: description != null ? Text(description) : null, + alignment: Alignment.topCenter, + autoCloseDuration: const Duration(seconds: 3, milliseconds: 500), + animationBuilder: (context, animation, alignment, child) { + return ScaleTransition(scale: animation, child: child); + }, + icon: Icon(Iconsax.tick_circle), + borderRadius: BorderRadius.circular(12.0), + showProgressBar: true, + dragToClose: true, + applyBlurEffect: true, + ); + + static void warning( + BuildContext context, { + required String message, + String? description, + }) => toastification.show( + context: context, + type: ToastificationType.warning, + style: ToastificationStyle.fillColored, + title: Text(message), + description: description != null ? Text(description) : null, + alignment: Alignment.topCenter, + autoCloseDuration: const Duration(seconds: 3, milliseconds: 500), + animationBuilder: (context, animation, alignment, child) { + return ScaleTransition(scale: animation, child: child); + }, + icon: Icon(Iconsax.danger), + borderRadius: BorderRadius.circular(12.0), + showProgressBar: true, + dragToClose: true, + applyBlurEffect: true, + ); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/extensions/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/extensions/index.dart new file mode 100644 index 0000000..526a048 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/extensions/index.dart @@ -0,0 +1,2 @@ +//GENERATED BARREL FILE +export 'string.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/extensions/string.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/extensions/string.dart new file mode 100644 index 0000000..c68c177 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/extensions/string.dart @@ -0,0 +1,33 @@ +/// String extensions for common utility operations +extension StringExtensions on String? { + /// Returns true if the string is not null and not empty + /// + /// Example: + /// ```dart + /// String? text = "Hello"; + /// print(text.isNotNullAndNotEmpty); // true + /// + /// String? emptyText = ""; + /// print(emptyText.isNotNullAndNotEmpty); // false + /// + /// String? nullText = null; + /// print(nullText.isNotNullAndNotEmpty); // false + /// ``` + bool get isNotNullAndNotEmpty { + return this != null && this!.isNotEmpty; + } + + /// Returns an empty string if the string is null, otherwise returns the string itself + /// + /// Example: + /// ```dart + /// String? text = "Hello"; + /// print(text.asEmptyIfNull); // "Hello" + /// + /// String? nullText = null; + /// print(nullText.asEmptyIfNull); // "" + /// ``` + String get asEmptyIfNull { + return this ?? ''; + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/index.dart new file mode 100644 index 0000000..fd6ab25 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/utils/index.dart @@ -0,0 +1,2 @@ +//GENERATED BARREL FILE +export 'extensions/index.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/wrapper/wrappers.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/wrapper/wrappers.dart new file mode 100644 index 0000000..1a08c02 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/wrapper/wrappers.dart @@ -0,0 +1,54 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:loader_overlay/loader_overlay.dart'; +import 'package:toastification/toastification.dart'; + +class DesignSystemWrapper extends StatefulWidget { + const DesignSystemWrapper({super.key, required this.builder}); + + final Widget Function(BuildContext context, ThemeData theme) builder; + + @override + State createState() => _DesignSystemWrapperState(); +} + +class _DesignSystemWrapperState extends State + with WidgetsBindingObserver { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangePlatformBrightness() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + final brightness = MediaQuery.platformBrightnessOf(context); + final materialTheme = DesignSystem(context); + final theme = brightness != Brightness.dark + ? materialTheme.light() + : materialTheme.dark(); + + return Theme( + data: theme, + child: GlobalLoaderOverlay( + overlayColor: theme.colorScheme.surface.withValues(alpha: 0.8), + duration: const Duration(milliseconds: 300), + reverseDuration: const Duration(milliseconds: 200), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + child: ToastificationWrapper(child: widget.builder(context, theme)), + ), + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/pubspec.yaml new file mode 100644 index 0000000..71dfdf9 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/pubspec.yaml @@ -0,0 +1,33 @@ +name: design_system +description: "Theme, components and dialogs shared across {{name.titleCase()}}." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + cached_network_image: ^3.4.1 + flutter_svg: ^2.2.0 + google_fonts: ^6.2.1 + iconsax_flutter: ^1.0.1 + loader_overlay: ^5.0.0 + toastification: ^3.0.3 + core: + path: ../core + di: + path: ../di + localization: + path: ../localization +{{#network}} network: + path: ../network +{{/network}} +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/test/design_system_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/test/design_system_test.dart new file mode 100644 index 0000000..8cd1ae7 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/test/design_system_test.dart @@ -0,0 +1,25 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('DesignSystemWrapper hands the builder a themed context', ( + tester, + ) async { + late ThemeData built; + + await tester.pumpWidget( + MaterialApp( + home: DesignSystemWrapper( + builder: (context, theme) { + built = theme; + return const SizedBox.shrink(); + }, + ), + ), + ); + + expect(built.useMaterial3, isTrue); + expect(built.colorScheme.primary, isNot(Colors.transparent)); + }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/README.md new file mode 100644 index 0000000..e641145 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/README.md @@ -0,0 +1,6 @@ +# Dependency Injection (DI) Package + +A clean dependency injection abstraction for the {{name.titleCase()}} monorepo, built on top of GetIt. + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/di.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/di.dart new file mode 100644 index 0000000..8136bb6 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/di.dart @@ -0,0 +1,14 @@ +/// Dependency Injection package for {{name.titleCase()}} +/// +/// This package provides a clean abstraction over dependency injection +/// using GetIt as the underlying implementation. +library; + +import 'package:di/di.dart'; + +// Core interfaces +export 'src/dependency_injection.dart'; +// GetIt implementation +export 'src/dependency_injection_imp.dart'; + +final di = GetItDependencyInjection(); diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/src/dependency_injection.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/src/dependency_injection.dart new file mode 100644 index 0000000..1e5b2f0 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/src/dependency_injection.dart @@ -0,0 +1,22 @@ +import 'dart:async'; + +typedef DisposeFunc = FutureOr Function(T param); + +abstract class DependencyInjection { + Future init(); + + Future dispose(); + + void register( + final T instance, { + final DisposeFunc? dispose, + }); + + Future unregister(final T? instance); + + T get(); + + bool has(); + + Future reset(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/src/dependency_injection_imp.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/src/dependency_injection_imp.dart new file mode 100644 index 0000000..7fb9bfe --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/lib/src/dependency_injection_imp.dart @@ -0,0 +1,115 @@ +import 'dart:async'; + +import 'dart:developer' as dev; + +import 'package:di/src/dependency_injection.dart'; +import 'package:get_it/get_it.dart'; + +/// Implementation of DependencyInjection using GetIt +class GetItDependencyInjection implements DependencyInjection { + final GetIt _getIt; + final Map _disposeFunctions = {}; + final Map _registeredInstances = {}; + + /// Creates a new instance with optional GetIt instance + /// If no instance is provided, uses GetIt.instance + GetItDependencyInjection({GetIt? getIt}) : _getIt = getIt ?? GetIt.instance; + + @override + Future init() async { + // GetIt doesn't require explicit initialization + // but we can use this to set up any global configurations + } + + @override + Future dispose() async { + // Call dispose functions for all registered instances + for (final entry in _disposeFunctions.entries) { + final type = entry.key; + final disposeFunc = entry.value; + final instance = _registeredInstances[type]; + + if (instance != null) { + try { + await disposeFunc(instance); + } catch (e) { + // Log error but continue disposing other instances + dev.log( + 'Error disposing instance of type $type', + name: 'di', + error: e, + ); + } + } + } + + // Clear dispose functions and registered instances maps + _disposeFunctions.clear(); + _registeredInstances.clear(); + + // Reset GetIt instance + await _getIt.reset(); + } + + @override + void register( + final T instance, { + final DisposeFunc? dispose, + }) { + // Register the instance as a singleton + _getIt.registerSingleton(instance); + + // Store the instance and dispose function if provided + _registeredInstances[T] = instance; + if (dispose != null) { + _disposeFunctions[T] = dispose; + } + } + + @override + Future unregister(final T? instance) async { + if (!_getIt.isRegistered()) { + return; + } + + // Call dispose function if it exists + final disposeFunc = _disposeFunctions[T]; + if (disposeFunc != null) { + try { + final registeredInstance = instance ?? _registeredInstances[T]; + if (registeredInstance != null) { + await disposeFunc(registeredInstance); + } + } catch (e) { + // Log error but continue with unregistration + dev.log('Error disposing instance of type $T', name: 'di', error: e); + } + _disposeFunctions.remove(T); + } + + // Remove from our tracking + _registeredInstances.remove(T); + + // Unregister from GetIt + await _getIt.unregister(instance: instance); + } + + @override + T get() { + return _getIt.get(); + } + + @override + bool has() { + return _getIt.isRegistered(); + } + + @override + Future reset() async { + await dispose(); + await init(); + } + + /// Get the underlying GetIt instance (for advanced usage) + GetIt get getIt => _getIt; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/pubspec.yaml new file mode 100644 index 0000000..381b1d0 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/pubspec.yaml @@ -0,0 +1,20 @@ +name: di +description: "Service locator abstraction for {{name.titleCase()}}, backed by GetIt." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + get_it: ^8.0.3 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/test/di_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/test/di_test.dart new file mode 100644 index 0000000..6772212 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/test/di_test.dart @@ -0,0 +1,253 @@ +import 'package:di/di.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; + +// Test classes for dependency injection +class TestService { + final String name; + bool isDisposed = false; + + TestService(this.name); + + void dispose() { + isDisposed = true; + } +} + +class TestRepository { + final TestService service; + bool isDisposed = false; + + TestRepository(this.service); + + void dispose() { + isDisposed = true; + } +} + +void main() { + group('GetItDependencyInjection', () { + late GetItDependencyInjection di; + late GetIt testGetIt; + + setUp(() { + // Create a separate GetIt instance for testing + testGetIt = GetIt.asNewInstance(); + di = GetItDependencyInjection(getIt: testGetIt); + }); + + tearDown(() async { + await di.dispose(); + }); + + group('Registration', () { + setUp(() async { + await di.init(); + }); + + test('should register and retrieve instances', () async { + final service = TestService('test'); + + di.register(service); + + expect(di.has(), true); + final retrieved = di.get(); + expect(retrieved, same(service)); + expect(retrieved.name, 'test'); + }); + + test('should register with dispose function', () async { + final service = TestService('test'); + bool disposeCalled = false; + + di.register( + service, + dispose: (instance) { + disposeCalled = true; + instance.dispose(); + }, + ); + + expect(di.has(), true); + await di.unregister(null); + + expect(disposeCalled, true); + expect(service.isDisposed, true); + expect(di.has(), false); + }); + }); + + group('Error handling', () { + test('registering before init() is allowed', () { + // init() exists for symmetry with dispose(); GetIt needs no setup, so + // registration works straight away. + final service = TestService('test'); + + di.register(service); + + expect(di.get(), same(service)); + }); + + test('should throw when getting before initialization', () { + expect(() => di.get(), throwsA(isA())); + }); + + test('should return false when not initialized', () { + expect(di.has(), false); + }); + }); + + group('Retrieval', () { + setUp(() async { + await di.init(); + }); + + test('should get registered instances', () { + final service = TestService('test'); + di.register(service); + + final retrieved = di.get(); + expect(retrieved, same(service)); + }); + + test('should throw when getting unregistered type', () { + expect(() => di.get(), throwsA(isA())); + }); + }); + + group('Has/Contains', () { + setUp(() async { + await di.init(); + }); + + test('should return true for registered types', () { + final service = TestService('test'); + di.register(service); + + expect(di.has(), true); + }); + + test('should return false for unregistered types', () { + expect(di.has(), false); + }); + }); + + group('Unregistration', () { + setUp(() async { + await di.init(); + }); + + test('should unregister instances', () async { + final service = TestService('test'); + di.register(service); + + expect(di.has(), true); + await di.unregister(null); + expect(di.has(), false); + }); + + test('should call dispose function when unregistering', () async { + final service = TestService('test'); + bool disposeCalled = false; + + di.register( + service, + dispose: (instance) { + disposeCalled = true; + instance.dispose(); + }, + ); + + await di.unregister(null); + + expect(disposeCalled, true); + expect(service.isDisposed, true); + }); + + test( + 'should handle unregistering non-existent types gracefully', + () async { + // Should not throw + await di.unregister(null); + expect(di.has(), false); + }, + ); + + test('should handle unregistering when not initialized', () async { + // Should not throw + await di.unregister(null); + }); + }); + + group('Disposal', () { + setUp(() async { + await di.init(); + }); + + test('should dispose all registered instances', () async { + final service = TestService('service'); + final repository = TestRepository(service); + + di.register( + service, + dispose: (instance) => instance.dispose(), + ); + + di.register( + repository, + dispose: (instance) => instance.dispose(), + ); + + await di.dispose(); + + expect(service.isDisposed, true); + expect(repository.isDisposed, true); + expect(di.has(), false); + expect(di.has(), false); + }); + }); + + group('Reset', () { + test('should reset and reinitialize', () async { + await di.init(); + + final service = TestService('test'); + di.register( + service, + dispose: (instance) => instance.dispose(), + ); + + expect(di.has(), true); + + await di.reset(); + + expect(di.has(), false); + expect(service.isDisposed, true); + }); + }); + + group('Complex scenarios', () { + setUp(() async { + await di.init(); + }); + + test('should handle multiple dependencies', () { + final service = TestService('service'); + final repository = TestRepository(service); + + di.register(service); + di.register(repository); + + expect(di.has(), true); + expect(di.has(), true); + + final retrievedService = di.get(); + final retrievedRepository = di.get(); + + expect(retrievedService, same(service)); + expect(retrievedRepository, same(repository)); + expect(retrievedRepository.service, same(service)); + }); + }); + }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/README.md new file mode 100644 index 0000000..0090626 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/README.md @@ -0,0 +1,169 @@ +# Localization + +Localization package for {{name.titleCase()}} using i69n for type-safe internationalization. + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) + +## Features โœจ + +- **Type-safe translations** - Generated classes with compile-time string validation +- **Easy integration** - Simple API with global `strings` access +- **Organized structure** - Categorized messages (auth, profile, generic, etc.) +- **Future-ready** - Built for multi-language support +- **Melos integration** - Seamless monorepo workflow + +## Installation ๐Ÿ’ป + +Add to your `pubspec.yaml`: + +```yaml +dependencies: + localization: + path: ../packages/localization +``` + +## Usage ๐Ÿš€ + +### Basic Usage + +```dart +import 'package:localization/localization.dart'; + +// Simple access to any string +Text(strings.auth.login); // "Login" +Text(strings.generic.ok); // "OK" +Text(strings.profile.name); // "Name" +Text(strings.validation.required_field); // "This field is required" +``` + +### In Widgets + +```dart +class LoginButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () => _login(), + child: Text(strings.auth.login), + ); + } +} +``` + +### Error Handling + +```dart +void showError() { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(strings.errors.network_error)), + ); +} +``` + +### Form Validation + +```dart +String? validateEmail(String? value) { + if (value?.isEmpty ?? true) { + return strings.validation.required_field; + } + if (!value!.contains('@')) { + return strings.validation.invalid_email; + } + return null; +} +``` + +## Development ๐Ÿ› ๏ธ + +### Adding New Strings + +1. Edit `lib/messages.i69n.yaml`: +```yaml +new_category: + new_message: "New Message Text" + another_message: "Another Message" +``` + +2. Generate code: +```bash +# From the monorepo root +melos run generate:i69n + +# Or from this package +dart run build_runner build --delete-conflicting-outputs +``` + +3. Use the new strings: +```dart +Text(strings.new_category.new_message); // "New Message Text" +``` + +### Testing + +```bash +# Run tests +flutter test + +# Run with coverage +flutter test --coverage +``` + +### Linting + +```bash +# From monorepo root +melos run lint + +# Or from this package +dart format . && dart analyze --fatal-infos . +``` + +## Advanced Usage ๐Ÿ”ง + +### Using LocalizationProvider + +```dart +import 'package:localization/localization.dart'; + +// Initialize (currently only supports English) +LocalizationProvider.initialize(); + +// Check supported locales +if (LocalizationProvider.isLocaleSupported('en')) { + // Handle supported locale +} + +// Simple access to any string +Text(strings.auth.login); // "Login" + +``` + +### Future Multi-language Support + +This package is designed to support multiple languages in the future. The current implementation supports English only, but the structure is ready for: + +- Spanish (`es`) +- French (`fr`) +- German (`de`) +- Additional languages as needed + +## Package Structure ๐Ÿ“ + +``` +packages/localization/ +โ”œโ”€โ”€ lib/ +โ”‚ โ”œโ”€โ”€ localization.dart # Main export file +โ”‚ โ”œโ”€โ”€ messages.i69n.dart # Generated i69n classes +โ”‚ โ”œโ”€โ”€ messages.i69n.yaml # Translation source +โ”‚ โ””โ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ localization_provider.dart # Advanced utilities +โ”‚ โ””โ”€โ”€ strings.dart # Global strings instance +โ”œโ”€โ”€ test/ +โ”‚ โ””โ”€โ”€ localization_test.dart # Comprehensive tests +โ”œโ”€โ”€ build.yaml # i69n build configuration +โ””โ”€โ”€ pubspec.yaml # Package dependencies +``` + +[flutter_install_link]: https://docs.flutter.dev/get-started/install diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/build.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/build.yaml new file mode 100644 index 0000000..f1b199a --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/build.yaml @@ -0,0 +1,13 @@ +targets: + $default: + builders: + i69n: + options: + default_locale: en + locales: + - en + - es + - fr + - de + generate_for: + - lib/messages.i69n.yaml \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/localization.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/localization.dart new file mode 100644 index 0000000..1691dfb --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/localization.dart @@ -0,0 +1,14 @@ +/// {{name.titleCase()}} Localization Package +/// +/// This package provides internationalization support using i69n. +/// It contains generated message classes and utilities for managing translations. +library; + +// Export generated i69n messages +export 'messages.i69n.dart'; + +// Export localization utilities +export 'src/localization_provider.dart'; + +// Export global strings instance for easy access +export 'src/strings.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/messages.i69n.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/messages.i69n.dart new file mode 100644 index 0000000..52f436d --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/messages.i69n.dart @@ -0,0 +1,579 @@ +// ignore_for_file: unused_element, unused_field, camel_case_types, annotate_overrides, prefer_single_quotes +// GENERATED FILE, do not edit! +// dart format off +import 'package:i69n/i69n.dart' as i69n; + +String get _languageCode => 'en'; +String get _localeName => 'en'; + +String _plural(int count, + {String? zero, + String? one, + String? two, + String? few, + String? many, + String? other}) => + i69n.plural(count, _languageCode, + zero: zero, one: one, two: two, few: few, many: many, other: other); +String _ordinal(int count, + {String? zero, + String? one, + String? two, + String? few, + String? many, + String? other}) => + i69n.ordinal(count, _languageCode, + zero: zero, one: one, two: two, few: few, many: many, other: other); +String _cardinal(int count, + {String? zero, + String? one, + String? two, + String? few, + String? many, + String? other}) => + i69n.cardinal(count, _languageCode, + zero: zero, one: one, two: two, few: few, many: many, other: other); + +class Messages implements i69n.I69nMessageBundle { + const Messages(); + AppMessages get app => AppMessages(this); + GenericMessages get generic => GenericMessages(this); + CommonMessages get common => CommonMessages(this); + AuthMessages get auth => AuthMessages(this); + ProfileMessages get profile => ProfileMessages(this); + NavMessages get nav => NavMessages(this); + NotificationsMessages get notifications => NotificationsMessages(this); + ErrorsMessages get errors => ErrorsMessages(this); + ValidationMessages get validation => ValidationMessages(this); + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'app': + return app; + case 'generic': + return generic; + case 'common': + return common; + case 'auth': + return auth; + case 'profile': + return profile; + case 'nav': + return nav; + case 'notifications': + return notifications; + case 'errors': + return errors; + case 'validation': + return validation; + default: + return key; + } + } +} + +class AppMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const AppMessages(this._parent); + String get name => "{{name.titleCase()}}"; + String get description => "{{{description}}}"; + String get welcome_to_app => "Welcome to {{name.titleCase()}}!"; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'name': + return name; + case 'description': + return description; + case 'welcome_to_app': + return welcome_to_app; + default: + return key; + } + } +} + +class GenericMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const GenericMessages(this._parent); + String get ok => "OK"; + String get cancel => "Cancel"; + String get save => "Save"; + String get delete => "Delete"; + String get edit => "Edit"; + String get update => "Update"; + String get submit => "Submit"; + String get close => "Close"; + String get back => "Back"; + String get next => "Next"; + String get previous => "Previous"; + String get done => "Done"; + String get loading => "Loading..."; + String get error => "Error"; + String get success => "Success"; + String get warning => "Warning"; + String get info => "Info"; + String get retry => "Retry"; + String get refresh => "Refresh"; + String get yes => "Yes"; + String get no => "No"; + String get add => "+ Add"; + String get try_again => "Try Again"; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'ok': + return ok; + case 'cancel': + return cancel; + case 'save': + return save; + case 'delete': + return delete; + case 'edit': + return edit; + case 'update': + return update; + case 'submit': + return submit; + case 'close': + return close; + case 'back': + return back; + case 'next': + return next; + case 'previous': + return previous; + case 'done': + return done; + case 'loading': + return loading; + case 'error': + return error; + case 'success': + return success; + case 'warning': + return warning; + case 'info': + return info; + case 'retry': + return retry; + case 'refresh': + return refresh; + case 'yes': + return yes; + case 'no': + return no; + case 'add': + return add; + case 'try_again': + return try_again; + default: + return key; + } + } +} + +class CommonMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const CommonMessages(this._parent); + String get cancel => "Cancel"; + String get ok => "OK"; + String get save => "Save"; + String get delete => "Delete"; + String get edit => "Edit"; + String get try_again => "Try Again"; + String get week => "Week"; + String get month => "Month"; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'cancel': + return cancel; + case 'ok': + return ok; + case 'save': + return save; + case 'delete': + return delete; + case 'edit': + return edit; + case 'try_again': + return try_again; + case 'week': + return week; + case 'month': + return month; + default: + return key; + } + } +} + +class AuthMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const AuthMessages(this._parent); + String get register => "Register"; + String get sign_in => "Sign In"; + String get sign_out => "Sign Out"; + String get dont_have_account => "Don't have an account? "; + String get already_have_account => "Already have an account? "; + String get sign_out_confirmation => "Are you sure you want to sign out?"; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'register': + return register; + case 'sign_in': + return sign_in; + case 'sign_out': + return sign_out; + case 'dont_have_account': + return dont_have_account; + case 'already_have_account': + return already_have_account; + case 'sign_out_confirmation': + return sign_out_confirmation; + default: + return key; + } + } +} + +class ProfileMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const ProfileMessages(this._parent); + String get profile => "Profile"; + String get settings => "Settings"; + String get account => "Account"; + String get personal_info => "Personal Information"; + String get privacy_settings => "Privacy Settings"; + String get name => "Name"; + String get email_address => "Email Address"; + String get phone_number => "Phone Number"; + String get date_of_birth => "Date of Birth"; + String get delete_confirmation => "Delete Confirmation"; + String get delete_confirmation_message => + "Are you sure you want to delete this item? This action cannot be undone."; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'profile': + return profile; + case 'settings': + return settings; + case 'account': + return account; + case 'personal_info': + return personal_info; + case 'privacy_settings': + return privacy_settings; + case 'name': + return name; + case 'email_address': + return email_address; + case 'phone_number': + return phone_number; + case 'date_of_birth': + return date_of_birth; + case 'delete_confirmation': + return delete_confirmation; + case 'delete_confirmation_message': + return delete_confirmation_message; + default: + return key; + } + } +} + +class NavMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const NavMessages(this._parent); + String get home => "Home"; + String get dashboard => "Dashboard"; + String get profile => "Profile"; + String get settings => "Settings"; + String get notifications => "Notifications"; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'home': + return home; + case 'dashboard': + return dashboard; + case 'profile': + return profile; + case 'settings': + return settings; + case 'notifications': + return notifications; + default: + return key; + } + } +} + +class NotificationsMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const NotificationsMessages(this._parent); + String get title => "Notifications"; + String get mark_as_read => "Mark as read"; + String get mark_all_read => "Mark all as read"; + String get delete => "Delete"; + String get filter_all => "All"; + String get filter_unread => "Unread"; + String get filter_read => "Read"; + String get type_reminder => "Reminder"; + String get type_alert => "Alert"; + String get type_promotion => "Promotion"; + String get type_system => "System"; + String get type_custom => "Custom"; + String get empty_title => "No notifications"; + String get empty_description => + "You're all caught up! New notifications will appear here."; + String get delete_confirmation_title => "Delete notification?"; + String get delete_confirmation_message => + "This notification will be permanently removed from your list."; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'title': + return title; + case 'mark_as_read': + return mark_as_read; + case 'mark_all_read': + return mark_all_read; + case 'delete': + return delete; + case 'filter_all': + return filter_all; + case 'filter_unread': + return filter_unread; + case 'filter_read': + return filter_read; + case 'type_reminder': + return type_reminder; + case 'type_alert': + return type_alert; + case 'type_promotion': + return type_promotion; + case 'type_system': + return type_system; + case 'type_custom': + return type_custom; + case 'empty_title': + return empty_title; + case 'empty_description': + return empty_description; + case 'delete_confirmation_title': + return delete_confirmation_title; + case 'delete_confirmation_message': + return delete_confirmation_message; + default: + return key; + } + } +} + +class ErrorsMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const ErrorsMessages(this._parent); + String get network_error => "Network error. Please check your connection."; + String get unknown_error => "An unknown error occurred."; + String get validation_error => "Please check your input and try again."; + String get server_error => "Server error. Please try again later."; + String get default_error_message => + "Oops! Something went wrong. Please try again."; + String get user_not_found => "User not found. Please check your credentials."; + String get default_error_description => + "We encountered an error while processing your request. We apologize for the inconvenience. Please try again later or contact support if the issue persists."; + String get page_not_found => "Page Not Found"; + String get page_not_found_description => + "The page you are looking for does not exist."; + String get unexpected_error => "An unexpected error occurred."; + String get redirect_error => "Redirect Error"; + String get bad_request => "Invalid request. Please check your input."; + String get unauthorized => "Authentication required. Please sign in again."; + String get forbidden => "Access denied. You don't have permission."; + String get not_found => "Requested resource not found."; + String get conflict => "Data conflict. Please refresh and try again."; + String get unprocessable_entity => + "Invalid data format. Please check your input."; + String get internal_server_error => "Server error. Please try again later."; + String get connection_timeout => + "Connection timeout. Please check your internet."; + String get receive_timeout => "Request timeout. Please try again."; + String get send_timeout => "Upload timeout. Please try again."; + String get no_internet => + "No internet connection. Please check your network."; + String get unknown_network => "Network error occurred. Please try again."; + String format_exception_message(String code, String postfix) => + "This data is wearing the wrong costume, I don't recognize it [$code] $postfix"; + String type_error_message(String code, String postfix) => + "This data is not what I expected, I can't process it [$code] $postfix"; + String index_error_message(String code, String postfix) => + "Hmm, I can't seem to find that item in the list [$code] $postfix"; + String range_error_message(String code, String postfix) => + "Oops! That number is way out of my comfort zone [$code] $postfix"; + String argument_error_message(String code, String postfix) => + "Hey! Something's not right with what you gave me [$code] $postfix"; + String state_error_message(String code, String postfix) => + "I'm a bit confused about what I should be doing right now [$code] $postfix"; + String unimplemented_error_message(String code, String postfix) => + "This feature is still under construction [$code] $postfix"; + String unsupported_error_message(String code, String postfix) => + "Sorry, I don't know how to do that yet [$code] $postfix"; + String concurrent_modification_error_message(String code, String postfix) => + "Whoa! Too many things happening at once [$code] $postfix"; + String out_of_memory_error_message(String code, String postfix) => + "My brain is full! Need to clear some space [$code] $postfix"; + String stack_overflow_error_message(String code, String postfix) => + "I'm stuck in a loop and getting dizzy [$code] $postfix"; + String unknown_error_message(String code, String postfix) => + "Something unexpected happened, but don't worry [$code] $postfix"; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'network_error': + return network_error; + case 'unknown_error': + return unknown_error; + case 'validation_error': + return validation_error; + case 'server_error': + return server_error; + case 'default_error_message': + return default_error_message; + case 'user_not_found': + return user_not_found; + case 'default_error_description': + return default_error_description; + case 'page_not_found': + return page_not_found; + case 'page_not_found_description': + return page_not_found_description; + case 'unexpected_error': + return unexpected_error; + case 'redirect_error': + return redirect_error; + case 'bad_request': + return bad_request; + case 'unauthorized': + return unauthorized; + case 'forbidden': + return forbidden; + case 'not_found': + return not_found; + case 'conflict': + return conflict; + case 'unprocessable_entity': + return unprocessable_entity; + case 'internal_server_error': + return internal_server_error; + case 'connection_timeout': + return connection_timeout; + case 'receive_timeout': + return receive_timeout; + case 'send_timeout': + return send_timeout; + case 'no_internet': + return no_internet; + case 'unknown_network': + return unknown_network; + case 'format_exception_message': + return format_exception_message; + case 'type_error_message': + return type_error_message; + case 'index_error_message': + return index_error_message; + case 'range_error_message': + return range_error_message; + case 'argument_error_message': + return argument_error_message; + case 'state_error_message': + return state_error_message; + case 'unimplemented_error_message': + return unimplemented_error_message; + case 'unsupported_error_message': + return unsupported_error_message; + case 'concurrent_modification_error_message': + return concurrent_modification_error_message; + case 'out_of_memory_error_message': + return out_of_memory_error_message; + case 'stack_overflow_error_message': + return stack_overflow_error_message; + case 'unknown_error_message': + return unknown_error_message; + default: + return key; + } + } +} + +class ValidationMessages implements i69n.I69nMessageBundle { + final Messages _parent; + const ValidationMessages(this._parent); + String get required_field => "This field is required"; + String get invalid_email => "Please enter a valid email address"; + String get password_too_short => "Password must be at least 8 characters"; + String get passwords_dont_match => "Passwords do not match"; + String invalid_key_config(String of, String key) => + "Invalid configuration for $key in $of. Please check your settings."; + Object operator [](String key) { + var index = key.indexOf('.'); + if (index > 0) { + return (this[key.substring(0, index)] + as i69n.I69nMessageBundle)[key.substring(index + 1)]; + } + switch (key) { + case 'required_field': + return required_field; + case 'invalid_email': + return invalid_email; + case 'password_too_short': + return password_too_short; + case 'passwords_dont_match': + return passwords_dont_match; + case 'invalid_key_config': + return invalid_key_config; + default: + return key; + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/messages.i69n.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/messages.i69n.yaml new file mode 100644 index 0000000..e807aa8 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/messages.i69n.yaml @@ -0,0 +1,148 @@ +# {{name.titleCase()}} - Internationalization Messages +# Base translations for the application + +# application metadata +app: + name: "{{name.titleCase()}}" + description: "{{{description}}}" + welcome_to_app: "Welcome to {{name.titleCase()}}!" + +# Common/Generic messages +generic: + ok: "OK" + cancel: "Cancel" + save: "Save" + delete: "Delete" + edit: "Edit" + update: "Update" + submit: "Submit" + close: "Close" + back: "Back" + next: "Next" + previous: "Previous" + done: "Done" + loading: "Loading..." + error: "Error" + success: "Success" + warning: "Warning" + info: "Info" + retry: "Retry" + refresh: "Refresh" + yes: "Yes" + no: "No" + add: "+ Add" + try_again: "Try Again" + +# Common messages (used for consistency across modules) +common: + cancel: "Cancel" + ok: "OK" + save: "Save" + delete: "Delete" + edit: "Edit" + try_again: "Try Again" + week: "Week" + month: "Month" + +# Authentication +auth: + register: "Register" + sign_in: "Sign In" + sign_out: "Sign Out" + dont_have_account: "Don't have an account? " + already_have_account: "Already have an account? " + sign_out_confirmation: "Are you sure you want to sign out?" + +# Profile +# Profile +profile: + profile: "Profile" + settings: "Settings" + account: "Account" + personal_info: "Personal Information" + privacy_settings: "Privacy Settings" + name: "Name" + email_address: "Email Address" + phone_number: "Phone Number" + date_of_birth: "Date of Birth" + delete_confirmation: "Delete Confirmation" + delete_confirmation_message: "Are you sure you want to delete this item? This action cannot be undone." + +nav: + home: "Home" + dashboard: "Dashboard" + profile: "Profile" + settings: "Settings" + notifications: "Notifications" + +# Notifications +notifications: + title: "Notifications" + mark_as_read: "Mark as read" + mark_all_read: "Mark all as read" + delete: "Delete" + + # Filter tabs + filter_all: "All" + filter_unread: "Unread" + filter_read: "Read" + + # Notification types + type_reminder: "Reminder" + type_alert: "Alert" + type_promotion: "Promotion" + type_system: "System" + type_custom: "Custom" + + # Empty states + empty_title: "No notifications" + empty_description: "You're all caught up! New notifications will appear here." + + # Delete confirmation + delete_confirmation_title: "Delete notification?" + delete_confirmation_message: "This notification will be permanently removed from your list." + +# Errors +errors: + network_error: "Network error. Please check your connection." + unknown_error: "An unknown error occurred." + validation_error: "Please check your input and try again." + server_error: "Server error. Please try again later." + default_error_message: "Oops! Something went wrong. Please try again." + user_not_found: "User not found. Please check your credentials." + default_error_description: "We encountered an error while processing your request. We apologize for the inconvenience. Please try again later or contact support if the issue persists." + page_not_found: "Page Not Found" + page_not_found_description: "The page you are looking for does not exist." + unexpected_error: "An unexpected error occurred." + redirect_error: "Redirect Error" + bad_request: "Invalid request. Please check your input." + unauthorized: "Authentication required. Please sign in again." + forbidden: "Access denied. You don't have permission." + not_found: "Requested resource not found." + conflict: "Data conflict. Please refresh and try again." + unprocessable_entity: "Invalid data format. Please check your input." + internal_server_error: "Server error. Please try again later." + connection_timeout: "Connection timeout. Please check your internet." + receive_timeout: "Request timeout. Please try again." + send_timeout: "Upload timeout. Please try again." + no_internet: "No internet connection. Please check your network." + unknown_network: "Network error occurred. Please try again." + format_exception_message(String code, String postfix): "This data is wearing the wrong costume, I don't recognize it [$code] $postfix" + type_error_message(String code, String postfix): "This data is not what I expected, I can't process it [$code] $postfix" + index_error_message(String code, String postfix): "Hmm, I can't seem to find that item in the list [$code] $postfix" + range_error_message(String code, String postfix): "Oops! That number is way out of my comfort zone [$code] $postfix" + argument_error_message(String code, String postfix): "Hey! Something's not right with what you gave me [$code] $postfix" + state_error_message(String code, String postfix): "I'm a bit confused about what I should be doing right now [$code] $postfix" + unimplemented_error_message(String code, String postfix): "This feature is still under construction [$code] $postfix" + unsupported_error_message(String code, String postfix): "Sorry, I don't know how to do that yet [$code] $postfix" + concurrent_modification_error_message(String code, String postfix): "Whoa! Too many things happening at once [$code] $postfix" + out_of_memory_error_message(String code, String postfix): "My brain is full! Need to clear some space [$code] $postfix" + stack_overflow_error_message(String code, String postfix): "I'm stuck in a loop and getting dizzy [$code] $postfix" + unknown_error_message(String code, String postfix): "Something unexpected happened, but don't worry [$code] $postfix" +# Validation messages +validation: + required_field: "This field is required" + invalid_email: "Please enter a valid email address" + password_too_short: "Password must be at least 8 characters" + passwords_dont_match: "Passwords do not match" + invalid_key_config(String of, String key): "Invalid configuration for $key in $of. Please check your settings." diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/src/localization_provider.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/src/localization_provider.dart new file mode 100644 index 0000000..1598ccb --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/src/localization_provider.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:localization/messages.i69n.dart'; + +/// Provides localization utilities for the {{name.titleCase()}} application +class LocalizationProvider { + static Messages _messages = const Messages(); + + /// Get the current messages instance + static Messages get messages => _messages; + + /// Initialize localization (currently only supports English) + static void initialize({String locale = 'en'}) { + // Currently only supports English, future versions will support multiple locales + _messages = const Messages(); + } + + /// Get messages (currently only supports English) + static Messages getMessages(String locale) { + // Currently only supports English, future versions will support multiple locales + return const Messages(); + } + + /// Get the current locale + static String get currentLocale => 'en'; + + /// Get supported locales (currently only English) + static List get supportedLocales => ['en']; + + /// Check if a locale is supported + static bool isLocaleSupported(String locale) { + return supportedLocales.contains(locale); + } + + /// Get the best matching locale from a list of preferred locales + static String getBestMatchingLocale(List preferredLocales) { + for (final locale in preferredLocales) { + if (isLocaleSupported(locale.languageCode)) { + return locale.languageCode; + } + } + return 'en'; // Default to English + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/src/strings.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/src/strings.dart new file mode 100644 index 0000000..30e8e64 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/lib/src/strings.dart @@ -0,0 +1,14 @@ +import 'package:localization/localization.dart'; + +/// Global strings instance for easy access throughout the application +/// +/// Usage: +/// ```dart +/// import 'package:localization/localization.dart'; +/// +/// // Access strings easily +/// Text(strings.auth.login); +/// Text(strings.generic.ok); +/// Text(strings.profile.name); +/// ``` +Messages get strings => LocalizationProvider.messages; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/pubspec.yaml new file mode 100644 index 0000000..76ebee1 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/pubspec.yaml @@ -0,0 +1,21 @@ +name: localization +description: "Type-safe strings for {{name.titleCase()}}, generated from YAML with i69n." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + i69n: ^3.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + build_runner: ^2.4.15 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/test/localization_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/test/localization_test.dart new file mode 100644 index 0000000..52f0988 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/test/localization_test.dart @@ -0,0 +1,26 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:localization/localization.dart'; + +void main() { + group('LocalizationProvider Tests', () { + test('should initialize with default locale', () { + LocalizationProvider.initialize(); + expect(LocalizationProvider.currentLocale, 'en'); + }); + + test('should return Messages instance', () { + final messages = LocalizationProvider.messages; + expect(messages, isA()); + }); + + test('should return supported locales', () { + final locales = LocalizationProvider.supportedLocales; + expect(locales, contains('en')); + }); + + test('should check if locale is supported', () { + expect(LocalizationProvider.isLocaleSupported('en'), true); + expect(LocalizationProvider.isLocaleSupported('es'), false); + }); + }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/lib/src/number_formatter.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/lib/src/number_formatter.dart new file mode 100644 index 0000000..06e58a4 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/lib/src/number_formatter.dart @@ -0,0 +1,79 @@ +import 'package:intl/intl.dart'; + +/// Extension for formatting decimal numbers with clean, professional display +extension DoubleFormatting on double { + /// Formats decimal values, removing unnecessary .0 for whole numbers + String format({int decimalPlaces = 1}) { + final formatter = NumberFormat('#.${'#' * decimalPlaces}'); + return formatter.format(this); + } + + /// Formats score values with clean decimal handling + String asScore() => format(decimalPlaces: 1); + + /// Formats HRV values with smart decimal handling + String asHrv() => format(decimalPlaces: 1); + + /// Formats sleep hours with smart decimal handling + String asSleepHours() => format(decimalPlaces: 1); + + /// Formats respiratory rate with smart decimal handling + String asRespiratoryRate() => format(decimalPlaces: 1); +} + +/// Extension for formatting any numeric value +extension NumFormatting on num { + /// Formats large numbers with K, M, B suffixes and smart decimal handling + String compact() { + final NumberFormat formatter = NumberFormat.compact(locale: 'en_US'); + return formatter.format(this); + } + + /// Formats heart rate values (always whole numbers) + String asHeartRate() => round().toString(); +} + +/// Extension for formatting time duration from seconds +extension IntTimeFormatting on int { + /// Format time from seconds to 1 sec or 1min 1sec or 1hr 1min + String asTime() { + if (this < 60) { + return '$this sec'; + } else if (this < 3600) { + final minutes = this ~/ 60; + final remainingSeconds = this % 60; + if (remainingSeconds == 0) { + return '$minutes min'; + } + return '$minutes min $remainingSeconds sec'; + } else { + final hours = this ~/ 3600; + final remainingMinutes = (this % 3600) ~/ 60; + if (remainingMinutes == 0) { + return '$hours hr'; + } + return '$hours hr $remainingMinutes min'; + } + } +} + +extension DateTimeFormatting on DateTime { + String formatRelative() { + final now = DateTime.now(); + final difference = now.difference(this); + + if (difference.inMinutes < 1) { + return 'Just now'; + } else if (difference.inMinutes < 60) { + return '${difference.inMinutes}m ago'; + } else if (difference.inHours < 24) { + return '${difference.inHours}h ago'; + } else if (difference.inDays == 1) { + return 'Yesterday'; + } else if (difference.inDays < 7) { + return '${difference.inDays}d ago'; + } else { + return '$day/$month/$year'; + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/lib/utils.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/lib/utils.dart new file mode 100644 index 0000000..036f0c1 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/lib/utils.dart @@ -0,0 +1,3 @@ +library; + +export 'src/number_formatter.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/pubspec.yaml new file mode 100644 index 0000000..ba5d039 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/pubspec.yaml @@ -0,0 +1,20 @@ +name: utils +description: "Shared, dependency-light helpers for {{name.titleCase()}}." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + intl: ^0.20.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/README.md new file mode 100644 index 0000000..5a7fc51 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/README.md @@ -0,0 +1,247 @@ +# Analytics Package + +A structured analytics package for {{name.titleCase()}} with Firebase Analytics integration and pluggable provider architecture. + +## Features + +- โœ… **Interface-based design** - Easy to switch analytics providers +- โœ… **Automatic screen tracking** - Route observer tracks navigation automatically +- โœ… **Structured event names** - Type-safe, organized event constants +- โœ… **Dependency injection** - Integrated with DI container +- โœ… **Error handling** - Graceful failures don't crash the app +- โœ… **Debug logging** - Configurable logging for development + +## Quick Start + +### 1. Initialize Analytics + +```dart +import 'package:analytics/analytics.dart'; + +final analyticsConfig = DefaultAnalyticsConfig( + enableAnalytics: true, + enableDebugLogging: true, // Set to false in production +); +await init(config: analyticsConfig); +``` + +### 2. Automatic Screen Tracking + +Add the `AnalyticsRouteObserver` to your router: + +```dart +GoRouter( + observers: [ + AnalyticsRouteObserver(), // Uses FirebaseAnalyticsObserver internally + ], + // ... routes +); +``` + +The `AnalyticsRouteObserver` internally uses Firebase's native `FirebaseAnalyticsObserver` for proper screen tracking while adding additional logging and error handling. + +### 3. Manual Event Tracking + +#### Using Structured Event Names (Recommended) + +```dart +// User events +await AnalyticsHelper.logEvent(AnalyticsEvents.user.authenticatedRedirect); + +// Authentication events +await AnalyticsHelper.logEvent( + AnalyticsEvents.auth.signIn, + parameters: {'method': 'google'}, +); + +// Feature usage +await AnalyticsHelper.logEvent( + AnalyticsEvents.feature.used, + parameters: {'feature_name': 'export'}, +); + +// A group of your own, added to AnalyticsEvents +await AnalyticsHelper.logEvent( + AnalyticsEvents.feature.tutorialCompleted, + parameters: { + 'tutorial_id': 'getting_started', + 'duration_seconds': 120, + }, +); +``` + +#### Using Helper Methods + +```dart +// Feature tracking +await AnalyticsHelper.logFeatureUsed('export'); + +// Error tracking +await AnalyticsHelper.logAppError('network_timeout'); + +// Button interactions +await AnalyticsHelper.logButtonPressed('get_started'); + +// Authentication events +await AnalyticsHelper.logSignIn(method: 'google'); +await AnalyticsHelper.logSignUp(method: 'email'); + +// User management +await AnalyticsHelper.setUserId('user123'); +await AnalyticsHelper.setUserProperty(name: 'plan', value: 'premium'); + +// App lifecycle +await AnalyticsHelper.logAppOpen(); +``` + +#### Direct Client Usage + +```dart +final analyticsClient = di.get(); + +await analyticsClient.logEvent( + name: 'custom_event', + parameters: {'key': 'value'}, +); + +await analyticsClient.setUserId('user123'); +await analyticsClient.setUserProperty(name: 'plan', value: 'premium'); +``` + +## Structured Event Names + +The package provides organized, type-safe event names through `AnalyticsEvents`: + +### Available Event Categories + +```dart +// User-related events +AnalyticsEvents.user.authenticatedRedirect +AnalyticsEvents.user.profileUpdated +AnalyticsEvents.user.onboardingCompleted + +// Authentication events +AnalyticsEvents.auth.signIn +AnalyticsEvents.auth.signUp +AnalyticsEvents.auth.signOut + +// Navigation events +AnalyticsEvents.navigation.screenView +AnalyticsEvents.navigation.tabSwitched + +// Feature usage +AnalyticsEvents.feature.used +AnalyticsEvents.feature.tutorialCompleted + +// App lifecycle +AnalyticsEvents.app.open +AnalyticsEvents.app.background +AnalyticsEvents.app.foreground + +// Error events +AnalyticsEvents.error.appError +AnalyticsEvents.error.networkError +``` + +## Configuration + +### AnalyticsConfig Options + +```dart +final config = DefaultAnalyticsConfig( + enableAnalytics: true, // Enable/disable analytics collection + enableDebugLogging: false, // Debug logging (development only) + userId: 'user123', // Optional: Set initial user ID + defaultUserProperties: { // Optional: Default user properties + 'user_type': 'premium', + 'app_version': '1.0.0', + }, +); +``` + +### Environment-based Configuration + +```dart +import 'package:flutter/foundation.dart'; + +final config = DefaultAnalyticsConfig( + enableAnalytics: !kDebugMode, // Disable in debug builds + enableDebugLogging: kDebugMode, // Debug logging in development +); +``` + +## Architecture + +### Components + +- **AnalyticsClient** - Abstract interface for analytics operations +- **FirebaseAnalyticsClient** - Firebase Analytics implementation +- **AnalyticsRouteObserver** - Automatic screen tracking +- **AnalyticsHelper** - Convenience methods for common events +- **AnalyticsEvents** - Structured event name constants + +### Pluggable Design + +Switch analytics providers by implementing `AnalyticsClient`: + +```dart +class MixpanelAnalyticsClient implements AnalyticsClient { + @override + Future logEvent({required String name, Map? parameters}) { + // Mixpanel implementation + } + // ... other methods +} + +// Register with DI +di.register(MixpanelAnalyticsClient(config)); +``` + +## Best Practices + +1. **Use Structured Events**: Always prefer `AnalyticsEvents.category.eventName` over string literals +2. **Include Context**: Add relevant parameters like `screen_name`, `user_id`, etc. +3. **Consistent Naming**: Use snake_case for event names and parameters +4. **Error Handling**: Analytics failures should never crash the app +5. **Privacy**: Respect user privacy settings and data regulations + +## Examples + +### Track User Journey + +```dart +// App launch +await AnalyticsHelper.logAppOpen(); + +// User signs in +await AnalyticsHelper.logSignIn(method: 'google'); + +// User completes onboarding +await AnalyticsHelper.logEvent( + AnalyticsEvents.user.onboardingCompleted, + parameters: {'steps_completed': 5}, +); + +// User uses a feature +await AnalyticsHelper.logFeatureUsed('export'); +``` + +### Error Tracking + +```dart +try { + // Some operation +} catch (error) { + await AnalyticsHelper.logAppError( + 'api_call_failed', + errorMessage: error.toString(), + parameters: { + 'endpoint': '/api/items', + 'user_id': userId, + }, + ); + rethrow; +} +``` + +This structured approach ensures consistent, discoverable, and maintainable analytics throughout the {{name.titleCase()}} app. \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/analytics.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/analytics.dart new file mode 100644 index 0000000..e1cc4bf --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/analytics.dart @@ -0,0 +1,37 @@ +library; + +import 'package:analytics/src/client/index.dart'; +import 'package:analytics/src/config/index.dart'; +import 'package:core/core.dart'; +import 'package:di/di.dart'; + +export 'src/client/index.dart'; +export 'src/config/index.dart'; +export 'src/models/index.dart'; +export 'src/observer/index.dart'; +export 'src/utils/index.dart'; + +Future init({ + AnalyticsConfig config = const DefaultAnalyticsConfig(), +}) async { + await registerAnalyticsWithDI(config); + final logger = di.get(); + logger.i('Analytics module initialized with Firebase Analytics'); +} + +Future registerAnalyticsWithDI(AnalyticsConfig config) async { + final logger = di.get(); + + final analyticsClient = FirebaseAnalyticsClient(config); + di.register( + analyticsClient, + dispose: (client) => client.dispose(), + ); + di.register(config); + + if (config.enableAnalytics) { + logger.i('๐Ÿ” Analytics client registered with collection enabled'); + } else { + logger.i('๐Ÿ” Analytics client registered with collection disabled'); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/analytics_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/analytics_client.dart new file mode 100644 index 0000000..40f738a --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/analytics_client.dart @@ -0,0 +1,44 @@ +import 'package:analytics/src/models/analytics_event.dart'; + +abstract class AnalyticsClient { + Future logEvent({ + required String name, + Map? parameters, + }); + + Future logCustomEvent(AnalyticsEvent event); + + Future setUserId(String? userId); + + Future setUserProperty({required String name, required String? value}); + + Future resetAnalyticsData(); + + Future setAnalyticsCollectionEnabled(bool enabled); + + Future logScreenView({ + required String screenName, + String? screenClass, + Map? parameters, + }); + + Future logAppOpen({Map? parameters}); + + Future logLogin({ + String? loginMethod, + Map? parameters, + }); + + Future logSignUp({ + String? signUpMethod, + Map? parameters, + }); + + Future logPurchase({ + required String currency, + required double value, + Map? parameters, + }); + + void dispose(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart new file mode 100644 index 0000000..d9f7206 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart @@ -0,0 +1,204 @@ +import 'package:analytics/src/client/analytics_client.dart'; +import 'package:analytics/src/config/analytics_config.dart'; +import 'package:analytics/src/models/analytics_event.dart'; +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; + +class FirebaseAnalyticsClient implements AnalyticsClient { + FirebaseAnalyticsClient(this._config) + : _analytics = FirebaseAnalytics.instance { + _setupAnalytics(); + } + + final AnalyticsConfig _config; + final FirebaseAnalytics _analytics; + final Logger _logger = di.get(); + + void _setupAnalytics() { + _analytics.setAnalyticsCollectionEnabled(_config.enableAnalytics); + + if (_config.userId != null) { + _analytics.setUserId(id: _config.userId); + _logger.d('๐Ÿ” Analytics user ID set: ${_config.userId}'); + } + + if (_config.defaultUserProperties != null) { + for (final entry in _config.defaultUserProperties!.entries) { + _analytics.setUserProperty(name: entry.key, value: entry.value); + } + _logger.d('๐Ÿ” Analytics default user properties set'); + } + + if (_config.enableDebugLogging) { + _logger.d('๐Ÿ” Firebase Analytics initialized with debug logging enabled'); + } else { + _logger.i('๐Ÿ” Firebase Analytics initialized'); + } + } + + @override + Future logEvent({ + required String name, + Map? parameters, + }) async { + try { + if (!_config.enableAnalytics) { + if (_config.enableDebugLogging) { + _logger.d('๐Ÿ” Analytics disabled, skipping event: $name'); + } + return; + } + + await _analytics.logEvent( + name: name, + parameters: parameters?.cast(), + ); + + if (_config.enableDebugLogging) { + _logger.d( + '๐Ÿ” Analytics event logged: $name with parameters: $parameters', + ); + } + } catch (e, s) { + _logger.e('Failed to log analytics event: $name', e, s); + } + } + + @override + Future logCustomEvent(AnalyticsEvent event) async { + await logEvent(name: event.name, parameters: event.parameters); + } + + @override + Future setUserId(String? userId) async { + try { + await _analytics.setUserId(id: userId); + + if (_config.enableDebugLogging) { + _logger.d('๐Ÿ” Analytics user ID set: $userId'); + } + } catch (e, s) { + _logger.e('Failed to set analytics user ID: $userId', e, s); + } + } + + @override + Future setUserProperty({ + required String name, + required String? value, + }) async { + try { + await _analytics.setUserProperty(name: name, value: value); + + if (_config.enableDebugLogging) { + _logger.d('๐Ÿ” Analytics user property set: $name = $value'); + } + } catch (e, s) { + _logger.e('Failed to set analytics user property: $name', e, s); + } + } + + @override + Future resetAnalyticsData() async { + try { + await _analytics.resetAnalyticsData(); + + if (_config.enableDebugLogging) { + _logger.d('๐Ÿ” Analytics data reset'); + } + } catch (e, s) { + _logger.e('Failed to reset analytics data', e, s); + } + } + + @override + Future setAnalyticsCollectionEnabled(bool enabled) async { + try { + await _analytics.setAnalyticsCollectionEnabled(enabled); + + if (_config.enableDebugLogging) { + _logger.d( + '๐Ÿ” Analytics collection ${enabled ? 'enabled' : 'disabled'}', + ); + } + } catch (e, s) { + _logger.e('Failed to set analytics collection enabled: $enabled', e, s); + } + } + + @override + Future logScreenView({ + required String screenName, + String? screenClass, + Map? parameters, + }) async { + final eventParameters = { + PredefinedParameters.screenName: screenName, + if (screenClass != null) PredefinedParameters.screenClass: screenClass, + ...?parameters, + }; + + await logEvent( + name: PredefinedEvents.screenView, + parameters: eventParameters, + ); + } + + @override + Future logAppOpen({Map? parameters}) async { + await logEvent(name: PredefinedEvents.appOpen, parameters: parameters); + } + + @override + Future logLogin({ + String? loginMethod, + Map? parameters, + }) async { + final eventParameters = { + if (loginMethod != null) PredefinedParameters.method: loginMethod, + ...?parameters, + }; + + await logEvent(name: PredefinedEvents.login, parameters: eventParameters); + } + + @override + Future logSignUp({ + String? signUpMethod, + Map? parameters, + }) async { + final eventParameters = { + if (signUpMethod != null) PredefinedParameters.method: signUpMethod, + ...?parameters, + }; + + await logEvent(name: PredefinedEvents.signUp, parameters: eventParameters); + } + + @override + Future logPurchase({ + required String currency, + required double value, + Map? parameters, + }) async { + final eventParameters = { + PredefinedParameters.currency: currency, + PredefinedParameters.value: value, + ...?parameters, + }; + + await logEvent( + name: PredefinedEvents.purchase, + parameters: eventParameters, + ); + } + + @override + void dispose() { + // Firebase Analytics doesn't need explicit disposal + if (_config.enableDebugLogging) { + _logger.d('๐Ÿ” Firebase Analytics client disposed'); + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/index.dart new file mode 100644 index 0000000..959ed69 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/index.dart @@ -0,0 +1,2 @@ +export 'analytics_client.dart'; +export 'firebase_analytics_client.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/config/analytics_config.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/config/analytics_config.dart new file mode 100644 index 0000000..d4b6d02 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/config/analytics_config.dart @@ -0,0 +1,42 @@ +abstract class AnalyticsConfig { + bool get enableAnalytics; + bool get enableDebugLogging; + String? get userId; + Map? get defaultUserProperties; +} + +class DefaultAnalyticsConfig implements AnalyticsConfig { + const DefaultAnalyticsConfig({ + this.enableAnalytics = true, + this.enableDebugLogging = false, + this.userId, + this.defaultUserProperties, + }); + + @override + final bool enableAnalytics; + + @override + final bool enableDebugLogging; + + @override + final String? userId; + + @override + final Map? defaultUserProperties; + + DefaultAnalyticsConfig copyWith({ + bool? enableAnalytics, + bool? enableDebugLogging, + String? userId, + Map? defaultUserProperties, + }) { + return DefaultAnalyticsConfig( + enableAnalytics: enableAnalytics ?? this.enableAnalytics, + enableDebugLogging: enableDebugLogging ?? this.enableDebugLogging, + userId: userId ?? this.userId, + defaultUserProperties: + defaultUserProperties ?? this.defaultUserProperties, + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/config/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/config/index.dart new file mode 100644 index 0000000..cfd2ef0 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/config/index.dart @@ -0,0 +1 @@ +export 'analytics_config.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/analytics_event.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/analytics_event.dart new file mode 100644 index 0000000..97ab101 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/analytics_event.dart @@ -0,0 +1,63 @@ +class AnalyticsEvent { + const AnalyticsEvent({required this.name, this.parameters}); + + final String name; + final Map? parameters; + + Map toMap() { + return {'name': name, if (parameters != null) 'parameters': parameters}; + } + + @override + String toString() => 'AnalyticsEvent(name: $name, parameters: $parameters)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is AnalyticsEvent && + other.name == name && + _mapEquals(other.parameters, parameters); + } + + @override + int get hashCode => name.hashCode ^ parameters.hashCode; + + bool _mapEquals(Map? a, Map? b) { + if (a == null) return b == null; + if (b == null || a.length != b.length) return false; + for (final key in a.keys) { + if (!b.containsKey(key) || a[key] != b[key]) return false; + } + return true; + } +} + +class PredefinedEvents { + static const String appOpen = 'app_open'; + static const String login = 'login'; + static const String signUp = 'sign_up'; + static const String screenView = 'screen_view'; + static const String purchase = 'purchase'; + static const String selectContent = 'select_content'; + static const String share = 'share'; + static const String search = 'search'; + static const String beginCheckout = 'begin_checkout'; + static const String addToCart = 'add_to_cart'; + static const String removeFromCart = 'remove_from_cart'; + static const String viewItem = 'view_item'; + static const String viewItemList = 'view_item_list'; +} + +class PredefinedParameters { + static const String screenName = 'screen_name'; + static const String screenClass = 'screen_class'; + static const String method = 'method'; + static const String currency = 'currency'; + static const String value = 'value'; + static const String itemId = 'item_id'; + static const String itemName = 'item_name'; + static const String itemCategory = 'item_category'; + static const String contentType = 'content_type'; + static const String searchTerm = 'search_term'; + static const String userId = 'user_id'; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/analytics_events.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/analytics_events.dart new file mode 100644 index 0000000..ea381eb --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/analytics_events.dart @@ -0,0 +1,167 @@ +/// Structured analytics event names organized by category. +/// +/// Usage: `AnalyticsEvents.user.authenticatedRedirect`. Naming events here +/// instead of inline keeps them discoverable and typo-free, and gives you one +/// place to add the groups your own product needs. +class AnalyticsEvents { + AnalyticsEvents._(); + + /// User-related events + static const user = _UserEvents(); + + /// Authentication-related events + static const auth = _AuthEvents(); + + /// Navigation-related events + static const navigation = _NavigationEvents(); + + /// Feature usage events + static const feature = _FeatureEvents(); + + /// Error and debugging events + static const error = _ErrorEvents(); + + /// App lifecycle events + static const app = _AppEvents(); + + /// Profile-related events + static const profile = _ProfileEvents(); +} + +class _UserEvents { + const _UserEvents(); + + /// User successfully authenticated and redirected + String get authenticatedRedirect => 'user_authenticated_redirect'; + + /// User profile updated + String get profileUpdated => 'user_profile_updated'; + + /// User preferences changed + String get preferencesChanged => 'user_preferences_changed'; + + /// User completed onboarding + String get onboardingCompleted => 'user_onboarding_completed'; + + /// User account deleted + String get accountDeleted => 'user_account_deleted'; +} + +class _AuthEvents { + const _AuthEvents(); + + /// User signed in + String get signIn => 'auth_sign_in'; + + /// User signed up + String get signUp => 'auth_sign_up'; + + /// User signed out + String get signOut => 'auth_sign_out'; + + /// Password reset requested + String get passwordReset => 'auth_password_reset'; + + /// Authentication failed + String get failed => 'auth_failed'; + + /// Social authentication (Google, Apple, etc.) + String get socialLogin => 'auth_social_login'; +} + +class _NavigationEvents { + const _NavigationEvents(); + + /// Screen viewed (automatically tracked) + String get screenView => 'screen_view'; + + /// Tab switched in bottom navigation + String get tabSwitched => 'navigation_tab_switched'; + + /// Back button pressed + String get backPressed => 'navigation_back_pressed'; + + /// Deep link opened + String get deepLinkOpened => 'navigation_deep_link_opened'; +} + +class _FeatureEvents { + const _FeatureEvents(); + + /// Feature was used/accessed + String get used => 'feature_used'; + + /// Feature tutorial started + String get tutorialStarted => 'feature_tutorial_started'; + + /// Feature tutorial completed + String get tutorialCompleted => 'feature_tutorial_completed'; + + /// Feature feedback given + String get feedbackGiven => 'feature_feedback_given'; + + /// Button pressed + String get buttonPressed => 'button_pressed'; +} + +class _ErrorEvents { + const _ErrorEvents(); + + /// General app error occurred + String get appError => 'app_error'; + + /// Network error occurred + String get networkError => 'network_error'; + + /// Crash occurred + String get crash => 'app_crash'; + + /// API error occurred + String get apiError => 'api_error'; + + /// Validation error + String get validationError => 'validation_error'; +} + +class _AppEvents { + const _AppEvents(); + + /// App opened/launched + String get open => 'app_open'; + + /// App went to background + String get background => 'app_background'; + + /// App came to foreground + String get foreground => 'app_foreground'; + + /// App updated to new version + String get updated => 'app_updated'; + + /// App installed for first time + String get installed => 'app_installed'; + + /// App uninstalled + String get uninstalled => 'app_uninstalled'; +} + + + +class _ProfileEvents { + const _ProfileEvents(); + + /// Profile viewed + String get viewed => 'profile_viewed'; + + /// Profile edited + String get edited => 'profile_edited'; + + /// Profile photo updated + String get photoUpdated => 'profile_photo_updated'; + + /// Profile settings changed + String get settingsChanged => 'profile_settings_changed'; + + /// Profile privacy settings updated + String get privacyUpdated => 'profile_privacy_updated'; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/index.dart new file mode 100644 index 0000000..17f5809 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/index.dart @@ -0,0 +1,2 @@ +export 'analytics_event.dart'; +export 'analytics_events.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/observer/analytics_route_observer.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/observer/analytics_route_observer.dart new file mode 100644 index 0000000..d30f281 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/observer/analytics_route_observer.dart @@ -0,0 +1,65 @@ +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter/material.dart'; + +/// Analytics route observer that uses Firebase Analytics Observer internally +/// while providing additional logging and error handling capabilities +class AnalyticsRouteObserver extends NavigatorObserver { + AnalyticsRouteObserver() + : _logger = di.get(), + _firebaseObserver = FirebaseAnalyticsObserver( + analytics: FirebaseAnalytics.instance, + ); + + final Logger _logger; + final FirebaseAnalyticsObserver _firebaseObserver; + + @override + void didPush(Route route, Route? previousRoute) { + super.didPush(route, previousRoute); + + try { + // Delegate to Firebase Analytics Observer for native screen tracking + _firebaseObserver.didPush(route, previousRoute); + } catch (e, s) { + _logger.e('Failed to track screen push event', e, s); + } + } + + @override + void didPop(Route route, Route? previousRoute) { + super.didPop(route, previousRoute); + + try { + // Delegate to Firebase Analytics Observer + _firebaseObserver.didPop(route, previousRoute); + } catch (e, s) { + _logger.e('Failed to track screen pop event', e, s); + } + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + super.didReplace(newRoute: newRoute, oldRoute: oldRoute); + + try { + // Delegate to Firebase Analytics Observer + _firebaseObserver.didReplace(newRoute: newRoute, oldRoute: oldRoute); + } catch (e, s) { + _logger.e('Failed to track screen replace event', e, s); + } + } + + @override + void didRemove(Route route, Route? previousRoute) { + super.didRemove(route, previousRoute); + + try { + // Delegate to Firebase Analytics Observer + _firebaseObserver.didRemove(route, previousRoute); + } catch (e, s) { + _logger.e('Failed to track screen remove event', e, s); + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/observer/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/observer/index.dart new file mode 100644 index 0000000..e4f9659 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/observer/index.dart @@ -0,0 +1 @@ +export 'analytics_route_observer.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart new file mode 100644 index 0000000..90509ed --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart @@ -0,0 +1,173 @@ +import 'package:analytics/src/client/analytics_client.dart'; +import 'package:analytics/src/models/analytics_event.dart'; +import 'package:analytics/src/models/analytics_events.dart'; +import 'package:core/core.dart'; +import 'package:di/di.dart'; + +class AnalyticsHelper { + static AnalyticsClient? _client; + static Logger? _logger; + + static AnalyticsClient? get _analyticsClient { + _client ??= _getAnalyticsClient(); + return _client; + } + + static Logger get _log { + _logger ??= di.get(); + return _logger!; + } + + static AnalyticsClient? _getAnalyticsClient() { + try { + return di.get(); + } catch (e) { + _log.w('Analytics client not available: $e'); + return null; + } + } + + /// Log a custom event with optional parameters + static Future logEvent( + String name, { + Map? parameters, + }) async { + final client = _analyticsClient; + if (client == null) return; + + try { + await client.logEvent(name: name, parameters: parameters); + } catch (e, s) { + _log.e('Failed to log analytics event: $name', e, s); + } + } + + /// Log user sign in + static Future logSignIn({ + String? method, + Map? parameters, + }) async { + final client = _analyticsClient; + if (client == null) return; + + try { + await client.logLogin(loginMethod: method, parameters: parameters); + } catch (e, s) { + _log.e('Failed to log sign in event', e, s); + } + } + + /// Log user sign up + static Future logSignUp({ + String? method, + Map? parameters, + }) async { + final client = _analyticsClient; + if (client == null) return; + + try { + await client.logSignUp(signUpMethod: method, parameters: parameters); + } catch (e, s) { + _log.e('Failed to log sign up event', e, s); + } + } + + /// Set user ID for analytics + static Future setUserId(String? userId) async { + final client = _analyticsClient; + if (client == null) return; + + try { + await client.setUserId(userId); + } catch (e, s) { + _log.e('Failed to set analytics user ID', e, s); + } + } + + /// Set user property + static Future setUserProperty({ + required String name, + required String? value, + }) async { + final client = _analyticsClient; + if (client == null) return; + + try { + await client.setUserProperty(name: name, value: value); + } catch (e, s) { + _log.e('Failed to set analytics user property: $name', e, s); + } + } + + /// Log app open event + static Future logAppOpen({Map? parameters}) async { + final client = _analyticsClient; + if (client == null) return; + + try { + await client.logAppOpen(parameters: parameters); + } catch (e, s) { + _log.e('Failed to log app open event', e, s); + } + } + + /// Log custom analytics event + static Future logCustomEvent(AnalyticsEvent event) async { + final client = _analyticsClient; + if (client == null) return; + + try { + await client.logCustomEvent(event); + } catch (e, s) { + _log.e('Failed to log custom analytics event: ${event.name}', e, s); + } + } + + /// Common business events + static Future logFeatureUsed( + String featureName, { + Map? parameters, + }) async { + await logEvent( + AnalyticsEvents.feature.used, + parameters: { + 'feature_name': featureName, + 'timestamp': DateTime.now().toIso8601String(), + ...?parameters, + }, + ); + } + + static Future logButtonPressed( + String buttonName, { + String? screenName, + Map? parameters, + }) async { + await logEvent( + AnalyticsEvents.feature.buttonPressed, + parameters: { + 'button_name': buttonName, + if (screenName != null) 'screen_name': screenName, + 'timestamp': DateTime.now().toIso8601String(), + ...?parameters, + }, + ); + } + + static Future logAppError( + String errorType, { + String? errorMessage, + Map? parameters, + }) async { + await logEvent( + AnalyticsEvents.error.appError, + parameters: { + 'error_type': errorType, + if (errorMessage != null) 'error_message': errorMessage, + 'timestamp': DateTime.now().toIso8601String(), + ...?parameters, + }, + ); + } + +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/index.dart new file mode 100644 index 0000000..7195697 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/index.dart @@ -0,0 +1 @@ +export 'analytics_helper.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml new file mode 100644 index 0000000..8253dde --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml @@ -0,0 +1,24 @@ +name: analytics +description: "Event tracking for {{name.titleCase()}} behind a swappable client interface." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + firebase_analytics: ^11.3.5 + core: + path: ../core + di: + path: ../di + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/test/analytics_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/test/analytics_test.dart new file mode 100644 index 0000000..4414841 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/test/analytics_test.dart @@ -0,0 +1,26 @@ +import 'package:analytics/analytics.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Analytics Package', () { + test('should create AnalyticsEvent correctly', () { + const event = AnalyticsEvent( + name: 'test_event', + parameters: {'key': 'value'}, + ); + + expect(event.name, 'test_event'); + expect(event.parameters, {'key': 'value'}); + }); + + test('should create DefaultAnalyticsConfig correctly', () { + const config = DefaultAnalyticsConfig( + enableAnalytics: true, + enableDebugLogging: false, + ); + + expect(config.enableAnalytics, isTrue); + expect(config.enableDebugLogging, isFalse); + }); + }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/README.md new file mode 100644 index 0000000..df673db --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/README.md @@ -0,0 +1,274 @@ +# Crashlytics + +Firebase Crashlytics integration package for {{name.titleCase()}} - providing comprehensive crash reporting and error tracking capabilities. + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) + +## Features โœจ + +- ๐Ÿ”ฅ **Firebase Crashlytics Integration** - Complete Firebase Crashlytics implementation +- ๐Ÿš€ **Automatic Error Handling** - Catches Flutter framework errors automatically +- ๐Ÿ‘ค **User Context** - Track crashes with user information and metadata +- ๐Ÿ”‘ **Custom Attributes** - Add custom key-value pairs for debugging +- ๐Ÿ“Š **Custom Logging** - Log custom messages for crash analysis +- โš™๏ธ **Configurable** - Flexible configuration for different environments +- ๐Ÿงฉ **DI Integration** - Seamless integration with dependency injection +- ๐Ÿ›ก๏ธ **Type Safe** - Full TypeScript-style type safety with Dart + +## Installation ๐Ÿ’ป + +Add to your `pubspec.yaml`: + +```yaml +dependencies: + crashlytics: + path: ../packages/crashlytics +``` + +## Quick Start ๐Ÿš€ + +### 1. Initialize in your app + +```dart +import 'package:crashlytics/crashlytics.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize crashlytics with default config + await crashlytics.init(); + + runApp(MyApp()); +} +``` + +### 2. Use the service + +```dart +import 'package:crashlytics/crashlytics.dart'; +import 'package:di/di.dart'; + +class MyWidget extends StatefulWidget { + @override + _MyWidgetState createState() => _MyWidgetState(); +} + +class _MyWidgetState extends State { + late final CrashlyticsService _crashlytics; + + @override + void initState() { + super.initState(); + _crashlytics = di.get(); + } + + void _reportError() async { + try { + // Some risky operation + throw Exception('Something went wrong!'); + } catch (error, stackTrace) { + // Report the error + await _crashlytics.reportError( + error, + stackTrace: stackTrace, + context: {'feature': 'user_action', 'screen': 'home'}, + ); + } + } +} +``` + +## Configuration โš™๏ธ + +### Basic Configuration + +```dart +import 'package:crashlytics/crashlytics.dart'; + +await crashlytics.init( + config: CrashlyticsConfig( + enableInDebugMode: false, + enableAutomaticDataCollection: true, + enableCustomLogs: true, + logBufferSize: 100, + enableUserMetadata: true, + customKeys: { + 'app_version': '1.0.0', + 'environment': 'production', + }, + ), +); +``` + +### Advanced Configuration + +```dart +// Custom configuration for different environments +final config = kDebugMode + ? CrashlyticsConfig( + enableInDebugMode: true, + enableAutomaticDataCollection: false, + customKeys: {'environment': 'debug'}, + ) + : CrashlyticsConfig( + enableAutomaticDataCollection: true, + logBufferSize: 200, + customKeys: {'environment': 'production'}, + ); + +await crashlytics.init(config: config); +``` + +## Usage Examples ๐Ÿ“ + +### Error Reporting + +```dart +final crashlytics = di.get(); + +// Report a non-fatal error +await crashlytics.reportError( + Exception('Network timeout'), + stackTrace: StackTrace.current, + context: {'endpoint': '/api/users', 'timeout': '30s'}, +); + +// Report a fatal error +await crashlytics.reportError( + error, + stackTrace: stackTrace, + fatal: true, + userId: 'user123', +); +``` + +### User Context + +```dart +// Set user information +await crashlytics.setUser( + userId: 'user123', + email: 'user@example.com', + name: 'John Doe', + customAttributes: { + 'subscription': 'premium', + 'last_login': DateTime.now().toIso8601String(), + }, +); + +// Clear user information (e.g., on logout) +await crashlytics.clearUser(); +``` + +### Custom Logging + +```dart +// Log custom messages +await crashlytics.logMessage('User completed onboarding'); +await crashlytics.logMessage('API call started: /users/profile'); + +// Set custom attributes +await crashlytics.setCustomAttributes({ + 'feature_flag_a': true, + 'api_version': 'v2', + 'device_type': 'mobile', +}); + +// Set individual custom attribute +await crashlytics.setCustomAttribute('current_screen', 'profile'); +``` + +### Manual Crash Reporting + +```dart +// Use the client directly for advanced scenarios +final client = di.get(); + +await client.recordError( + CustomException('Database connection failed'), + StackTrace.current, + fatal: false, + information: ['Database host: db.example.com', 'Connection timeout: 30s'], +); +``` + +## API Reference ๐Ÿ“š + +### CrashlyticsService + +Main service class for crash reporting: + +| Method | Description | +|--------|-------------| +| `reportError()` | Report an error or exception | +| `reportFlutterError()` | Report Flutter framework errors | +| `logMessage()` | Log custom messages | +| `setUser()` | Set user information | +| `clearUser()` | Clear user information | +| `setCustomAttributes()` | Set multiple custom attributes | +| `setCustomAttribute()` | Set single custom attribute | +| `setCrashReportingEnabled()` | Enable/disable crash reporting | +| `sendPendingReports()` | Send unsent crash reports | +| `deletePendingReports()` | Delete unsent crash reports | + +### CrashlyticsConfig + +Configuration options: + +| Property | Default | Description | +|----------|---------|-------------| +| `enableInDebugMode` | `false` | Enable crashlytics in debug mode | +| `enableAutomaticDataCollection` | `true` | Enable automatic error collection | +| `enableCustomLogs` | `true` | Enable custom logging | +| `logBufferSize` | `100` | Maximum log buffer size | +| `enableUserMetadata` | `true` | Enable user metadata collection | +| `customKeys` | `{}` | Default custom keys | + +## Integration with DI ๐Ÿ”ง + +The package integrates seamlessly with the DI container: + +```dart +// Get services from DI +final crashlytics = di.get(); +final client = di.get(); +final config = di.get(); + +// Services are automatically disposed when DI container is disposed +``` + +## Testing ๐Ÿงช + +```dart +// Update test file +import 'package:flutter_test/flutter_test.dart'; +import 'package:crashlytics/crashlytics.dart'; + +void main() { + group('Crashlytics', () { + test('can be initialized', () async { + // Test initialization + expect(() => crashlytics.init(), isNot(throwsException)); + }); + }); +} +``` + +## Best Practices ๐Ÿ’ก + +1. **Initialize Early**: Initialize crashlytics as early as possible in your app +2. **Environment Configuration**: Use different configs for debug/release builds +3. **User Privacy**: Be mindful of user privacy when setting user metadata +4. **Context is Key**: Always provide context when reporting errors +5. **Test in Debug**: Enable debug mode during development to test integration +6. **Monitor Usage**: Regularly check Firebase console for crash reports + +## Requirements ๐Ÿ“‹ + +- Flutter SDK >=3.19.0 +- Dart SDK ^3.3.0 +- Firebase project with Crashlytics enabled +- `core` and `di` packages from {{name.titleCase()}} monorepo + +[flutter_install_link]: https://docs.flutter.dev/get-started/install \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/crashlytics.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/crashlytics.dart new file mode 100644 index 0000000..ca23fce --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/crashlytics.dart @@ -0,0 +1,56 @@ +library; + +import 'dart:async'; + +import 'package:core/core.dart'; +import 'package:crashlytics/src/client/index.dart'; +import 'package:crashlytics/src/config/index.dart'; +import 'package:di/di.dart'; + +export 'src/client/index.dart'; +export 'src/config/index.dart'; +export 'src/models/index.dart'; + +/// Initialize the crashlytics module with dependency injection +Future init({CrashlyticsConfig? config}) async { + await registerCrashlyticsWithDI(config ?? const DefaultCrashlyticsConfig()); + final logger = di.get(); + logger.i('๐Ÿ”ฅ Crashlytics module initialized'); +} + +/// Register crashlytics components with DI container +Future registerCrashlyticsWithDI(CrashlyticsConfig config) async { + final logger = di.get(); + + try { + // Register config + di.register(config); + + // Create and register crashlytics client + final client = FirebaseCrashlyticsClient(config: config, logger: logger); + di.register( + client, + dispose: (client) => client.dispose(), + ); + + // Initialize the client + await client.initialize(); + + // Check if there are any unsent reports + unawaited( + client.checkForUnsentReports().then((hasReports) { + if (hasReports) { + logger.w('โš ๏ธ There are unsent crash reports.'); + unawaited(client.sendUnsentReports()); + } else { + logger.i('โœ… No unsent crash reports found.'); + } + }), + ); + + logger.i('๐Ÿ”ฅ Crashlytics registered with DI container'); + } catch (e, stackTrace) { + logger.e('โŒ Failed to register crashlytics with DI', e, stackTrace); + rethrow; + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/crashlytics_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/crashlytics_client.dart new file mode 100644 index 0000000..717d855 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/crashlytics_client.dart @@ -0,0 +1,54 @@ +import 'package:crashlytics/src/models/user_metadata.dart'; + +abstract class CrashlyticsClient { + /// Initialize the crashlytics client + Future initialize(); + + /// Record a fatal error/exception + Future recordError( + dynamic exception, + StackTrace? stackTrace, { + bool fatal = false, + Iterable information = const [], + }); + + /// Record a non-fatal error with custom context + Future recordFlutterFatalError( + dynamic exception, + StackTrace stackTrace, { + Map? context, + }); + + /// Log a custom message for debugging + Future log(String message); + + /// Set user identifier for crash reports + Future setUserIdentifier(String identifier); + + /// Set user metadata + Future setUserMetadata(UserMetadata metadata); + + /// Set custom key-value pairs for debugging + Future setCustomKey(String key, dynamic value); + + /// Set custom keys from a map + Future setCustomKeys(Map keys); + + /// Check if crashlytics collection is enabled + bool get isCrashlyticsCollectionEnabled; + + /// Enable or disable crashlytics collection + Future setCrashlyticsCollectionEnabled(bool enabled); + + /// Send any unsent crash reports + Future sendUnsentReports(); + + /// Delete any unsent crash reports + Future deleteUnsentReports(); + + /// Check if there are any unsent crash reports + Future checkForUnsentReports(); + + /// Dispose of resources + void dispose(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/firebase_crashlytics_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/firebase_crashlytics_client.dart new file mode 100644 index 0000000..5317dba --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/firebase_crashlytics_client.dart @@ -0,0 +1,286 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:core/core.dart'; +import 'package:crashlytics/src/client/crashlytics_client.dart'; +import 'package:crashlytics/src/config/crashlytics_config.dart'; +import 'package:crashlytics/src/models/user_metadata.dart'; +import 'package:di/di.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/foundation.dart'; + +class FirebaseCrashlyticsClient implements CrashlyticsClient { + FirebaseCrashlyticsClient({required this.config, Logger? logger}) + : _logger = logger ?? di.get(); + + final CrashlyticsConfig config; + final Logger _logger; + late final FirebaseCrashlytics _crashlytics; + final Queue _logBuffer = Queue(); + bool _isInitialized = false; + + @override + Future initialize() async { + try { + _crashlytics = FirebaseCrashlytics.instance; + + // Set crashlytics collection enabled based on config + await _crashlytics.setCrashlyticsCollectionEnabled( + config.enableInDebugMode || kReleaseMode, + ); + + // Set up automatic Flutter error handling + if (config.enableAutomaticDataCollection) { + FlutterError.onError = (errorDetails) { + _crashlytics.recordFlutterFatalError(errorDetails); + }; + + // Catch errors from the Flutter framework that are not handled by FlutterError + PlatformDispatcher.instance.onError = (error, stack) { + _crashlytics.recordError(error, stack, fatal: true); + return true; + }; + } + + // Set default custom keys + if (config.customKeys.isNotEmpty) { + await setCustomKeys(config.customKeys); + } + + _isInitialized = true; + _logger.i('๐Ÿ”ฅ Firebase Crashlytics initialized successfully'); + } catch (e, stackTrace) { + _logger.e('โŒ Failed to initialize Firebase Crashlytics: $e', stackTrace); + rethrow; + } + } + + @override + Future recordError( + dynamic exception, + StackTrace? stackTrace, { + bool fatal = false, + Iterable information = const [], + }) async { + if (!_isInitialized) { + _logger.w('โš ๏ธ Crashlytics not initialized, skipping error recording'); + return; + } + + try { + await _crashlytics.recordError( + exception, + stackTrace, + fatal: fatal, + information: information, + ); + + _logger.d( + '๐Ÿ“ Recorded ${fatal ? 'fatal' : 'non-fatal'} error: $exception', + ); + } catch (e) { + _logger.e('โŒ Failed to record error: $e'); + } + } + + @override + Future recordFlutterFatalError( + dynamic exception, + StackTrace stackTrace, { + Map? context, + }) async { + if (!_isInitialized) { + _logger.w( + 'โš ๏ธ Crashlytics not initialized, skipping Flutter fatal error recording', + ); + return; + } + + try { + // Set context as custom keys if provided + if (context != null && context.isNotEmpty) { + await setCustomKeys(context); + } + + await _crashlytics.recordError(exception, stackTrace, fatal: true); + + _logger.d('๐Ÿ“ Recorded Flutter fatal error: $exception'); + } catch (e) { + _logger.e('โŒ Failed to record Flutter fatal error: $e'); + } + } + + @override + Future log(String message) async { + if (!_isInitialized || !config.enableCustomLogs) { + return; + } + + try { + await _crashlytics.log(message); + + // Add to local buffer for debugging + _logBuffer.add('[${DateTime.now().toIso8601String()}] $message'); + + // Maintain buffer size + while (_logBuffer.length > config.logBufferSize) { + _logBuffer.removeFirst(); + } + + _logger.d('๐Ÿ“Š Logged message: $message'); + } catch (e) { + _logger.e('โŒ Failed to log message: $e'); + } + } + + @override + Future setUserIdentifier(String identifier) async { + if (!_isInitialized || !config.enableUserMetadata) { + return; + } + + try { + await _crashlytics.setUserIdentifier(identifier); + _logger.d('๐Ÿ‘ค Set user identifier: $identifier'); + } catch (e) { + _logger.e('โŒ Failed to set user identifier: $e'); + } + } + + @override + Future setUserMetadata(UserMetadata metadata) async { + if (!_isInitialized || !config.enableUserMetadata) { + return; + } + + try { + if (metadata.userId != null) { + await setUserIdentifier(metadata.userId!); + } + + // Set custom attributes + final allAttributes = { + if (metadata.email != null) 'email': metadata.email!, + if (metadata.name != null) 'name': metadata.name!, + ...metadata.customAttributes, + }; + + await setCustomKeys(allAttributes); + _logger.d('๐Ÿ‘ค Set user metadata: $metadata'); + } catch (e) { + _logger.e('โŒ Failed to set user metadata: $e'); + } + } + + @override + Future setCustomKey(String key, dynamic value) async { + if (!_isInitialized) { + return; + } + + try { + await _crashlytics.setCustomKey(key, value); + _logger.d('๐Ÿ”‘ Set custom key: $key = $value'); + } catch (e) { + _logger.e('โŒ Failed to set custom key: $e'); + } + } + + @override + Future setCustomKeys(Map keys) async { + if (!_isInitialized || keys.isEmpty) { + return; + } + + try { + for (final entry in keys.entries) { + await _crashlytics.setCustomKey(entry.key, entry.value); + } + _logger.d('๐Ÿ”‘ Set ${keys.length} custom keys'); + } catch (e) { + _logger.e('โŒ Failed to set custom keys: $e'); + } + } + + @override + bool get isCrashlyticsCollectionEnabled { + if (!_isInitialized) return false; + return _crashlytics.isCrashlyticsCollectionEnabled; + } + + @override + Future setCrashlyticsCollectionEnabled(bool enabled) async { + if (!_isInitialized) { + return; + } + + try { + await _crashlytics.setCrashlyticsCollectionEnabled(enabled); + _logger.d( + 'โš™๏ธ Crashlytics collection ${enabled ? 'enabled' : 'disabled'}', + ); + } catch (e) { + _logger.e('โŒ Failed to set crashlytics collection enabled: $e'); + } + } + + @override + Future sendUnsentReports() async { + if (!_isInitialized) { + return; + } + + try { + await _crashlytics.sendUnsentReports(); + _logger.d('๐Ÿ“ค Sent unsent crash reports'); + } catch (e) { + _logger.e('โŒ Failed to send unsent reports: $e'); + } + } + + @override + Future deleteUnsentReports() async { + if (!_isInitialized) { + return; + } + + try { + await _crashlytics.deleteUnsentReports(); + _logger.d('๐Ÿ—‘๏ธ Deleted unsent crash reports'); + } catch (e) { + _logger.e('โŒ Failed to delete unsent reports: $e'); + } + } + + @override + Future checkForUnsentReports() async { + if (!_isInitialized) { + return false; + } + + try { + final hasUnsent = await _crashlytics.checkForUnsentReports(); + _logger.d('๐Ÿ“‹ Has unsent reports: $hasUnsent'); + return hasUnsent; + } catch (e) { + _logger.e('โŒ Failed to check for unsent reports: $e'); + return false; + } + } + + /// Get buffered log messages for debugging + List get bufferedLogs => _logBuffer.toList(); + + /// Clear the log buffer + void clearLogBuffer() { + _logBuffer.clear(); + _logger.d('๐Ÿงน Cleared log buffer'); + } + + @override + void dispose() { + _logBuffer.clear(); + _isInitialized = false; + _logger.d('๐Ÿงน Firebase Crashlytics client disposed'); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/index.dart new file mode 100644 index 0000000..5d20960 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/index.dart @@ -0,0 +1,2 @@ +export 'crashlytics_client.dart'; +export 'firebase_crashlytics_client.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/config/crashlytics_config.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/config/crashlytics_config.dart new file mode 100644 index 0000000..5a52e29 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/config/crashlytics_config.dart @@ -0,0 +1,104 @@ +class CrashlyticsConfig { + const CrashlyticsConfig({ + this.enableInDebugMode = false, + this.enableAutomaticDataCollection = true, + this.enableCustomLogs = true, + this.logBufferSize = 100, + this.enableUserMetadata = true, + this.customKeys = const {}, + }); + + /// Enable crashlytics in debug mode (typically disabled for development) + final bool enableInDebugMode; + + /// Enable automatic data collection + final bool enableAutomaticDataCollection; + + /// Enable custom logging + final bool enableCustomLogs; + + /// Maximum number of log messages to buffer + final int logBufferSize; + + /// Enable user metadata collection + final bool enableUserMetadata; + + /// Default custom keys to set on initialization + final Map customKeys; + + CrashlyticsConfig copyWith({ + bool? enableInDebugMode, + bool? enableAutomaticDataCollection, + bool? enableCustomLogs, + int? logBufferSize, + bool? enableUserMetadata, + Map? customKeys, + }) { + return CrashlyticsConfig( + enableInDebugMode: enableInDebugMode ?? this.enableInDebugMode, + enableAutomaticDataCollection: + enableAutomaticDataCollection ?? this.enableAutomaticDataCollection, + enableCustomLogs: enableCustomLogs ?? this.enableCustomLogs, + logBufferSize: logBufferSize ?? this.logBufferSize, + enableUserMetadata: enableUserMetadata ?? this.enableUserMetadata, + customKeys: customKeys ?? this.customKeys, + ); + } + + Map toMap() { + return { + 'enableInDebugMode': enableInDebugMode, + 'enableAutomaticDataCollection': enableAutomaticDataCollection, + 'enableCustomLogs': enableCustomLogs, + 'logBufferSize': logBufferSize, + 'enableUserMetadata': enableUserMetadata, + 'customKeys': customKeys, + }; + } + + @override + String toString() { + return 'CrashlyticsConfig{enableInDebugMode: $enableInDebugMode, enableAutomaticDataCollection: $enableAutomaticDataCollection, enableCustomLogs: $enableCustomLogs, logBufferSize: $logBufferSize, enableUserMetadata: $enableUserMetadata, customKeys: $customKeys}'; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CrashlyticsConfig && + runtimeType == other.runtimeType && + enableInDebugMode == other.enableInDebugMode && + enableAutomaticDataCollection == + other.enableAutomaticDataCollection && + enableCustomLogs == other.enableCustomLogs && + logBufferSize == other.logBufferSize && + enableUserMetadata == other.enableUserMetadata && + _mapEquals(customKeys, other.customKeys); + + @override + int get hashCode => + enableInDebugMode.hashCode ^ + enableAutomaticDataCollection.hashCode ^ + enableCustomLogs.hashCode ^ + logBufferSize.hashCode ^ + enableUserMetadata.hashCode ^ + customKeys.hashCode; + + bool _mapEquals(Map a, Map b) { + if (a.length != b.length) return false; + for (final key in a.keys) { + if (!b.containsKey(key) || a[key] != b[key]) return false; + } + return true; + } +} + +class DefaultCrashlyticsConfig extends CrashlyticsConfig { + const DefaultCrashlyticsConfig({ + super.enableInDebugMode = false, + super.enableAutomaticDataCollection = true, + super.enableCustomLogs = true, + super.logBufferSize = 100, + super.enableUserMetadata = true, + super.customKeys = const {}, + }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/config/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/config/index.dart new file mode 100644 index 0000000..2caf5ad --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/config/index.dart @@ -0,0 +1 @@ +export 'crashlytics_config.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/crash_report.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/crash_report.dart new file mode 100644 index 0000000..e86c428 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/crash_report.dart @@ -0,0 +1,73 @@ +class CrashReport { + const CrashReport({ + required this.exception, + this.stackTrace, + this.fatal = false, + this.timestamp, + this.customKeys = const {}, + this.logs = const [], + this.userMetadata, + }); + + final dynamic exception; + final StackTrace? stackTrace; + final bool fatal; + final DateTime? timestamp; + final Map customKeys; + final List logs; + final Map? userMetadata; + + CrashReport copyWith({ + dynamic exception, + StackTrace? stackTrace, + bool? fatal, + DateTime? timestamp, + Map? customKeys, + List? logs, + Map? userMetadata, + }) { + return CrashReport( + exception: exception ?? this.exception, + stackTrace: stackTrace ?? this.stackTrace, + fatal: fatal ?? this.fatal, + timestamp: timestamp ?? this.timestamp, + customKeys: customKeys ?? this.customKeys, + logs: logs ?? this.logs, + userMetadata: userMetadata ?? this.userMetadata, + ); + } + + Map toMap() { + return { + 'exception': exception.toString(), + 'stackTrace': stackTrace?.toString(), + 'fatal': fatal, + 'timestamp': timestamp?.toIso8601String(), + 'customKeys': customKeys, + 'logs': logs, + 'userMetadata': userMetadata, + }; + } + + @override + String toString() { + return 'CrashReport{exception: $exception, fatal: $fatal, timestamp: $timestamp}'; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CrashReport && + runtimeType == other.runtimeType && + exception.toString() == other.exception.toString() && + stackTrace.toString() == other.stackTrace.toString() && + fatal == other.fatal && + timestamp == other.timestamp; + + @override + int get hashCode => + exception.hashCode ^ + stackTrace.hashCode ^ + fatal.hashCode ^ + timestamp.hashCode; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/index.dart new file mode 100644 index 0000000..8a9f2b6 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/index.dart @@ -0,0 +1,2 @@ +export 'crash_report.dart'; +export 'user_metadata.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/user_metadata.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/user_metadata.dart new file mode 100644 index 0000000..fc8633c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/user_metadata.dart @@ -0,0 +1,66 @@ +class UserMetadata { + const UserMetadata({ + this.userId, + this.email, + this.name, + this.customAttributes = const {}, + }); + + final String? userId; + final String? email; + final String? name; + final Map customAttributes; + + UserMetadata copyWith({ + String? userId, + String? email, + String? name, + Map? customAttributes, + }) { + return UserMetadata( + userId: userId ?? this.userId, + email: email ?? this.email, + name: name ?? this.name, + customAttributes: customAttributes ?? this.customAttributes, + ); + } + + Map toMap() { + return { + 'userId': userId, + 'email': email, + 'name': name, + 'customAttributes': customAttributes, + }; + } + + @override + String toString() { + return 'UserMetadata{userId: $userId, email: $email, name: $name, customAttributes: $customAttributes}'; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UserMetadata && + runtimeType == other.runtimeType && + userId == other.userId && + email == other.email && + name == other.name && + _mapEquals(customAttributes, other.customAttributes); + + @override + int get hashCode => + userId.hashCode ^ + email.hashCode ^ + name.hashCode ^ + customAttributes.hashCode; + + bool _mapEquals(Map a, Map b) { + if (a.length != b.length) return false; + for (final key in a.keys) { + if (!b.containsKey(key) || a[key] != b[key]) return false; + } + return true; + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml new file mode 100644 index 0000000..7a0ae42 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml @@ -0,0 +1,25 @@ +name: crashlytics +description: "Crash and non-fatal error reporting for {{name.titleCase()}}." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + firebase_core: ^3.14.0 + firebase_crashlytics: ^4.1.4 + core: + path: ../core + di: + path: ../di + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/test/crashlytics_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/test/crashlytics_test.dart new file mode 100644 index 0000000..a0cad7d --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/test/crashlytics_test.dart @@ -0,0 +1,133 @@ +import 'package:crashlytics/crashlytics.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Crashlytics', () { + group('CrashlyticsConfig', () { + test('creates default config correctly', () { + const config = DefaultCrashlyticsConfig(); + + expect(config.enableInDebugMode, false); + expect(config.enableAutomaticDataCollection, true); + expect(config.enableCustomLogs, true); + expect(config.logBufferSize, 100); + expect(config.enableUserMetadata, true); + expect(config.customKeys, isEmpty); + }); + + test('creates custom config correctly', () { + const config = CrashlyticsConfig( + enableInDebugMode: true, + enableAutomaticDataCollection: false, + logBufferSize: 50, + customKeys: {'test': 'value'}, + ); + + expect(config.enableInDebugMode, true); + expect(config.enableAutomaticDataCollection, false); + expect(config.logBufferSize, 50); + expect(config.customKeys, {'test': 'value'}); + }); + + test('copyWith works correctly', () { + const config = CrashlyticsConfig(); + final updated = config.copyWith(enableInDebugMode: true); + + expect(updated.enableInDebugMode, true); + expect( + updated.enableAutomaticDataCollection, + config.enableAutomaticDataCollection, + ); + }); + }); + + group('UserMetadata', () { + test('creates user metadata correctly', () { + const metadata = UserMetadata( + userId: 'user123', + email: 'test@example.com', + name: 'Test User', + customAttributes: {'role': 'admin'}, + ); + + expect(metadata.userId, 'user123'); + expect(metadata.email, 'test@example.com'); + expect(metadata.name, 'Test User'); + expect(metadata.customAttributes, {'role': 'admin'}); + }); + + test('toMap works correctly', () { + const metadata = UserMetadata( + userId: 'user123', + email: 'test@example.com', + ); + + final map = metadata.toMap(); + expect(map['userId'], 'user123'); + expect(map['email'], 'test@example.com'); + expect(map['name'], null); + expect(map['customAttributes'], isEmpty); + }); + + test('copyWith works correctly', () { + const metadata = UserMetadata(userId: 'user123'); + final updated = metadata.copyWith(email: 'new@example.com'); + + expect(updated.userId, 'user123'); + expect(updated.email, 'new@example.com'); + expect(updated.name, null); + }); + + test('equality works correctly', () { + const metadata1 = UserMetadata(userId: 'user123'); + const metadata2 = UserMetadata(userId: 'user123'); + const metadata3 = UserMetadata(userId: 'user456'); + + expect(metadata1, equals(metadata2)); + expect(metadata1, isNot(equals(metadata3))); + }); + }); + + group('CrashReport', () { + test('creates crash report correctly', () { + final timestamp = DateTime.now(); + final report = CrashReport( + exception: Exception('Test error'), + stackTrace: StackTrace.current, + fatal: true, + timestamp: timestamp, + customKeys: {'key': 'value'}, + ); + + expect(report.exception.toString(), contains('Test error')); + expect(report.fatal, true); + expect(report.timestamp, timestamp); + expect(report.customKeys, {'key': 'value'}); + }); + + test('toMap works correctly', () { + final report = CrashReport( + exception: Exception('Test error'), + fatal: false, + customKeys: {'test': 'data'}, + ); + + final map = report.toMap(); + expect(map['fatal'], false); + expect(map['customKeys'], {'test': 'data'}); + expect(map['exception'], contains('Test error')); + }); + + test('copyWith works correctly', () { + final report = CrashReport( + exception: Exception('Test error'), + fatal: false, + ); + + final updated = report.copyWith(fatal: true); + expect(updated.fatal, true); + expect(updated.exception.toString(), report.exception.toString()); + }); + }); + }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/developer.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/developer.dart new file mode 100644 index 0000000..546a189 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/developer.dart @@ -0,0 +1,3 @@ +export 'src/routes/index.dart'; +export 'src/screens/index.dart'; +export 'src/widgets/index.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/routes/developer_routes.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/routes/developer_routes.dart new file mode 100644 index 0000000..2992c41 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/routes/developer_routes.dart @@ -0,0 +1,21 @@ +import 'package:core/core.dart' as core; +import 'package:developer/src/screens/index.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class DeveloperRoutes { + static const String developer = '/developer'; +} + +class DeveloperRouter extends core.CoreRouter { + @override + List get routes => [ + GoRoute( + path: DeveloperRoutes.developer, + name: 'developer-screen', + builder: (BuildContext context, GoRouterState state) { + return const DeveloperScreen(); + }, + ), + ]; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/routes/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/routes/index.dart new file mode 100644 index 0000000..34e52a8 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/routes/index.dart @@ -0,0 +1 @@ +export 'developer_routes.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/screens/developer_screen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/screens/developer_screen.dart new file mode 100644 index 0000000..9c4cba5 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/screens/developer_screen.dart @@ -0,0 +1,13 @@ +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:flutter/material.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +class DeveloperScreen extends StatelessWidget { + const DeveloperScreen({super.key}); + + @override + Widget build(BuildContext context) { + return TalkerScreen(talker: di.get().logger as Talker); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/screens/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/screens/index.dart new file mode 100644 index 0000000..1f2dffb --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/screens/index.dart @@ -0,0 +1 @@ +export 'developer_screen.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/widgets/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/widgets/index.dart new file mode 100644 index 0000000..dfcb372 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/widgets/index.dart @@ -0,0 +1 @@ +export 'open_dev_tools_wrapper.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/widgets/open_dev_tools_wrapper.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/widgets/open_dev_tools_wrapper.dart new file mode 100644 index 0000000..f65abee --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/widgets/open_dev_tools_wrapper.dart @@ -0,0 +1,187 @@ +import 'dart:async'; + +import 'package:developer/src/routes/index.dart'; +import 'package:feature_flags/feature_flags.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; + +class OpenDevToolsWrapper extends StatefulWidget { + const OpenDevToolsWrapper({ + super.key, + required this.child, + this.tapCount = 5, + this.tapWindow = const Duration(seconds: 2), + this.enableHaptics = true, + this.enableVisualFeedback = false, + }); + + final Widget child; + final int tapCount; + final Duration tapWindow; + final bool enableHaptics; + final bool enableVisualFeedback; + + @override + State createState() => _OpenDevToolsWrapperState(); +} + +class _OpenDevToolsWrapperState extends State + with SingleTickerProviderStateMixin { + int _currentTapCount = 0; + Timer? _resetTimer; + late AnimationController _animationController; + late Animation _scaleAnimation; + bool _isAnimating = false; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ); + _scaleAnimation = Tween(begin: 1.0, end: 0.95).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + } + + @override + void dispose() { + _resetTimer?.cancel(); + _animationController.dispose(); + super.dispose(); + } + + void _handleTap() { + setState(() { + _currentTapCount++; + }); + + // Provide haptic feedback for progress + if (widget.enableHaptics) { + HapticFeedback.selectionClick(); + } + + // Visual feedback animation + if (widget.enableVisualFeedback && !_isAnimating) { + _isAnimating = true; + _animationController.forward().then((_) { + _animationController.reverse().then((_) { + if (mounted) { + setState(() { + _isAnimating = false; + }); + } + }); + }); + } + + // Check if we've reached the required tap count + if (_currentTapCount >= widget.tapCount) { + _navigateToDeveloperScreen(); + return; + } + + // Reset the timer + _resetTimer?.cancel(); + _resetTimer = Timer(widget.tapWindow, _resetTapCount); + } + + void _navigateToDeveloperScreen() { + // Provide success haptic feedback + if (widget.enableHaptics) { + HapticFeedback.mediumImpact(); + } + + // Reset state + _resetTapCount(); + + // Navigate to developer screen + if (mounted) { + context.push(DeveloperRoutes.developer); + } + } + + void _resetTapCount() { + _resetTimer?.cancel(); + if (mounted) { + setState(() { + _currentTapCount = 0; + }); + } + } + + Widget _buildChild() { + if (widget.enableVisualFeedback) { + return AnimatedBuilder( + animation: _scaleAnimation, + builder: (context, child) { + return Transform.scale( + scale: _scaleAnimation.value, + child: widget.child, + ); + }, + ); + } + return widget.child; + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return FeatureFlagWrapper( + flagKey: 'developer_screen_enabled', + builder: (context, isEnabled) { + if (!isEnabled) { + return widget.child; // Just return the child if feature is disabled + } + + return GestureDetector( + onTap: _handleTap, + behavior: HitTestBehavior.translucent, + child: Stack( + children: [ + _buildChild(), + // Optional debug indicator (only visible in debug and with visual feedback enabled) + if (widget.enableVisualFeedback && _currentTapCount > 0) ...[ + Positioned.fill( + child: IgnorePointer( + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: theme.colorScheme.error.withValues(alpha: 0.3), + width: 2, + ), + ), + child: Center( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.red.withValues(alpha: 0.8), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '$_currentTapCount/${widget.tapCount}', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onError, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + ), + ], + ], + ), + ); + }, + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml new file mode 100644 index 0000000..f2f8c42 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml @@ -0,0 +1,29 @@ +name: developer +description: "In-app developer tools for {{name.titleCase()}}: logs, flags and environment inspection." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + go_router: ^15.1.3 + talker_flutter: ^4.4.1 + core: + path: ../core + design_system: + path: ../design_system + di: + path: ../di + feature_flags: + path: ../feature_flags + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/test/developer_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/test/developer_test.dart new file mode 100644 index 0000000..f8dde7b --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/test/developer_test.dart @@ -0,0 +1,38 @@ +import 'package:developer/developer.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Developer Package', () { + test('package exports are available', () { + expect(DeveloperRoutes.developer, '/developer'); + }); + + testWidgets('DebugGestureDetector exports are available', (tester) async { + // Test that the widget can be instantiated + const widget = OpenDevToolsWrapper(child: Text('Test')); + + expect(widget, isNotNull); + expect(widget.tapCount, 5); // Default value + expect(widget.tapWindow, const Duration(seconds: 2)); // Default value + expect(widget.enableHaptics, true); // Default value + expect(widget.enableVisualFeedback, false); // Default value + }); + + testWidgets('DebugGestureDetector custom values work', (tester) async { + // Test custom configuration + const widget = OpenDevToolsWrapper( + tapCount: 7, + tapWindow: Duration(seconds: 2), + enableHaptics: false, + enableVisualFeedback: true, + child: Text('Test'), + ); + + expect(widget.tapCount, 7); + expect(widget.tapWindow, const Duration(seconds: 2)); + expect(widget.enableHaptics, false); + expect(widget.enableVisualFeedback, true); + }); + }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/README.md new file mode 100644 index 0000000..eb82669 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/README.md @@ -0,0 +1,18 @@ +# Feature Flags + +Feature flags and remote configuration management for {{name.titleCase()}} + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) + +## Installation ๐Ÿ’ป + +**โ— In order to start using Feature Flags you must have the [Flutter SDK][flutter_install_link] installed on your machine.** + +Install via `dart pub add`: + +```sh +dart pub add feature_flags +``` + +[flutter_install_link]: https://docs.flutter.dev/get-started/install \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/feature_flags.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/feature_flags.dart new file mode 100644 index 0000000..e683947 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/feature_flags.dart @@ -0,0 +1,29 @@ +library; + +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; +import 'package:feature_flags/src/feature_flag_service.dart'; +import 'package:feature_flags/src/register.dart'; + +export 'src/index.dart'; + +/// Initialize the feature flags module and register with DI +Future init({ + FeatureFlagsConfig config = const FeatureFlagsConfig( + fetchTimeout: Duration(seconds: 10), + minimumFetchInterval: Duration(minutes: 30), + defaultParameters: {}, + ), +}) async { + final logger = di.get(); + try { + logger.i('init feature flags module with Firebase Remote Config'); + await registerFeatureFlagsWithDI(config: config); + final service = di.get(); + await service.init(); + logger.i('Feature flags module initialized with Firebase Remote Config'); + } catch (e, stackTrace) { + logger.e('Failed to initialize feature flags module', e, stackTrace); + rethrow; + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_provider.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_provider.dart new file mode 100644 index 0000000..23c1788 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_provider.dart @@ -0,0 +1,23 @@ +/// Abstract interface for feature flag providers +abstract class FeatureFlagProvider { + /// Initialize the feature flag provider + Future init(); + + /// Get a boolean feature flag value + Future getBool(String key, {bool defaultValue = false}); + + /// Get a string feature flag value + Future getString(String key, {String defaultValue = ''}); + + /// Get an integer feature flag value + Future getInt(String key, {int defaultValue = 0}); + + /// Get a double feature flag value + Future getDouble(String key, {double defaultValue = 0.0}); + + /// Check if a feature flag exists + Future hasFlag(String key); + + /// Dispose resources + void dispose(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_service.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_service.dart new file mode 100644 index 0000000..400d945 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_service.dart @@ -0,0 +1,95 @@ +import 'package:core/logger/logger.dart'; +import 'package:feature_flags/src/feature_flag_provider.dart'; + +/// Configuration class for feature flags initialization +class FeatureFlagsConfig { + const FeatureFlagsConfig({ + this.fetchTimeout, + this.minimumFetchInterval, + required this.defaultParameters, + }); + + final Duration? fetchTimeout; + final Duration? minimumFetchInterval; + final Map defaultParameters; +} + +/// Service for managing feature flags across the application +class FeatureFlag { + final FeatureFlagProvider _provider; + final Logger _logger; + bool _initialized = false; + + FeatureFlag({required FeatureFlagProvider provider, required Logger logger}) + : _provider = provider, + _logger = logger; + + /// Initialize the feature flag service + Future init() async { + if (_initialized) { + _logger.w('FeatureFlag already initialized'); + return; + } + + try { + await _provider.init(); + _initialized = true; + _logger.i('FeatureFlag initialized successfully'); + } catch (e, stackTrace) { + _logger.e('Failed to initialize FeatureFlag', e, stackTrace); + rethrow; + } + } + + /// Check if service is initialized + bool get isInitialized => _initialized; + + /// Get a boolean feature flag + Future isEnabled(String key, {bool defaultValue = false}) async { + _ensureInitialized(); + final value = await _provider.getBool(key, defaultValue: defaultValue); + _logger.i('isEnabled($key): Provider returned = $value'); + return value; + } + + /// Get a string configuration value + Future getConfig(String key, {String defaultValue = ''}) async { + _ensureInitialized(); + + final value = await _provider.getString(key, defaultValue: defaultValue); + return value; + } + + /// Get an integer configuration value + Future getIntConfig(String key, {int defaultValue = 0}) async { + _ensureInitialized(); + + final value = await _provider.getInt(key, defaultValue: defaultValue); + return value; + } + + /// Get a double configuration value + Future getDoubleConfig( + String key, { + double defaultValue = 0.0, + }) async { + _ensureInitialized(); + + final value = await _provider.getDouble(key, defaultValue: defaultValue); + return value; + } + + /// Dispose the service + void dispose() { + _provider.dispose(); + _initialized = false; + _logger.i('FeatureFlag disposed'); + } + + /// Ensure the service is initialized + void _ensureInitialized() { + if (!_initialized) { + throw StateError('FeatureFlag not initialized. Call initialize() first.'); + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_wrapper.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_wrapper.dart new file mode 100644 index 0000000..56d22a6 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_wrapper.dart @@ -0,0 +1,56 @@ +import 'package:di/di.dart'; +import 'package:feature_flags/src/feature_flag_service.dart'; +import 'package:flutter/material.dart'; + +/// A wrapper widget that conditionally renders content based on feature flags +class FeatureFlagWrapper extends StatelessWidget { + const FeatureFlagWrapper({ + super.key, + required this.flagKey, + required this.builder, + this.defaultValue = false, + this.loading, + }); + + /// The feature flag key to check + final String flagKey; + + /// Builder function that receives the context and feature flag value + final Widget Function(BuildContext context, bool value) builder; + + /// The default value to use if the feature flag service is unavailable + final bool defaultValue; + + /// Widget to show while loading the feature flag value + final Widget? loading; + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _checkFeatureFlag(), + builder: (context, snapshot) { + // Show loading widget while waiting for feature flag + if (snapshot.connectionState == ConnectionState.waiting) { + return loading ?? const SizedBox.shrink(); + } + + // Get the feature flag value, defaulting to false if there's an error + final isEnabled = snapshot.data ?? defaultValue; + + // Return the widget built by the builder function + return builder(context, isEnabled); + }, + ); + } + + Future _checkFeatureFlag() async { + try { + final featureFlag = di.get(); + return await featureFlag.isEnabled(flagKey, defaultValue: defaultValue); + } catch (e) { + // If there's an error accessing the feature flag service, + // return the default value + return defaultValue; + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/firebase_remote_config_provider.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/firebase_remote_config_provider.dart new file mode 100644 index 0000000..7116a8c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/firebase_remote_config_provider.dart @@ -0,0 +1,144 @@ +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; +import 'package:feature_flags/feature_flags.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; + +/// Firebase Remote Config implementation of FeatureFlagProvider +class FirebaseRemoteConfigProvider implements FeatureFlagProvider { + late final FirebaseRemoteConfig _remoteConfig; + final FeatureFlagsConfig _config; + late final Logger logger; + + FirebaseRemoteConfigProvider({FeatureFlagsConfig? config}) + : _config = + config ?? + const FeatureFlagsConfig( + defaultParameters: {}, + fetchTimeout: Duration(seconds: 10), + minimumFetchInterval: Duration(hours: 1), + ) { + if (di.has()) { + logger = di.get(); + } + } + + @override + Future init() async { + try { + _remoteConfig = FirebaseRemoteConfig.instance; + + // Configure settings + await _remoteConfig.setConfigSettings( + RemoteConfigSettings( + fetchTimeout: _config.fetchTimeout ?? const Duration(seconds: 10), + minimumFetchInterval: + _config.minimumFetchInterval ?? const Duration(hours: 1), + ), + ); + + // Set default values + await _setDefaults(); + + // Fetch and activate + final activated = await _remoteConfig.fetchAndActivate(); + + logger.i('Firebase Remote Config initialized successfully'); + logger.i('Remote Config activated: $activated'); + logger.i('Last fetch status: ${_remoteConfig.lastFetchStatus}'); + logger.i('Last fetch time: ${_remoteConfig.lastFetchTime}'); + + // Log all current values for debugging + final allValues = _remoteConfig.getAll(); + for (final entry in allValues.entries) { + logger.i('Remote Config - ${entry.key}: ${entry.value.asString()}'); + } + } catch (e, stackTrace) { + logger.e('Failed to initialize Firebase Remote Config', e, stackTrace); + rethrow; + } + } + + Future _setDefaults() async { + await _remoteConfig.setDefaults(_config.defaultParameters); + } + + @override + Future getBool(String key, {bool defaultValue = false}) async { + try { + final value = _remoteConfig.getBool(key); + final configValue = _remoteConfig.getValue(key); + logger.i( + 'Getting bool flag "$key": value=$value,' + ' source=${configValue.source}', + ); + return value; + } catch (e) { + logger.e( + 'Failed to get bool flag: $key, using default: $defaultValue', + e, + ); + return defaultValue; + } + } + + @override + Future getString(String key, {String defaultValue = ''}) async { + try { + final value = _remoteConfig.getString(key); + return value.isEmpty ? defaultValue : value; + } catch (e) { + logger.w('Failed to get string flag: $key, using default: $defaultValue'); + return defaultValue; + } + } + + @override + Future getInt(String key, {int defaultValue = 0}) async { + try { + return _remoteConfig.getInt(key); + } catch (e) { + logger.w('Failed to get int flag: $key, using default: $defaultValue'); + return defaultValue; + } + } + + @override + Future getDouble(String key, {double defaultValue = 0.0}) async { + try { + return _remoteConfig.getDouble(key); + } catch (e) { + logger.w('Failed to get double flag: $key, using default: $defaultValue'); + return defaultValue; + } + } + + @override + Future hasFlag(String key) async { + try { + final value = _remoteConfig.getValue(key); + return value.source != ValueSource.valueStatic; + } catch (e) { + logger.w('Failed to check if flag exists: $key'); + return false; + } + } + + @override + void dispose() { + // Firebase Remote Config doesn't require explicit disposal + logger.i('Firebase Remote Config provider disposed'); + } + + /// Get the last fetch time + DateTime get lastFetchTime => _remoteConfig.lastFetchTime; + + /// Get the last fetch status + RemoteConfigFetchStatus get lastFetchStatus => _remoteConfig.lastFetchStatus; + + /// Add a listener for config updates + Stream get onConfigUpdated => + _remoteConfig.onConfigUpdated; + + /// Get the current configuration + FeatureFlagsConfig get config => _config; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/index.dart new file mode 100644 index 0000000..dc262f4 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/index.dart @@ -0,0 +1,5 @@ +export 'feature_flag_provider.dart'; +export 'feature_flag_service.dart'; +export 'feature_flag_wrapper.dart'; +export 'firebase_remote_config_provider.dart'; +export 'register.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/register.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/register.dart new file mode 100644 index 0000000..0cc8c04 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/register.dart @@ -0,0 +1,19 @@ +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; +import 'package:feature_flags/src/feature_flag_provider.dart'; +import 'package:feature_flags/src/feature_flag_service.dart'; +import 'package:feature_flags/src/firebase_remote_config_provider.dart'; + +/// Register feature flags services with dependency injection +/// +Future registerFeatureFlagsWithDI({FeatureFlagsConfig? config}) async { + final logger = di.get(); + + // Register the provider + final provider = FirebaseRemoteConfigProvider(config: config); + di.register(provider, dispose: (p) => p.dispose()); + + // Register the service + final service = FeatureFlag(provider: provider, logger: logger); + di.register(service, dispose: (s) => s.dispose()); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml new file mode 100644 index 0000000..60d0a14 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml @@ -0,0 +1,25 @@ +name: feature_flags +description: "Remote-config backed feature flags for {{name.titleCase()}}." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + firebase_core: ^3.14.0 + firebase_remote_config: ^5.1.6 + core: + path: ../core + di: + path: ../di + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/README.md new file mode 100644 index 0000000..f88d62b --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/README.md @@ -0,0 +1,18 @@ +# Network + +A Melos-managed project for mono-repo, created using NonStop CLI. + +[![nonstop_cli](https://img.shields.io/badge/started%20with-nonstop_cli-166C4E.svg?style=flat-square)](https://pub.dev/packages/nonstop_cli) +[![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) + +## Installation ๐Ÿ’ป + +**โ— In order to start using Network you must have the [Flutter SDK][flutter_install_link] installed on your machine.** + +Install via `dart pub add`: + +```sh +dart pub add network +``` + +[flutter_install_link]: https://docs.flutter.dev/get-started/install \ No newline at end of file diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/network.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/network.dart new file mode 100644 index 0000000..4cabe41 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/network.dart @@ -0,0 +1,78 @@ +library; + +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:network/src/auth/index.dart'; +import 'package:network/src/client/index.dart'; +import 'package:network/src/config/index.dart'; + +export 'src/api/index.dart'; +export 'src/auth/index.dart'; +export 'src/client/index.dart'; +export 'src/config/index.dart'; +export 'src/exceptions/index.dart'; +export 'src/interceptors/index.dart'; +export 'src/models/index.dart'; +export 'src/utils/index.dart'; + +Future init({ + required NetworkConfig config, + bool useAuthentication = true, +}) async { + await registerNetworkWithDI(config, useAuthentication: useAuthentication); + final logger = di.get(); + logger.i('Network module initialized with base URL: ${config.baseUrl}'); +} + +Future registerNetworkWithDI( + NetworkConfig config, { + bool useAuthentication = true, +}) async { + final logger = di.get(); + + // Try to get AuthTokenProvider from DI if authentication is enabled + AuthTokenProvider? authTokenProvider; + if (useAuthentication && config.authTokenProvider == null) { + try { + authTokenProvider = di.get(); + logger.d('๐Ÿ” Found AuthTokenProvider in DI, using for authentication'); + } catch (e) { + logger.d( + '๐Ÿ”“ No AuthTokenProvider found in DI, creating unauthenticated client', + ); + } + } else if (config.authTokenProvider != null) { + authTokenProvider = config.authTokenProvider; + logger.d('๐Ÿ” Using provided AuthTokenProvider'); + } + + // Create network config with auth provider if available + late final NetworkConfig finalConfig; + if (authTokenProvider != null && config.authTokenProvider == null) { + // Create a new config with the auth provider + finalConfig = DefaultNetworkConfig( + baseUrl: config.baseUrl, + connectTimeout: config.connectTimeout, + receiveTimeout: config.receiveTimeout, + sendTimeout: config.sendTimeout, + defaultHeaders: config.defaultHeaders, + enableLogging: config.enableLogging, + authTokenProvider: authTokenProvider, + ); + } else { + finalConfig = config; + } + + final networkClient = DioNetworkClient(finalConfig); + di.register( + networkClient, + dispose: (client) => client.dispose(), + ); + di.register(finalConfig); + + if (finalConfig.authTokenProvider != null) { + logger.i('๐Ÿ” Network client registered with authentication support'); + } else { + logger.i('๐Ÿ”“ Network client registered without authentication'); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/api/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/api/index.dart new file mode 100644 index 0000000..e3427eb --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/api/index.dart @@ -0,0 +1,2 @@ +//GENERATED BARREL FILE +export 'network_api.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/api/network_api.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/api/network_api.dart new file mode 100644 index 0000000..66a684c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/api/network_api.dart @@ -0,0 +1,13 @@ +abstract class ApiUrlConfig { + final String pathPrefix; + + const ApiUrlConfig({required this.pathPrefix}); +} + +abstract class NetworkApi { + final T urlConfig; + + const NetworkApi({required this.urlConfig}); + + Map get headers; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/auth/auth_token_provider.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/auth/auth_token_provider.dart new file mode 100644 index 0000000..eb3e08c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/auth/auth_token_provider.dart @@ -0,0 +1,27 @@ +abstract class AuthTokenProvider { + /// Gets a valid authentication token + /// Returns null if user is not authenticated + Future getValidToken(); + + /// Gets the current token without validation + /// Returns null if no token is available + String? getCurrentToken(); + + /// Stream of authentication token changes + /// Emits new tokens when authentication state changes + Stream get tokenStream; + + /// Forces a token refresh + /// Returns the new token or null if refresh fails + Future refreshToken(); + + /// Checks if the user is currently authenticated + bool get isAuthenticated; + + /// Checks if the current token is expired + /// Returns true if token is null or expired + bool get isTokenExpired; + + /// Sign out the current user + Future signOut(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/auth/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/auth/index.dart new file mode 100644 index 0000000..5f2f025 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/auth/index.dart @@ -0,0 +1 @@ +export 'auth_token_provider.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/dio_network_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/dio_network_client.dart new file mode 100644 index 0000000..5c5712c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/dio_network_client.dart @@ -0,0 +1,339 @@ +import 'dart:io'; + +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:localization/localization.dart'; +import 'package:network/src/client/network_client.dart'; +import 'package:network/src/config/network_config.dart'; +import 'package:network/src/exceptions/network_exceptions.dart'; +import 'package:network/src/interceptors/auth_interceptor.dart'; +import 'package:network/src/models/network_response.dart'; +import 'package:talker_dio_logger/talker_dio_logger.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +class DioNetworkClient implements NetworkClient { + DioNetworkClient(this._config) : _dio = Dio() { + _setupDio(); + } + + final NetworkConfig _config; + final Dio _dio; + final Logger _logger = di.get(); + + void _setupDio() { + _dio.options = BaseOptions( + baseUrl: _config.baseUrl, + connectTimeout: _config.connectTimeout, + receiveTimeout: _config.receiveTimeout, + //sendTimeout cannot be used without a request body to send on Web + sendTimeout: kIsWeb ? null : _config.sendTimeout, + headers: _config.defaultHeaders, + ); + + // Add authentication interceptor if token provider is available + if (_config.authTokenProvider != null) { + _dio.interceptors.add(AuthInterceptor(_config.authTokenProvider!)); + _logger.d('๐Ÿ” Auth interceptor added to network client'); + } + + if (_config.enableLogging) { + _dio.interceptors.add( + TalkerDioLogger( + talker: di.get().logger as Talker, + settings: TalkerDioLoggerSettings( + printResponseData: true, + printResponseTime: true, + ), + ), + ); + } + } + + @override + Future> get( + String path, { + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }) async { + try { + final response = await _dio.get( + path, + queryParameters: queryParameters, + options: Options(headers: headers), + ); + return _handleResponse(response, fromJsonT: fromJsonT); + } catch (e) { + return _handleError(e); + } + } + + @override + Future> post( + String path, { + dynamic data, + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }) async { + try { + final response = await _dio.post( + path, + data: data, + queryParameters: queryParameters, + options: Options(headers: headers), + ); + return _handleResponse(response, fromJsonT: fromJsonT); + } catch (e) { + return _handleError(e); + } + } + + @override + Future> put( + String path, { + dynamic data, + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }) async { + try { + final response = await _dio.put( + path, + data: data, + queryParameters: queryParameters, + options: Options(headers: headers), + ); + return _handleResponse(response, fromJsonT: fromJsonT); + } catch (e) { + return _handleError(e); + } + } + + @override + Future> patch( + String path, { + dynamic data, + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }) async { + try { + final response = await _dio.patch( + path, + data: data, + queryParameters: queryParameters, + options: Options(headers: headers), + ); + return _handleResponse(response, fromJsonT: fromJsonT); + } catch (e) { + return _handleError(e); + } + } + + @override + Future> delete( + String path, { + dynamic data, + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }) async { + try { + final response = await _dio.delete( + path, + data: data, + queryParameters: queryParameters, + options: Options(headers: headers), + ); + return _handleResponse(response, fromJsonT: fromJsonT); + } catch (e) { + return _handleError(e); + } + } + + NetworkResponse _handleResponse( + Response response, { + required T Function(Object? json) fromJsonT, + }) { + try { + if (response.data is Map) { + final responseData = response.data as Map; + + if (responseData.containsKey('success')) { + if (responseData['success'] == true) { + return SuccessResponse.fromJson({ + 'statusCode': response.statusCode ?? 200, + 'headers': response.headers.map, + ...responseData, + }, fromJsonT); + } else { + ErrorResponse.fromJson({ + 'statusCode': response.statusCode ?? 500, + 'headers': response.headers.map, + ...responseData, + }); + } + } + } + + // Ideally, should not reach here if the API follows the + // standard response format + _logger.w('Invalid response format: ${response.data}'); + + // Fallback to a generic success response + return SuccessResponse.fromJson({ + 'statusCode': response.statusCode ?? 200, + 'headers': response.headers.map, + 'data': response.data, + }, (value) => value as T); + } catch (e, s) { + _logger.e('Error parsing response', e); + return ErrorResponse( + statusCode: response.statusCode ?? 500, + success: false, + message: 'Network Client failed to parse response!', + error: ErrorDetails( + code: 'PARSE_ERROR', + message: e.toString(), + details: {'stackTrace': s.toString()}, + ), + timestamp: DateTime.now().toIso8601String(), + ); + } + } + + NetworkResponse _handleError(dynamic error) { + NetworkException networkException; + ErrorResponse? apiError; + + if (error is DioException) { + apiError = _parseApiError(error.response); + networkException = _mapDioException(error, apiError); + } else { + networkException = UnknownNetworkException(message: error.toString()); + } + + _logger.e('Network error occurred', networkException); + + throw networkException; + } + + ErrorResponse? _parseApiError(Response? response) { + try { + final responseData = response?.data; + if (responseData is Map && + responseData.containsKey('success') && + responseData['success'] == false) { + return ErrorResponse.fromJson({ + 'statusCode': response?.statusCode ?? 500, + 'headers': response?.headers.map, + ...responseData, + }); + } + } catch (e) { + _logger.w('Failed to parse API error: $e'); + } + return null; + } + + NetworkException _mapDioException( + DioException dioException, + ErrorResponse? apiError, + ) { + final message = apiError?.error.message; + + switch (dioException.type) { + case DioExceptionType.connectionTimeout: + return ConnectionTimeoutException(message: message); + case DioExceptionType.receiveTimeout: + return ReceiveTimeoutException(message: message); + case DioExceptionType.sendTimeout: + return SendTimeoutException(message: message); + case DioExceptionType.connectionError: + if (dioException.error is SocketException) { + return NoInternetConnectionException(message: message); + } + return UnknownNetworkException(message: message); + case DioExceptionType.badResponse: + final statusCode = dioException.response?.statusCode; + switch (statusCode) { + case 400: + return BadRequestException(message: message, error: apiError); + case 401: + return UnauthorizedException(message: message, error: apiError); + case 403: + return ForbiddenException(message: message, error: apiError); + case 404: + return NotFoundException(message: message, error: apiError); + case 409: + return ConflictException(message: message, error: apiError); + case 422: + return UnprocessableEntityException( + message: message, + error: apiError, + ); + case 500: + case 502: + case 503: + case 504: + return InternalServerException(message: message, error: apiError); + default: + return UnknownNetworkException( + message: message, + statusCode: statusCode, + error: apiError, + ); + } + default: + return UnknownNetworkException(message: message, error: apiError); + } + } + + @override + T handleResponse(final NetworkResponse response) { + switch (response) { + case SuccessResponse(): + return response.data!; + case ErrorResponse(): + throw UnknownNetworkException( + message: response.message ?? strings.errors.network_error, + statusCode: response.statusCode, + error: response, + ); + default: + throw UnknownNetworkException( + message: strings.errors.unknown_error, + statusCode: response.statusCode, + ); + } + } + + @override + SuccessResponse handleSuccessResponse( + final NetworkResponse response, + ) { + switch (response) { + case SuccessResponse(): + return response; + case ErrorResponse(): + throw UnknownNetworkException( + message: response.message ?? strings.errors.network_error, + statusCode: response.statusCode, + error: response, + ); + default: + throw UnknownNetworkException( + message: strings.errors.unknown_error, + statusCode: response.statusCode, + ); + } + } + + @override + void dispose() { + _dio.close(); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/index.dart new file mode 100644 index 0000000..23a5e8f --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/index.dart @@ -0,0 +1,2 @@ +export 'dio_network_client.dart'; +export 'network_client.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/network_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/network_client.dart new file mode 100644 index 0000000..4d87172 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/network_client.dart @@ -0,0 +1,50 @@ +import 'package:network/src/models/network_response.dart'; + +abstract class NetworkClient { + Future> get( + String path, { + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }); + + Future> post( + String path, { + dynamic data, + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }); + + Future> put( + String path, { + dynamic data, + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }); + + Future> patch( + String path, { + dynamic data, + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }); + + Future> delete( + String path, { + dynamic data, + Map? queryParameters, + Map? headers, + required T Function(Object? json) fromJsonT, + }); + + T handleResponse(final NetworkResponse response); + + SuccessResponse handleSuccessResponse( + final NetworkResponse response, + ); + + void dispose(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/config/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/config/index.dart new file mode 100644 index 0000000..81201c9 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/config/index.dart @@ -0,0 +1 @@ +export 'network_config.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/config/network_config.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/config/network_config.dart new file mode 100644 index 0000000..9b31a45 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/config/network_config.dart @@ -0,0 +1,44 @@ +import 'package:network/src/auth/auth_token_provider.dart'; + +abstract class NetworkConfig { + String get baseUrl; + Duration get connectTimeout; + Duration get receiveTimeout; + Duration get sendTimeout; + Map get defaultHeaders; + bool get enableLogging; + AuthTokenProvider? get authTokenProvider; +} + +class DefaultNetworkConfig implements NetworkConfig { + const DefaultNetworkConfig({ + required this.baseUrl, + this.connectTimeout = const Duration(seconds: 30), + this.receiveTimeout = const Duration(seconds: 30), + this.sendTimeout = const Duration(seconds: 30), + this.defaultHeaders = const {}, + this.enableLogging = true, + this.authTokenProvider, + }); + + @override + final String baseUrl; + + @override + final Duration connectTimeout; + + @override + final Duration receiveTimeout; + + @override + final Duration sendTimeout; + + @override + final Map defaultHeaders; + + @override + final bool enableLogging; + + @override + final AuthTokenProvider? authTokenProvider; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/exceptions/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/exceptions/index.dart new file mode 100644 index 0000000..8df1c24 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/exceptions/index.dart @@ -0,0 +1 @@ +export 'network_exceptions.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/exceptions/network_exceptions.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/exceptions/network_exceptions.dart new file mode 100644 index 0000000..5464888 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/exceptions/network_exceptions.dart @@ -0,0 +1,107 @@ +import 'package:localization/localization.dart'; +import 'package:network/network.dart'; + +abstract class NetworkException implements Exception { + const NetworkException({this.message, this.statusCode, this.error}); + + final String? message; + final int? statusCode; + final ErrorResponse? error; + + String get displayMessage => message ?? strings.errors.unknown_network; + + @override + String toString() => '$runtimeType: $displayMessage'; +} + +class BadRequestException extends NetworkException { + const BadRequestException({super.message, super.error}) + : super(statusCode: 400); + + @override + String get displayMessage => message ?? strings.errors.bad_request; +} + +class UnauthorizedException extends NetworkException { + const UnauthorizedException({super.message, super.error}) + : super(statusCode: 401); + + @override + String get displayMessage => message ?? strings.errors.unauthorized; +} + +class ForbiddenException extends NetworkException { + const ForbiddenException({super.message, super.error}) + : super(statusCode: 403); + + @override + String get displayMessage => message ?? strings.errors.forbidden; +} + +class NotFoundException extends NetworkException { + const NotFoundException({super.message, super.error}) + : super(statusCode: 404); + + @override + String get displayMessage => message ?? strings.errors.not_found; +} + +class ConflictException extends NetworkException { + const ConflictException({super.message, super.error}) + : super(statusCode: 409); + + @override + String get displayMessage => message ?? strings.errors.conflict; +} + +class UnprocessableEntityException extends NetworkException { + const UnprocessableEntityException({super.message, super.error}) + : super(statusCode: 422); + + @override + String get displayMessage => message ?? strings.errors.unprocessable_entity; +} + +class InternalServerException extends NetworkException { + const InternalServerException({super.message, super.error}) + : super(statusCode: 500); + + @override + String get displayMessage => message ?? strings.errors.internal_server_error; +} + +class ConnectionTimeoutException extends NetworkException { + const ConnectionTimeoutException({super.message}) : super(statusCode: 599); + + @override + String get displayMessage => message ?? strings.errors.connection_timeout; +} + +class ReceiveTimeoutException extends NetworkException { + const ReceiveTimeoutException({super.message}) : super(statusCode: null); + + @override + String get displayMessage => message ?? strings.errors.receive_timeout; +} + +class SendTimeoutException extends NetworkException { + const SendTimeoutException({super.message}) : super(statusCode: null); + + @override + String get displayMessage => message ?? strings.errors.send_timeout; +} + +class NoInternetConnectionException extends NetworkException { + const NoInternetConnectionException({super.message}) + : super(statusCode: null); + + @override + String get displayMessage => message ?? strings.errors.no_internet; +} + +class UnknownNetworkException extends NetworkException { + const UnknownNetworkException({super.message, super.statusCode, super.error}); + + @override + String get displayMessage => message ?? strings.errors.unknown_network; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/auth_interceptor.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/auth_interceptor.dart new file mode 100644 index 0000000..8aed94b --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/auth_interceptor.dart @@ -0,0 +1,73 @@ +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:dio/dio.dart'; +import 'package:network/src/auth/auth_token_provider.dart'; + +class AuthInterceptor extends Interceptor { + AuthInterceptor(this._authTokenProvider) : _logger = di.get(); + + final AuthTokenProvider _authTokenProvider; + final Logger _logger; + + @override + void onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + try { + // Skip authentication for requests that already have authorization header + if (options.headers.containsKey('Authorization')) { + handler.next(options); + return; + } + + final token = await _authTokenProvider.getValidToken(); + if (token != null) { + options.headers['Authorization'] = 'Bearer $token'; + _logger.d('๐Ÿ” Added Bearer token to request: ${options.uri}'); + } else { + _logger.d('๐Ÿ”“ No auth token available for request: ${options.uri}'); + } + + handler.next(options); + } catch (e) { + _logger.e('๐Ÿšจ Error adding auth token to request', e); + handler.next(options); + } + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + // Handle 401 Unauthorized - token might be expired + if (err.response?.statusCode == 401) { + _logger.w('๐Ÿ” Received 401 Unauthorized, attempting token refresh'); + + try { + final newToken = await _authTokenProvider.refreshToken(); + if (newToken != null) { + _logger.i('๐Ÿ”„ Token refreshed successfully, retrying request'); + + // Clone the request with new token + final requestOptions = err.requestOptions; + requestOptions.headers['Authorization'] = 'Bearer $newToken'; + + // Retry the request + final response = await Dio().fetch(requestOptions); + handler.resolve(response); + return; + } else { + _logger.w('โŒ Token refresh failed, user needs to re-authenticate'); + } + } catch (refreshError) { + _logger.e('๐Ÿšจ Error during token refresh', refreshError); + } + } + + // Handle 403 Forbidden - user doesn't have permission + if (err.response?.statusCode == 403) { + _logger.w('๐Ÿšซ Received 403 Forbidden, insufficient permissions'); + } + + handler.next(err); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/index.dart new file mode 100644 index 0000000..075b202 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/index.dart @@ -0,0 +1,2 @@ +export 'auth_interceptor.dart'; +export 'logging_interceptor.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/logging_interceptor.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/logging_interceptor.dart new file mode 100644 index 0000000..897650e --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/logging_interceptor.dart @@ -0,0 +1,44 @@ +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:dio/dio.dart'; + +class LoggingInterceptor extends Interceptor { + LoggingInterceptor() : _logger = di.get(); + + final Logger _logger; + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + _logger.d('๐Ÿš€ Request: ${options.method} ${options.uri}'); + _logger.d('Headers: ${options.headers}'); + if (options.data != null) { + _logger.d('Data: ${options.data}'); + } + if (options.queryParameters.isNotEmpty) { + _logger.d('Query Parameters: ${options.queryParameters}'); + } + super.onRequest(options, handler); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + _logger.d( + 'โœ… Response: ${response.statusCode} ${response.requestOptions.uri}', + ); + _logger.d('Response Data: ${response.data}'); + super.onResponse(response, handler); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + _logger.e( + 'โŒ Error: ${err.response?.statusCode} ${err.requestOptions.uri}', + err, + err.stackTrace, + ); + if (err.response?.data != null) { + _logger.e('Error Data: ${err.response?.data}'); + } + super.onError(err, handler); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/index.dart new file mode 100644 index 0000000..87c17f7 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/index.dart @@ -0,0 +1 @@ +export 'network_response.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/network_response.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/network_response.dart new file mode 100644 index 0000000..cb65ffd --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/network_response.dart @@ -0,0 +1,102 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'network_response.g.dart'; + +abstract class NetworkResponse { + const NetworkResponse({ + required this.statusCode, + required this.success, + this.message, + required this.timestamp, + this.headers, + }); + + final int statusCode; + final bool success; + final String? message; + final String timestamp; + final Map>? headers; + + bool get isSuccess => success && statusCode >= 200 && statusCode < 300; +} + +@JsonSerializable(genericArgumentFactories: true) +class SuccessResponse extends NetworkResponse { + final T data; + final Pagination? pagination; + + const SuccessResponse({ + required super.statusCode, + required super.success, + required this.data, + super.message, + required super.timestamp, + this.pagination, + super.headers, + }); + + factory SuccessResponse.fromJson( + Map json, + T Function(Object? json) fromJsonT, + ) => _$SuccessResponseFromJson(json, fromJsonT); + + Map toJson(Object Function(T value) toJsonT) => + _$SuccessResponseToJson(this, toJsonT); +} + +@JsonSerializable() +class ErrorResponse extends NetworkResponse { + final ErrorDetails error; + + const ErrorResponse({ + required super.statusCode, + required super.success, + required this.error, + required super.timestamp, + super.message, + super.headers, + }); + + factory ErrorResponse.fromJson(Map json) => + _$ErrorResponseFromJson(json); + + Map toJson() => _$ErrorResponseToJson(this); +} + +@JsonSerializable() +class ErrorDetails { + const ErrorDetails({required this.code, required this.message, this.details}); + + final String code; + final String message; + final Map? details; + + factory ErrorDetails.fromJson(Map json) => + _$ErrorDetailsFromJson(json); + + Map toJson() => _$ErrorDetailsToJson(this); +} + +@JsonSerializable() +class Pagination { + const Pagination({ + required this.page, + required this.limit, + required this.total, + required this.totalPages, + required this.hasNextPage, + required this.hasPreviousPage, + }); + + final int page; + final int limit; + final int total; + final int totalPages; + final bool hasNextPage; + final bool hasPreviousPage; + + factory Pagination.fromJson(Map json) => + _$PaginationFromJson(json); + + Map toJson() => _$PaginationToJson(this); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/network_response.g.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/network_response.g.dart new file mode 100644 index 0000000..7445c78 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/network_response.g.dart @@ -0,0 +1,93 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'network_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SuccessResponse _$SuccessResponseFromJson( + Map json, + T Function(Object? json) fromJsonT, +) => SuccessResponse( + statusCode: (json['statusCode'] as num).toInt(), + success: json['success'] as bool, + data: fromJsonT(json['data']), + message: json['message'] as String?, + timestamp: json['timestamp'] as String, + pagination: json['pagination'] == null + ? null + : Pagination.fromJson(json['pagination'] as Map), + headers: (json['headers'] as Map?)?.map( + (k, e) => + MapEntry(k, (e as List).map((e) => e as String).toList()), + ), +); + +Map _$SuccessResponseToJson( + SuccessResponse instance, + Object? Function(T value) toJsonT, +) => { + 'statusCode': instance.statusCode, + 'success': instance.success, + 'message': instance.message, + 'timestamp': instance.timestamp, + 'headers': instance.headers, + 'data': toJsonT(instance.data), + 'pagination': instance.pagination, +}; + +ErrorResponse _$ErrorResponseFromJson(Map json) => + ErrorResponse( + statusCode: (json['statusCode'] as num).toInt(), + success: json['success'] as bool, + error: ErrorDetails.fromJson(json['error'] as Map), + timestamp: json['timestamp'] as String, + message: json['message'] as String?, + headers: (json['headers'] as Map?)?.map( + (k, e) => + MapEntry(k, (e as List).map((e) => e as String).toList()), + ), + ); + +Map _$ErrorResponseToJson(ErrorResponse instance) => + { + 'statusCode': instance.statusCode, + 'success': instance.success, + 'message': instance.message, + 'timestamp': instance.timestamp, + 'headers': instance.headers, + 'error': instance.error, + }; + +ErrorDetails _$ErrorDetailsFromJson(Map json) => ErrorDetails( + code: json['code'] as String, + message: json['message'] as String, + details: json['details'] as Map?, +); + +Map _$ErrorDetailsToJson(ErrorDetails instance) => + { + 'code': instance.code, + 'message': instance.message, + 'details': instance.details, + }; + +Pagination _$PaginationFromJson(Map json) => Pagination( + page: (json['page'] as num).toInt(), + limit: (json['limit'] as num).toInt(), + total: (json['total'] as num).toInt(), + totalPages: (json['totalPages'] as num).toInt(), + hasNextPage: json['hasNextPage'] as bool, + hasPreviousPage: json['hasPreviousPage'] as bool, +); + +Map _$PaginationToJson(Pagination instance) => + { + 'page': instance.page, + 'limit': instance.limit, + 'total': instance.total, + 'totalPages': instance.totalPages, + 'hasNextPage': instance.hasNextPage, + 'hasPreviousPage': instance.hasPreviousPage, + }; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/extensions/error_message_extension.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/extensions/error_message_extension.dart new file mode 100644 index 0000000..a715c62 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/extensions/error_message_extension.dart @@ -0,0 +1,97 @@ +import 'package:core/core.dart'; +import 'package:flutter/foundation.dart'; +import 'package:localization/localization.dart'; +import 'package:network/network.dart'; + +extension ErrorMessageExtension on Object? { + String message() { + return switch (this) { + NetworkException error => error.displayMessage, + CoreException error => error.message, + FormatException _ => strings.errors.format_exception_message( + ErrorCodes.formatException, + debugMessage, + ), + Error error => error.message(), + _ => strings.errors.default_error_message, + }; + } + + String get debugMessage { + if (kDebugMode) { + return '\n${toString()}\ntype: $runtimeType'; + } + return ''; + } +} + +// Simple creative error messages with codes +extension ErrorMessageOnError on Error { + String message() { + return switch (this) { + TypeError _ => strings.errors.type_error_message( + ErrorCodes.typeError, + debugMessage, + ), + IndexError _ => strings.errors.index_error_message( + ErrorCodes.indexError, + debugMessage, + ), + RangeError _ => strings.errors.range_error_message( + ErrorCodes.rangeError, + debugMessage, + ), + ArgumentError _ => strings.errors.argument_error_message( + ErrorCodes.argumentError, + debugMessage, + ), + StateError _ => strings.errors.state_error_message( + ErrorCodes.stateError, + debugMessage, + ), + UnimplementedError _ => strings.errors.unimplemented_error_message( + ErrorCodes.unimplementedError, + debugMessage, + ), + UnsupportedError _ => strings.errors.unsupported_error_message( + ErrorCodes.unsupportedError, + debugMessage, + ), + ConcurrentModificationError _ => + strings.errors.concurrent_modification_error_message( + ErrorCodes.concurrentModificationError, + debugMessage, + ), + OutOfMemoryError _ => strings.errors.out_of_memory_error_message( + ErrorCodes.outOfMemoryError, + debugMessage, + ), + StackOverflowError _ => strings.errors.stack_overflow_error_message( + ErrorCodes.stackOverflowError, + debugMessage, + ), + _ => strings.errors.unknown_error_message( + ErrorCodes.unknownError, + debugMessage, + ), + }; + } +} + +sealed class ErrorCodes { + static const String typeError = "E001"; + static const String argumentError = "E002"; + static const String rangeError = "E003"; + static const String indexError = "E004"; + static const String stateError = "E005"; + static const String unsupportedError = "E006"; + static const String unimplementedError = "E007"; + static const String concurrentModificationError = "E008"; + static const String outOfMemoryError = "E009"; + static const String stackOverflowError = "E010"; + static const String formatException = "E011"; + static const String unknownError = "E999"; + static const String networkException = "E100"; + static const String coreException = "E101"; + static const String defaultError = "E000"; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/extensions/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/extensions/index.dart new file mode 100644 index 0000000..76768c9 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/extensions/index.dart @@ -0,0 +1 @@ +export 'error_message_extension.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/index.dart new file mode 100644 index 0000000..49893a1 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/index.dart @@ -0,0 +1 @@ +export 'extensions/index.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml new file mode 100644 index 0000000..6fe7754 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml @@ -0,0 +1,31 @@ +name: network +description: "HTTP layer for {{name.titleCase()}}: Dio client, interceptors, typed responses and error mapping." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + dio: ^5.7.0 + json_annotation: ^4.9.0 + talker_dio_logger: ^4.9.1 + talker_flutter: ^4.4.1 + core: + path: ../core + localization: + path: ../localization + di: + path: ../di + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + build_runner: ^2.4.15 + json_serializable: ^6.9.5 + +flutter: diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/test/network_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/test/network_test.dart new file mode 100644 index 0000000..b7e0d4e --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/test/network_test.dart @@ -0,0 +1,15 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:network/network.dart'; + +void main() { + test('DefaultNetworkConfig creates instance with provided values', () { + const config = DefaultNetworkConfig( + baseUrl: 'https://api.example.com', + enableLogging: true, + ); + + expect(config.baseUrl, 'https://api.example.com'); + expect(config.enableLogging, true); + expect(config.connectTimeout, const Duration(seconds: 30)); + }); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/notifications.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/notifications.dart new file mode 100644 index 0000000..21ecd44 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/notifications.dart @@ -0,0 +1,28 @@ +library; + +import 'dart:async'; + +import 'package:core/core.dart'; +import 'package:di/di.dart'; +import 'package:notifications/src/config/index.dart'; +import 'package:notifications/src/di/index.dart'; + +export 'src/client/index.dart'; +export 'src/config/index.dart'; +export 'src/device_info/index.dart'; +export 'src/exceptions/index.dart'; +export 'src/models/index.dart'; +export 'src/services/index.dart'; + +/// Initialize the notifications module with dependency injection +Future init({ + NotificationConfig config = const DefaultNotificationConfig(), +}) async { + final logger = di.get(); + try { + await registerNotificationWithDI(config); + logger.i('๐Ÿ”” Notifications module initialized'); + } catch (e, s) { + logger.e('๐Ÿ”” Failed to initialize notifications module', e, s); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/firebase_notification_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/firebase_notification_client.dart new file mode 100644 index 0000000..cbe1cb7 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/firebase_notification_client.dart @@ -0,0 +1,255 @@ +import 'dart:async'; + +import 'package:app_badge_plus/app_badge_plus.dart'; +import 'package:core/logger/logger.dart'; +import 'package:design_system/toast/toasts.dart'; +import 'package:di/di.dart'; +import 'package:firebase_messaging/firebase_messaging.dart' + hide NotificationSettings; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:network/network.dart'; +import 'package:notifications/notifications.dart'; + +const String _tag = 'FirebaseNotificationClient'; + +/// Firebase implementation of the notification client +class FirebaseNotificationClient implements NotificationClient { + FirebaseNotificationClient({ + required this.config, + Logger? logger, + NotificationTokenManager? tokenManager, + NotificationPermissionManager? permissionManager, + }) : _logger = logger ?? di.get(), + _tokenManager = tokenManager ?? di.get(), + _permissionManager = + permissionManager ?? di.get(); + + final NotificationConfig config; + final Logger _logger; + final NotificationTokenManager _tokenManager; + final NotificationPermissionManager _permissionManager; + bool _isGranted = false; + + FirebaseMessaging? _firebaseMessaging; + + String? _fcmToken; + String? _deviceId; + + // Store subscriptions for cleanup + StreamSubscription? _foregroundMessageSubscription; + StreamSubscription? _messageOpenedAppSubscription; + StreamSubscription? _tokenRefreshSubscription; + + @override + Future init() async { + try { + _logger.i('$_tag: Initializing Firebase notification client'); + + // Initialize Firebase Messaging + _firebaseMessaging = FirebaseMessaging.instance; + + final isGranted = await requestPermissions(); + if (!isGranted) { + _logger.w('$_tag: Notification permissions not granted'); + return; + } + _isGranted = isGranted; + + // Setup FCM handlers if background notifications are enabled + await _setupFCMHandlers(); + + // Get FCM token and register if backend registration is enabled + await getFCMToken(); + + // Clear badge count as the last step of initialization + await clearBadge(); + + _logger.i('$_tag: Firebase notification client initialized successfully'); + } catch (e, s) { + _logger.e('$_tag: Error initializing Firebase notification client', e, s); + throw NotificationException(message: e.message()); + } + } + + @override + Future requestPermissions({bool provisional = false}) async { + return await _permissionManager.requestPermissions(); + } + + @override + Future clearBadge() async { + try { + _logger.i('๐Ÿ”” Clearing badge count'); + if (!_isGranted) { + _logger.w( + '๐Ÿ”” Notification permissions ' + 'not granted, cannot clear badge', + ); + return; + } + if (!await AppBadgePlus.isSupported()) { + _logger.w('๐Ÿ”” AppBadgePlus is not supported on this platform'); + return; + } + await AppBadgePlus.updateBadge(0); + _logger.i('๐Ÿ”” Badge count cleared'); + } catch (e) { + _logger.w('๐Ÿ”” Failed to clear badge count: $e'); + } + } + + Future _setupFCMHandlers() async { + // Handle background messages (when app is terminated) + FirebaseMessaging.onBackgroundMessage(_handleBackgroundNotification); + + // Handle foreground messages + _foregroundMessageSubscription = FirebaseMessaging.onMessage.listen( + (RemoteMessage message) { + _logger.i('$_tag: Received foreground message: ${message.messageId}'); + handleForegroundNotification(message); + }, + onError: (error) { + _logger.e('$_tag: Error in foreground message handler', error); + }, + ); + + // Handle notification opened (from background/terminated) + _messageOpenedAppSubscription = FirebaseMessaging.onMessageOpenedApp.listen( + (RemoteMessage message) { + _logger.i('$_tag: Notification opened app: ${message.messageId}'); + handleNotificationOpened(message, source: 'background'); + }, + onError: (error) { + _logger.e('$_tag: Error in notification opened handler', error); + }, + ); + + // Handle initial message (if app was opened from terminated state) + if (_firebaseMessaging != null) { + final initialMessage = await _firebaseMessaging!.getInitialMessage(); + if (initialMessage != null) { + _logger.i( + '$_tag: App opened from terminated state: ${initialMessage.messageId}', + ); + handleNotificationOpened(initialMessage, source: 'terminated'); + } + + // Handle token refresh + _tokenRefreshSubscription = _firebaseMessaging!.onTokenRefresh.listen( + (String newToken) { + _fcmToken = newToken; + _tokenManager.handleTokenRefresh(newToken); + }, + onError: (error) { + _logger.e('$_tag: Error in token refresh handler', error); + }, + ); + } + + _logger.i('$_tag: FCM handlers setup complete'); + } + + @override + Future getFCMToken() async { + try { + _fcmToken = await _tokenManager.getFCMToken(); + + if (_fcmToken != null) { + _deviceId = await _tokenManager.registerToken(_fcmToken!); + _logger.i('$_tag: Token registered with device ID: $_deviceId'); + } + + return _fcmToken; + } catch (e, s) { + _logger.e('$_tag: Error getting FCM token', e, s); + return null; + } + } + + /// Handle background notification (when app is not running) + @pragma('vm:entry-point') + static Future _handleBackgroundNotification( + RemoteMessage message, + ) async { + try { + debugPrint( + '$_tag: Handling background notification: ${message.messageId}', + ); + } catch (e, s) { + debugPrint( + '$_tag: Error handling background notification:' + ' ${message.messageId} - $e $s', + ); + } + } + + @override + Future handleForegroundNotification(RemoteMessage message) async { + try { + _logger.i( + '$_tag: Handling foreground notification: ${message.messageId}', + ); + + final notification = message.notification; + if (notification != null && + notification.title != null && + notification.body != null) { + Toast.notification( + title: notification.title!, + body: notification.body!, + ); + } + } catch (e, s) { + _logger.e('$_tag: Error handling foreground notification', e, s); + } + } + + @override + Future handleNotificationOpened( + RemoteMessage message, { + String? source, + }) async { + try { + _logger.i('$_tag: Handling notification opened: ${message.messageId}'); + + final notification = message.notification; + + _logger.i( + '$_tag: Notification opened from $source: ${notification?.title} - ${notification?.body}', + ); + + // Deep link contract: send a `route` in the message data and the app + // navigates there, e.g. {"route": "/home/orders/42"}. Branch on your own + // payload fields here if you need something richer than a path. + final route = message.data['route']; + if (route is! String || route.isEmpty) return; + + di.get().push(route); + } catch (e, s) { + _logger.e('$_tag: Error handling notification opened', e, s); + } + } + + @override + String? get fcmToken => _fcmToken; + + @override + String? get deviceId => _deviceId; + + @override + Future dispose() async { + _logger.i('$_tag: Disposing Firebase notification client'); + + // Cancel all subscriptions to prevent memory leaks + _foregroundMessageSubscription?.cancel(); + _messageOpenedAppSubscription?.cancel(); + _tokenRefreshSubscription?.cancel(); + + _foregroundMessageSubscription = null; + _messageOpenedAppSubscription = null; + _tokenRefreshSubscription = null; + + _firebaseMessaging = null; + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/index.dart new file mode 100644 index 0000000..d7fab81 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/index.dart @@ -0,0 +1,2 @@ +export 'firebase_notification_client.dart'; +export 'notification_client.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/notification_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/notification_client.dart new file mode 100644 index 0000000..38007ce --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/notification_client.dart @@ -0,0 +1,27 @@ +import 'package:firebase_messaging/firebase_messaging.dart' + hide NotificationSettings; + +/// Abstract notification client interface +/// Simplified interface following network package patterns +abstract class NotificationClient { + Future init(); + + Future requestPermissions({bool provisional = false}); + + Future getFCMToken(); + + Future handleForegroundNotification(RemoteMessage message); + + Future handleNotificationOpened( + RemoteMessage message, { + String? source, + }); + + Future clearBadge(); + + String? get fcmToken; + + String? get deviceId; + + Future dispose(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/config/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/config/index.dart new file mode 100644 index 0000000..8ca3673 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/config/index.dart @@ -0,0 +1 @@ +export 'notification_config.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/config/notification_config.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/config/notification_config.dart new file mode 100644 index 0000000..54d3860 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/config/notification_config.dart @@ -0,0 +1,7 @@ +class NotificationConfig { + const NotificationConfig(); +} + +class DefaultNotificationConfig extends NotificationConfig { + const DefaultNotificationConfig() : super(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/device_info.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/device_info.dart new file mode 100644 index 0000000..7e9166c --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/device_info.dart @@ -0,0 +1,5 @@ +abstract class DeviceInfo { + Future generateDeviceId(); + + Future getDeviceName(); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/device_info_impl.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/device_info_impl.dart new file mode 100644 index 0000000..9f11e5a --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/device_info_impl.dart @@ -0,0 +1,210 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:core/logger/logger.dart'; +import 'package:crypto/crypto.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:di/di.dart'; +import 'package:notifications/src/device_info/device_info.dart'; + +/// Concrete implementation of DeviceInfo for Android and iOS only +/// Generates consistent device identifiers using stable device characteristics +class DeviceInfoImpl implements DeviceInfo { + static const String _tag = 'DeviceInfoImpl'; + + final Logger _logger; + final DeviceInfoPlugin _deviceInfoPlugin; + + DeviceInfoImpl({Logger? logger, DeviceInfoPlugin? deviceInfoPlugin}) + : _logger = logger ?? di.get(), + _deviceInfoPlugin = deviceInfoPlugin ?? DeviceInfoPlugin(); + + @override + Future generateDeviceId() async { + try { + if (Platform.isAndroid) { + return await _generateAndroidDeviceId(); + } else if (Platform.isIOS) { + return await _generateIOSDeviceId(); + } else { + _logger.w('$_tag: Unsupported platform: ${Platform.operatingSystem}'); + throw UnsupportedError('Only Android and iOS platforms are supported'); + } + } catch (e) { + _logger.w('$_tag: Error generating device ID: $e'); + return _generateFallbackId(); + } + } + + @override + Future getDeviceName() async { + try { + if (Platform.isAndroid) { + return await _getAndroidDeviceName(); + } else if (Platform.isIOS) { + return await _getIOSDeviceName(); + } else { + _logger.w('$_tag: Unsupported platform: ${Platform.operatingSystem}'); + throw UnsupportedError('Only Android and iOS platforms are supported'); + } + } catch (e) { + _logger.w('$_tag: Error getting device name: $e'); + return _getFallbackDeviceName(); + } + } + + /// Generate Android device ID using stable hardware characteristics + Future _generateAndroidDeviceId() async { + try { + final androidInfo = await _deviceInfoPlugin.androidInfo; + + // Use only the most stable hardware characteristics that don't change + final stableCharacteristics = [ + androidInfo.brand, + androidInfo.model, + androidInfo.device, + androidInfo.hardware, + androidInfo.board, + androidInfo.bootloader, + androidInfo.product, + // fingerprint contains build info but is generally stable for same device + androidInfo.fingerprint, + // Physical device indicator + androidInfo.isPhysicalDevice.toString(), + // Hardware features that are stable + androidInfo.supportedAbis.join(','), + androidInfo.supported32BitAbis.join(','), + androidInfo.supported64BitAbis.join(','), + ]; + + // Remove empty values and create a deterministic string + final cleanCharacteristics = stableCharacteristics + .where((char) => char.isNotEmpty && char != 'null') + .toList(); + + if (cleanCharacteristics.isEmpty) { + throw Exception('No stable characteristics found for Android device'); + } + + // Sort to ensure consistent ordering + cleanCharacteristics.sort(); + + final deviceString = cleanCharacteristics.join('|'); + final deviceHash = _generateDeterministicHash(deviceString); + + _logger.d( + '$_tag: Generated Android device ID from ' + '${cleanCharacteristics.length} characteristics', + ); + return 'android_$deviceHash'; + } catch (e) { + _logger.w('$_tag: Android device ID generation failed: $e'); + rethrow; + } + } + + /// Generate iOS device ID using identifierForVendor or stable characteristics + Future _generateIOSDeviceId() async { + try { + final iosInfo = await _deviceInfoPlugin.iosInfo; + + // First preference: use identifierForVendor as it's designed to be stable + final identifier = iosInfo.identifierForVendor; + if (identifier != null && identifier.isNotEmpty && identifier != 'null') { + _logger.d('$_tag: Using iOS identifierForVendor'); + return 'ios_$identifier'; + } + + // Fallback: use stable device characteristics + _logger.i( + '$_tag: iOS identifierForVendor not available, ' + 'using device characteristics', + ); + + final stableCharacteristics = [ + iosInfo.model, + iosInfo.localizedModel, + iosInfo.systemName, + iosInfo.isPhysicalDevice.toString(), + // Machine identifier from utsname (hardware model) + iosInfo.utsname.machine, + iosInfo.utsname.sysname, + // These are generally stable for the same device model + iosInfo.utsname.nodename, + ]; + + // Remove empty values and create a deterministic string + final cleanCharacteristics = stableCharacteristics + .where((char) => char.isNotEmpty && char != 'null') + .toList(); + + if (cleanCharacteristics.isEmpty) { + throw Exception('No stable characteristics found for iOS device'); + } + + // Sort to ensure consistent ordering + cleanCharacteristics.sort(); + + final deviceString = cleanCharacteristics.join('|'); + final deviceHash = _generateDeterministicHash(deviceString); + + _logger.d( + '$_tag: Generated iOS device ID from ' + '${cleanCharacteristics.length} characteristics', + ); + return 'ios_$deviceHash'; + } catch (e) { + _logger.w('$_tag: iOS device ID generation failed: $e'); + rethrow; + } + } + + /// Get Android device name in format "Brand Model" + Future _getAndroidDeviceName() async { + final androidInfo = await _deviceInfoPlugin.androidInfo; + final brand = androidInfo.brand; + final model = androidInfo.model; + + return '$brand $model'; + } + + /// Get iOS device name in format "Name (Model)" + Future _getIOSDeviceName() async { + final iosInfo = await _deviceInfoPlugin.iosInfo; + final name = iosInfo.name; + final model = iosInfo.model; + + return '$name ($model)'; + } + + /// Generate a deterministic hash from device characteristics + /// Uses SHA-256 to create a consistent, stable hash + String _generateDeterministicHash(String input) { + // Add a salt to make the hash more unique while keeping it deterministic + const salt = 'device_identifier_salt_2024'; + final saltedInput = '$salt|$input'; + + final bytes = utf8.encode(saltedInput); + final digest = sha256.convert(bytes); + + // Return first 16 characters for reasonable length + return digest.toString().substring(0, 16); + } + + /// Generate a fallback device ID when all other methods fail + String _generateFallbackId() { + final platform = Platform.isAndroid ? 'android' : 'ios'; + + // Use a deterministic fallback based on platform + final fallbackString = '${platform}_fallback_device'; + final fallbackHash = _generateDeterministicHash(fallbackString); + + _logger.w('$_tag: Using deterministic fallback ID'); + return '${platform}_$fallbackHash'; + } + + /// Get fallback device name when detection fails + String _getFallbackDeviceName() { + return Platform.isAndroid ? 'Android Device' : 'iOS Device'; + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/index.dart new file mode 100644 index 0000000..2bdcc90 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/index.dart @@ -0,0 +1,2 @@ +export 'device_info.dart'; +export 'device_info_impl.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/di/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/di/index.dart new file mode 100644 index 0000000..44acbaa --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/di/index.dart @@ -0,0 +1 @@ +export 'notification_di.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/di/notification_di.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/di/notification_di.dart new file mode 100644 index 0000000..160c0a7 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/di/notification_di.dart @@ -0,0 +1,27 @@ +import 'package:di/di.dart'; +import 'package:notifications/src/client/index.dart'; +import 'package:notifications/src/config/index.dart'; +import 'package:notifications/src/device_info/index.dart'; +import 'package:notifications/src/services/notification_permission_manager.dart'; +import 'package:notifications/src/services/notification_token_manager.dart'; + +/// Register notification services with dependency injection +Future registerNotificationWithDI(NotificationConfig config) async { + // Register config + di.register(config); + + // Register device info service + di.register(DeviceInfoImpl()); + + // Register token manager + di.register(FirebaseTokenManager()); + + // Register permission manager + di.register(FirebasePermissionManager()); + + // Register notification client with proper disposal + di.register( + FirebaseNotificationClient(config: config), + dispose: (client) => client.dispose(), + ); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/exceptions/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/exceptions/index.dart new file mode 100644 index 0000000..27be259 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/exceptions/index.dart @@ -0,0 +1 @@ +export 'notification_exceptions.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/exceptions/notification_exceptions.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/exceptions/notification_exceptions.dart new file mode 100644 index 0000000..603caf4 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/exceptions/notification_exceptions.dart @@ -0,0 +1,5 @@ +import 'package:core/core.dart'; + +class NotificationException extends CoreException { + NotificationException({required super.message}); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/device_token_models.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/device_token_models.dart new file mode 100644 index 0000000..c6236a7 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/device_token_models.dart @@ -0,0 +1,35 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'device_token_models.g.dart'; + +@JsonSerializable() +class DeviceTokenRequest { + final String fcmToken; + final String deviceId; + final String deviceName; + final String deviceType; + + const DeviceTokenRequest({ + required this.fcmToken, + required this.deviceId, + required this.deviceName, + required this.deviceType, + }); + + factory DeviceTokenRequest.fromJson(Map json) => + _$DeviceTokenRequestFromJson(json); + + Map toJson() => _$DeviceTokenRequestToJson(this); +} + +@JsonSerializable() +class DeviceTokenUpdateRequest { + final String fcmToken; + + const DeviceTokenUpdateRequest({required this.fcmToken}); + + factory DeviceTokenUpdateRequest.fromJson(Map json) => + _$DeviceTokenUpdateRequestFromJson(json); + + Map toJson() => _$DeviceTokenUpdateRequestToJson(this); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/device_token_models.g.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/device_token_models.g.dart new file mode 100644 index 0000000..bfadaaa --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/device_token_models.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'device_token_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DeviceTokenRequest _$DeviceTokenRequestFromJson(Map json) => + DeviceTokenRequest( + fcmToken: json['fcmToken'] as String, + deviceId: json['deviceId'] as String, + deviceName: json['deviceName'] as String, + deviceType: json['deviceType'] as String, + ); + +Map _$DeviceTokenRequestToJson(DeviceTokenRequest instance) => + { + 'fcmToken': instance.fcmToken, + 'deviceId': instance.deviceId, + 'deviceName': instance.deviceName, + 'deviceType': instance.deviceType, + }; + +DeviceTokenUpdateRequest _$DeviceTokenUpdateRequestFromJson( + Map json, +) => DeviceTokenUpdateRequest(fcmToken: json['fcmToken'] as String); + +Map _$DeviceTokenUpdateRequestToJson( + DeviceTokenUpdateRequest instance, +) => {'fcmToken': instance.fcmToken}; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/index.dart new file mode 100644 index 0000000..c8ac690 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/index.dart @@ -0,0 +1 @@ +export 'device_token_models.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/index.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/index.dart new file mode 100644 index 0000000..78c2c15 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/index.dart @@ -0,0 +1,2 @@ +export 'notification_permission_manager.dart'; +export 'notification_token_manager.dart'; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/notification_permission_manager.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/notification_permission_manager.dart new file mode 100644 index 0000000..3c5cd20 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/notification_permission_manager.dart @@ -0,0 +1,45 @@ +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:network/network.dart'; +import 'package:notifications/src/exceptions/notification_exceptions.dart'; + +abstract class NotificationPermissionManager { + Future requestPermissions(); +} + +class FirebasePermissionManager implements NotificationPermissionManager { + static const String _tag = 'FirebasePermissionManager'; + + final Logger _logger; + final FirebaseMessaging _firebaseMessaging; + + FirebasePermissionManager({ + Logger? logger, + FirebaseMessaging? firebaseMessaging, + }) : _logger = logger ?? di.get(), + _firebaseMessaging = firebaseMessaging ?? FirebaseMessaging.instance; + + @override + Future requestPermissions() async { + try { + _logger.i('$_tag: Requesting notification permissions'); + final authSettings = await _firebaseMessaging.requestPermission(); + + final isGranted = + authSettings.authorizationStatus == AuthorizationStatus.authorized || + authSettings.authorizationStatus == AuthorizationStatus.provisional; + + _logger.i( + '$_tag: Notification permissions requested ' + 'with status: ${authSettings.authorizationStatus}, ' + 'granted: $isGranted', + ); + + return isGranted; + } catch (e, s) { + _logger.e('$_tag: Error requesting permissions', e, s); + throw NotificationException(message: e.message()); + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/notification_token_manager.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/notification_token_manager.dart new file mode 100644 index 0000000..fc10913 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/notification_token_manager.dart @@ -0,0 +1,103 @@ +import 'dart:io'; + +import 'package:core/logger/logger.dart'; +import 'package:di/di.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:network/network.dart'; +import 'package:notifications/notifications.dart'; + +abstract class NotificationTokenManager { + Future getFCMToken(); + + Future registerToken(String fcmToken); + + Future unRegisterToken(String deviceId); + + Future handleTokenRefresh(String newToken); +} + +class FirebaseTokenManager implements NotificationTokenManager { + static const String _tag = 'FirebaseTokenManager'; + + final Logger _logger; + final NetworkClient _networkClient; + final DeviceInfo _deviceInfo; + final FirebaseMessaging _firebaseMessaging; + + FirebaseTokenManager({ + Logger? logger, + NetworkClient? networkClient, + DeviceInfo? deviceInfo, + FirebaseMessaging? firebaseMessaging, + }) : _logger = logger ?? di.get(), + _networkClient = networkClient ?? di.get(), + _deviceInfo = deviceInfo ?? di.get(), + _firebaseMessaging = firebaseMessaging ?? FirebaseMessaging.instance; + + @override + Future getFCMToken() async { + try { + final token = await _firebaseMessaging.getToken(); + if (token != null) { + _logger.i('$_tag: FCM Token obtained successfully'); + } else { + _logger.w('$_tag: Failed to obtain FCM token'); + } + return token; + } catch (e, s) { + _logger.e('$_tag: Error getting FCM token', e, s); + rethrow; + } + } + + @override + Future registerToken(String fcmToken) async { + try { + _logger.i('$_tag: Registering FCM token with backend'); + + final deviceId = await _deviceInfo.generateDeviceId(); + final deviceName = await _deviceInfo.getDeviceName(); + final deviceType = Platform.isIOS ? 'ios' : 'android'; + + final request = DeviceTokenRequest( + fcmToken: fcmToken, + deviceId: deviceId, + deviceName: deviceName, + deviceType: deviceType, + ); + + await _networkClient.post>( + '/device-tokens/me', + data: request.toJson(), + fromJsonT: (json) => json as Map, + ); + _logger.i('$_tag: FCM token registered successfully'); + return deviceId; + } catch (e, s) { + _logger.e('$_tag: Error registering FCM token', e, s); + rethrow; + } + } + + @override + Future unRegisterToken(String deviceId) async { + try { + _logger.i('$_tag: Removing FCM token from backend'); + + await _networkClient.delete>( + '/device-tokens/me/$deviceId', + fromJsonT: (json) => json as Map, + ); + _logger.i('$_tag: FCM token removed successfully'); + } catch (e, s) { + _logger.e('$_tag: Error removing FCM token', e, s); + rethrow; + } + } + + @override + Future handleTokenRefresh(String newToken) async { + _logger.i('$_tag: FCM token refreshed'); + await registerToken(newToken); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml new file mode 100644 index 0000000..e555076 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml @@ -0,0 +1,41 @@ +name: notifications +description: "Firebase Cloud Messaging, permissions and device-token management for {{name.titleCase()}}." +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ^3.8.0 + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + app_badge_plus: ^1.1.2 + crypto: ^3.0.6 + device_info_plus: ^11.1.1 + firebase_core: ^3.14.0 + firebase_messaging: ^15.1.6 + go_router: ^15.1.3 + json_annotation: ^4.9.0 + permission_handler: ^11.1.0 + timezone: ^0.9.4 + core: + path: ../core + design_system: + path: ../design_system + di: + path: ../di +{{#network}} network: + path: ../network +{{/network}}{{#analytics}} analytics: + path: ../analytics +{{/analytics}} +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + build_runner: ^2.4.15 + json_serializable: ^6.9.5 + +flutter: + uses-material-design: true diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/plugins/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/plugins/README.md index d1f52ac..38747c9 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/plugins/README.md +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/plugins/README.md @@ -1 +1,7 @@ -# Put all your plugins in this folder +# Plugins + +Platform channels and native integrations. + +```sh +nonstop create plugin my_plugin -o plugins +``` diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/pubspec.yaml index 6211a92..07334b7 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/pubspec.yaml @@ -1,8 +1,9 @@ name: {{ name.snakeCase() }}_workspace -description: {{ description }} +description: "{{{description}}}" +publish_to: 'none' environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.8.0 <4.0.0' dev_dependencies: flutter_lints: ^5.0.0 diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/brick.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/brick.yaml index 6b5f65d..439affd 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/brick.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/brick.yaml @@ -1,9 +1,9 @@ name: flutter_project_with_mono_repo -description: A Flutter project within a Melos-managed mono-repo. +description: A production-shaped Flutter mono-repo - app shell, design system, localization and the platform modules you pick. -repository: https://github.com/nonstopio/flutter_forge/tree/main/plugins/nonstop_cli +repository: https://github.com/nonstopio/flutter_forge/tree/main/packages/nonstop_cli -version: 1.0.0 +version: 2.0.0 environment: mason: ">=0.1.0-dev.52 <0.1.0" @@ -22,3 +22,48 @@ vars: description: Organization name prompt: What is your organization name? default: com.example + network: + type: boolean + description: Dio-based HTTP client with auth and logging interceptors + prompt: Include the network package? + default: true + auth: + type: boolean + description: Firebase Authentication with email, Google and Apple sign-in + prompt: Include authentication? + default: true + firestore: + type: boolean + description: Cloud Firestore and Cloud Functions wiring + prompt: Include Cloud Firestore? + default: false + notifications: + type: boolean + description: Firebase Cloud Messaging, permissions and token registration + prompt: Include push notifications? + default: true + analytics: + type: boolean + description: Event tracking behind a swappable client + prompt: Include analytics? + default: true + crashlytics: + type: boolean + description: Crash and non-fatal error reporting + prompt: Include crash reporting? + default: true + feature_flags: + type: boolean + description: Firebase Remote Config feature flags + prompt: Include feature flags? + default: true + developer: + type: boolean + description: In-app developer tools behind a hidden gesture + prompt: Include developer tools? + default: true + dashboard: + type: boolean + description: Bottom-navigation shell with starter tabs + prompt: Include the dashboard shell? + default: true diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/checked_process.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/checked_process.dart new file mode 100644 index 0000000..d787477 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/checked_process.dart @@ -0,0 +1,51 @@ +import 'dart:io'; + +import 'package:mason/mason.dart'; + +/// Runs [executable] and throws when it fails. +/// +/// `cli_core`'s `trackOperation` reports success as soon as the future +/// completes, so a non-zero exit code shows a green tick and generation +/// carries on to produce a half-built project. Every step here is load-bearing +/// - if `flutter create` or `melos bootstrap` fails there is nothing worth +/// continuing for - so the exit code is checked and the child's stderr is +/// surfaced. +Future runChecked( + HookContext context, { + required String startMessage, + required String endMessage, + required String executable, + required List arguments, + String? workingDirectory, +}) async { + final progress = context.logger.progress(startMessage); + + final ProcessResult result; + try { + result = await Process.run( + executable, + arguments, + workingDirectory: workingDirectory, + runInShell: true, + ); + } catch (error) { + progress.fail('$startMessage failed: $error'); + rethrow; + } + + if (result.exitCode != 0) { + progress.fail('$startMessage failed (exit ${result.exitCode})'); + final details = '${result.stderr}'.trim().isNotEmpty + ? '${result.stderr}'.trim() + : '${result.stdout}'.trim(); + if (details.isNotEmpty) context.logger.err(details); + throw ProcessException( + executable, + arguments, + 'exited with ${result.exitCode}', + result.exitCode, + ); + } + + progress.complete(endMessage); +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/flutter_create_command.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/flutter_create_command.dart index 267a00b..7219430 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/flutter_create_command.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/flutter_create_command.dart @@ -1,29 +1,54 @@ import 'dart:async'; +import 'dart:io'; -import 'package:cli_core/cli_core.dart' show BaseFlutterCommand; +import 'package:cli_core/cli_core.dart' show CliCommand, FileUtils; import 'package:mason/mason.dart'; import 'package:path/path.dart' as p; -final class FlutterCreateCommand extends BaseFlutterCommand { +import 'checked_process.dart'; + +/// Scaffolds the application with `flutter create`. +/// +/// This runs in pre-gen, so the brick's own `lib/` and `pubspec.yaml` are +/// written over the counter-app that `flutter create` produces. +final class FlutterCreateCommand extends CliCommand { @override Future run(HookContext context) async { final String name = context.vars['name']; final String description = context.vars['description']; final String orgName = context.vars['org_name'] ?? 'com.example'; final appName = name.snakeCase; - final outputPath = p.normalize('$appName/apps'); - - await createFlutterProject( - context: context, - name: appName, - description: description, - outputPath: outputPath, - orgName: orgName, - ); + final appsPath = p.normalize('$appName/apps'); + + await FileUtils.ensureDirectory(appsPath); - await removeAnalysisOptions( - context: context, - projectPath: p.join(outputPath, appName), + await runChecked( + context, + startMessage: 'Creating Flutter app: $appName', + endMessage: 'Flutter app created', + executable: 'flutter', + arguments: [ + 'create', + appName, + '--template=app', + '--platforms=ios,android,web', + '--description=$description', + '--org=$orgName', + ], + workingDirectory: appsPath, ); + + final appPath = p.join(appsPath, appName); + + // The workspace owns lint configuration. + await FileUtils.deleteFile(p.join(appPath, 'analysis_options.yaml')); + + // `flutter create` leaves a widget test for the counter app it just wrote; + // the brick replaces that app, so the test would not compile. + await FileUtils.deleteFile(p.join(appPath, 'test', 'widget_test.dart')); + final testDir = Directory(p.join(appPath, 'test')); + if (testDir.existsSync() && testDir.listSync().isEmpty) { + await testDir.delete(); + } } } diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/format_command.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/format_command.dart new file mode 100644 index 0000000..ad82202 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/format_command.dart @@ -0,0 +1,25 @@ +import 'package:cli_core/cli_core.dart' show CliCommand; +import 'package:mason/mason.dart'; + +import 'checked_process.dart'; + +/// Formats the generated workspace. +/// +/// Templates are rendered from mustache, so the exact line breaks depend on +/// which modules were selected. Running the formatter once here means a fresh +/// project passes its own `melos lint` whatever the answers were. +final class FormatCommand extends CliCommand { + @override + Future run(HookContext context) async { + final String name = context.vars['name']; + + await runChecked( + context, + startMessage: 'Formatting generated code', + endMessage: 'Code formatted', + executable: 'dart', + arguments: ['format', '.'], + workingDirectory: name.snakeCase, + ); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/melos_command.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/melos_command.dart index 90bfb21..15ad78a 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/melos_command.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/melos_command.dart @@ -1,28 +1,29 @@ -import 'dart:io'; - -import 'package:cli_core/cli_core.dart' show BaseMelosCommand; +import 'package:cli_core/cli_core.dart' show CliCommand; import 'package:mason/mason.dart'; -final class MelosCommand extends BaseMelosCommand { +import 'checked_process.dart'; + +final class MelosCommand extends CliCommand { @override Future run(HookContext context) async { final String name = context.vars['name']; final appName = name.snakeCase; - await trackOperation( + await runChecked( context, startMessage: 'Activating Melos globally', endMessage: 'Melos activated globally', - operation: () => Process.run( - 'dart', - ['pub', 'global', 'activate', 'melos'], - runInShell: true, - ), + executable: 'dart', + arguments: ['pub', 'global', 'activate', 'melos'], ); - await bootstrap( - context: context, - workspacePath: appName, + await runChecked( + context, + startMessage: 'Running melos bootstrap', + endMessage: 'Dependencies installed', + executable: 'melos', + arguments: ['bootstrap'], + workingDirectory: appName, ); } } diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/module_vars.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/module_vars.dart new file mode 100644 index 0000000..b152038 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/module_vars.dart @@ -0,0 +1,54 @@ +import 'package:mason/mason.dart'; + +/// Optional modules, each of which maps to a directory in the brick. +/// +/// A directory named `{{_dir}}` renders to an empty path segment when +/// the module is off, and mason skips every file underneath it. +const optionalModules = [ + 'network', + 'notifications', + 'analytics', + 'crashlytics', + 'feature_flags', + 'developer', + 'auth', + 'dashboard', +]; + +/// Modules that cannot work without `Firebase.initializeApp`. +const _firebaseBackedModules = [ + 'notifications', + 'analytics', + 'crashlytics', + 'feature_flags', + 'auth', + 'firestore', +]; + +bool _flag(HookContext context, String key) => context.vars[key] == true; + +/// Normalises the module answers and derives everything the templates read. +void resolveModuleVars(HookContext context) { + // A module can be implied by another (dev tools read feature flags), so + // resolve implications before deriving anything from them. `nonstop create` + // already does this in lib/commands/create/modules.dart; it is repeated here + // so `mason make` on its own still produces a project that compiles. + if (_flag(context, 'developer')) context.vars['feature_flags'] = true; + if (_flag(context, 'notifications')) context.vars['network'] = true; + + for (final module in optionalModules) { + final enabled = _flag(context, module); + context.vars[module] = enabled; + context.vars['${module}_dir'] = enabled ? module : ''; + } + + context.vars['firestore'] = _flag(context, 'firestore'); + final usesFirebase = + _firebaseBackedModules.any((module) => _flag(context, module)); + context.vars['firebase'] = usesFirebase; + + // The boot test runs the real bootstrap, which a Firebase project cannot do + // until `flutterfire configure` has been run. An empty file name makes mason + // skip the file, so those projects simply ship without it. + context.vars['smoke_test_file'] = usesFirebase ? '' : 'smoke_test.dart'; +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/next_steps_command.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/next_steps_command.dart new file mode 100644 index 0000000..5fe5092 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/next_steps_command.dart @@ -0,0 +1,28 @@ +import 'package:cli_core/cli_core.dart' show CliCommand; +import 'package:mason/mason.dart'; + +/// Prints the handful of things the generator cannot do for you. +final class NextStepsCommand extends CliCommand { + @override + Future run(HookContext context) async { + final String name = context.vars['name']; + final appName = name.snakeCase; + final logger = context.logger; + + if (context.vars['firebase'] == true) { + logger + ..info('') + ..warn('Firebase modules are enabled but not configured yet.') + ..info(' dart pub global activate flutterfire_cli') + ..info(' cd $appName/apps/$appName && flutterfire configure') + ..info( + ' (until then lib/firebase_options.dart throws on purpose)', + ); + } + + logger + ..info('') + ..info('Run the app:') + ..info(' cd $appName/apps/$appName && flutter run'); + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/post_gen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/post_gen.dart index 8008827..2db5689 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/post_gen.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/post_gen.dart @@ -2,13 +2,15 @@ import 'dart:async'; import 'package:mason/mason.dart'; -import 'commands/flutter_create_command.dart'; +import 'commands/format_command.dart'; import 'commands/melos_command.dart'; +import 'commands/next_steps_command.dart'; Future run(HookContext context) async { final commands = [ - FlutterCreateCommand(), + FormatCommand(), MelosCommand(), + NextStepsCommand(), ]; for (final command in commands) { diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pre_gen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pre_gen.dart index 85edb08..a85eaba 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pre_gen.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pre_gen.dart @@ -3,7 +3,17 @@ import 'dart:async'; import 'package:cli_core/cli_core.dart'; import 'package:mason/mason.dart'; +import 'commands/flutter_create_command.dart'; +import 'commands/module_vars.dart'; + Future run(HookContext context) async { - // Check if we're in a mono repo using the shared utility context.vars['is_mono_repo'] = await FileUtils.isMonoRepo(); + + // Turn the module answers into the `*_dir` path variables the brick uses to + // include or skip whole directories, and derive `firebase`. + resolveModuleVars(context); + + // The app is scaffolded before generation so the brick's `lib/` and + // `pubspec.yaml` land on top of what `flutter create` produced. + await FlutterCreateCommand().run(context); } diff --git a/packages/nonstop_cli/lib/commands/create/command.dart b/packages/nonstop_cli/lib/commands/create/command.dart index df63d75..d175c8d 100644 --- a/packages/nonstop_cli/lib/commands/create/command.dart +++ b/packages/nonstop_cli/lib/commands/create/command.dart @@ -5,12 +5,17 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:mason/mason.dart'; import 'package:meta/meta.dart'; +import 'package:nonstop_cli/commands/create/modules.dart'; import 'package:nonstop_cli/commands/create/templates.dart'; import 'package:nonstop_cli/template.dart'; import 'package:nonstop_cli/utils/utils.dart'; import 'package:path/path.dart' as path; const _defaultOrgName = 'com.example'; + +/// Characters that cannot survive being written into a double-quoted YAML +/// scalar and a Dart string literal unchanged. +const _unsafeDescriptionCharacters = ['"', r'\', r'$']; const _defaultDescription = 'A Melos-managed project for mono-repo, created using NonStop CLI.'; @@ -54,7 +59,22 @@ class CreateCommand extends Command { 'Generate a Flutter application for a Melos-managed mono-repo.', 'plugin': 'Generate a Flutter plugin for a Melos-managed mono-repo.', }, + ) + ..addFlag( + 'defaults', + abbr: 'y', + negatable: false, + help: 'Skip the module picker and accept the recommended selection.', ); + + for (final module in templateModules) { + argParser.addFlag( + module.flag, + help: '${module.description}.', + defaultsTo: null, + hide: true, + ); + } } final Logger logger; @@ -104,7 +124,53 @@ class CreateCommand extends Command { return args.first; } - String get projectDescription => argResults['description'] as String? ?? ''; + String get projectDescription { + final description = _askIfUnset( + 'description', + 'Describe ${lightCyan.wrap(projectName)}:', + ); + _validateDescription(description); + return description; + } + + /// The description is interpolated into YAML, Dart string literals and + /// Markdown, so a few characters would produce a project that does not + /// parse. Rejecting them beats silently mangling the text. + void _validateDescription(String description) { + final offending = + _unsafeDescriptionCharacters.where(description.contains).toList(); + if (offending.isEmpty) return; + + usageException( + 'The description cannot contain ${offending.map((c) => '"$c"').join(', ')}.\n\n' + 'It is written into pubspec.yaml, Dart string literals and README.md, ' + 'and these characters break at least one of them.', + ); + } + + /// Whether this process can hold an interactive conversation. + /// + /// Both ends have to be a terminal: mason_logger reads from stdin but draws + /// the prompt on stdout, and piping either one is enough to break it. + bool get _canPrompt => + argResults['defaults'] != true && stdin.hasTerminal && stdout.hasTerminal; + + /// Returns [option], prompting for it when the user did not pass it and + /// there is a terminal to ask on. + /// + /// Falling back to the option's default keeps scripted and CI runs working. + String _askIfUnset(String option, String question) { + final fallback = argResults[option] as String? ?? ''; + if (argResults.wasParsed(option) || !_canPrompt) return fallback; + try { + final answer = logger.prompt(question, defaultValue: fallback).trim(); + return answer.isEmpty ? fallback : answer; + } catch (error) { + // Some CI runners advertise a terminal they cannot actually drive. + logger.detail('Prompt unavailable, using default: $error'); + return fallback; + } + } @override String get invocation => 'nonstop $name [arguments]'; @@ -179,7 +245,14 @@ class CreateCommand extends Command { logger: logger, ); - final _ = await generator.generate(target, vars: vars, logger: logger); + // The app skeleton is scaffolded by `flutter create` in the pre-gen hook, + // so the brick's own `lib/` and `pubspec.yaml` are expected to land on top. + final _ = await generator.generate( + target, + vars: vars, + logger: logger, + fileConflictResolution: FileConflictResolution.overwrite, + ); await generator.hooks.postGen( vars: vars, @@ -204,13 +277,80 @@ class CreateCommand extends Command { 'name': projectName, 'description': projectDescription, 'org_name': orgName, + if (argResults['template'] == 'mono') ...selectedModules(), + }; + } + + /// Resolves which optional modules the generated mono-repo should contain. + /// + /// Explicit `--network` / `--no-network` style flags always win. Whatever is + /// left is asked for once, as a single multi-select, unless `--defaults` was + /// passed or there is no terminal to ask on. + @visibleForTesting + Map selectedModules() { + final answered = { + for (final module in templateModules) + if (argResults.wasParsed(module.flag)) + module.key: argResults[module.flag] as bool, + }; + + final shouldPrompt = answered.length < templateModules.length && _canPrompt; + + final selection = shouldPrompt ? _promptForModules(answered) : answered; + + final requested = { + for (final module in templateModules) + module.key: selection[module.key] ?? module.defaultValue, + }; + final resolved = resolveModuleImplications(requested); + + for (final module in templateModules) { + if (resolved[module.key]! && !requested[module.key]!) { + logger.info( + '${lightYellow.wrap('+')} ${module.label} is required by ' + '${module.impliedBy.where((o) => resolved[o]!).join(', ')}, ' + 'adding it.', + ); + } + } + + return resolved; + } + + Map _promptForModules(Map answered) { + final pending = templateModules + .where((module) => !answered.containsKey(module.key)) + .toList(); + + logger.info(''); + final List chosen; + try { + chosen = logger.chooseAny( + 'Which modules should ${lightCyan.wrap(projectName)} start with?', + choices: pending, + defaultValues: pending.where((module) => module.defaultValue).toList(), + display: (module) => + '${module.label} ${darkGray.wrap('- ${module.description}')}', + ); + } catch (error) { + // Some CI runners advertise a terminal they cannot actually drive. + logger.detail('Module picker unavailable, using defaults: $error'); + return answered; + } + + return { + ...answered, + for (final module in pending) module.key: chosen.contains(module), }; } } extension on CreateCommand { String get orgName { - final orgName = argResults['org-name'] as String? ?? _defaultOrgName; + final orgName = _askIfUnset( + 'org-name', + 'What is the organization for ${lightCyan.wrap(projectName)}?', + ); _validateOrgName(orgName); return orgName; } diff --git a/packages/nonstop_cli/lib/commands/create/flutter_project_with_mono_repo_bundle.dart b/packages/nonstop_cli/lib/commands/create/flutter_project_with_mono_repo_bundle.dart index 758898c..1ab07a8 100644 --- a/packages/nonstop_cli/lib/commands/create/flutter_project_with_mono_repo_bundle.dart +++ b/packages/nonstop_cli/lib/commands/create/flutter_project_with_mono_repo_bundle.dart @@ -8,78 +8,1449 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/.gitignore", "data": - "Kiovbm9kZV9tb2R1bGVzCioubG9nCmJ1aWxkCmRpc3QKLnNhbml0eQouRFNfU3RvcmUKLmlkZWEKY292ZXJhZ2UKLmRhcnRfdG9vbApwdWJzcGVjX292ZXJyaWRlcy55YW1sCi5mbHV0dGVyLXBsdWdpbnMKLmZsdXR0ZXItcGx1Z2lucy1kZXBlbmRlbmNpZXMKKi5pbWwKKi5sb2NrCm1hc29uLWxvY2suanNvbgoubWFzb24K", + "Kiovbm9kZV9tb2R1bGVzCioubG9nCmJ1aWxkCmRpc3QKLnNhbml0eQouRFNfU3RvcmUKLmlkZWEKY292ZXJhZ2UKLmRhcnRfdG9vbApwdWJzcGVjX292ZXJyaWRlcy55YW1sCi5mbHV0dGVyLXBsdWdpbnMKLmZsdXR0ZXItcGx1Z2lucy1kZXBlbmRlbmNpZXMKKi5pbWwKKi5sb2NrCiMgQXBwcyBzaGlwIGEgYmluYXJ5OyBwaW4gdGhlaXIgcmVzb2x1dGlvbi4KIWFwcHMvKi9wdWJzcGVjLmxvY2sKbWFzb24tbG9jay5qc29uCi5tYXNvbgo=", "type": "text" }, { "path": "{{ name.snakeCase() }}/analysis_options.yaml", "data": - "aW5jbHVkZTogcGFja2FnZTpmbHV0dGVyX2xpbnRzL2ZsdXR0ZXIueWFtbAoKbGludGVyOgogIHJ1bGVzOgogICAgcmVxdWlyZV90cmFpbGluZ19jb21tYXM6IHRydWU=", + "aW5jbHVkZTogcGFja2FnZTpmbHV0dGVyX2xpbnRzL2ZsdXR0ZXIueWFtbAoKYW5hbHl6ZXI6CiAgZXhjbHVkZToKICAgICMgR2VuZXJhdGVkIGNvZGUgLSByZWdlbmVyYXRlIGl0LCBkbyBub3QgbGludCBpdC4KICAgIC0gIioqLyouZy5kYXJ0IgogICAgLSAiKiovKi5mcmVlemVkLmRhcnQiCiAgICAtICIqKi8qLmk2OW4uZGFydCIKICAgICMgQnVpbGQgb3V0cHV0IGFuZCB0aGUgcGx1Z2luIHNvdXJjZXMgQ29jb2FQb2RzIHN5bWxpbmtzIGludG8gdGhlIGFwcC4KICAgIC0gIioqL2J1aWxkLyoqIgogICAgLSAiKiovaW9zLy5zeW1saW5rcy8qKiIKICAgIC0gIioqL21hY29zLy5zeW1saW5rcy8qKiIKCmxpbnRlcjoKICBydWxlczoKICAgIHJlcXVpcmVfdHJhaWxpbmdfY29tbWFzOiB0cnVlCg==", "type": "text" }, { "path": "{{ name.snakeCase() }}/apps/README.md", - "data": "IyBQdXQgYWxsIHlvdXIgYXBwcyBpbiB0aGlzIGZvbGRlcgo=", + "data": + "IyBBcHBzCgpSdW5uYWJsZSBGbHV0dGVyIGFwcGxpY2F0aW9ucy4gQW4gYXBwIHdpcmVzIG1vZHVsZXMgdG9nZXRoZXIgaW4KYGxpYi9ib290c3RyYXAuZGFydGAgYW5kIGNvbXBvc2VzIGZlYXR1cmUgcm91dGVzIGluIGBsaWIvcm91dGVyL3JvdXRlci5kYXJ0YCAtCml0IHNob3VsZCBob2xkIGFsbW9zdCBubyBidXNpbmVzcyBsb2dpYyBvZiBpdHMgb3duLgoKQWRkIGFub3RoZXIgKGFuIGFkbWluIGJ1aWxkLCBhIHdoaXRlLWxhYmVsIHZhcmlhbnQpIHdpdGg6CgpgYGBzaApub25zdG9wIGNyZWF0ZSBhcHAgbXlfb3RoZXJfYXBwIC1vIGFwcHMKYGBgCg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/app.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vZGVzaWduX3N5c3RlbS5kYXJ0JzsKe3sjbm90aWZpY2F0aW9uc319aW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwp7ey9ub3RpZmljYXRpb25zfX1pbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7Cnt7I25vdGlmaWNhdGlvbnN9fWltcG9ydCAncGFja2FnZTpub3RpZmljYXRpb25zL25vdGlmaWNhdGlvbnMuZGFydCc7Cnt7L25vdGlmaWNhdGlvbnN9fWltcG9ydCAncGFja2FnZTp7e25hbWUuc25ha2VDYXNlKCl9fS9yb3V0ZXIvcm91dGVyLmRhcnQnOwoKY2xhc3MgQXBwIGV4dGVuZHMgU3RhdGVmdWxXaWRnZXQgewogIGNvbnN0IEFwcCh7c3VwZXIua2V5fSk7CgogIEBvdmVycmlkZQogIFN0YXRlPEFwcD4gY3JlYXRlU3RhdGUoKSA9PiBfQXBwU3RhdGUoKTsKfQoKY2xhc3MgX0FwcFN0YXRlIGV4dGVuZHMgU3RhdGU8QXBwPiB7Cnt7I25vdGlmaWNhdGlvbnN9fSAgbGF0ZSBmaW5hbCBBcHBMaWZlY3ljbGVMaXN0ZW5lciBfbGlmZWN5Y2xlTGlzdGVuZXI7Cgp7ey9ub3RpZmljYXRpb25zfX0gIEBvdmVycmlkZQogIHZvaWQgaW5pdFN0YXRlKCkgewogICAgc3VwZXIuaW5pdFN0YXRlKCk7Cnt7I25vdGlmaWNhdGlvbnN9fSAgICAvLyBBc2tpbmcgZm9yIG5vdGlmaWNhdGlvbiBwZXJtaXNzaW9uIG5lZWRzIGEgZm9yZWdyb3VuZCBzdXJmYWNlLCBzbyB0aGlzCiAgICAvLyBydW5zIGZyb20gdGhlIGZpcnN0IGZyYW1lIHJhdGhlciB0aGFuIGZyb20gYm9vdHN0cmFwLiBNb3ZlIGl0IGJlaGluZAogICAgLy8gc2lnbi1pbiBvbmNlIHlvdSB3b3VsZCByYXRoZXIgYXNrIGEgdXNlciB3aG8gaGFzIGNvbW1pdHRlZCB0byB0aGUgYXBwLgogICAgX2xpZmVjeWNsZUxpc3RlbmVyID0gQXBwTGlmZWN5Y2xlTGlzdGVuZXIob25SZXN1bWU6IF9jbGVhckJhZGdlKTsKICAgIFdpZGdldHNCaW5kaW5nLmluc3RhbmNlLmFkZFBvc3RGcmFtZUNhbGxiYWNrKChfKSBhc3luYyB7CiAgICAgIGZpbmFsIGxvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CiAgICAgIHRyeSB7CiAgICAgICAgYXdhaXQgZGkuZ2V0PE5vdGlmaWNhdGlvbkNsaWVudD4oKS5pbml0KCk7CiAgICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgICBsb2dnZXIuZSgnRmFpbGVkIHRvIGluaXRpYWxpemUgTm90aWZpY2F0aW9uQ2xpZW50JywgZSwgcyk7CiAgICAgIH0KICAgIH0pOwp7ey9ub3RpZmljYXRpb25zfX0gIH0Ke3sjbm90aWZpY2F0aW9uc319CiAgdm9pZCBfY2xlYXJCYWRnZSgpIHsKICAgIGZpbmFsIGxvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CiAgICB0cnkgewogICAgICBkaS5nZXQ8Tm90aWZpY2F0aW9uQ2xpZW50PigpLmNsZWFyQmFkZ2UoKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgbG9nZ2VyLmUoJ0ZhaWxlZCB0byBjbGVhciBiYWRnZSBvbiBhcHAgcmVzdW1lJywgZSwgcyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIGRpc3Bvc2UoKSB7CiAgICBfbGlmZWN5Y2xlTGlzdGVuZXIuZGlzcG9zZSgpOwogICAgc3VwZXIuZGlzcG9zZSgpOwogIH0Ke3svbm90aWZpY2F0aW9uc319CiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKEJ1aWxkQ29udGV4dCBjb250ZXh0KSB7CiAgICByZXR1cm4gR2xvYmFsRXZlbnRDaGFubmVsUHJvdmlkZXIoCiAgICAgIGNoaWxkOiBEZXNpZ25TeXN0ZW1XcmFwcGVyKAogICAgICAgIGJ1aWxkZXI6IChjb250ZXh0LCB0aGVtZSkgPT4gTWF0ZXJpYWxBcHAucm91dGVyKAogICAgICAgICAgZGVidWdTaG93Q2hlY2tlZE1vZGVCYW5uZXI6IGZhbHNlLAogICAgICAgICAgdGl0bGU6IHN0cmluZ3MuYXBwLm5hbWUsCiAgICAgICAgICB0aGVtZTogdGhlbWUsCiAgICAgICAgICByb3V0ZUluZm9ybWF0aW9uUGFyc2VyOiBBcHBSb3V0ZXIucm91dGVJbmZvcm1hdGlvblBhcnNlciwKICAgICAgICAgIHJvdXRlSW5mb3JtYXRpb25Qcm92aWRlcjogQXBwUm91dGVyLnJvdXRlSW5mb3JtYXRpb25Qcm92aWRlciwKICAgICAgICAgIHJvdXRlckRlbGVnYXRlOiBBcHBSb3V0ZXIucm91dGVyRGVsZWdhdGUsCiAgICAgICAgKSwKICAgICAgKSwKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/bootstrap.dart", + "data": + "e3sjYW5hbHl0aWNzfX1pbXBvcnQgJ3BhY2thZ2U6YW5hbHl0aWNzL2FuYWx5dGljcy5kYXJ0JyBhcyBhbmFseXRpY3M7Cnt7L2FuYWx5dGljc319e3sjYXV0aH19aW1wb3J0ICdwYWNrYWdlOmF1dGgvYXV0aC5kYXJ0JyBhcyBhdXRoOwp7ey9hdXRofX1pbXBvcnQgJ3BhY2thZ2U6YmxvYy9ibG9jLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnIGFzIGNvcmU7Cnt7I2ZpcmViYXNlfX1pbXBvcnQgJ3BhY2thZ2U6Y29yZS9kZXZlbG9wZXIvZW11bGF0b3JzLmRhcnQnIGFzIGVtdWxhdG9yczsKe3svZmlyZWJhc2V9fWltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci9sb2dnZXIuZGFydCc7Cnt7I2NyYXNobHl0aWNzfX1pbXBvcnQgJ3BhY2thZ2U6Y3Jhc2hseXRpY3MvY3Jhc2hseXRpY3MuZGFydCcgYXMgY3Jhc2hseXRpY3M7Cnt7L2NyYXNobHl0aWNzfX1pbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7Cnt7I2ZlYXR1cmVfZmxhZ3N9fWltcG9ydCAncGFja2FnZTpmZWF0dXJlX2ZsYWdzL2ZlYXR1cmVfZmxhZ3MuZGFydCcgYXMgZmVhdHVyZV9mbGFnczsKe3svZmVhdHVyZV9mbGFnc319e3sjZmlyZWJhc2V9fWltcG9ydCAncGFja2FnZTpmaXJlYmFzZV9jb3JlL2ZpcmViYXNlX2NvcmUuZGFydCc7Cnt7L2ZpcmViYXNlfX1pbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci93aWRnZXRzLmRhcnQnOwp7eyNuZXR3b3JrfX1pbXBvcnQgJ3BhY2thZ2U6bmV0d29yay9uZXR3b3JrLmRhcnQnIGFzIG5ldHdvcms7Cnt7L25ldHdvcmt9fXt7I25vdGlmaWNhdGlvbnN9fWltcG9ydCAncGFja2FnZTpub3RpZmljYXRpb25zL25vdGlmaWNhdGlvbnMuZGFydCcgYXMgbm90aWZpY2F0aW9uczsKe3svbm90aWZpY2F0aW9uc319e3sjZmlyZWJhc2V9fWltcG9ydCAncGFja2FnZTp7e25hbWUuc25ha2VDYXNlKCl9fS9maXJlYmFzZV9vcHRpb25zLmRhcnQnOwp7ey9maXJlYmFzZX19Ci8vLyBCcmluZ3MgZXZlcnkgbW9kdWxlIHVwLCBpbiBkZXBlbmRlbmN5IG9yZGVyLCBiZWZvcmUgdGhlIGZpcnN0IGZyYW1lLgovLy8KLy8vIE9yZGVyIG1hdHRlcnM6IHRoZSBsb2dnZXIgaXMgcmVnaXN0ZXJlZCBmaXJzdCBzbyBldmVyeXRoaW5nIGFmdGVyIGl0IGNhbgovLy8gcmVwb3J0IGZhaWx1cmVzLCBhbmQgRmlyZWJhc2UgbXVzdCBiZSBsaXZlIGJlZm9yZSBhbnkgRmlyZWJhc2UtYmFja2VkCi8vLyBtb2R1bGUgaW5pdGlhbGlzZXMuCkZ1dHVyZTx2b2lkPiBpbml0KCkgYXN5bmMgewogIFdpZGdldHNGbHV0dGVyQmluZGluZy5lbnN1cmVJbml0aWFsaXplZCgpOwogIGZpbmFsIHN0b3B3YXRjaCA9IFN0b3B3YXRjaCgpLi5zdGFydCgpOwoKICBhd2FpdCBjb3JlLmluaXQoKTsKICBmaW5hbCBsb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpOwogIGxvZ2dlci5pKCdDb3JlIHJlYWR5IGluICR7c3RvcHdhdGNoLmVsYXBzZWRNaWxsaXNlY29uZHN9IG1zJyk7CgogIEJsb2Mub2JzZXJ2ZXIgPSBjb3JlLkNvcmVCbG9jT2JzZXJ2ZXIoKTsKe3sjZmlyZWJhc2V9fQogIGZpbmFsIG9wdGlvbnMgPSBEZWZhdWx0RmlyZWJhc2VPcHRpb25zLmN1cnJlbnRQbGF0Zm9ybTsKICBhd2FpdCBGaXJlYmFzZS5pbml0aWFsaXplQXBwKG9wdGlvbnM6IG9wdGlvbnMpOwoKICBpZiAoY29yZS5FbnZpcm9ubWVudC51c2VFbXVsYXRvcnMpIHsKICAgIGF3YWl0IGVtdWxhdG9ycy5pbml0KCk7CiAgfQp7ey9maXJlYmFzZX19Cnt7I2NyYXNobHl0aWNzfX0gIGF3YWl0IGNyYXNobHl0aWNzLmluaXQoKTsKe3svY3Jhc2hseXRpY3N9fXt7I2FuYWx5dGljc319ICBhd2FpdCBhbmFseXRpY3MuaW5pdCgpOwp7ey9hbmFseXRpY3N9fXt7I2ZlYXR1cmVfZmxhZ3N9fSAgYXdhaXQgZmVhdHVyZV9mbGFncy5pbml0KCk7Cnt7L2ZlYXR1cmVfZmxhZ3N9fXt7I2F1dGh9fQogIGZpbmFsIGNsaWVudElkID0gb3B0aW9ucy5pb3NDbGllbnRJZCA/PyBvcHRpb25zLmFuZHJvaWRDbGllbnRJZCA/PyAnJzsKICBhd2FpdCBhdXRoLmluaXQoYXV0aC5EZWZhdWx0QXV0aENvbmZpZyhjbGllbnRJZDogY2xpZW50SWQpKTsKe3svYXV0aH19e3sjbmV0d29ya319CiAgYXdhaXQgbmV0d29yay5pbml0KAogICAgY29uZmlnOiBuZXR3b3JrLkRlZmF1bHROZXR3b3JrQ29uZmlnKGJhc2VVcmw6IGNvcmUuRW52aXJvbm1lbnQuYmFzZVVybCksCiAgKTsKe3svbmV0d29ya319e3sjbm90aWZpY2F0aW9uc319CiAgYXdhaXQgbm90aWZpY2F0aW9ucy5pbml0KCk7Cnt7L25vdGlmaWNhdGlvbnN9fQogIC8vIFRPRE86IGluaXRpYWxpc2UgeW91ciBvd24gZmVhdHVyZSBtb2R1bGVzIGhlcmUuCgogIHN0b3B3YXRjaC5zdG9wKCk7CiAgbG9nZ2VyLmkoJ0Jvb3RzdHJhcCBjb21wbGV0ZWQgaW4gJHtzdG9wd2F0Y2guZWxhcHNlZE1pbGxpc2Vjb25kc30gbXMnKTsKe3sjYW5hbHl0aWNzfX0KICBhd2FpdCBhbmFseXRpY3MuQW5hbHl0aWNzSGVscGVyLmxvZ0FwcE9wZW4oCiAgICBwYXJhbWV0ZXJzOiB7J2Jvb3RzdHJhcF9kdXJhdGlvbic6IHN0b3B3YXRjaC5lbGFwc2VkTWlsbGlzZWNvbmRzfSwKICApOwp7ey9hbmFseXRpY3N9fX0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/firebase_options.dart", + "data": + "e3sjZmlyZWJhc2V9fS8vIFRoaXMgZmlsZSBpcyBhIHBsYWNlaG9sZGVyLiBHZW5lcmF0ZSB0aGUgcmVhbCBvbmUgd2l0aDoKLy8KLy8gICBjZCBhcHBzL3t7bmFtZS5zbmFrZUNhc2UoKX19ICYmIGZsdXR0ZXJmaXJlIGNvbmZpZ3VyZQovLwovLyBGbHV0dGVyRmlyZSBvdmVyd3JpdGVzIHRoaXMgZmlsZSB3aXRoIHlvdXIgcHJvamVjdCdzIG9wdGlvbnMgYW5kIGFsc28gZHJvcHMKLy8gZ29vZ2xlLXNlcnZpY2VzLmpzb24gLyBHb29nbGVTZXJ2aWNlLUluZm8ucGxpc3QgaW50byB0aGUgbmF0aXZlIGZvbGRlcnMuCmltcG9ydCAncGFja2FnZTpmaXJlYmFzZV9jb3JlL2ZpcmViYXNlX2NvcmUuZGFydCcgc2hvdyBGaXJlYmFzZU9wdGlvbnM7CgpjbGFzcyBEZWZhdWx0RmlyZWJhc2VPcHRpb25zIHsKICBjb25zdCBEZWZhdWx0RmlyZWJhc2VPcHRpb25zLl8oKTsKCiAgc3RhdGljIEZpcmViYXNlT3B0aW9ucyBnZXQgY3VycmVudFBsYXRmb3JtID0+IHRocm93IFVuc3VwcG9ydGVkRXJyb3IoCiAgICAnRmlyZWJhc2UgaGFzIG5vdCBiZWVuIGNvbmZpZ3VyZWQgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LlxuJwogICAgJ1J1biBgZmx1dHRlcmZpcmUgY29uZmlndXJlYCBpbnNpZGUgYXBwcy97e25hbWUuc25ha2VDYXNlKCl9fSAnCiAgICAndG8gcmVwbGFjZSBsaWIvZmlyZWJhc2Vfb3B0aW9ucy5kYXJ0LicsCiAgKTsKfXt7L2ZpcmViYXNlfX0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/main.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTp7e25hbWUuc25ha2VDYXNlKCl9fS9hcHAuZGFydCc7CmltcG9ydCAncGFja2FnZTp7e25hbWUuc25ha2VDYXNlKCl9fS9ib290c3RyYXAuZGFydCcgYXMgYm9vdHN0cmFwOwoKdm9pZCBtYWluKCkgYXN5bmMgewogIGF3YWl0IGJvb3RzdHJhcC5pbml0KCk7CiAgcnVuQXBwKGNvbnN0IEFwcCgpKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/router/router.dart", + "data": + "e3sjYW5hbHl0aWNzfX1pbXBvcnQgJ3BhY2thZ2U6YW5hbHl0aWNzL2FuYWx5dGljcy5kYXJ0JzsKe3svYW5hbHl0aWNzfX17eyNhdXRofX1pbXBvcnQgJ3BhY2thZ2U6YXV0aC9hdXRoLmRhcnQnIGFzIGF1dGg7Cnt7L2F1dGh9fWltcG9ydCAncGFja2FnZTpjb3JlL2NvcmUuZGFydCcgYXMgY29yZTsKe3sjZGFzaGJvYXJkfX1pbXBvcnQgJ3BhY2thZ2U6ZGFzaGJvYXJkL2Rhc2hib2FyZC5kYXJ0JzsKe3svZGFzaGJvYXJkfX1pbXBvcnQgJ3BhY2thZ2U6ZGVzaWduX3N5c3RlbS9kZXNpZ25fc3lzdGVtLmRhcnQnOwp7eyNkZXZlbG9wZXJ9fWltcG9ydCAncGFja2FnZTpkZXZlbG9wZXIvZGV2ZWxvcGVyLmRhcnQnIGFzIGRldmVsb3BlcjsKe3svZGV2ZWxvcGVyfX1pbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL2ZvdW5kYXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Z29fcm91dGVyL2dvX3JvdXRlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpuYXZpZ2F0aW9uX2hpc3Rvcnlfb2JzZXJ2ZXIvbmF2aWdhdGlvbl9oaXN0b3J5X29ic2VydmVyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6e3tuYW1lLnNuYWtlQ2FzZSgpfX0vdWkvc3BsYXNoX3NjcmVlbi5kYXJ0JzsKCi8vLyBUaGUgYXBwJ3Mgc2luZ2xlIFtHb1JvdXRlcl0uCi8vLwovLy8gRmVhdHVyZSBwYWNrYWdlcyBleHBvc2UgdGhlaXIgb3duIGByb3V0ZXNgIGxpc3QgYW5kIGFyZSBzcGxpY2VkIGluIGhlcmUsIHNvCi8vLyBhZGRpbmcgYSBmZWF0dXJlIGlzIG9uZSBpbXBvcnQgcGx1cyBvbmUgc3ByZWFkIC0gbm8gcm91dGUgdGFibGUgdG8gbWVyZ2UuCmNsYXNzIEFwcFJvdXRlciB7CiAgY29uc3QgQXBwUm91dGVyLl8oKTsKCiAgc3RhdGljIGZpbmFsIEdvUm91dGVyIF9yb3V0ZXIgPSBjcmVhdGVSb3V0ZXIoKTsKCiAgc3RhdGljIFJvdXRlckRlbGVnYXRlPE9iamVjdD4gZ2V0IHJvdXRlckRlbGVnYXRlID0+IF9yb3V0ZXIucm91dGVyRGVsZWdhdGU7CgogIHN0YXRpYyBSb3V0ZUluZm9ybWF0aW9uUGFyc2VyPE9iamVjdD4gZ2V0IHJvdXRlSW5mb3JtYXRpb25QYXJzZXIgPT4KICAgICAgX3JvdXRlci5yb3V0ZUluZm9ybWF0aW9uUGFyc2VyOwoKICBzdGF0aWMgUm91dGVJbmZvcm1hdGlvblByb3ZpZGVyPyBnZXQgcm91dGVJbmZvcm1hdGlvblByb3ZpZGVyID0+CiAgICAgIF9yb3V0ZXIucm91dGVJbmZvcm1hdGlvblByb3ZpZGVyOwoKICBzdGF0aWMgR29Sb3V0ZXIgY3JlYXRlUm91dGVyKCkgewogICAgZmluYWwgcm91dGVyID0gR29Sb3V0ZXIoCiAgICAgIGRlYnVnTG9nRGlhZ25vc3RpY3M6IGtEZWJ1Z01vZGUsCiAgICAgIGluaXRpYWxMb2NhdGlvbjogY29yZS5Db3JlUm91dGVzLnJvb3QsCiAgICAgIG9ic2VydmVyczogWwogICAgICAgIE5hdmlnYXRpb25IaXN0b3J5T2JzZXJ2ZXIoKSwKICAgICAgICBjb3JlLkNvcmVSb3V0ZU9ic2VydmVyKCksCnt7I2FuYWx5dGljc319ICAgICAgICBBbmFseXRpY3NSb3V0ZU9ic2VydmVyKCksCnt7L2FuYWx5dGljc319ICAgICAgXSwKICAgICAgZXJyb3JCdWlsZGVyOiAoY29udGV4dCwgc3RhdGUpID0+IEVycm9yU2NyZWVuKAogICAgICAgIHRpdGxlOiBzdHJpbmdzLmVycm9ycy5wYWdlX25vdF9mb3VuZCwKICAgICAgICBtZXNzYWdlOiBzdHJpbmdzLmVycm9ycy5wYWdlX25vdF9mb3VuZF9kZXNjcmlwdGlvbiwKICAgICAgICBvbkdvSG9tZTogKCkgPT4gY29udGV4dC5nbyhjb3JlLkNvcmVSb3V0ZXMucm9vdCksCiAgICAgICAgZXJyb3I6IHN0YXRlLnVyaS50b1N0cmluZygpLAogICAgICApLAogICAgICByb3V0ZXM6IFsKICAgICAgICBHb1JvdXRlKAogICAgICAgICAgcGF0aDogY29yZS5Db3JlUm91dGVzLnJvb3QsCiAgICAgICAgICBidWlsZGVyOiAoY29udGV4dCwgc3RhdGUpID0+IGNvbnN0IFNwbGFzaFNjcmVlbigpLAogICAgICAgICksCnt7I2Rhc2hib2FyZH19ICAgICAgICBHb1JvdXRlKAogICAgICAgICAgcGF0aDogY29yZS5Db3JlUm91dGVzLmhvbWUsCiAgICAgICAgICByZWRpcmVjdDogKGNvbnRleHQsIHN0YXRlKSA9PiBEYXNoYm9hcmRSb3V0ZXIuaG9tZSwKICAgICAgICApLAogICAgICAgIERhc2hib2FyZFJvdXRlci5jcmVhdGVTaGVsbFJvdXRlKCksCnt7L2Rhc2hib2FyZH19e3teZGFzaGJvYXJkfX0gICAgICAgIEdvUm91dGUoCiAgICAgICAgICBwYXRoOiBjb3JlLkNvcmVSb3V0ZXMuaG9tZSwKICAgICAgICAgIGJ1aWxkZXI6IChjb250ZXh0LCBzdGF0ZSkgPT4gY29uc3QgX0hvbWVTY3JlZW4oKSwKICAgICAgICApLAp7ey9kYXNoYm9hcmR9fXt7I2F1dGh9fSAgICAgICAgLi4uYXV0aC5BdXRoUm91dGVyKAogICAgICAgICAgb25TaWduZWRJbjogX29uQXV0aGVudGljYXRlZCwKICAgICAgICAgIG9uU2lnbmVkVXA6IF9vbkF1dGhlbnRpY2F0ZWQsCiAgICAgICAgKS5yb3V0ZXMsCnt7L2F1dGh9fXt7I2RldmVsb3Blcn19ICAgICAgICAuLi5kZXZlbG9wZXIuRGV2ZWxvcGVyUm91dGVyKCkucm91dGVzLAp7ey9kZXZlbG9wZXJ9fSAgICAgICAgR29Sb3V0ZSgKICAgICAgICAgIHBhdGg6IGNvcmUuQ29yZVJvdXRlcy5lcnJvciwKICAgICAgICAgIGJ1aWxkZXI6IChjb250ZXh0LCBzdGF0ZSkgPT4gRXJyb3JTY3JlZW4oCiAgICAgICAgICAgIHRpdGxlOgogICAgICAgICAgICAgICAgc3RhdGUudXJpLnF1ZXJ5UGFyYW1ldGVyc1tjb3JlLktleXMudGl0bGVdID8/CiAgICAgICAgICAgICAgICBzdHJpbmdzLmdlbmVyaWMuZXJyb3IsCiAgICAgICAgICAgIG1lc3NhZ2U6CiAgICAgICAgICAgICAgICBzdGF0ZS51cmkucXVlcnlQYXJhbWV0ZXJzW2NvcmUuS2V5cy5tZXNzYWdlXSA/PwogICAgICAgICAgICAgICAgc3RyaW5ncy5lcnJvcnMudW5leHBlY3RlZF9lcnJvciwKICAgICAgICAgICAgZXJyb3I6IHN0YXRlLnVyaS5xdWVyeVBhcmFtZXRlcnNbY29yZS5LZXlzLmVycm9yXSwKICAgICAgICAgICAgb25Hb0hvbWU6ICgpID0+IGNvbnRleHQuZ28oY29yZS5Db3JlUm91dGVzLnJvb3QpLAogICAgICAgICAgKSwKICAgICAgICApLAogICAgICBdLAogICAgKTsKCiAgICBkaS5yZWdpc3RlcjxHb1JvdXRlcj4ocm91dGVyKTsKICAgIHJldHVybiByb3V0ZXI7CiAgfQp7eyNhdXRofX0KICAvLy8gV2hlcmUgYSB1c2VyIGxhbmRzIG9uY2Ugc2lnbi1pbiBvciBzaWduLXVwIHN1Y2NlZWRzLgogIC8vLwogIC8vLyBMb2FkIHRoZSBwcm9maWxlIC8gZW50aXRsZW1lbnRzIHlvdSBuZWVkIGhlcmUgYmVmb3JlIHJvdXRpbmcgb24uCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBfb25BdXRoZW50aWNhdGVkKEJ1aWxkQ29udGV4dCBjb250ZXh0KSBhc3luYyB7Cnt7I2FuYWx5dGljc319ICAgIGF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dFdmVudChBbmFseXRpY3NFdmVudHMudXNlci5hdXRoZW50aWNhdGVkUmVkaXJlY3QpOwp7ey9hbmFseXRpY3N9fSAgICBpZiAoY29udGV4dC5tb3VudGVkKSB7CiAgICAgIGNvbnRleHQuZ28oe3sjZGFzaGJvYXJkfX1EYXNoYm9hcmRSb3V0ZXIuaG9tZXt7L2Rhc2hib2FyZH19e3teZGFzaGJvYXJkfX1jb3JlLkNvcmVSb3V0ZXMuaG9tZXt7L2Rhc2hib2FyZH19KTsKICAgIH0KICB9Cnt7L2F1dGh9fX0Ke3teZGFzaGJvYXJkfX0KLy8vIFBsYWNlaG9sZGVyIGxhbmRpbmcgc2NyZWVuIC0gcmVwbGFjZSBpdCB3aXRoIHlvdXIgZmlyc3QgZmVhdHVyZS4KY2xhc3MgX0hvbWVTY3JlZW4gZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGNvbnN0IF9Ib21lU2NyZWVuKCk7CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgcmV0dXJuIFNjYWZmb2xkKAogICAgICBhcHBCYXI6IEFwcEJhcih0aXRsZTogVGV4dChzdHJpbmdzLmFwcC5uYW1lKSksCiAgICAgIGJvZHk6IENlbnRlcigKICAgICAgICBjaGlsZDoge3sjZGV2ZWxvcGVyfX1kZXZlbG9wZXIuT3BlbkRldlRvb2xzV3JhcHBlcigKICAgICAgICAgIC8vIFRhcCA1eCB0byByZWFjaCB0aGUgZGV2ZWxvcGVyIHRvb2xzLgogICAgICAgICAgY2hpbGQ6IFRleHQoc3RyaW5ncy5hcHAud2VsY29tZV90b19hcHApLAogICAgICAgICl7ey9kZXZlbG9wZXJ9fXt7XmRldmVsb3Blcn19VGV4dChzdHJpbmdzLmFwcC53ZWxjb21lX3RvX2FwcCl7ey9kZXZlbG9wZXJ9fSwKICAgICAgKSwKICAgICk7CiAgfQp9Cnt7L2Rhc2hib2FyZH19", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/lib/ui/splash_screen.dart", + "data": + "e3sjYXV0aH19aW1wb3J0ICdwYWNrYWdlOmF1dGgvYXV0aC5kYXJ0JyBhcyBhdXRoOwp7ey9hdXRofX17e15kYXNoYm9hcmR9fWltcG9ydCAncGFja2FnZTpjb3JlL2NvcmUuZGFydCcgYXMgY29yZTsKe3svZGFzaGJvYXJkfX17eyNkYXNoYm9hcmR9fWltcG9ydCAncGFja2FnZTpkYXNoYm9hcmQvZGFzaGJvYXJkLmRhcnQnOwp7ey9kYXNoYm9hcmR9fWltcG9ydCAncGFja2FnZTpkZXNpZ25fc3lzdGVtL2Rlc2lnbl9zeXN0ZW0uZGFydCc7Cnt7I2F1dGh9fWltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKe3svYXV0aH19aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpnb19yb3V0ZXIvZ29fcm91dGVyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bG9jYWxpemF0aW9uL2xvY2FsaXphdGlvbi5kYXJ0JzsKCi8vLyBGaXJzdCBmcmFtZSB0aGUgdXNlciBzZWVzLgovLy8KLy8vIEl0IGV4aXN0cyB0byBnaXZlIGFzeW5jIHN0YXJ0dXAgd29yayAoc2Vzc2lvbiByZXN0b3JlLCBwcm9maWxlIGZldGNoKSBhCi8vLyBwbGFjZSB0byBoYXBwZW4gYmVmb3JlIHRoZSBhcHAgY29tbWl0cyB0byBhIGRlc3RpbmF0aW9uLgpjbGFzcyBTcGxhc2hTY3JlZW4gZXh0ZW5kcyBTdGF0ZWZ1bFdpZGdldCB7CiAgY29uc3QgU3BsYXNoU2NyZWVuKHtzdXBlci5rZXl9KTsKCiAgQG92ZXJyaWRlCiAgU3RhdGU8U3BsYXNoU2NyZWVuPiBjcmVhdGVTdGF0ZSgpID0+IF9TcGxhc2hTY3JlZW5TdGF0ZSgpOwp9CgpjbGFzcyBfU3BsYXNoU2NyZWVuU3RhdGUgZXh0ZW5kcyBTdGF0ZTxTcGxhc2hTY3JlZW4+IHsKICBAb3ZlcnJpZGUKICB2b2lkIGluaXRTdGF0ZSgpIHsKICAgIHN1cGVyLmluaXRTdGF0ZSgpOwogICAgV2lkZ2V0c0JpbmRpbmcuaW5zdGFuY2UuYWRkUG9zdEZyYW1lQ2FsbGJhY2soKF8pID0+IF9yZWRpcmVjdCgpKTsKICB9CgogIEZ1dHVyZTx2b2lkPiBfcmVkaXJlY3QoKSBhc3luYyB7CiAgICBpZiAoIW1vdW50ZWQpIHJldHVybjsKe3sjYXV0aH19CiAgICBpZiAoIWRpLmdldDxhdXRoLkF1dGhTZXJ2aWNlPigpLmlzU2lnbmVkSW4pIHsKICAgICAgY29udGV4dC5nbyhhdXRoLkF1dGhSb3V0ZXMuc2lnbkluKTsKICAgICAgcmV0dXJuOwogICAgfQp7ey9hdXRofX0KICAgIC8vIFRPRE86IGxvYWQgd2hhdGV2ZXIgdGhlIGZpcnN0IHNjcmVlbiBuZWVkcyBiZWZvcmUgbmF2aWdhdGluZy4KICAgIGNvbnRleHQuZ28oe3sjZGFzaGJvYXJkfX1EYXNoYm9hcmRSb3V0ZXIuaG9tZXt7L2Rhc2hib2FyZH19e3teZGFzaGJvYXJkfX1jb3JlLkNvcmVSb3V0ZXMuaG9tZXt7L2Rhc2hib2FyZH19KTsKICB9CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgZmluYWwgdGhlbWUgPSBUaGVtZS5vZihjb250ZXh0KTsKICAgIHJldHVybiBTY2FmZm9sZCgKICAgICAgYm9keTogQ2VudGVyKAogICAgICAgIGNoaWxkOiBDb2x1bW4oCiAgICAgICAgICBtYWluQXhpc0FsaWdubWVudDogTWFpbkF4aXNBbGlnbm1lbnQuY2VudGVyLAogICAgICAgICAgY2hpbGRyZW46IFsKICAgICAgICAgICAgVGV4dCgKICAgICAgICAgICAgICBzdHJpbmdzLmFwcC5uYW1lLAogICAgICAgICAgICAgIHN0eWxlOiB0aGVtZS50ZXh0VGhlbWUuaGVhZGxpbmVNZWRpdW0/LmNvcHlXaXRoKAogICAgICAgICAgICAgICAgZm9udFdlaWdodDogRm9udFdlaWdodC5ib2xkLAogICAgICAgICAgICAgICksCiAgICAgICAgICAgICksCiAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KGhlaWdodDogMzIpLAogICAgICAgICAgICBjb25zdCBEZWZhdWx0TG9hZGVyKCksCiAgICAgICAgICBdLAogICAgICAgICksCiAgICAgICksCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml", + "data": + "bmFtZToge3tuYW1lLnNuYWtlQ2FzZSgpfX0KZGVzY3JpcHRpb246ICJ7e3tkZXNjcmlwdGlvbn19fSIKcHVibGlzaF90bzogJ25vbmUnCnZlcnNpb246IDAuMS4wKzEKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBjdXBlcnRpbm9faWNvbnM6IF4xLjAuOAogIGdvX3JvdXRlcjogXjE1LjEuMwogIGJsb2M6IF45LjAuMAogIG5hdmlnYXRpb25faGlzdG9yeV9vYnNlcnZlcjogXjEuMS4xCiAgY29yZToKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2NvcmUKICBkZXNpZ25fc3lzdGVtOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvZGVzaWduX3N5c3RlbQogIGRpOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvZGkKICBsb2NhbGl6YXRpb246CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9sb2NhbGl6YXRpb24KICB1dGlsczoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL3V0aWxzCnt7I2ZpcmViYXNlfX0gIGZpcmViYXNlX2NvcmU6IF4zLjE0LjAKe3svZmlyZWJhc2V9fXt7I25ldHdvcmt9fSAgbmV0d29yazoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL25ldHdvcmsKe3svbmV0d29ya319e3sjYW5hbHl0aWNzfX0gIGFuYWx5dGljczoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2FuYWx5dGljcwp7ey9hbmFseXRpY3N9fXt7I2NyYXNobHl0aWNzfX0gIGNyYXNobHl0aWNzOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvY3Jhc2hseXRpY3MKe3svY3Jhc2hseXRpY3N9fXt7I2ZlYXR1cmVfZmxhZ3N9fSAgZmVhdHVyZV9mbGFnczoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2ZlYXR1cmVfZmxhZ3MKe3svZmVhdHVyZV9mbGFnc319e3sjbm90aWZpY2F0aW9uc319ICBub3RpZmljYXRpb25zOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvbm90aWZpY2F0aW9ucwp7ey9ub3RpZmljYXRpb25zfX17eyNkZXZlbG9wZXJ9fSAgZGV2ZWxvcGVyOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvZGV2ZWxvcGVyCnt7L2RldmVsb3Blcn19e3sjYXV0aH19ICBhdXRoOgogICAgcGF0aDogLi4vLi4vZmVhdHVyZXMvYXV0aAp7ey9hdXRofX17eyNkYXNoYm9hcmR9fSAgZGFzaGJvYXJkOgogICAgcGF0aDogLi4vLi4vZmVhdHVyZXMvZGFzaGJvYXJkCnt7L2Rhc2hib2FyZH19CmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCgpkZXBlbmRlbmN5X292ZXJyaWRlczoKICBpbnRsOiBeMC4yMC4yCgpmbHV0dGVyOgogIHVzZXMtbWF0ZXJpYWwtZGVzaWduOiB0cnVlCnt7I2F1dGh9fSAgZm9udHM6CiAgICAjIGZpcmViYXNlX3VpX2F1dGggc2hpcHMgdGhpcyBmb250IGJ1dCBuZXZlciByZWdpc3RlcnMgaXQsIHNvIHRoZSBHb29nbGUKICAgICMgYW5kIEFwcGxlIGJ1dHRvbnMgcmVuZGVyIGFzIHRvZnUgdW5sZXNzIHRoZSBhcHAgZGVjbGFyZXMgaXQgaGVyZS4KICAgIC0gZmFtaWx5OiBTb2NpYWxJY29ucwogICAgICBmb250czoKICAgICAgICAtIGFzc2V0OiBwYWNrYWdlcy9maXJlYmFzZV91aV9hdXRoL2ZvbnRzL1NvY2lhbEljb25zLnR0Zgp7ey9hdXRofX0=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/test/{{smoke_test_file}}", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXJfdGVzdC9mbHV0dGVyX3Rlc3QuZGFydCc7CmltcG9ydCAncGFja2FnZTp7e25hbWUuc25ha2VDYXNlKCl9fS9hcHAuZGFydCc7CmltcG9ydCAncGFja2FnZTp7e25hbWUuc25ha2VDYXNlKCl9fS9ib290c3RyYXAuZGFydCcgYXMgYm9vdHN0cmFwOwoKdm9pZCBtYWluKCkgewogIHRlc3RXaWRnZXRzKCd0aGUgYXBwIGJvb3RzIGFuZCByZWFjaGVzIGl0cyBmaXJzdCBzY3JlZW4nLCAodGVzdGVyKSBhc3luYyB7CiAgICAvLyBFeGVyY2lzZXMgdGhlIHdob2xlIGdyYXBoOiBtb2R1bGUgaW5pdCBvcmRlciwgREkgcmVnaXN0cmF0aW9uIGFuZCB0aGUKICAgIC8vIHNwbGFzaCByZWRpcmVjdC4gSWYgYSBtb2R1bGUgZm9yZ2V0cyB0byByZWdpc3RlciBzb21ldGhpbmcsIHRoaXMgZmFpbHMKICAgIC8vIGhlcmUgcmF0aGVyIHRoYW4gb24gYSBkZXZpY2UuCiAgICBhd2FpdCBib290c3RyYXAuaW5pdCgpOwogICAgYXdhaXQgdGVzdGVyLnB1bXBXaWRnZXQoY29uc3QgQXBwKCkpOwogICAgYXdhaXQgdGVzdGVyLnB1bXBBbmRTZXR0bGUoKTsKCiAgICBleHBlY3QodGVzdGVyLnRha2VFeGNlcHRpb24oKSwgaXNOdWxsKTsKe3sjZGFzaGJvYXJkfX0gICAgZXhwZWN0KGZpbmQudGV4dCgnRXhwbG9yZScpLCBmaW5kc1dpZGdldHMpOwp7ey9kYXNoYm9hcmR9fSAgfSk7Cn0K", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/features/README.md", + "data": + "IyBGZWF0dXJlcwoKVmVydGljYWwgc2xpY2VzIG9mIHRoZSBwcm9kdWN0LiBBIGZlYXR1cmUgb3ducyBpdHMgVUksIHN0YXRlIGFuZCBkYXRhIGFjY2VzcywKYW5kIGV4cG9zZXMgdHdvIHRoaW5ncyB0byB0aGUgYXBwOgoKLSBhbiBgaW5pdCgpYCB0aGF0IHJlZ2lzdGVycyBpdHMgc2VydmljZXMgd2l0aCB0aGUgbG9jYXRvcgotIGEgYFJvdXRlcmAgZXhwb3NpbmcgdGhlIHJvdXRlcyB0aGUgYXBwIHNwbGljZXMgaW50byBpdHMgcm91dGUgdGFibGUKCkZlYXR1cmVzIG1heSBkZXBlbmQgb24gYHBhY2thZ2VzL2AsIGFuZCBzaG91bGQgYXZvaWQgZGVwZW5kaW5nIG9uIGVhY2ggb3RoZXIuCgpBZGQgb25lIHdpdGg6CgpgYGBzaApub25zdG9wIGNyZWF0ZSBwYWNrYWdlIG15X2ZlYXR1cmUgLW8gZmVhdHVyZXMKYGBgCg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/analytics/analytics.dart", + "data": + "e3sjYW5hbHl0aWNzfX1pbXBvcnQgJ3BhY2thZ2U6YW5hbHl0aWNzL2FuYWx5dGljcy5kYXJ0JzsKe3svYW5hbHl0aWNzfX1pbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfYXV0aC9maXJlYmFzZV9hdXRoLmRhcnQnOwoKLy8vIEV2ZXJ5IGF1dGgtcmVsYXRlZCBhbmFseXRpY3MgZXZlbnQgaW4gb25lIHBsYWNlLgovLy8KLy8vIFNjcmVlbnMgY2FsbCB0aGVzZSBpbnN0ZWFkIG9mIGBBbmFseXRpY3NIZWxwZXJgIGRpcmVjdGx5IHNvIHRoZSBldmVudCBuYW1lcwovLy8gYW5kIHBhcmFtZXRlciBzaGFwZXMgc3RheSBjb25zaXN0ZW50IGFjcm9zcyBzaWduLWluLCBzaWduLXVwIGFuZCByZWNvdmVyeS4Ke3teYW5hbHl0aWNzfX0vLy8KLy8vIEFuYWx5dGljcyBpcyBub3QgZW5hYmxlZCBpbiB0aGlzIHByb2plY3QsIHNvIHRoZSBsb2cgbWV0aG9kcyBhcmUgbm8tb3BzLgovLy8gV2lyZSB0aGVtIHVwIGJ5IGFkZGluZyB0aGUgYGFuYWx5dGljc2AgcGFja2FnZSBiYWNrIHRvIGBwdWJzcGVjLnlhbWxgLgp7ey9hbmFseXRpY3N9fWNsYXNzIEF1dGhBbmFseXRpY3MgewogIGNvbnN0IEF1dGhBbmFseXRpY3MuXygpOwoKICAvLy8gJ2dvb2dsZScsICdhcHBsZScgb3IgJ2VtYWlsJywgZGVyaXZlZCBmcm9tIHRoZSBzaWduZWQtaW4gcHJvdmlkZXIuCiAgc3RhdGljIFN0cmluZyBnZXRBdXRoTWV0aG9kKExpc3Q8VXNlckluZm8+PyBwcm92aWRlckRhdGEpIHsKICAgIGlmIChwcm92aWRlckRhdGE/LmlzTm90RW1wdHkgPz8gZmFsc2UpIHsKICAgICAgZmluYWwgcHJvdmlkZXJJZCA9IHByb3ZpZGVyRGF0YSEuZmlyc3QucHJvdmlkZXJJZDsKICAgICAgaWYgKHByb3ZpZGVySWQuY29udGFpbnMoJ2dvb2dsZScpKSByZXR1cm4gJ2dvb2dsZSc7CiAgICAgIGlmIChwcm92aWRlcklkLmNvbnRhaW5zKCdhcHBsZScpKSByZXR1cm4gJ2FwcGxlJzsKICAgIH0KICAgIHJldHVybiAnZW1haWwnOwogIH0KCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dTaWduSW5TdWNjZXNzKHsKICAgIHJlcXVpcmVkIFN0cmluZyBtZXRob2QsCiAgICBTdHJpbmc/IHVzZXJFbWFpbCwKICB9KSBhc3luYyB7Cnt7I2FuYWx5dGljc319ICAgIGF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dTaWduSW4oCiAgICAgIG1ldGhvZDogbWV0aG9kLAogICAgICBwYXJhbWV0ZXJzOiBfc3VjY2Vzc1BhcmFtcyh1c2VyRW1haWwpLAogICAgKTsKe3svYW5hbHl0aWNzfX0gIH0KCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dTaWduVXBTdWNjZXNzKHsKICAgIHJlcXVpcmVkIFN0cmluZyBtZXRob2QsCiAgICBTdHJpbmc/IHVzZXJFbWFpbCwKICB9KSBhc3luYyB7Cnt7I2FuYWx5dGljc319ICAgIGF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dTaWduVXAoCiAgICAgIG1ldGhvZDogbWV0aG9kLAogICAgICBwYXJhbWV0ZXJzOiBfc3VjY2Vzc1BhcmFtcyh1c2VyRW1haWwpLAogICAgKTsKe3svYW5hbHl0aWNzfX0gIH0KCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dVc2VyQ3JlYXRpb25TdWNjZXNzKHsKICAgIHJlcXVpcmVkIFN0cmluZyBtZXRob2QsCiAgICBTdHJpbmc/IHVzZXJFbWFpbCwKICB9KSBhc3luYyB7Cnt7I2FuYWx5dGljc319ICAgIGF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dFdmVudCgKICAgICAgQW5hbHl0aWNzRXZlbnRzLnVzZXIuYXV0aGVudGljYXRlZFJlZGlyZWN0LAogICAgICBwYXJhbWV0ZXJzOiB7CiAgICAgICAgJ2F1dGhfbWV0aG9kJzogbWV0aG9kLAogICAgICAgICdmbG93X3R5cGUnOiAncmVnaXN0cmF0aW9uJywKICAgICAgICAuLi5fc3VjY2Vzc1BhcmFtcyh1c2VyRW1haWwpLAogICAgICB9LAogICAgKTsKe3svYW5hbHl0aWNzfX0gIH0KCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dTaWduT3V0U3VjY2VzcygpIGFzeW5jIHsKe3sjYW5hbHl0aWNzfX0gICAgYXdhaXQgQW5hbHl0aWNzSGVscGVyLmxvZ0V2ZW50KAogICAgICBBbmFseXRpY3NFdmVudHMuYXV0aC5zaWduT3V0LAogICAgICBwYXJhbWV0ZXJzOiBfc3VjY2Vzc1BhcmFtcyhudWxsKSwKICAgICk7Cnt7L2FuYWx5dGljc319ICB9CgogIC8vLyBbZmxvd1R5cGVdIGlzIG9uZSBvZiAnc2lnbl9pbicsICdzaWduX3VwJywgJ2ZvcmdvdF9wYXNzd29yZCcuCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dBdXRoRXJyb3IoewogICAgcmVxdWlyZWQgU3RyaW5nIGVycm9yVHlwZSwKICAgIHJlcXVpcmVkIFN0cmluZyBmbG93VHlwZSwKICAgIFN0cmluZz8gZXJyb3JNZXNzYWdlLAogICAgU3RyaW5nPyBtZXRob2QsCiAgfSkgYXN5bmMgewp7eyNhbmFseXRpY3N9fSAgICBhd2FpdCBBbmFseXRpY3NIZWxwZXIubG9nRXZlbnQoCiAgICAgIEFuYWx5dGljc0V2ZW50cy5hdXRoLmZhaWxlZCwKICAgICAgcGFyYW1ldGVyczogewogICAgICAgICdlcnJvcl90eXBlJzogZXJyb3JUeXBlLAogICAgICAgICdmbG93X3R5cGUnOiBmbG93VHlwZSwKICAgICAgICAnYXV0aF9tZXRob2QnOiBtZXRob2QsCiAgICAgICAgJ2Vycm9yX21lc3NhZ2UnOiBlcnJvck1lc3NhZ2UsCiAgICAgICAgJ3RpbWVzdGFtcCc6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogICAgICB9LAogICAgKTsKe3svYW5hbHl0aWNzfX0gIH0KCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dTaWduT3V0RXJyb3Ioe3JlcXVpcmVkIFN0cmluZyBlcnJvck1lc3NhZ2V9KSBhc3luYyB7Cnt7I2FuYWx5dGljc319ICAgIGF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dFdmVudCgKICAgICAgQW5hbHl0aWNzRXZlbnRzLmVycm9yLmFwcEVycm9yLAogICAgICBwYXJhbWV0ZXJzOiB7CiAgICAgICAgJ2Vycm9yX3R5cGUnOiAnc2lnbl9vdXRfZmFpbGVkJywKICAgICAgICAnZXJyb3JfbWVzc2FnZSc6IGVycm9yTWVzc2FnZSwKICAgICAgICAnZmVhdHVyZSc6ICdhdXRoJywKICAgICAgICAndGltZXN0YW1wJzogRGF0ZVRpbWUubm93KCkudG9Jc284NjAxU3RyaW5nKCksCiAgICAgIH0sCiAgICApOwp7ey9hbmFseXRpY3N9fSAgfQp7eyNhbmFseXRpY3N9fQogIHN0YXRpYyBNYXA8U3RyaW5nLCBPYmplY3Q/PiBfc3VjY2Vzc1BhcmFtcyhTdHJpbmc/IHVzZXJFbWFpbCkgPT4gewogICAgJ3N1Y2Nlc3MnOiAndHJ1ZScsCiAgICAndXNlcl9lbWFpbF9kb21haW4nOiB1c2VyRW1haWw/LnNwbGl0KCdAJykubGFzdCwKICAgICd0aW1lc3RhbXAnOiBEYXRlVGltZS5ub3coKS50b0lzbzg2MDFTdHJpbmcoKSwKICB9Owp7ey9hbmFseXRpY3N9fX0K", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/auth.dart", + "data": + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTphdXRoL2NvbmZpZy9jb25maWcuZGFydCc7CmltcG9ydCAncGFja2FnZTphdXRoL2RhdGEvaW5kZXguZGFydCc7CmltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci9sb2dnZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX3VpX2F1dGgvZmlyZWJhc2VfdWlfYXV0aC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX3VpX29hdXRoX2FwcGxlL2ZpcmViYXNlX3VpX29hdXRoX2FwcGxlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfdWlfb2F1dGhfZ29vZ2xlL2ZpcmViYXNlX3VpX29hdXRoX2dvb2dsZS5kYXJ0JzsKe3sjbmV0d29ya319aW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvbmV0d29yay5kYXJ0JzsKe3svbmV0d29ya319CmV4cG9ydCAnYW5hbHl0aWNzL2FuYWx5dGljcy5kYXJ0JzsKZXhwb3J0ICdjb25maWcvaW5kZXguZGFydCc7CmV4cG9ydCAnY29uc3RhbnRzL2luZGV4LmRhcnQnOwpleHBvcnQgJ2RhdGEvaW5kZXguZGFydCc7CmV4cG9ydCAncm91dGVyL3JvdXRlci5kYXJ0JzsKZXhwb3J0ICd1aS9zY3JlZW5zL2luZGV4LmRhcnQnOwoKLy8vIFJlZ2lzdGVycyB0aGUgYXV0aCBwcm92aWRlcnMgYW5kIHNlcnZpY2VzLgovLy8KLy8vIENhbGwgdGhpcyBhZnRlciBgRmlyZWJhc2UuaW5pdGlhbGl6ZUFwcGAgYW5kIGJlZm9yZSB0aGUgcm91dGVyIGlzIGJ1aWx0IC0KLy8vIHJvdXRlIGd1YXJkcyByZXNvbHZlIFtBdXRoU2VydmljZV0gZnJvbSB0aGUgbG9jYXRvciBvbiB0aGUgZmlyc3QgZnJhbWUuCkZ1dHVyZTx2b2lkPiBpbml0KEF1dGhDb25maWcgY29uZmlnKSBhc3luYyB7CiAgZmluYWwgbG9nZ2VyID0gZGkuZ2V0PExvZ2dlcj4oKTsKICBsb2dnZXIuaSgnSW5pdGlhbGl6aW5nIEF1dGguLi4nKTsKCiAgRmlyZWJhc2VVSUF1dGguY29uZmlndXJlUHJvdmlkZXJzKFsKICAgIEVtYWlsQXV0aFByb3ZpZGVyKCksCiAgICBHb29nbGVQcm92aWRlcihjbGllbnRJZDogY29uZmlnLmNsaWVudElkLCBpT1NQcmVmZXJQbGlzdDogdHJ1ZSksCiAgICBpZiAoUGxhdGZvcm0uaXNJT1MpIEFwcGxlUHJvdmlkZXIoKSwKICBdKTsKCiAgZGkucmVnaXN0ZXI8QXV0aFNlcnZpY2U+KEF1dGhTZXJ2aWNlSW1wKCkpOwp7eyNuZXR3b3JrfX0KICAvLyBUaGUgbmV0d29yayBsYXllciByZWFkcyBiZWFyZXIgdG9rZW5zIHRocm91Z2ggdGhpcyBwcm92aWRlciwgc28gYXV0aCBoYXMKICAvLyB0byBvd24gaXQgLSByZWdpc3RlcmluZyBpdCBhbnl3aGVyZSBlbHNlIHJpc2tzIGEgc3RhbGUgdG9rZW4gb24gcmVmcmVzaC4KICBkaS5yZWdpc3RlcjxBdXRoVG9rZW5Qcm92aWRlcj4oRmlyZWJhc2VBdXRoVG9rZW5Qcm92aWRlcigpKTsKe3svbmV0d29ya319CiAgbG9nZ2VyLmkoJ0F1dGggaW5pdGlhbGl6ZWQnKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/config.dart", + "data": + "YWJzdHJhY3QgY2xhc3MgQXV0aENvbmZpZyB7CiAgU3RyaW5nIGdldCBjbGllbnRJZDsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/config_imp.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmF1dGgvY29uZmlnL2NvbmZpZy5kYXJ0JzsKCmNsYXNzIERlZmF1bHRBdXRoQ29uZmlnIGltcGxlbWVudHMgQXV0aENvbmZpZyB7CiAgQG92ZXJyaWRlCiAgU3RyaW5nIGNsaWVudElkOwoKICBEZWZhdWx0QXV0aENvbmZpZyh7cmVxdWlyZWQgdGhpcy5jbGllbnRJZH0pOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/config/index.dart", + "data": + "ZXhwb3J0ICdjb25maWcuZGFydCc7CmV4cG9ydCAnY29uZmlnX2ltcC5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/constants/index.dart", + "data": "ZXhwb3J0ICdyb3V0ZXMuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/constants/routes.dart", + "data": + "Y2xhc3MgQXV0aFJvdXRlcyB7CiAgQXV0aFJvdXRlcy5fKCk7CgogIHN0YXRpYyBjb25zdCBTdHJpbmcgYXV0aCA9ICcvYXV0aCc7CgogIHN0YXRpYyBjb25zdCBTdHJpbmcgc2lnbkluID0gJyRhdXRoL3NpZ24taW4nOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgc2lnblVwID0gJyRhdXRoL3NpZ24tdXAnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgZm9yZ290UGFzc3dvcmQgPSAnJGF1dGgvZm9yZ290LXBhc3N3b3JkJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHJlc2V0UGFzc3dvcmQgPSAnJGF1dGgvcmVzZXQtcGFzc3dvcmQnOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/index.dart", + "data": + "ZXhwb3J0ICcuL3NlcnZpY2VzL2luZGV4LmRhcnQnOwp7eyNuZXR3b3JrfX1leHBvcnQgJy4vdG9rZW4vZmlyZWJhc2VfYXV0aF90b2tlbl9wcm92aWRlci5kYXJ0JzsKe3svbmV0d29ya319", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/auth_service.dart", + "data": + "YWJzdHJhY3QgY2xhc3MgQXV0aFNlcnZpY2UgewogIFN0cmluZyBnZXQgdWlkOwoKICBib29sIGdldCBpc1NpZ25lZEluOwoKICBGdXR1cmU8dm9pZD4gc2lnbk91dCgpOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/auth_service_imp.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmF1dGgvYW5hbHl0aWNzL2FuYWx5dGljcy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmF1dGgvZGF0YS9zZXJ2aWNlcy9hdXRoX3NlcnZpY2UuZGFydCc7CmltcG9ydCAncGFja2FnZTpjb3JlL2NvcmUuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX2F1dGgvZmlyZWJhc2VfYXV0aC5kYXJ0JzsKCmNsYXNzIEF1dGhTZXJ2aWNlSW1wIGltcGxlbWVudHMgQXV0aFNlcnZpY2UgewogIGZpbmFsIF9sb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpOwogIGZpbmFsIEZpcmViYXNlQXV0aCBfZmlyZWJhc2VBdXRoID0gRmlyZWJhc2VBdXRoLmluc3RhbmNlOwoKICBAb3ZlcnJpZGUKICBTdHJpbmcgZ2V0IHVpZCA9PiBfZmlyZWJhc2VBdXRoLmN1cnJlbnRVc2VyPy51aWQgPz8gJyc7CgogIEBvdmVycmlkZQogIGJvb2wgZ2V0IGlzU2lnbmVkSW4gPT4gX2ZpcmViYXNlQXV0aC5jdXJyZW50VXNlciAhPSBudWxsOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gc2lnbk91dCgpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIF9sb2dnZXIuZCgnQXR0ZW1wdGluZyB0byBzaWduIG91dCB1c2VyJyk7CiAgICAgIGF3YWl0IF9maXJlYmFzZUF1dGguc2lnbk91dCgpOwogICAgICBfbG9nZ2VyLmQoJ1VzZXIgc2lnbmVkIG91dCBzdWNjZXNzZnVsbHknKTsKCiAgICAgIC8vIExvZyBzdWNjZXNzZnVsIHNpZ24gb3V0CiAgICAgIEF1dGhBbmFseXRpY3MubG9nU2lnbk91dFN1Y2Nlc3MoKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCdFcnJvciBzaWduaW5nIG91dCB1c2VyOiAkZScsIGUsIHMpOwoKICAgICAgLy8gTG9nIHNpZ24gb3V0IGVycm9yCiAgICAgIEF1dGhBbmFseXRpY3MubG9nU2lnbk91dEVycm9yKGVycm9yTWVzc2FnZTogZS50b1N0cmluZygpKTsKCiAgICAgIHJldGhyb3c7CiAgICB9CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/services/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdhdXRoX3NlcnZpY2UuZGFydCc7CmV4cG9ydCAnYXV0aF9zZXJ2aWNlX2ltcC5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/data/token/firebase_auth_token_provider.dart", + "data": + "e3sjbmV0d29ya319aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpjb3JlL2NvcmUuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX2F1dGgvZmlyZWJhc2VfYXV0aC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvbmV0d29yay5kYXJ0JzsKCmZpbmFsIF90YWcgPSAnRmlyZWJhc2VBdXRoVG9rZW5Qcm92aWRlcic7CgpjbGFzcyBGaXJlYmFzZUF1dGhUb2tlblByb3ZpZGVyIGltcGxlbWVudHMgQXV0aFRva2VuUHJvdmlkZXIgewogIEZpcmViYXNlQXV0aFRva2VuUHJvdmlkZXIoKSA6IF9sb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpIHsKICAgIF9pbml0aWFsaXplVG9rZW5TdHJlYW0oKTsKICB9CgogIGZpbmFsIEZpcmViYXNlQXV0aCBfZmlyZWJhc2VBdXRoID0gRmlyZWJhc2VBdXRoLmluc3RhbmNlOwogIGZpbmFsIExvZ2dlciBfbG9nZ2VyOwoKICBsYXRlIGZpbmFsIFN0cmVhbUNvbnRyb2xsZXI8U3RyaW5nPz4gX3Rva2VuQ29udHJvbGxlcjsKICBTdHJlYW1TdWJzY3JpcHRpb248VXNlcj8+PyBfYXV0aFN0YXRlU3Vic2NyaXB0aW9uOwogIFN0cmluZz8gX2NhY2hlZFRva2VuOwogIERhdGVUaW1lPyBfdG9rZW5FeHBpcnk7CgogIHZvaWQgX2luaXRpYWxpemVUb2tlblN0cmVhbSgpIHsKICAgIF90b2tlbkNvbnRyb2xsZXIgPSBTdHJlYW1Db250cm9sbGVyPFN0cmluZz8+LmJyb2FkY2FzdCgpOwoKICAgIC8vIExpc3RlbiB0byBhdXRoIHN0YXRlIGNoYW5nZXMKICAgIF9hdXRoU3RhdGVTdWJzY3JpcHRpb24gPSBfZmlyZWJhc2VBdXRoLmF1dGhTdGF0ZUNoYW5nZXMoKS5saXN0ZW4oCiAgICAgIChVc2VyPyB1c2VyKSBhc3luYyB7CiAgICAgICAgaWYgKHVzZXIgIT0gbnVsbCkgewogICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgZmluYWwgdG9rZW4gPSBhd2FpdCB1c2VyLmdldElkVG9rZW4oKTsKICAgICAgICAgICAgX2NhY2hlZFRva2VuID0gdG9rZW47CiAgICAgICAgICAgIF9zZXRUb2tlbkV4cGlyeSh0b2tlbiEpOwogICAgICAgICAgICBfdG9rZW5Db250cm9sbGVyLmFkZCh0b2tlbik7CiAgICAgICAgICAgIF9sb2dnZXIuZCgn8J+UkCRfdGFnOiBBdXRoIHRva2VuIHVwZGF0ZWQgZm9yIHVzZXI6ICR7dXNlci51aWR9Jyk7CiAgICAgICAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgICAgICAgIF9sb2dnZXIuZSgKICAgICAgICAgICAgICAn8J+aqCRfdGFnOiBFcnJvciBnZXR0aW5nIHRva2VuIGZvciB1c2VyOiAke3VzZXIudWlkfScsCiAgICAgICAgICAgICAgZSwKICAgICAgICAgICAgICBzLAogICAgICAgICAgICApOwogICAgICAgICAgICBfY2FjaGVkVG9rZW4gPSBudWxsOwogICAgICAgICAgICBfdG9rZW5FeHBpcnkgPSBudWxsOwogICAgICAgICAgICBfdG9rZW5Db250cm9sbGVyLmFkZChudWxsKTsKICAgICAgICAgIH0KICAgICAgICB9IGVsc2UgewogICAgICAgICAgX2NhY2hlZFRva2VuID0gbnVsbDsKICAgICAgICAgIF90b2tlbkV4cGlyeSA9IG51bGw7CiAgICAgICAgICBfdG9rZW5Db250cm9sbGVyLmFkZChudWxsKTsKICAgICAgICAgIF9sb2dnZXIuZCgn8J+UkyRfdGFnOiBVc2VyIHNpZ25lZCBvdXQsIHRva2VuIGNsZWFyZWQnKTsKICAgICAgICB9CiAgICAgIH0sCiAgICAgIG9uRXJyb3I6IChlLCBzKSB7CiAgICAgICAgX2xvZ2dlci5lKCfwn5qoICRfdGFnOiBBdXRoIHN0YXRlIGNoYW5nZSBlcnJvcicsIGUsIHMpOwogICAgICAgIF90b2tlbkNvbnRyb2xsZXIuYWRkKG51bGwpOwogICAgICB9LAogICAgKTsKICB9CgogIHZvaWQgX3NldFRva2VuRXhwaXJ5KFN0cmluZyB0b2tlbikgewogICAgdHJ5IHsKICAgICAgLy8gRmlyZWJhc2UgSUQgdG9rZW5zIGFyZSB0eXBpY2FsbHkgdmFsaWQgZm9yIDEgaG91cgogICAgICAvLyBXZSdsbCByZWZyZXNoIDUgbWludXRlcyBiZWZvcmUgZXhwaXJ5CiAgICAgIF90b2tlbkV4cGlyeSA9IERhdGVUaW1lLm5vdygpLmFkZChjb25zdCBEdXJhdGlvbihtaW51dGVzOiA1NSkpOwogICAgfSBjYXRjaCAoZSkgewogICAgICBfbG9nZ2VyLncoJ+KaoO+4jyRfdGFnOiBDb3VsZCBub3QgZGV0ZXJtaW5lIHRva2VuIGV4cGlyeSwgdXNpbmcgZGVmYXVsdCAkZScpOwogICAgICBfdG9rZW5FeHBpcnkgPSBEYXRlVGltZS5ub3coKS5hZGQoY29uc3QgRHVyYXRpb24obWludXRlczogNTUpKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTxTdHJpbmc/PiBnZXRWYWxpZFRva2VuKCkgYXN5bmMgewogICAgZmluYWwgdXNlciA9IF9maXJlYmFzZUF1dGguY3VycmVudFVzZXI7CiAgICBpZiAodXNlciA9PSBudWxsKSB7CiAgICAgIF9sb2dnZXIuZCgn8J+UkyRfdGFnOiBObyBhdXRoZW50aWNhdGVkIHVzZXIgYXZhaWxhYmxlJyk7CiAgICAgIHJldHVybiBudWxsOwogICAgfQoKICAgIC8vIENoZWNrIGlmIGNhY2hlZCB0b2tlbiBpcyBzdGlsbCB2YWxpZAogICAgaWYgKF9jYWNoZWRUb2tlbiAhPSBudWxsICYmICFpc1Rva2VuRXhwaXJlZCkgewogICAgICBfbG9nZ2VyLmQoJ/CflJAkX3RhZzogVXNpbmcgY2FjaGVkIHRva2VuJyk7CiAgICAgIHJldHVybiBfY2FjaGVkVG9rZW47CiAgICB9CgogICAgLy8gR2V0IGZyZXNoIHRva2VuCiAgICB0cnkgewogICAgICBfbG9nZ2VyLmQoJ/CflIQkX3RhZzogRmV0Y2hpbmcgZnJlc2ggdG9rZW4gZm9yIHVzZXI6ICR7dXNlci51aWR9Jyk7CiAgICAgIGZpbmFsIHRva2VuID0gYXdhaXQgdXNlci5nZXRJZFRva2VuKHRydWUpOyAvLyBGb3JjZSByZWZyZXNoCiAgICAgIF9jYWNoZWRUb2tlbiA9IHRva2VuOwogICAgICBfc2V0VG9rZW5FeHBpcnkodG9rZW4hKTsKICAgICAgX3Rva2VuQ29udHJvbGxlci5hZGQodG9rZW4pOwogICAgICByZXR1cm4gdG9rZW47CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgn8J+aqCRfdGFnOiBFcnJvciBnZXR0aW5nIGZyZXNoIHRva2VuJywgZSwgcyk7CiAgICAgIF9jYWNoZWRUb2tlbiA9IG51bGw7CiAgICAgIF90b2tlbkV4cGlyeSA9IG51bGw7CiAgICAgIHJldHVybiBudWxsOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgU3RyaW5nPyBnZXRDdXJyZW50VG9rZW4oKSB7CiAgICByZXR1cm4gX2NhY2hlZFRva2VuOwogIH0KCiAgQG92ZXJyaWRlCiAgU3RyZWFtPFN0cmluZz8+IGdldCB0b2tlblN0cmVhbSA9PiBfdG9rZW5Db250cm9sbGVyLnN0cmVhbTsKCiAgQG92ZXJyaWRlCiAgRnV0dXJlPFN0cmluZz8+IHJlZnJlc2hUb2tlbigpIGFzeW5jIHsKICAgIGZpbmFsIHVzZXIgPSBfZmlyZWJhc2VBdXRoLmN1cnJlbnRVc2VyOwogICAgaWYgKHVzZXIgPT0gbnVsbCkgewogICAgICBfbG9nZ2VyLncoJ/CflJMkX3RhZzogQ2Fubm90IHJlZnJlc2ggdG9rZW4gLSBubyBhdXRoZW50aWNhdGVkIHVzZXInKTsKICAgICAgcmV0dXJuIG51bGw7CiAgICB9CgogICAgdHJ5IHsKICAgICAgX2xvZ2dlci5kKCfwn5SEJF90YWc6IFJlZnJlc2hpbmcgdG9rZW4gZm9yIHVzZXI6ICR7dXNlci51aWR9Jyk7CiAgICAgIGZpbmFsIHRva2VuID0gYXdhaXQgdXNlci5nZXRJZFRva2VuKHRydWUpOyAvLyBGb3JjZSByZWZyZXNoCiAgICAgIF9jYWNoZWRUb2tlbiA9IHRva2VuOwogICAgICBfc2V0VG9rZW5FeHBpcnkodG9rZW4hKTsKICAgICAgX3Rva2VuQ29udHJvbGxlci5hZGQodG9rZW4pOwogICAgICBfbG9nZ2VyLmkoJ+KchSRfdGFnOiBUb2tlbiByZWZyZXNoZWQgc3VjY2Vzc2Z1bGx5Jyk7CiAgICAgIHJldHVybiB0b2tlbjsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCfwn5qoJF90YWc6IFRva2VuIHJlZnJlc2ggZmFpbGVkJywgZSwgcyk7CiAgICAgIF9jYWNoZWRUb2tlbiA9IG51bGw7CiAgICAgIF90b2tlbkV4cGlyeSA9IG51bGw7CiAgICAgIF90b2tlbkNvbnRyb2xsZXIuYWRkKG51bGwpOwogICAgICByZXR1cm4gbnVsbDsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIGJvb2wgZ2V0IGlzQXV0aGVudGljYXRlZCA9PiBfZmlyZWJhc2VBdXRoLmN1cnJlbnRVc2VyICE9IG51bGw7CgogIEBvdmVycmlkZQogIGJvb2wgZ2V0IGlzVG9rZW5FeHBpcmVkIHsKICAgIGlmIChfdG9rZW5FeHBpcnkgPT0gbnVsbCkgcmV0dXJuIHRydWU7CiAgICByZXR1cm4gRGF0ZVRpbWUubm93KCkuaXNBZnRlcihfdG9rZW5FeHBpcnkhKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBzaWduT3V0KCkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgX2xvZ2dlci5kKCfwn5STJF90YWc6IFNpZ25pbmcgb3V0IHVzZXInKTsKICAgICAgYXdhaXQgX2ZpcmViYXNlQXV0aC5zaWduT3V0KCk7CiAgICAgIF9jYWNoZWRUb2tlbiA9IG51bGw7CiAgICAgIF90b2tlbkV4cGlyeSA9IG51bGw7CiAgICAgIF90b2tlbkNvbnRyb2xsZXIuYWRkKG51bGwpOwogICAgICBfbG9nZ2VyLmkoJ+KchSRfdGFnOiBVc2VyIHNpZ25lZCBvdXQgc3VjY2Vzc2Z1bGx5Jyk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgn8J+aqCRfdGFnOiBFcnJvciBzaWduaW5nIG91dCB1c2VyJywgZSwgcyk7CiAgICAgIHJldGhyb3c7CiAgICB9CiAgfQoKICB2b2lkIGRpc3Bvc2UoKSB7CiAgICBfYXV0aFN0YXRlU3Vic2NyaXB0aW9uPy5jYW5jZWwoKTsKICAgIF90b2tlbkNvbnRyb2xsZXIuY2xvc2UoKTsKICAgIF9sb2dnZXIuZCgn8J+nuSRfdGFnOiBkaXNwb3NlZCcpOwogIH0KfXt7L25ldHdvcmt9fQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/router/router.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmF1dGgvY29uc3RhbnRzL2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6YXV0aC9kYXRhL3NlcnZpY2VzL2F1dGhfc2VydmljZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmF1dGgvdWkvc2NyZWVucy9pbmRleC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JyBhcyBjb3JlOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Z29fcm91dGVyL2dvX3JvdXRlci5kYXJ0JzsKCmNsYXNzIEF1dGhSb3V0ZXIgZXh0ZW5kcyBjb3JlLkNvcmVSb3V0ZXIgewogIGZpbmFsIEZ1bmN0aW9uKEJ1aWxkQ29udGV4dCBjb250ZXh0KSBvblNpZ25lZEluOwogIGZpbmFsIEZ1bmN0aW9uKEJ1aWxkQ29udGV4dCBjb250ZXh0KSBvblNpZ25lZFVwOwoKICBBdXRoUm91dGVyKHtyZXF1aXJlZCB0aGlzLm9uU2lnbmVkSW4sIHJlcXVpcmVkIHRoaXMub25TaWduZWRVcH0pOwoKICBAb3ZlcnJpZGUKICBMaXN0PFJvdXRlQmFzZT4gZ2V0IHJvdXRlcyA9PiBbCiAgICBHb1JvdXRlKAogICAgICBwYXRoOiBBdXRoUm91dGVzLnNpZ25JbiwKICAgICAgYnVpbGRlcjogKGNvbnRleHQsIHN0YXRlKSA9PiBTaWduSW5TY3JlZW4ob25TaWduZWRJbjogb25TaWduZWRJbiksCiAgICApLAogICAgR29Sb3V0ZSgKICAgICAgcGF0aDogQXV0aFJvdXRlcy5zaWduVXAsCiAgICAgIGJ1aWxkZXI6IChjb250ZXh0LCBzdGF0ZSkgPT4gUmVnaXN0ZXJTY3JlZW4ob25TaWduZWRVcDogb25TaWduZWRVcCksCiAgICApLAogICAgR29Sb3V0ZSgKICAgICAgcGF0aDogQXV0aFJvdXRlcy5mb3Jnb3RQYXNzd29yZCwKICAgICAgYnVpbGRlcjogKGNvbnRleHQsIHN0YXRlKSB7CiAgICAgICAgZmluYWwgZW1haWwgPSBzdGF0ZS51cmkucXVlcnlQYXJhbWV0ZXJzW2NvcmUuS2V5cy5lbWFpbF07CiAgICAgICAgcmV0dXJuIEZvcmdvdFBhc3N3b3JkU2NyZWVuKGVtYWlsOiBlbWFpbCk7CiAgICAgIH0sCiAgICApLAogIF07Cn0KCmZpbmFsIGNsYXNzIEdvQXV0aFJvdXRlIGV4dGVuZHMgR29Sb3V0ZSB7CiAgLy8vIENyZWF0ZSBhIHNlY3VyZSByb3V0ZSB0aGF0IHJlcXVpcmVzIGF1dGhlbnRpY2F0aW9uCiAgR29BdXRoUm91dGUoewogICAgcmVxdWlyZWQgc3VwZXIucGF0aCwKICAgIHJlcXVpcmVkIHN1cGVyLmJ1aWxkZXIsCiAgICBMaXN0PEdvUm91dGU+IHN1cGVyLnJvdXRlcyA9IGNvbnN0IFtdLAogICAgc3VwZXIucGFnZUJ1aWxkZXIsCiAgfSkgOiBzdXBlcigKICAgICAgICAgcmVkaXJlY3Q6IChjb250ZXh0LCBzdGF0ZSkgYXN5bmMgewogICAgICAgICAgIGZpbmFsIGF1dGhTZXJ2aWNlID0gZGkuZ2V0PEF1dGhTZXJ2aWNlPigpOwoKICAgICAgICAgICBpZiAoIWF1dGhTZXJ2aWNlLmlzU2lnbmVkSW4pIHsKICAgICAgICAgICAgIHJldHVybiBBdXRoUm91dGVzLnNpZ25JbjsKICAgICAgICAgICB9CgogICAgICAgICAgIHJldHVybiBudWxsOwogICAgICAgICB9LAogICAgICAgKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/components/footer_builder.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmF1dGgvY29uc3RhbnRzL2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9sb2dnZXIvbG9nZ2VyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CmltcG9ydCAncGFja2FnZTpmaXJlYmFzZV91aV9hdXRoL2ZpcmViYXNlX3VpX2F1dGguZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Z29fcm91dGVyL2dvX3JvdXRlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CgplbnVtIEZvb3RlclR5cGUgeyBzaWduSW4sIHJlZ2lzdGVyIH0KCldpZGdldCBmb290ZXJCdWlsZGVyKEJ1aWxkQ29udGV4dCBjb250ZXh0LCBBdXRoQWN0aW9uIGFjdGlvbiwgRm9vdGVyVHlwZSB0eXBlKSB7CiAgZmluYWwgbG9nZ2VyID0gZGkuZ2V0PExvZ2dlcj4oKTsKICBzd2l0Y2ggKHR5cGUpIHsKICAgIGNhc2UgRm9vdGVyVHlwZS5zaWduSW46CiAgICAgIHJldHVybiBfQXV0aEZvb3RlcigKICAgICAgICB0aXRsZTogc3RyaW5ncy5hdXRoLmRvbnRfaGF2ZV9hY2NvdW50LAogICAgICAgIGxpbmtUZXh0OiBzdHJpbmdzLmF1dGgucmVnaXN0ZXIsCiAgICAgICAgb25UYXA6ICgpIHsKICAgICAgICAgIGxvZ2dlci5kKCdOYXZpZ2F0aW5nIHRvIHJlZ2lzdGVyIHNjcmVlbicpOwoKICAgICAgICAgIGNvbnRleHQuZ28oQXV0aFJvdXRlcy5zaWduVXApOwogICAgICAgIH0sCiAgICAgICk7CiAgICBjYXNlIEZvb3RlclR5cGUucmVnaXN0ZXI6CiAgICAgIHJldHVybiBfQXV0aEZvb3RlcigKICAgICAgICB0aXRsZTogc3RyaW5ncy5hdXRoLmFscmVhZHlfaGF2ZV9hY2NvdW50LAogICAgICAgIGxpbmtUZXh0OiBzdHJpbmdzLmF1dGguc2lnbl9pbiwKICAgICAgICBvblRhcDogKCkgewogICAgICAgICAgbG9nZ2VyLmQoJ05hdmlnYXRpbmcgdG8gc2lnbiBpbiBzY3JlZW4nKTsKCiAgICAgICAgICBjb250ZXh0LmdvKEF1dGhSb3V0ZXMuc2lnbkluKTsKICAgICAgICB9LAogICAgICApOwogIH0KfQoKY2xhc3MgX0F1dGhGb290ZXIgZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGZpbmFsIFN0cmluZyB0aXRsZTsKICBmaW5hbCBTdHJpbmcgbGlua1RleHQ7CiAgZmluYWwgVm9pZENhbGxiYWNrIG9uVGFwOwoKICBjb25zdCBfQXV0aEZvb3Rlcih7CiAgICByZXF1aXJlZCB0aGlzLnRpdGxlLAogICAgcmVxdWlyZWQgdGhpcy5saW5rVGV4dCwKICAgIHJlcXVpcmVkIHRoaXMub25UYXAsCiAgfSk7CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgcmV0dXJuIFBhZGRpbmcoCiAgICAgIHBhZGRpbmc6IGNvbnN0IEVkZ2VJbnNldHMuc3ltbWV0cmljKHZlcnRpY2FsOiAxNi4wKSwKICAgICAgY2hpbGQ6IFJvdygKICAgICAgICBtYWluQXhpc0FsaWdubWVudDogTWFpbkF4aXNBbGlnbm1lbnQuY2VudGVyLAogICAgICAgIGNoaWxkcmVuOiBbCiAgICAgICAgICBUZXh0KHRpdGxlKSwKICAgICAgICAgIEdlc3R1cmVEZXRlY3RvcigKICAgICAgICAgICAgb25UYXA6IG9uVGFwLAogICAgICAgICAgICBjaGlsZDogVGV4dCgKICAgICAgICAgICAgICBsaW5rVGV4dCwKICAgICAgICAgICAgICBzdHlsZTogVGhlbWUub2YoY29udGV4dCkudGV4dFRoZW1lLmJvZHlNZWRpdW0/LmNvcHlXaXRoKAogICAgICAgICAgICAgICAgY29sb3I6IFRoZW1lLm9mKGNvbnRleHQpLmNvbG9yU2NoZW1lLnByaW1hcnksCiAgICAgICAgICAgICAgICBmb250V2VpZ2h0OiBGb250V2VpZ2h0LmJvbGQsCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgKSwKICAgICAgICAgICksCiAgICAgICAgXSwKICAgICAgKSwKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/components/header_builder.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2NhbGl6YXRpb24vbG9jYWxpemF0aW9uLmRhcnQnOwoKLy8vIEJhbm5lciBzaG93biBhYm92ZSB0aGUgc2lnbi1pbiBhbmQgcmVnaXN0ZXIgZm9ybXMuCi8vLwovLy8gRGVsaWJlcmF0ZWx5IGRyYXduIGZyb20gdGhlIHRoZW1lIHJhdGhlciB0aGFuIGFuIGltYWdlLCBzbyBhIGZyZXNoIHByb2plY3QKLy8vIGhhcyBubyBhc3NldCB0byBzaGlwIGFuZCBub3RoaW5nIHRvIGZldGNoLiBTd2FwIGl0IGZvciB5b3VyIG93biBhcnR3b3JrOgovLy8gYWRkIHRoZSBmaWxlIHVuZGVyIHRoZSBhcHAncyBgYXNzZXRzOmAgYW5kIHVzZSBgZGVzaWduX3N5c3RlbWAncyBgSGVhZGVyYAovLy8gd2l0aCBgSGVhZGVyVHlwZS5hc3NldGAuCldpZGdldCBoZWFkZXJCdWlsZGVyKEJ1aWxkQ29udGV4dCBjb250ZXh0KSB7CiAgZmluYWwgdGhlbWUgPSBUaGVtZS5vZihjb250ZXh0KTsKCiAgcmV0dXJuIFBhZGRpbmcoCiAgICBwYWRkaW5nOiBjb25zdCBFZGdlSW5zZXRzLnN5bW1ldHJpYyh2ZXJ0aWNhbDogMjQpLAogICAgY2hpbGQ6IENvbHVtbigKICAgICAgY2hpbGRyZW46IFsKICAgICAgICBDb250YWluZXIoCiAgICAgICAgICBoZWlnaHQ6IDcyLAogICAgICAgICAgd2lkdGg6IDcyLAogICAgICAgICAgZGVjb3JhdGlvbjogQm94RGVjb3JhdGlvbigKICAgICAgICAgICAgY29sb3I6IHRoZW1lLmNvbG9yU2NoZW1lLnByaW1hcnlDb250YWluZXIsCiAgICAgICAgICAgIGJvcmRlclJhZGl1czogQm9yZGVyUmFkaXVzLmNpcmN1bGFyKDIwKSwKICAgICAgICAgICksCiAgICAgICAgICBjaGlsZDogSWNvbigKICAgICAgICAgICAgSWNvbnMuYm9sdF9vdXRsaW5lZCwKICAgICAgICAgICAgc2l6ZTogNDAsCiAgICAgICAgICAgIGNvbG9yOiB0aGVtZS5jb2xvclNjaGVtZS5vblByaW1hcnlDb250YWluZXIsCiAgICAgICAgICApLAogICAgICAgICksCiAgICAgICAgY29uc3QgU2l6ZWRCb3goaGVpZ2h0OiAxNiksCiAgICAgICAgVGV4dCgKICAgICAgICAgIHN0cmluZ3MuYXBwLm5hbWUsCiAgICAgICAgICBzdHlsZTogdGhlbWUudGV4dFRoZW1lLnRpdGxlTGFyZ2U/LmNvcHlXaXRoKAogICAgICAgICAgICBmb250V2VpZ2h0OiBGb250V2VpZ2h0LmJvbGQsCiAgICAgICAgICApLAogICAgICAgICksCiAgICAgIF0sCiAgICApLAogICk7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/forgot_password_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmF1dGgvdWkvY29tcG9uZW50cy9oZWFkZXJfYnVpbGRlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX3VpX2F1dGgvZmlyZWJhc2VfdWlfYXV0aC5kYXJ0JyBhcyB1aV9hdXRoOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKCmNsYXNzIEZvcmdvdFBhc3N3b3JkU2NyZWVuIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBmaW5hbCBTdHJpbmc/IGVtYWlsOwoKICBjb25zdCBGb3Jnb3RQYXNzd29yZFNjcmVlbih7c3VwZXIua2V5LCB0aGlzLmVtYWlsfSk7CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgcmV0dXJuIHVpX2F1dGguRm9yZ290UGFzc3dvcmRTY3JlZW4oCiAgICAgIGVtYWlsOiBlbWFpbCwKICAgICAgaGVhZGVyQnVpbGRlcjogKGNvbnRleHQsIGNvbnN0cmFpbnRzLCBzaHJpbmtPZmZzZXQpIHsKICAgICAgICByZXR1cm4gaGVhZGVyQnVpbGRlcihjb250ZXh0KTsKICAgICAgfSwKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdmb3Jnb3RfcGFzc3dvcmRfc2NyZWVuLmRhcnQnOwpleHBvcnQgJ3JlZ2lzdGVyX3NjcmVlbi5kYXJ0JzsKZXhwb3J0ICdzaWduX2luX3NjcmVlbi5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/register_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmF1dGgvYW5hbHl0aWNzL2FuYWx5dGljcy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmF1dGgvdWkvY29tcG9uZW50cy9mb290ZXJfYnVpbGRlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmF1dGgvdWkvY29tcG9uZW50cy9oZWFkZXJfYnVpbGRlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL2xvZ2dlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfdWlfYXV0aC9maXJlYmFzZV91aV9hdXRoLmRhcnQnIGFzIHVpX2F1dGg7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwoKY2xhc3MgUmVnaXN0ZXJTY3JlZW4gZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGNvbnN0IFJlZ2lzdGVyU2NyZWVuKHtzdXBlci5rZXksIHJlcXVpcmVkIHRoaXMub25TaWduZWRVcH0pOwoKICBmaW5hbCB2b2lkIEZ1bmN0aW9uKEJ1aWxkQ29udGV4dCBjb250ZXh0KSBvblNpZ25lZFVwOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIGZpbmFsIGxvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CgogICAgcmV0dXJuIHVpX2F1dGguUmVnaXN0ZXJTY3JlZW4oCiAgICAgIHNob3dBdXRoQWN0aW9uU3dpdGNoOiBmYWxzZSwKICAgICAgYWN0aW9uczogWwogICAgICAgIHVpX2F1dGguQXV0aFN0YXRlQ2hhbmdlQWN0aW9uPHVpX2F1dGguU2lnbmVkSW4+KChjb250ZXh0LCBzdGF0ZSkgYXN5bmMgewogICAgICAgICAgbG9nZ2VyLmQoJ1VzZXIgcmVnaXN0ZXJlZCBzdWNjZXNzZnVsbHk6ICR7c3RhdGUudXNlcj8uZW1haWx9Jyk7CgogICAgICAgICAgZmluYWwgbWV0aG9kID0gQXV0aEFuYWx5dGljcy5nZXRBdXRoTWV0aG9kKHN0YXRlLnVzZXI/LnByb3ZpZGVyRGF0YSk7CgogICAgICAgICAgLy8gTG9nIHN1Y2Nlc3NmdWwgc2lnbiBpbiAod2hlbiB1c2VyIGFscmVhZHkgZXhpc3RzKQogICAgICAgICAgQXV0aEFuYWx5dGljcy5sb2dTaWduSW5TdWNjZXNzKAogICAgICAgICAgICBtZXRob2Q6IG1ldGhvZCwKICAgICAgICAgICAgdXNlckVtYWlsOiBzdGF0ZS51c2VyPy5lbWFpbCwKICAgICAgICAgICk7CgogICAgICAgICAgb25TaWduZWRVcChjb250ZXh0KTsKICAgICAgICB9KSwKICAgICAgICB1aV9hdXRoLkF1dGhTdGF0ZUNoYW5nZUFjdGlvbjx1aV9hdXRoLlVzZXJDcmVhdGVkPigoCiAgICAgICAgICBjb250ZXh0LAogICAgICAgICAgc3RhdGUsCiAgICAgICAgKSBhc3luYyB7CiAgICAgICAgICBsb2dnZXIuZCgKICAgICAgICAgICAgJ1VzZXIgY3JlYXRlZCBzdWNjZXNzZnVsbHk6ICR7c3RhdGUuY3JlZGVudGlhbC51c2VyPy5lbWFpbH0nLAogICAgICAgICAgKTsKCiAgICAgICAgICBmaW5hbCBtZXRob2QgPSBBdXRoQW5hbHl0aWNzLmdldEF1dGhNZXRob2QoCiAgICAgICAgICAgIHN0YXRlLmNyZWRlbnRpYWwudXNlcj8ucHJvdmlkZXJEYXRhLAogICAgICAgICAgKTsKCiAgICAgICAgICAvLyBMb2cgc3VjY2Vzc2Z1bCBzaWduIHVwCiAgICAgICAgICBBdXRoQW5hbHl0aWNzLmxvZ1NpZ25VcFN1Y2Nlc3MoCiAgICAgICAgICAgIG1ldGhvZDogbWV0aG9kLAogICAgICAgICAgICB1c2VyRW1haWw6IHN0YXRlLmNyZWRlbnRpYWwudXNlcj8uZW1haWwsCiAgICAgICAgICApOwoKICAgICAgICAgIC8vIExvZyB1c2VyIGNyZWF0aW9uIHN1Y2Nlc3MKICAgICAgICAgIEF1dGhBbmFseXRpY3MubG9nVXNlckNyZWF0aW9uU3VjY2VzcygKICAgICAgICAgICAgbWV0aG9kOiBtZXRob2QsCiAgICAgICAgICAgIHVzZXJFbWFpbDogc3RhdGUuY3JlZGVudGlhbC51c2VyPy5lbWFpbCwKICAgICAgICAgICk7CgogICAgICAgICAgb25TaWduZWRVcChjb250ZXh0KTsKICAgICAgICB9KSwKICAgICAgICB1aV9hdXRoLkF1dGhTdGF0ZUNoYW5nZUFjdGlvbjx1aV9hdXRoLkF1dGhGYWlsZWQ+KCgKICAgICAgICAgIGNvbnRleHQsCiAgICAgICAgICBzdGF0ZSwKICAgICAgICApIGFzeW5jIHsKICAgICAgICAgIGxvZ2dlci5lKCdSZWdpc3RyYXRpb24gZmFpbGVkOiAke3N0YXRlLmV4Y2VwdGlvbn0nKTsKCiAgICAgICAgICAvLyBMb2cgYXV0aCBlcnJvcgogICAgICAgICAgQXV0aEFuYWx5dGljcy5sb2dBdXRoRXJyb3IoCiAgICAgICAgICAgIGVycm9yVHlwZTogc3RhdGUuZXhjZXB0aW9uLnJ1bnRpbWVUeXBlLnRvU3RyaW5nKCksCiAgICAgICAgICAgIGZsb3dUeXBlOiAnc2lnbl91cCcsCiAgICAgICAgICAgIGVycm9yTWVzc2FnZTogc3RhdGUuZXhjZXB0aW9uLnRvU3RyaW5nKCksCiAgICAgICAgICAgIG1ldGhvZDogJ2VtYWlsJywKICAgICAgICAgICk7CiAgICAgICAgfSksCiAgICAgIF0sCiAgICAgIGZvb3RlckJ1aWxkZXI6IChjb250ZXh0LCBhY3Rpb24pIHsKICAgICAgICByZXR1cm4gZm9vdGVyQnVpbGRlcihjb250ZXh0LCBhY3Rpb24sIEZvb3RlclR5cGUucmVnaXN0ZXIpOwogICAgICB9LAogICAgICBoZWFkZXJCdWlsZGVyOiAoY29udGV4dCwgY29uc3RyYWludHMsIHNocmlua09mZnNldCkgewogICAgICAgIHJldHVybiBoZWFkZXJCdWlsZGVyKGNvbnRleHQpOwogICAgICB9LAogICAgKTsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{auth_dir}}/lib/ui/screens/sign_in_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmF1dGgvYW5hbHl0aWNzL2FuYWx5dGljcy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmF1dGgvY29uc3RhbnRzL2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6YXV0aC91aS9jb21wb25lbnRzL2Zvb3Rlcl9idWlsZGVyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6YXV0aC91aS9jb21wb25lbnRzL2hlYWRlcl9idWlsZGVyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnIGFzIGNvcmU7CmltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci9sb2dnZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpkZXNpZ25fc3lzdGVtL3RvYXN0L3RvYXN0cy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfdWlfYXV0aC9maXJlYmFzZV91aV9hdXRoLmRhcnQnIGFzIHVpX2F1dGg7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Z29fcm91dGVyL2dvX3JvdXRlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CgpjbGFzcyBTaWduSW5TY3JlZW4gZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGNvbnN0IFNpZ25JblNjcmVlbih7c3VwZXIua2V5LCByZXF1aXJlZCB0aGlzLm9uU2lnbmVkSW59KTsKCiAgZmluYWwgRnVuY3Rpb24oQnVpbGRDb250ZXh0IGNvbnRleHQpIG9uU2lnbmVkSW47CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgZmluYWwgbG9nZ2VyID0gZGkuZ2V0PExvZ2dlcj4oKTsKCiAgICByZXR1cm4gdWlfYXV0aC5TaWduSW5TY3JlZW4oCiAgICAgIHNob3dBdXRoQWN0aW9uU3dpdGNoOiBmYWxzZSwKICAgICAgb2F1dGhCdXR0b25WYXJpYW50OiB1aV9hdXRoLk9BdXRoQnV0dG9uVmFyaWFudC5pY29uX2FuZF90ZXh0LAogICAgICBhY3Rpb25zOiBbCiAgICAgICAgdWlfYXV0aC5Gb3Jnb3RQYXNzd29yZEFjdGlvbigoY29udGV4dCwgZW1haWwpIHsKICAgICAgICAgIGxvZ2dlci5kKCdOYXZpZ2F0aW5nIHRvIGZvcmdvdCBwYXNzd29yZCBzY3JlZW4gZm9yIGVtYWlsOiAkZW1haWwnKTsKCiAgICAgICAgICBmaW5hbCB1cmkgPSBVcmkoCiAgICAgICAgICAgIHBhdGg6IEF1dGhSb3V0ZXMuZm9yZ290UGFzc3dvcmQsCiAgICAgICAgICAgIHF1ZXJ5UGFyYW1ldGVyczoge2NvcmUuS2V5cy5lbWFpbDogZW1haWx9LAogICAgICAgICAgKTsKICAgICAgICAgIGNvbnRleHQucHVzaCh1cmkudG9TdHJpbmcoKSk7CiAgICAgICAgfSksCiAgICAgICAgdWlfYXV0aC5BdXRoU3RhdGVDaGFuZ2VBY3Rpb248dWlfYXV0aC5TaWduZWRJbj4oKGNvbnRleHQsIHN0YXRlKSBhc3luYyB7CiAgICAgICAgICBsb2dnZXIuZCgnVXNlciBzaWduZWQgaW4gc3VjY2Vzc2Z1bGx5OiAke3N0YXRlLnVzZXI/LmVtYWlsfScpOwoKICAgICAgICAgIGZpbmFsIG1ldGhvZCA9IEF1dGhBbmFseXRpY3MuZ2V0QXV0aE1ldGhvZChzdGF0ZS51c2VyPy5wcm92aWRlckRhdGEpOwoKICAgICAgICAgIC8vIExvZyBzdWNjZXNzZnVsIHNpZ24gaW4KICAgICAgICAgIEF1dGhBbmFseXRpY3MubG9nU2lnbkluU3VjY2VzcygKICAgICAgICAgICAgbWV0aG9kOiBtZXRob2QsCiAgICAgICAgICAgIHVzZXJFbWFpbDogc3RhdGUudXNlcj8uZW1haWwsCiAgICAgICAgICApOwoKICAgICAgICAgIG9uU2lnbmVkSW4oY29udGV4dCk7CiAgICAgICAgfSksCiAgICAgICAgdWlfYXV0aC5BdXRoU3RhdGVDaGFuZ2VBY3Rpb248dWlfYXV0aC5Vc2VyQ3JlYXRlZD4oKAogICAgICAgICAgY29udGV4dCwKICAgICAgICAgIHN0YXRlLAogICAgICAgICkgYXN5bmMgewogICAgICAgICAgbG9nZ2VyLmQoCiAgICAgICAgICAgICdVc2VyIHNpZ25lZCBpbiBzdWNjZXNzZnVsbHk6ICR7c3RhdGUuY3JlZGVudGlhbC51c2VyPy5lbWFpbH0nLAogICAgICAgICAgKTsKCiAgICAgICAgICBmaW5hbCBtZXRob2QgPSBBdXRoQW5hbHl0aWNzLmdldEF1dGhNZXRob2QoCiAgICAgICAgICAgIHN0YXRlLmNyZWRlbnRpYWwudXNlcj8ucHJvdmlkZXJEYXRhLAogICAgICAgICAgKTsKCiAgICAgICAgICAvLyBMb2cgc3VjY2Vzc2Z1bCB1c2VyIGNyZWF0aW9uIChmcm9tIHNpZ24gaW4gc2NyZWVuKQogICAgICAgICAgQXV0aEFuYWx5dGljcy5sb2dVc2VyQ3JlYXRpb25TdWNjZXNzKAogICAgICAgICAgICBtZXRob2Q6IG1ldGhvZCwKICAgICAgICAgICAgdXNlckVtYWlsOiBzdGF0ZS5jcmVkZW50aWFsLnVzZXI/LmVtYWlsLAogICAgICAgICAgKTsKCiAgICAgICAgICBvblNpZ25lZEluKGNvbnRleHQpOwogICAgICAgIH0pLAogICAgICAgIHVpX2F1dGguQXV0aFN0YXRlQ2hhbmdlQWN0aW9uPHVpX2F1dGguQXV0aEZhaWxlZD4oKAogICAgICAgICAgY29udGV4dCwKICAgICAgICAgIHN0YXRlLAogICAgICAgICkgYXN5bmMgewogICAgICAgICAgbG9nZ2VyLmUoJ1NpZ24taW4gZmFpbGVkOiAke3N0YXRlLmV4Y2VwdGlvbn0nKTsKCiAgICAgICAgICAvLyBMb2cgYXV0aCBlcnJvcgogICAgICAgICAgQXV0aEFuYWx5dGljcy5sb2dBdXRoRXJyb3IoCiAgICAgICAgICAgIGVycm9yVHlwZTogc3RhdGUuZXhjZXB0aW9uLnJ1bnRpbWVUeXBlLnRvU3RyaW5nKCksCiAgICAgICAgICAgIGZsb3dUeXBlOiAnc2lnbl9pbicsCiAgICAgICAgICAgIGVycm9yTWVzc2FnZTogc3RhdGUuZXhjZXB0aW9uLnRvU3RyaW5nKCksCiAgICAgICAgICApOwoKICAgICAgICAgIFRvYXN0LmVycm9yKGNvbnRleHQsIG1lc3NhZ2U6IHN0cmluZ3MuZXJyb3JzLmRlZmF1bHRfZXJyb3JfbWVzc2FnZSk7CiAgICAgICAgfSksCiAgICAgICAgLy9UT0RPOiBIYW5kbGUgb3RoZXIgQXV0aFN0YXRlCiAgICAgIF0sCiAgICAgIGZvb3RlckJ1aWxkZXI6IChjb250ZXh0LCBhY3Rpb24pIHsKICAgICAgICByZXR1cm4gZm9vdGVyQnVpbGRlcihjb250ZXh0LCBhY3Rpb24sIEZvb3RlclR5cGUuc2lnbkluKTsKICAgICAgfSwKICAgICAgaGVhZGVyQnVpbGRlcjogKGNvbnRleHQsIGNvbnN0cmFpbnRzLCBzaHJpbmtPZmZzZXQpIHsKICAgICAgICByZXR1cm4gaGVhZGVyQnVpbGRlcihjb250ZXh0KTsKICAgICAgfSwKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml", + "data": + "bmFtZTogYXV0aApkZXNjcmlwdGlvbjogIkZpcmViYXNlIEF1dGhlbnRpY2F0aW9uIChlbWFpbCwgR29vZ2xlLCBBcHBsZSkgd2l0aCBnb19yb3V0ZXIgaW50ZWdyYXRpb24gZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZXF1YXRhYmxlOiBeMi4wLjcKICBmaXJlYmFzZV9hdXRoOiBeNS42LjAKICBmaXJlYmFzZV9jb3JlOiBeMy4xNC4wCiAgZmlyZWJhc2VfdWlfYXV0aDogXjEuMTcuMAogIGZpcmViYXNlX3VpX29hdXRoX2FwcGxlOiBeMS4zLjIKICBmaXJlYmFzZV91aV9vYXV0aF9nb29nbGU6IF4xLjQuMgogIGdvX3JvdXRlcjogXjE1LjEuMwogIGpzb25fYW5ub3RhdGlvbjogXjQuOS4wCiAgY29yZToKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2NvcmUKICBkZXNpZ25fc3lzdGVtOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvZGVzaWduX3N5c3RlbQogIGRpOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvZGkKICBsb2NhbGl6YXRpb246CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9sb2NhbGl6YXRpb24Ke3sjZmlyZXN0b3JlfX0gIGNsb3VkX2ZpcmVzdG9yZTogXjUuNi4wCnt7L2ZpcmVzdG9yZX19e3sjbmV0d29ya319ICBuZXR3b3JrOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvbmV0d29yawp7ey9uZXR3b3JrfX17eyNhbmFseXRpY3N9fSAgYW5hbHl0aWNzOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvYW5hbHl0aWNzCnt7L2FuYWx5dGljc319CmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCgpmbHV0dGVyOgo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/features/{{auth_dir}}/README.md", + "data": + "IyBBdXRoCgpBIE1lbG9zLW1hbmFnZWQgcHJvamVjdCBmb3IgbW9uby1yZXBvLCBjcmVhdGVkIHVzaW5nIE5vblN0b3AgQ0xJLgoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgojIyBJbnN0YWxsYXRpb24g8J+SuwoKKirinZcgSW4gb3JkZXIgdG8gc3RhcnQgdXNpbmcgQXV0aCB5b3UgbXVzdCBoYXZlIHRoZSBbRmx1dHRlciBTREtdW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXSBpbnN0YWxsZWQgb24geW91ciBtYWNoaW5lLioqCgpJbnN0YWxsIHZpYSBgZGFydCBwdWIgYWRkYDoKCmBgYHNoCmRhcnQgcHViIGFkZCBhdXRoCmBgYAoKW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXTogaHR0cHM6Ly9kb2NzLmZsdXR0ZXIuZGV2L2dldC1zdGFydGVkL2luc3RhbGw=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/dashboard.dart", + "data": + "bGlicmFyeTsKCmV4cG9ydCAncm91dGVyL2Rhc2hib2FyZF9yb3V0ZXIuZGFydCc7CmV4cG9ydCAndWkvc2NyZWVucy9pbmRleC5kYXJ0JzsKZXhwb3J0ICd1aS93aWRnZXRzL2luZGV4LmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/router/dashboard_router.dart", + "data": + "e3sjYXV0aH19aW1wb3J0ICdwYWNrYWdlOmF1dGgvYXV0aC5kYXJ0JyBhcyBhdXRoOwp7ey9hdXRofX1pbXBvcnQgJ3BhY2thZ2U6ZGFzaGJvYXJkL3VpL3NjcmVlbnMvaW5kZXguZGFydCc7CmltcG9ydCAncGFja2FnZTpnb19yb3V0ZXIvZ29fcm91dGVyLmRhcnQnOwoKe3sjYXV0aH19Ly8vIEV2ZXJ5IHNoZWxsIGJyYW5jaCBpcyBndWFyZGVkIC0gdW5hdXRoZW50aWNhdGVkIHVzZXJzIGJvdW5jZSB0byBzaWduLWluLgp0eXBlZGVmIF9TaGVsbFJvdXRlID0gYXV0aC5Hb0F1dGhSb3V0ZTt7ey9hdXRofX17e15hdXRofX10eXBlZGVmIF9TaGVsbFJvdXRlID0gR29Sb3V0ZTt7ey9hdXRofX0KCi8vLyBCb3R0b20tbmF2aWdhdGlvbiBzaGVsbCBmb3IgdGhlIHNpZ25lZC1pbiBwYXJ0IG9mIHRoZSBhcHAuCi8vLwovLy8gQWRkIGEgdGFiIGJ5IGFkZGluZyBhIFtTdGF0ZWZ1bFNoZWxsQnJhbmNoXSBoZXJlIGFuZCBhIGRlc3RpbmF0aW9uIGluCi8vLyBbRGFzaGJvYXJkU2hlbGxTY3JlZW5dLgpjbGFzcyBEYXNoYm9hcmRSb3V0ZXIgewogIGNvbnN0IERhc2hib2FyZFJvdXRlci5fKCk7CgogIHN0YXRpYyBjb25zdCBTdHJpbmcgaG9tZSA9ICcvaG9tZS9kYXNoYm9hcmQnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgZXhwbG9yZSA9ICcvaG9tZS9leHBsb3JlJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHByb2ZpbGUgPSAnL2hvbWUvcHJvZmlsZSc7CgogIHN0YXRpYyBTdGF0ZWZ1bFNoZWxsUm91dGUgY3JlYXRlU2hlbGxSb3V0ZSgpIHsKICAgIHJldHVybiBTdGF0ZWZ1bFNoZWxsUm91dGUuaW5kZXhlZFN0YWNrKAogICAgICBidWlsZGVyOiAoY29udGV4dCwgc3RhdGUsIG5hdmlnYXRpb25TaGVsbCkgPT4KICAgICAgICAgIERhc2hib2FyZFNoZWxsU2NyZWVuKG5hdmlnYXRpb25TaGVsbDogbmF2aWdhdGlvblNoZWxsKSwKICAgICAgYnJhbmNoZXM6IFsKICAgICAgICBTdGF0ZWZ1bFNoZWxsQnJhbmNoKAogICAgICAgICAgcm91dGVzOiBbCiAgICAgICAgICAgIF9TaGVsbFJvdXRlKAogICAgICAgICAgICAgIHBhdGg6IGhvbWUsCiAgICAgICAgICAgICAgYnVpbGRlcjogKGNvbnRleHQsIHN0YXRlKSA9PiBjb25zdCBIb21lVGFiU2NyZWVuKCksCiAgICAgICAgICAgICksCiAgICAgICAgICBdLAogICAgICAgICksCiAgICAgICAgU3RhdGVmdWxTaGVsbEJyYW5jaCgKICAgICAgICAgIHJvdXRlczogWwogICAgICAgICAgICBfU2hlbGxSb3V0ZSgKICAgICAgICAgICAgICBwYXRoOiBleHBsb3JlLAogICAgICAgICAgICAgIGJ1aWxkZXI6IChjb250ZXh0LCBzdGF0ZSkgPT4gY29uc3QgRXhwbG9yZVRhYlNjcmVlbigpLAogICAgICAgICAgICApLAogICAgICAgICAgXSwKICAgICAgICApLAogICAgICAgIFN0YXRlZnVsU2hlbGxCcmFuY2goCiAgICAgICAgICByb3V0ZXM6IFsKICAgICAgICAgICAgX1NoZWxsUm91dGUoCiAgICAgICAgICAgICAgcGF0aDogcHJvZmlsZSwKICAgICAgICAgICAgICBidWlsZGVyOiAoY29udGV4dCwgc3RhdGUpID0+IGNvbnN0IFByb2ZpbGVUYWJTY3JlZW4oKSwKICAgICAgICAgICAgKSwKICAgICAgICAgIF0sCiAgICAgICAgKSwKICAgICAgXSwKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/dashboard_shell_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vZGVzaWduX3N5c3RlbS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpnb19yb3V0ZXIvZ29fcm91dGVyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bG9jYWxpemF0aW9uL2xvY2FsaXphdGlvbi5kYXJ0JzsKCi8vLyBIb3N0cyB0aGUgYm90dG9tIG5hdmlnYXRpb24gYmFyIGFuZCBrZWVwcyBlYWNoIHRhYidzIG5hdmlnYXRpb24gc3RhY2sgYWxpdmUuCi8vLwovLy8gQWRkIGEgdGFiIGJ5IGFkZGluZyBhIFtTdGF0ZWZ1bFNoZWxsQnJhbmNoXSBpbiBgRGFzaGJvYXJkUm91dGVyYCBhbmQgYQovLy8gbWF0Y2hpbmcgZGVzdGluYXRpb24gaGVyZSAtIHRoZSB0d28gbGlzdHMgYXJlIHBvc2l0aW9uYWwuCmNsYXNzIERhc2hib2FyZFNoZWxsU2NyZWVuIGV4dGVuZHMgU3RhdGVmdWxXaWRnZXQgewogIGNvbnN0IERhc2hib2FyZFNoZWxsU2NyZWVuKHtzdXBlci5rZXksIHJlcXVpcmVkIHRoaXMubmF2aWdhdGlvblNoZWxsfSk7CgogIGZpbmFsIFN0YXRlZnVsTmF2aWdhdGlvblNoZWxsIG5hdmlnYXRpb25TaGVsbDsKCiAgQG92ZXJyaWRlCiAgU3RhdGU8RGFzaGJvYXJkU2hlbGxTY3JlZW4+IGNyZWF0ZVN0YXRlKCkgPT4gX0Rhc2hib2FyZFNoZWxsU2NyZWVuU3RhdGUoKTsKfQoKY2xhc3MgX0Rhc2hib2FyZFNoZWxsU2NyZWVuU3RhdGUgZXh0ZW5kcyBTdGF0ZTxEYXNoYm9hcmRTaGVsbFNjcmVlbj4gewogIHZvaWQgX29uRGVzdGluYXRpb25TZWxlY3RlZChpbnQgaW5kZXgpIHsKICAgIHdpZGdldC5uYXZpZ2F0aW9uU2hlbGwuZ29CcmFuY2goCiAgICAgIGluZGV4LAogICAgICBpbml0aWFsTG9jYXRpb246IGluZGV4ID09IHdpZGdldC5uYXZpZ2F0aW9uU2hlbGwuY3VycmVudEluZGV4LAogICAgKTsKICB9CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgcmV0dXJuIFNjYWZmb2xkKAogICAgICBib2R5OiB3aWRnZXQubmF2aWdhdGlvblNoZWxsLAogICAgICBib3R0b21OYXZpZ2F0aW9uQmFyOiBOYXZpZ2F0aW9uQmFyKAogICAgICAgIHNlbGVjdGVkSW5kZXg6IHdpZGdldC5uYXZpZ2F0aW9uU2hlbGwuY3VycmVudEluZGV4LAogICAgICAgIG9uRGVzdGluYXRpb25TZWxlY3RlZDogX29uRGVzdGluYXRpb25TZWxlY3RlZCwKICAgICAgICBkZXN0aW5hdGlvbnM6IFsKICAgICAgICAgIE5hdmlnYXRpb25EZXN0aW5hdGlvbigKICAgICAgICAgICAgaWNvbjogY29uc3QgSWNvbihOYXZpZ2F0aW9uSWNvbnMuaG9tZSksCiAgICAgICAgICAgIHNlbGVjdGVkSWNvbjogY29uc3QgSWNvbihOYXZpZ2F0aW9uSWNvbnMuaG9tZVNlbGVjdGVkKSwKICAgICAgICAgICAgbGFiZWw6IHN0cmluZ3MubmF2LmhvbWUsCiAgICAgICAgICApLAogICAgICAgICAgY29uc3QgTmF2aWdhdGlvbkRlc3RpbmF0aW9uKAogICAgICAgICAgICBpY29uOiBJY29uKE5hdmlnYXRpb25JY29ucy5leHBsb3JlKSwKICAgICAgICAgICAgc2VsZWN0ZWRJY29uOiBJY29uKE5hdmlnYXRpb25JY29ucy5leHBsb3JlU2VsZWN0ZWQpLAogICAgICAgICAgICBsYWJlbDogJ0V4cGxvcmUnLAogICAgICAgICAgKSwKICAgICAgICAgIE5hdmlnYXRpb25EZXN0aW5hdGlvbigKICAgICAgICAgICAgaWNvbjogY29uc3QgSWNvbihOYXZpZ2F0aW9uSWNvbnMucHJvZmlsZSksCiAgICAgICAgICAgIHNlbGVjdGVkSWNvbjogY29uc3QgSWNvbihOYXZpZ2F0aW9uSWNvbnMucHJvZmlsZVNlbGVjdGVkKSwKICAgICAgICAgICAgbGFiZWw6IHN0cmluZ3MubmF2LnByb2ZpbGUsCiAgICAgICAgICApLAogICAgICAgIF0sCiAgICAgICksCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/explore_tab_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRhc2hib2FyZC91aS93aWRnZXRzL2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGVzaWduX3N5c3RlbS9kZXNpZ25fc3lzdGVtLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKCmNsYXNzIEV4cGxvcmVUYWJTY3JlZW4gZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGNvbnN0IEV4cGxvcmVUYWJTY3JlZW4oe3N1cGVyLmtleX0pOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBTY2FmZm9sZCgKICAgICAgYXBwQmFyOiBBcHBCYXIodGl0bGU6IGNvbnN0IFRleHQoJ0V4cGxvcmUnKSksCiAgICAgIGJvZHk6IGNvbnN0IFNhZmVBcmVhKAogICAgICAgIGNoaWxkOiBQbGFjZWhvbGRlclRhYigKICAgICAgICAgIGljb246IE5hdmlnYXRpb25JY29ucy5leHBsb3JlU2VsZWN0ZWQsCiAgICAgICAgICB0aXRsZTogJ0V4cGxvcmUnLAogICAgICAgICAgc3VidGl0bGU6ICdZb3VyIHNlY29uZCB0YWIuIFJlcGxhY2UgdGhpcyB3aXRoIGEgcmVhbCBmZWF0dXJlLicsCiAgICAgICAgKSwKICAgICAgKSwKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/home_tab_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRhc2hib2FyZC91aS93aWRnZXRzL2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGVzaWduX3N5c3RlbS9kZXNpZ25fc3lzdGVtLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CgpjbGFzcyBIb21lVGFiU2NyZWVuIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBjb25zdCBIb21lVGFiU2NyZWVuKHtzdXBlci5rZXl9KTsKCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKEJ1aWxkQ29udGV4dCBjb250ZXh0KSB7CiAgICByZXR1cm4gU2NhZmZvbGQoCiAgICAgIGFwcEJhcjogQXBwQmFyKHRpdGxlOiBUZXh0KHN0cmluZ3MubmF2LmRhc2hib2FyZCkpLAogICAgICBib2R5OiBTYWZlQXJlYSgKICAgICAgICBjaGlsZDogUGxhY2Vob2xkZXJUYWIoCiAgICAgICAgICBpY29uOiBOYXZpZ2F0aW9uSWNvbnMuaG9tZVNlbGVjdGVkLAogICAgICAgICAgdGl0bGU6IHN0cmluZ3MuYXBwLndlbGNvbWVfdG9fYXBwLAogICAgICAgICAgc3VidGl0bGU6IHN0cmluZ3MuYXBwLmRlc2NyaXB0aW9uLAogICAgICAgICksCiAgICAgICksCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/index.dart", + "data": + "ZXhwb3J0ICdkYXNoYm9hcmRfc2hlbGxfc2NyZWVuLmRhcnQnOwpleHBvcnQgJ2V4cGxvcmVfdGFiX3NjcmVlbi5kYXJ0JzsKZXhwb3J0ICdob21lX3RhYl9zY3JlZW4uZGFydCc7CmV4cG9ydCAncHJvZmlsZV90YWJfc2NyZWVuLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/screens/profile_tab_screen.dart", + "data": + "e3sjYXV0aH19aW1wb3J0ICdwYWNrYWdlOmF1dGgvYXV0aC5kYXJ0JyBhcyBhdXRoOwp7ey9hdXRofX1pbXBvcnQgJ3BhY2thZ2U6ZGVzaWduX3N5c3RlbS9kZXNpZ25fc3lzdGVtLmRhcnQnOwp7eyNkZXZlbG9wZXJ9fWltcG9ydCAncGFja2FnZTpkZXZlbG9wZXIvZGV2ZWxvcGVyLmRhcnQnOwp7ey9kZXZlbG9wZXJ9fXt7I2F1dGh9fWltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKe3svYXV0aH19aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7Cnt7I2F1dGh9fWltcG9ydCAncGFja2FnZTpnb19yb3V0ZXIvZ29fcm91dGVyLmRhcnQnOwp7ey9hdXRofX1pbXBvcnQgJ3BhY2thZ2U6bG9jYWxpemF0aW9uL2xvY2FsaXphdGlvbi5kYXJ0JzsKCi8vLyBBY2NvdW50IHRhYjogaWRlbnRpdHksIHNldHRpbmdzIGVudHJ5IHBvaW50cywgc2lnbiBvdXQuCi8vLwovLy8gVGhpcyBpcyBkZWxpYmVyYXRlbHkgdGhpbiAtIGhhbmcgeW91ciByZWFsIHByb2ZpbGUgZmVhdHVyZSBvZmYgaXQuCmNsYXNzIFByb2ZpbGVUYWJTY3JlZW4gZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGNvbnN0IFByb2ZpbGVUYWJTY3JlZW4oe3N1cGVyLmtleX0pOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBTY2FmZm9sZCgKICAgICAgYXBwQmFyOiBBcHBCYXIodGl0bGU6IFRleHQoc3RyaW5ncy5wcm9maWxlLnByb2ZpbGUpKSwKICAgICAgYm9keTogU2FmZUFyZWEoCiAgICAgICAgY2hpbGQ6IExpc3RWaWV3KAogICAgICAgICAgY2hpbGRyZW46IFsKICAgICAgICAgICAgY29uc3QgU2l6ZWRCb3goaGVpZ2h0OiAxNiksCiAgICAgICAgICAgIHt7I2RldmVsb3Blcn19Ly8gVGFwIHRoZSBhdmF0YXIgNXggdG8gb3BlbiBkZXZlbG9wZXIgdG9vbHMuCiAgICAgICAgICAgIE9wZW5EZXZUb29sc1dyYXBwZXIoY2hpbGQ6IGNvbnN0IF9Qcm9maWxlQXZhdGFyKCkpLHt7L2RldmVsb3Blcn19e3teZGV2ZWxvcGVyfX1jb25zdCBfUHJvZmlsZUF2YXRhcigpLHt7L2RldmVsb3Blcn19CiAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KGhlaWdodDogMjQpLAogICAgICAgICAgICBMaXN0VGlsZSgKICAgICAgICAgICAgICBsZWFkaW5nOiBjb25zdCBJY29uKE5hdmlnYXRpb25JY29ucy5zZXR0aW5ncyksCiAgICAgICAgICAgICAgdGl0bGU6IFRleHQoc3RyaW5ncy5wcm9maWxlLnNldHRpbmdzKSwKICAgICAgICAgICAgICB0cmFpbGluZzogY29uc3QgSWNvbihJY29ucy5jaGV2cm9uX3JpZ2h0KSwKICAgICAgICAgICAgICBvblRhcDogKCkge30sCiAgICAgICAgICAgICksCiAgICAgICAgICAgIHt7I2F1dGh9fUxpc3RUaWxlKAogICAgICAgICAgICAgIGxlYWRpbmc6IGNvbnN0IEljb24oSWNvbnMubG9nb3V0KSwKICAgICAgICAgICAgICB0aXRsZTogVGV4dChzdHJpbmdzLmF1dGguc2lnbl9vdXQpLAogICAgICAgICAgICAgIG9uVGFwOiAoKSA9PiBfY29uZmlybVNpZ25PdXQoY29udGV4dCksCiAgICAgICAgICAgICkse3svYXV0aH19CiAgICAgICAgICBdLAogICAgICAgICksCiAgICAgICksCiAgICApOwogIH0Ke3sjYXV0aH19CiAgRnV0dXJlPHZvaWQ+IF9jb25maXJtU2lnbk91dChCdWlsZENvbnRleHQgY29udGV4dCkgYXN5bmMgewogICAgZmluYWwgY29uZmlybWVkID0gYXdhaXQgc2hvd0RpYWxvZzxib29sPigKICAgICAgY29udGV4dDogY29udGV4dCwKICAgICAgYnVpbGRlcjogKGNvbnRleHQpID0+IEFsZXJ0RGlhbG9nKAogICAgICAgIHRpdGxlOiBUZXh0KHN0cmluZ3MuYXV0aC5zaWduX291dCksCiAgICAgICAgY29udGVudDogVGV4dChzdHJpbmdzLmF1dGguc2lnbl9vdXRfY29uZmlybWF0aW9uKSwKICAgICAgICBhY3Rpb25zOiBbCiAgICAgICAgICBUZXh0QnV0dG9uKAogICAgICAgICAgICBvblByZXNzZWQ6ICgpID0+IE5hdmlnYXRvci5vZihjb250ZXh0KS5wb3AoZmFsc2UpLAogICAgICAgICAgICBjaGlsZDogVGV4dChzdHJpbmdzLmdlbmVyaWMuY2FuY2VsKSwKICAgICAgICAgICksCiAgICAgICAgICBGaWxsZWRCdXR0b24oCiAgICAgICAgICAgIG9uUHJlc3NlZDogKCkgPT4gTmF2aWdhdG9yLm9mKGNvbnRleHQpLnBvcCh0cnVlKSwKICAgICAgICAgICAgY2hpbGQ6IFRleHQoc3RyaW5ncy5hdXRoLnNpZ25fb3V0KSwKICAgICAgICAgICksCiAgICAgICAgXSwKICAgICAgKSwKICAgICk7CgogICAgaWYgKGNvbmZpcm1lZCAhPSB0cnVlIHx8ICFjb250ZXh0Lm1vdW50ZWQpIHJldHVybjsKCiAgICBhd2FpdCBkaS5nZXQ8YXV0aC5BdXRoU2VydmljZT4oKS5zaWduT3V0KCk7CiAgICBpZiAoY29udGV4dC5tb3VudGVkKSBjb250ZXh0LmdvKGF1dGguQXV0aFJvdXRlcy5zaWduSW4pOwogIH0Ke3svYXV0aH19fQoKY2xhc3MgX1Byb2ZpbGVBdmF0YXIgZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGNvbnN0IF9Qcm9maWxlQXZhdGFyKCk7CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgZmluYWwgY29sb3JTY2hlbWUgPSBUaGVtZS5vZihjb250ZXh0KS5jb2xvclNjaGVtZTsKICAgIHJldHVybiBDZW50ZXIoCiAgICAgIGNoaWxkOiBDaXJjbGVBdmF0YXIoCiAgICAgICAgcmFkaXVzOiA0NCwKICAgICAgICBiYWNrZ3JvdW5kQ29sb3I6IGNvbG9yU2NoZW1lLnByaW1hcnlDb250YWluZXIsCiAgICAgICAgY2hpbGQ6IEljb24oCiAgICAgICAgICBOYXZpZ2F0aW9uSWNvbnMucHJvZmlsZVNlbGVjdGVkLAogICAgICAgICAgc2l6ZTogNDQsCiAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUub25QcmltYXJ5Q29udGFpbmVyLAogICAgICAgICksCiAgICAgICksCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/widgets/index.dart", + "data": "ZXhwb3J0ICdwbGFjZWhvbGRlcl90YWIuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/features/{{dashboard_dir}}/lib/ui/widgets/placeholder_tab.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CgovLy8gRHJvcC1pbiAibm90aGluZyBoZXJlIHlldCIgdGFiIGJvZHkuCi8vLwovLy8gRGVsZXRlIHRoaXMgdGhlIG1vbWVudCBhIHRhYiBncm93cyByZWFsIGNvbnRlbnQuCmNsYXNzIFBsYWNlaG9sZGVyVGFiIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBjb25zdCBQbGFjZWhvbGRlclRhYih7CiAgICBzdXBlci5rZXksCiAgICByZXF1aXJlZCB0aGlzLmljb24sCiAgICByZXF1aXJlZCB0aGlzLnRpdGxlLAogICAgcmVxdWlyZWQgdGhpcy5zdWJ0aXRsZSwKICAgIHRoaXMuYWN0aW9uLAogIH0pOwoKICBmaW5hbCBJY29uRGF0YSBpY29uOwogIGZpbmFsIFN0cmluZyB0aXRsZTsKICBmaW5hbCBTdHJpbmcgc3VidGl0bGU7CiAgZmluYWwgV2lkZ2V0PyBhY3Rpb247CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgZmluYWwgdGhlbWUgPSBUaGVtZS5vZihjb250ZXh0KTsKICAgIGZpbmFsIGNvbG9yU2NoZW1lID0gdGhlbWUuY29sb3JTY2hlbWU7CiAgICBmaW5hbCB0ZXh0VGhlbWUgPSB0aGVtZS50ZXh0VGhlbWU7CgogICAgcmV0dXJuIENlbnRlcigKICAgICAgY2hpbGQ6IFBhZGRpbmcoCiAgICAgICAgcGFkZGluZzogY29uc3QgRWRnZUluc2V0cy5hbGwoMzIpLAogICAgICAgIGNoaWxkOiBDb2x1bW4oCiAgICAgICAgICBtYWluQXhpc0FsaWdubWVudDogTWFpbkF4aXNBbGlnbm1lbnQuY2VudGVyLAogICAgICAgICAgY2hpbGRyZW46IFsKICAgICAgICAgICAgQ29udGFpbmVyKAogICAgICAgICAgICAgIHBhZGRpbmc6IGNvbnN0IEVkZ2VJbnNldHMuYWxsKDI0KSwKICAgICAgICAgICAgICBkZWNvcmF0aW9uOiBCb3hEZWNvcmF0aW9uKAogICAgICAgICAgICAgICAgY29sb3I6IGNvbG9yU2NoZW1lLnByaW1hcnkud2l0aFZhbHVlcyhhbHBoYTogMC4xKSwKICAgICAgICAgICAgICAgIHNoYXBlOiBCb3hTaGFwZS5jaXJjbGUsCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICBjaGlsZDogSWNvbihpY29uLCBzaXplOiA3MiwgY29sb3I6IGNvbG9yU2NoZW1lLnByaW1hcnkpLAogICAgICAgICAgICApLAogICAgICAgICAgICBjb25zdCBTaXplZEJveChoZWlnaHQ6IDMyKSwKICAgICAgICAgICAgVGV4dCgKICAgICAgICAgICAgICB0aXRsZSwKICAgICAgICAgICAgICBzdHlsZTogdGV4dFRoZW1lLmhlYWRsaW5lTWVkaXVtPy5jb3B5V2l0aCgKICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5vblN1cmZhY2UsCiAgICAgICAgICAgICAgICBmb250V2VpZ2h0OiBGb250V2VpZ2h0LmJvbGQsCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgKSwKICAgICAgICAgICAgY29uc3QgU2l6ZWRCb3goaGVpZ2h0OiAxNiksCiAgICAgICAgICAgIFRleHQoCiAgICAgICAgICAgICAgc3VidGl0bGUsCiAgICAgICAgICAgICAgc3R5bGU6IHRleHRUaGVtZS5ib2R5TGFyZ2U/LmNvcHlXaXRoKAogICAgICAgICAgICAgICAgY29sb3I6IGNvbG9yU2NoZW1lLm9uU3VyZmFjZVZhcmlhbnQsCiAgICAgICAgICAgICAgICBoZWlnaHQ6IDEuNSwKICAgICAgICAgICAgICApLAogICAgICAgICAgICAgIHRleHRBbGlnbjogVGV4dEFsaWduLmNlbnRlciwKICAgICAgICAgICAgKSwKICAgICAgICAgICAgaWYgKGFjdGlvbiAhPSBudWxsKSAuLi5bY29uc3QgU2l6ZWRCb3goaGVpZ2h0OiAzMiksIGFjdGlvbiFdLAogICAgICAgICAgXSwKICAgICAgICApLAogICAgICApLAogICAgKTsKICB9Cn0K", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml", + "data": + "bmFtZTogZGFzaGJvYXJkCmRlc2NyaXB0aW9uOiAiQm90dG9tLW5hdmlnYXRpb24gc2hlbGwgYW5kIHN0YXJ0ZXIgdGFicyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBnb19yb3V0ZXI6IF4xNS4xLjMKICBjb3JlOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvY29yZQogIGRlc2lnbl9zeXN0ZW06CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kZXNpZ25fc3lzdGVtCiAgZGk6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2xvY2FsaXphdGlvbgp7eyNhdXRofX0gIGF1dGg6CiAgICBwYXRoOiAuLi9hdXRoCnt7L2F1dGh9fXt7I2RldmVsb3Blcn19ICBkZXZlbG9wZXI6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kZXZlbG9wZXIKe3svZGV2ZWxvcGVyfX0KZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmZsdXR0ZXI6Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/features/{{dashboard_dir}}/README.md", + "data": + "IyBEYXNoYm9hcmQKCkRhc2hib2FyZCBmZWF0dXJlIHdpdGggYm90dG9tIG5hdmlnYXRpb24KClshW25vbnN0b3BfY2xpXShodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlL3N0YXJ0ZWQlMjB3aXRoLW5vbnN0b3BfY2xpLTE2NkM0RS5zdmc/c3R5bGU9ZmxhdC1zcXVhcmUpXShodHRwczovL3B1Yi5kZXYvcGFja2FnZXMvbm9uc3RvcF9jbGkpClshW21lbG9zXShodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlL21haW50YWluZWQlMjB3aXRoLW1lbG9zLWY3MDBmZi5zdmc/c3R5bGU9ZmxhdC1zcXVhcmUpXShodHRwczovL2dpdGh1Yi5jb20vaW52ZXJ0YXNlL21lbG9zKQoKIyMgSW5zdGFsbGF0aW9uIPCfkrsKCioq4p2XIEluIG9yZGVyIHRvIHN0YXJ0IHVzaW5nIERhc2hib2FyZCB5b3UgbXVzdCBoYXZlIHRoZSBbRmx1dHRlciBTREtdW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXSBpbnN0YWxsZWQgb24geW91ciBtYWNoaW5lLioqCgpJbnN0YWxsIHZpYSBgZGFydCBwdWIgYWRkYDoKCmBgYHNoCmRhcnQgcHViIGFkZCBkYXNoYm9hcmQKYGBgCgpbZmx1dHRlcl9pbnN0YWxsX2xpbmtdOiBodHRwczovL2RvY3MuZmx1dHRlci5kZXYvZ2V0LXN0YXJ0ZWQvaW5zdGFsbA==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/melos.yaml", + "data": + "bmFtZToge3sgbmFtZS5zbmFrZUNhc2UoKSB9fV93b3Jrc3BhY2UKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBNT05PUkVQTyBDT05GSUdVUkFUSU9OCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBhcHBzLyAgICAgLSBydW5uYWJsZSBGbHV0dGVyIGFwcGxpY2F0aW9ucwojIGZlYXR1cmVzLyAtIHZlcnRpY2FsIHNsaWNlcyAodWkgKyBzdGF0ZSArIGRhdGEpIHRoYXQgYW4gYXBwIGNvbXBvc2VzCiMgcGFja2FnZXMvIC0gaG9yaXpvbnRhbCBjYXBhYmlsaXRpZXMgc2hhcmVkIGJ5IGZlYXR1cmVzIChuZXR3b3JrLCBkZXNpZ24sIC4uLikKIyBwbHVnaW5zLyAgLSBwbGF0Zm9ybSBjaGFubmVscyBhbmQgbmF0aXZlIGludGVncmF0aW9ucwpwYWNrYWdlczoKICAtIGFwcHMvKgogIC0gZmVhdHVyZXMvKgogIC0gcGFja2FnZXMvKgogIC0gcGx1Z2lucy8qCgpjb21tYW5kOgogIGJvb3RzdHJhcDoKICAgIGRlcGVuZGVuY2llczoKICAgICAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICAgIGRldl9kZXBlbmRlbmNpZXM6CiAgICAgIGJ1aWxkX3J1bm5lcjogXjIuNC4xNQogICAgICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKICAgICAganNvbl9zZXJpYWxpemFibGU6IF42LjkuNQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIFNDUklQVFMKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwpzY3JpcHRzOgogIGNsZWFuOmZsdXR0ZXI6CiAgICBkZXNjcmlwdGlvbjogIlJ1biBmbHV0dGVyIGNsZWFuIGluIGFsbCBGbHV0dGVyIHBhY2thZ2VzIgogICAgcnVuOiBmbHV0dGVyIGNsZWFuCiAgICBleGVjOgogICAgICBjb25jdXJyZW5jeTogNAogICAgcGFja2FnZUZpbHRlcnM6CiAgICAgIGZsdXR0ZXI6IHRydWUKCiAgbGludDoKICAgIGRlc2NyaXB0aW9uOiAiUnVuIGZvcm1hdCBhbmQgYW5hbHl6ZSBhbGwgcGFja2FnZXMiCiAgICAjIGBkYXJ0IGZvcm1hdGAgaGFzIG5vIGV4Y2x1ZGUgb2YgaXRzIG93biwgc28gYnVpbGQgb3V0cHV0IGFuZCB0aGUgcGx1Z2luCiAgICAjIHNvdXJjZXMgQ29jb2FQb2RzIHN5bWxpbmtzIGludG8gdGhlIGFwcCBhcmUgZmlsdGVyZWQgb3V0IGhlcmUuCiAgICBydW46IHwKICAgICAgZmluZCAuIC1uYW1lICcqLmRhcnQnIC1ub3QgLXBhdGggJyovYnVpbGQvKicgLW5vdCAtcGF0aCAnKi8uZGFydF90b29sLyonIFwKICAgICAgICAtbm90IC1wYXRoICcqLy5zeW1saW5rcy8qJyAtZXhlYyBkYXJ0IGZvcm1hdCAtLXNldC1leGl0LWlmLWNoYW5nZWQge30gKyAmJgogICAgICBkYXJ0IGFuYWx5emUgLS1mYXRhbC1pbmZvcyAuCiAgICBleGVjOgogICAgICBjb25jdXJyZW5jeTogNQogICAgICBmYWlsRmFzdDogdHJ1ZQoKICBmaXg6CiAgICBkZXNjcmlwdGlvbjogIkFwcGx5IERhcnQgYXV0b21hdGVkIGZpeGVzIHRvIGFsbCBwYWNrYWdlcyIKICAgIHJ1bjogZGFydCBmaXggLS1hcHBseQogICAgZXhlYzoKICAgICAgY29uY3VycmVuY3k6IDUKICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICBkaXJFeGlzdHM6IGxpYgoKICBmaXg6ZHJ5OgogICAgZGVzY3JpcHRpb246ICJTaG93IERhcnQgYXV0b21hdGVkIGZpeGVzIHdpdGhvdXQgYXBwbHlpbmcgdGhlbSIKICAgIHJ1bjogZGFydCBmaXggLS1kcnktcnVuCiAgICBleGVjOgogICAgICBjb25jdXJyZW5jeTogNQogICAgcGFja2FnZUZpbHRlcnM6CiAgICAgIGRpckV4aXN0czogbGliCgogIGdlbmVyYXRlOgogICAgZGVzY3JpcHRpb246ICJSdW4gYnVpbGRfcnVubmVyIGluIGV2ZXJ5IHBhY2thZ2UgdGhhdCBuZWVkcyBjb2RlZ2VuIgogICAgcnVuOiBkYXJ0IHJ1biBidWlsZF9ydW5uZXIgYnVpbGQgLS1kZWxldGUtY29uZmxpY3Rpbmctb3V0cHV0cwogICAgZXhlYzoKICAgICAgY29uY3VycmVuY3k6IDEKICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICBkZXBlbmRzT246IGJ1aWxkX3J1bm5lcgoKICBnZW5lcmF0ZTppNjluOgogICAgZGVzY3JpcHRpb246ICJSZWdlbmVyYXRlIGxvY2FsaXphdGlvbiBzdHJpbmdzIGZyb20gbWVzc2FnZXMuaTY5bi55YW1sIgogICAgcnVuOiB8CiAgICAgIGRhcnQgcnVuIGJ1aWxkX3J1bm5lciBidWlsZCAtLWRlbGV0ZS1jb25mbGljdGluZy1vdXRwdXRzICYmCiAgICAgIGRhcnQgZm9ybWF0IGxpYi9tZXNzYWdlcy5pNjluLmRhcnQKICAgIGV4ZWM6CiAgICAgIGNvbmN1cnJlbmN5OiAxCiAgICBwYWNrYWdlRmlsdGVyczoKICAgICAgc2NvcGU6ICJsb2NhbGl6YXRpb24iCgogIHRlc3Q6CiAgICBkZXNjcmlwdGlvbjogIlJ1biBhbGwgdGVzdHMgKERhcnQgYW5kIEZsdXR0ZXIpIgogICAgcnVuOiBtZWxvcyBydW4gdGVzdDpzZWxlY3QgLS1uby1zZWxlY3QKCiAgdGVzdDpzZWxlY3Q6CiAgICBkZXNjcmlwdGlvbjogIlJ1biB0ZXN0cyBpbiBGbHV0dGVyIHBhY2thZ2VzIgogICAgcnVuOiBmbHV0dGVyIHRlc3QgLS1jb3ZlcmFnZQogICAgZXhlYzoKICAgICAgY29uY3VycmVuY3k6IDUKICAgICAgZmFpbEZhc3Q6IHRydWUKICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICBkaXJFeGlzdHM6IHRlc3QKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBJREUgQ09ORklHVVJBVElPTgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCmlkZToKICBpbnRlbGxpajoKICAgIGVuYWJsZWQ6IHRydWUKICAgIG1vZHVsZU5hbWVQcmVmaXg6ICcnCg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/bloc/global_event.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmVxdWF0YWJsZS9lcXVhdGFibGUuZGFydCc7CgovLy8gQmFzZSB0eXBlIGZvciBldmVudHMgYnJvYWRjYXN0IG9uIHRoZSBhcHAtd2lkZSBldmVudCBjaGFubmVsLgovLy8KLy8vIFVzZSB0aGlzIGZvciB0aGUgaGFuZGZ1bCBvZiB0aGluZ3MgdGhhdCBnZW51aW5lbHkgY3Jvc3MgZmVhdHVyZQovLy8gYm91bmRhcmllcyAtICJ0aGUgc2lnbmVkLWluIHVzZXIgY2hhbmdlZCIsICJ0aGlzIGxpc3QgaXMgc3RhbGUiIC0gYW5kIGtlZXAKLy8vIGV2ZXJ5dGhpbmcgZWxzZSBpbnNpZGUgdGhlIGZlYXR1cmUgdGhhdCBvd25zIGl0LgphYnN0cmFjdCBjbGFzcyBHbG9iYWxFdmVudFR5cGUgZXh0ZW5kcyBFcXVhdGFibGUgewogIGNvbnN0IEdsb2JhbEV2ZW50VHlwZSgpOwoKICBAb3ZlcnJpZGUKICBMaXN0PE9iamVjdD8+IGdldCBwcm9wcyA9PiBbXTsKfQoKLy8vIEV4YW1wbGUgZXZlbnQ6IHNvbWV0aGluZyBpbnZhbGlkYXRlZCB0aGUgY3VycmVudCB1c2VyJ3MgcHJvZmlsZS4KLy8vCi8vLyBSZXBsYWNlIHRoaXMgd2l0aCB5b3VyIG93biBldmVudHM7IGl0IGlzIGhlcmUgdG8gc2hvdyB0aGUgc2hhcGUuCmNsYXNzIFJlZnJlc2hQcm9maWxlIGV4dGVuZHMgR2xvYmFsRXZlbnRUeXBlIHsKICBjb25zdCBSZWZyZXNoUHJvZmlsZSgpOwp9CgpjbGFzcyBGaXJlR2xvYmFsRXZlbnQgZXh0ZW5kcyBFcXVhdGFibGUgewogIGZpbmFsIEdsb2JhbEV2ZW50VHlwZSBldmVudFR5cGU7CgogIGNvbnN0IEZpcmVHbG9iYWxFdmVudCh7cmVxdWlyZWQgdGhpcy5ldmVudFR5cGV9KTsKCiAgQG92ZXJyaWRlCiAgTGlzdDxPYmplY3Q/PiBnZXQgcHJvcHMgPT4gW2V2ZW50VHlwZV07Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_bloc.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmJsb2MvYmxvYy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmNvcmUvYmxvYy9nbG9iYWxfZXZlbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTpjb3JlL2Jsb2MvZ2xvYmFsX2V2ZW50X2NoYW5uZWxfc3RhdGUuZGFydCc7CmltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci9sb2dnZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKCmNsYXNzIEdsb2JhbEV2ZW50Q2hhbm5lbCBleHRlbmRzIEJsb2M8RmlyZUdsb2JhbEV2ZW50LCBHbG9iYWxFdmVudFN0YXRlPiB7CiAgZmluYWwgTG9nZ2VyIF9sb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpOwoKICBHbG9iYWxFdmVudENoYW5uZWwoe3JlcXVpcmVkIGludCBtYXhSZWNlbnRFdmVudHN9KQogICAgOiBzdXBlcihHbG9iYWxFdmVudFN0YXRlKG1heFJlY2VudEV2ZW50czogbWF4UmVjZW50RXZlbnRzKSkgewogICAgb248RmlyZUdsb2JhbEV2ZW50Pihfb25GaXJlR2xvYmFsRXZlbnQpOwogIH0KCiAgdm9pZCBfb25GaXJlR2xvYmFsRXZlbnQoCiAgICBGaXJlR2xvYmFsRXZlbnQgZXZlbnQsCiAgICBFbWl0dGVyPEdsb2JhbEV2ZW50U3RhdGU+IGVtaXQsCiAgKSB7CiAgICBfbG9nZ2VyLmkoJ0dsb2JhbEV2ZW50Q2hhbm5lbDogUHJvY2Vzc2luZyAke2V2ZW50LmV2ZW50VHlwZS5ydW50aW1lVHlwZX0nKTsKCiAgICBmaW5hbCBldmVudFR5cGUgPSBldmVudC5ldmVudFR5cGU7CiAgICBmaW5hbCBuZXdIaXN0b3J5ID0gTGlzdDxHbG9iYWxFdmVudFR5cGU+LmZyb20oc3RhdGUuaGlzdG9yeSk7CiAgICBuZXdIaXN0b3J5Lmluc2VydCgwLCBldmVudFR5cGUpOwoKICAgIGlmIChuZXdIaXN0b3J5Lmxlbmd0aCA+IHN0YXRlLm1heFJlY2VudEV2ZW50cykgewogICAgICBuZXdIaXN0b3J5LnJlbW92ZUxhc3QoKTsKICAgIH0KCiAgICBmaW5hbCBuZXdFdmVudENvdW50cyA9IE1hcDxUeXBlLCBpbnQ+LmZyb20oc3RhdGUuZXZlbnRDb3VudHMpOwogICAgZmluYWwgZXZlbnRUeXBlS2V5ID0gZXZlbnRUeXBlLnJ1bnRpbWVUeXBlOwogICAgbmV3RXZlbnRDb3VudHNbZXZlbnRUeXBlS2V5XSA9IChuZXdFdmVudENvdW50c1tldmVudFR5cGVLZXldID8/IDApICsgMTsKCiAgICBlbWl0KAogICAgICBzdGF0ZS5jb3B5V2l0aCgKICAgICAgICBoaXN0b3J5OiBuZXdIaXN0b3J5LAogICAgICAgIGV2ZW50Q291bnRzOiBuZXdFdmVudENvdW50cywKICAgICAgICBjdXJyZW50OiBldmVudFR5cGUsCiAgICAgICksCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_provider.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvYmxvYy9nbG9iYWxfZXZlbnRfY2hhbm5lbF9ibG9jLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9ibG9jL2dsb2JhbF9ldmVudF9jaGFubmVsX3N0YXRlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXJfYmxvYy9mbHV0dGVyX2Jsb2MuZGFydCc7CgpjbGFzcyBHbG9iYWxFdmVudENoYW5uZWxQcm92aWRlciBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgZmluYWwgV2lkZ2V0IGNoaWxkOwogIGZpbmFsIGludCBtYXhSZWNlbnRFdmVudHM7CiAgZmluYWwgYm9vbCBsYXp5OwoKICBjb25zdCBHbG9iYWxFdmVudENoYW5uZWxQcm92aWRlcih7CiAgICBzdXBlci5rZXksCiAgICByZXF1aXJlZCB0aGlzLmNoaWxkLAogICAgdGhpcy5tYXhSZWNlbnRFdmVudHMgPSA1MCwKICAgIHRoaXMubGF6eSA9IHRydWUsCiAgfSk7CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgcmV0dXJuIEJsb2NQcm92aWRlcjxHbG9iYWxFdmVudENoYW5uZWw+KAogICAgICBsYXp5OiBsYXp5LAogICAgICBjcmVhdGU6IChjb250ZXh0KSA9PiBHbG9iYWxFdmVudENoYW5uZWwobWF4UmVjZW50RXZlbnRzOiBtYXhSZWNlbnRFdmVudHMpLAogICAgICBjaGlsZDogY2hpbGQsCiAgICApOwogIH0KfQoKY2xhc3MgR2xvYmFsRXZlbnRDaGFubmVsQnVpbGRlciBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgZmluYWwgQmxvY1dpZGdldEJ1aWxkZXI8R2xvYmFsRXZlbnRTdGF0ZT4gYnVpbGRlcjsKICBmaW5hbCBCbG9jQnVpbGRlckNvbmRpdGlvbjxHbG9iYWxFdmVudFN0YXRlPj8gYnVpbGRXaGVuOwoKICBjb25zdCBHbG9iYWxFdmVudENoYW5uZWxCdWlsZGVyKHsKICAgIHN1cGVyLmtleSwKICAgIHJlcXVpcmVkIHRoaXMuYnVpbGRlciwKICAgIHRoaXMuYnVpbGRXaGVuLAogIH0pOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBCbG9jQnVpbGRlcjxHbG9iYWxFdmVudENoYW5uZWwsIEdsb2JhbEV2ZW50U3RhdGU+KAogICAgICBidWlsZFdoZW46IGJ1aWxkV2hlbiwKICAgICAgYnVpbGRlcjogYnVpbGRlciwKICAgICk7CiAgfQp9CgpjbGFzcyBHbG9iYWxFdmVudENoYW5uZWxMaXN0ZW5lciBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgZmluYWwgV2lkZ2V0IGNoaWxkOwogIGZpbmFsIEJsb2NXaWRnZXRMaXN0ZW5lcjxHbG9iYWxFdmVudFN0YXRlPiBsaXN0ZW5lcjsKICBmaW5hbCBCbG9jTGlzdGVuZXJDb25kaXRpb248R2xvYmFsRXZlbnRTdGF0ZT4/IGxpc3RlbldoZW47CgogIGNvbnN0IEdsb2JhbEV2ZW50Q2hhbm5lbExpc3RlbmVyKHsKICAgIHN1cGVyLmtleSwKICAgIHJlcXVpcmVkIHRoaXMuY2hpbGQsCiAgICByZXF1aXJlZCB0aGlzLmxpc3RlbmVyLAogICAgdGhpcy5saXN0ZW5XaGVuLAogIH0pOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBCbG9jTGlzdGVuZXI8R2xvYmFsRXZlbnRDaGFubmVsLCBHbG9iYWxFdmVudFN0YXRlPigKICAgICAgbGlzdGVuV2hlbjogbGlzdGVuV2hlbiwKICAgICAgbGlzdGVuZXI6IGxpc3RlbmVyLAogICAgICBjaGlsZDogY2hpbGQsCiAgICApOwogIH0KfQoKY2xhc3MgR2xvYmFsRXZlbnRDaGFubmVsQ29uc3VtZXIgZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGZpbmFsIEJsb2NXaWRnZXRCdWlsZGVyPEdsb2JhbEV2ZW50U3RhdGU+IGJ1aWxkZXI7CiAgZmluYWwgQmxvY1dpZGdldExpc3RlbmVyPEdsb2JhbEV2ZW50U3RhdGU+IGxpc3RlbmVyOwogIGZpbmFsIEJsb2NCdWlsZGVyQ29uZGl0aW9uPEdsb2JhbEV2ZW50U3RhdGU+PyBidWlsZFdoZW47CiAgZmluYWwgQmxvY0xpc3RlbmVyQ29uZGl0aW9uPEdsb2JhbEV2ZW50U3RhdGU+PyBsaXN0ZW5XaGVuOwoKICBjb25zdCBHbG9iYWxFdmVudENoYW5uZWxDb25zdW1lcih7CiAgICBzdXBlci5rZXksCiAgICByZXF1aXJlZCB0aGlzLmJ1aWxkZXIsCiAgICByZXF1aXJlZCB0aGlzLmxpc3RlbmVyLAogICAgdGhpcy5idWlsZFdoZW4sCiAgICB0aGlzLmxpc3RlbldoZW4sCiAgfSk7CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgcmV0dXJuIEJsb2NDb25zdW1lcjxHbG9iYWxFdmVudENoYW5uZWwsIEdsb2JhbEV2ZW50U3RhdGU+KAogICAgICBidWlsZFdoZW46IGJ1aWxkV2hlbiwKICAgICAgbGlzdGVuV2hlbjogbGlzdGVuV2hlbiwKICAgICAgYnVpbGRlcjogYnVpbGRlciwKICAgICAgbGlzdGVuZXI6IGxpc3RlbmVyLAogICAgKTsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/bloc/global_event_channel_state.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvYmxvYy9nbG9iYWxfZXZlbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTplcXVhdGFibGUvZXF1YXRhYmxlLmRhcnQnOwoKY2xhc3MgR2xvYmFsRXZlbnRTdGF0ZSBleHRlbmRzIEVxdWF0YWJsZSB7CiAgZmluYWwgTGlzdDxHbG9iYWxFdmVudFR5cGU+IGhpc3Rvcnk7CiAgZmluYWwgTWFwPFR5cGUsIGludD4gZXZlbnRDb3VudHM7CiAgZmluYWwgR2xvYmFsRXZlbnRUeXBlPyBjdXJyZW50OwogIGZpbmFsIGludCBtYXhSZWNlbnRFdmVudHM7CgogIGNvbnN0IEdsb2JhbEV2ZW50U3RhdGUoewogICAgdGhpcy5oaXN0b3J5ID0gY29uc3QgW10sCiAgICB0aGlzLmV2ZW50Q291bnRzID0gY29uc3Qge30sCiAgICB0aGlzLmN1cnJlbnQsCiAgICB0aGlzLm1heFJlY2VudEV2ZW50cyA9IDUwLAogIH0pOwoKICBHbG9iYWxFdmVudFN0YXRlIGNvcHlXaXRoKHsKICAgIExpc3Q8R2xvYmFsRXZlbnRUeXBlPj8gaGlzdG9yeSwKICAgIE1hcDxUeXBlLCBpbnQ+PyBldmVudENvdW50cywKICAgIEdsb2JhbEV2ZW50VHlwZT8gY3VycmVudCwKICAgIGludD8gbWF4UmVjZW50RXZlbnRzLAogIH0pIHsKICAgIHJldHVybiBHbG9iYWxFdmVudFN0YXRlKAogICAgICBoaXN0b3J5OiBoaXN0b3J5ID8/IHRoaXMuaGlzdG9yeSwKICAgICAgZXZlbnRDb3VudHM6IGV2ZW50Q291bnRzID8/IHRoaXMuZXZlbnRDb3VudHMsCiAgICAgIGN1cnJlbnQ6IGN1cnJlbnQgPz8gdGhpcy5jdXJyZW50LAogICAgICBtYXhSZWNlbnRFdmVudHM6IG1heFJlY2VudEV2ZW50cyA/PyB0aGlzLm1heFJlY2VudEV2ZW50cywKICAgICk7CiAgfQoKICBAb3ZlcnJpZGUKICBMaXN0PE9iamVjdD8+IGdldCBwcm9wcyA9PiBbaGlzdG9yeSwgZXZlbnRDb3VudHMsIGN1cnJlbnQsIG1heFJlY2VudEV2ZW50c107Cn0K", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/bloc/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKCmltcG9ydCAncGFja2FnZTpjb3JlL2Jsb2MvZ2xvYmFsX2V2ZW50LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9ibG9jL2dsb2JhbF9ldmVudF9jaGFubmVsX2Jsb2MuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL2N1cGVydGluby5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXJfYmxvYy9mbHV0dGVyX2Jsb2MuZGFydCc7CgpleHBvcnQgJ2dsb2JhbF9ldmVudC5kYXJ0JzsKZXhwb3J0ICdnbG9iYWxfZXZlbnRfY2hhbm5lbF9ibG9jLmRhcnQnOwpleHBvcnQgJ2dsb2JhbF9ldmVudF9jaGFubmVsX3Byb3ZpZGVyLmRhcnQnOwpleHBvcnQgJ2dsb2JhbF9ldmVudF9jaGFubmVsX3N0YXRlLmRhcnQnOwoKZXh0ZW5zaW9uIEdsb2JhbEV2ZW50Q2hhbm5lbEV4dGVuc2lvbiBvbiBCdWlsZENvbnRleHQgewogIC8vLyBGaXJlcyBhIGdsb2JhbCBldmVudCB3aXRoIHRoZSBnaXZlbiBbZXZlbnRUeXBlXS4KICB2b2lkIGZpcmUoR2xvYmFsRXZlbnRUeXBlIGV2ZW50VHlwZSkgewogICAgZmluYWwgYmxvYyA9IHJlYWQ8R2xvYmFsRXZlbnRDaGFubmVsPigpOwogICAgYmxvYy5hZGQoRmlyZUdsb2JhbEV2ZW50KGV2ZW50VHlwZTogZXZlbnRUeXBlKSk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/constants/defaults.dart", + "data": + "Ly8vIEFwcC13aWRlIGRlZmF1bHQgdmFsdWVzLgpjbGFzcyBEZWZhdWx0cyB7CiAgRGVmYXVsdHMuXygpOwoKICBzdGF0aWMgY29uc3QgaW50IGRlZmF1bHRQYWdlU2l6ZSA9IDIwOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/constants/environment.dart", + "data": + "Ly8vIEJ1aWxkLXRpbWUgY29uZmlndXJhdGlvbi4KLy8vCi8vLyBFdmVyeSB2YWx1ZSBpcyBhIGAtLWRhcnQtZGVmaW5lYCwgc28gdGhlIHNhbWUgYmluYXJ5IGNhbiBiZSBwb2ludGVkIGF0IGEKLy8vIGRpZmZlcmVudCBiYWNrZW5kIHdpdGhvdXQgYSBjb2RlIGNoYW5nZToKLy8vCi8vLyBgYGBzaAovLy8gZmx1dHRlciBydW4gLS1kYXJ0LWRlZmluZT1CQVNFX1VSTD1odHRwczovL3N0YWdpbmcuZXhhbXBsZS5jb20KLy8vIGBgYApjbGFzcyBFbnZpcm9ubWVudCB7CiAgRW52aXJvbm1lbnQuXygpOwoKICBzdGF0aWMgY29uc3QgYm9vbCB1c2VFbXVsYXRvcnMgPSBib29sLmZyb21FbnZpcm9ubWVudCgnVVNFX0VNVUxBVE9SUycpOwoKICBzdGF0aWMgY29uc3QgU3RyaW5nIGJhc2VVcmwgPSBTdHJpbmcuZnJvbUVudmlyb25tZW50KAogICAgJ0JBU0VfVVJMJywKICAgIGRlZmF1bHRWYWx1ZTogJ2h0dHBzOi8vYXBpLmV4YW1wbGUuY29tJywKICApOwp9Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/constants/index.dart", + "data": + "ZXhwb3J0ICdkZWZhdWx0cy5kYXJ0JzsKZXhwb3J0ICdlbnZpcm9ubWVudC5kYXJ0JzsKZXhwb3J0ICdrZXlzLmRhcnQnOwpleHBvcnQgJ3JvdXRlcy5kYXJ0JzsK", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/constants/keys.dart", + "data": + "Ly8vIFF1ZXJ5LXBhcmFtZXRlciBhbmQgYGV4dHJhYCBrZXlzIHNoYXJlZCBiZXR3ZWVuIHJvdXRlcnMgYW5kIHNjcmVlbnMuCi8vLwovLy8gS2VlcCB0aGVzZSBoZXJlIHJhdGhlciB0aGFuIGlubGluZSBzbyBhIHJlbmFtZSBpcyBhIHNpbmdsZSBlZGl0LgpjbGFzcyBLZXlzIHsKICBLZXlzLl8oKTsKCiAgc3RhdGljIGNvbnN0IFN0cmluZyBlbWFpbCA9ICdlbWFpbCc7CiAgc3RhdGljIGNvbnN0IFN0cmluZyB0aXRsZSA9ICd0aXRsZSc7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBtZXNzYWdlID0gJ21lc3NhZ2UnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgZXJyb3IgPSAnZXJyb3InOwp9Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/constants/routes.dart", + "data": + "Ly8vIFBhdGhzIG93bmVkIGJ5IHRoZSBhcHAgc2hlbGwgaXRzZWxmLgovLy8KLy8vIEZlYXR1cmUgcGFja2FnZXMgZGVjbGFyZSB0aGVpciBvd24gcm91dGUgY29uc3RhbnRzOyB0aGVzZSBhcmUgdGhlIG9uZXMKLy8vIGV2ZXJ5IGFwcCBoYXMuCmNsYXNzIENvcmVSb3V0ZXMgewogIENvcmVSb3V0ZXMuXygpOwoKICBzdGF0aWMgY29uc3QgU3RyaW5nIHJvb3QgPSAnLyc7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBob21lID0gJy9ob21lJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGRhc2hib2FyZCA9ICcvaG9tZS9kYXNoYm9hcmQnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgcHJvZmlsZSA9ICcvaG9tZS9wcm9maWxlJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGVycm9yID0gJy9lcnJvcic7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/converters/datetime_converter.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmludGwvaW50bC5kYXJ0JzsKCmZpbmFsIGNsYXNzIERhdGVUaW1lQ29udmVydGVyIHsKICBjb25zdCBEYXRlVGltZUNvbnZlcnRlcigpOwoKICAvLy8gQ29udmVydHMgYSBbRGF0ZVRpbWVdIHRvIGEgW1N0cmluZ10gaW4gdGhlIGZvcm1hdCAnTU1NTSBkLCB5Jy4KICAvLy8gSWYgdGhlIFtkYXRlXSBpcyBudWxsLCBpdCByZXR1cm5zIGFuIGVtcHR5IHN0cmluZy4KICAvLy8KICAvLy8gRXhhbXBsZToKICAvLy8gYGBgZGFydAogIC8vLyBEYXRlVGltZSBkYXRlID0gRGF0ZVRpbWUubm93KCk7CiAgLy8vIFN0cmluZyBmb3JtYXR0ZWREYXRlID0gRGF0ZVRpbWVDb252ZXJ0ZXIudG9WaWV3Rm9ybWF0KGRhdGUpOwogIC8vLyBwcmludChmb3JtYXR0ZWREYXRlKTsgLy8gT3V0cHV0OiAnT2N0b2JlciAxLCAyMDIzJwogIC8vLyBgYGAKICBzdGF0aWMgU3RyaW5nIHRvVmlld0Zvcm1hdChEYXRlVGltZT8gZGF0ZSkgewogICAgaWYgKGRhdGUgPT0gbnVsbCkgcmV0dXJuICcnOwogICAgZmluYWwgZGF0ZUZvcm1hdGUgPSBEYXRlRm9ybWF0KCdNTU1NIGQsIHknKTsKCiAgICByZXR1cm4gZGF0ZUZvcm1hdGUuZm9ybWF0KGRhdGUudG9Mb2NhbCgpKTsKICB9CgogIHN0YXRpYyBTdHJpbmcgdG9WaWV3Rm9ybWF0V2l0aFRpbWUoRGF0ZVRpbWU/IGRhdGUpIHsKICAgIGlmIChkYXRlID09IG51bGwpIHJldHVybiAnJzsKICAgIGZpbmFsIGRhdGVGb3JtYXRlID0gRGF0ZUZvcm1hdCgnTU1NTSBkLCB5LCBoOm1tIGEnKTsKCiAgICByZXR1cm4gZGF0ZUZvcm1hdGUuZm9ybWF0KGRhdGUpOwogIH0KCiAgc3RhdGljIFN0cmluZyB0b1ZpZXdGb3JtYXRUaW1lKERhdGVUaW1lPyBkYXRlKSB7CiAgICBpZiAoZGF0ZSA9PSBudWxsKSByZXR1cm4gJyc7CiAgICBmaW5hbCBkYXRlRm9ybWF0ZSA9IERhdGVGb3JtYXQoJ2g6bW06c3MgYScpOwoKICAgIHJldHVybiBkYXRlRm9ybWF0ZS5mb3JtYXQoZGF0ZSk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/converters/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdkYXRldGltZV9jb252ZXJ0ZXIuZGFydCc7CmV4cG9ydCAndGltZXN0YW1wX2NvbnZlcnRlci5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/converters/timestamp_converter.dart", + "data": + "e3sjZmlyZXN0b3JlfX1pbXBvcnQgJ3BhY2thZ2U6Y2xvdWRfZmlyZXN0b3JlL2Nsb3VkX2ZpcmVzdG9yZS5kYXJ0JzsKe3svZmlyZXN0b3JlfX1pbXBvcnQgJ3BhY2thZ2U6anNvbl9hbm5vdGF0aW9uL2pzb25fYW5ub3RhdGlvbi5kYXJ0JzsKCi8vLyBBY2NlcHRzIHRoZSB0aHJlZSBzaGFwZXMgYSB0aW1lc3RhbXAgYXJyaXZlcyBpbiAtIElTTyBzdHJpbmcse3sjZmlyZXN0b3JlfX0KLy8vIEZpcmVzdG9yZSBbVGltZXN0YW1wXSx7ey9maXJlc3RvcmV9fSBvciB0aGUgYHtfc2Vjb25kcywgX25hbm9zZWNvbmRzfWAgbWFwIHRoYXQKLy8vIENsb3VkIEZ1bmN0aW9ucyBlbWl0IC0gYW5kIGFsd2F5cyB3cml0ZXMgYmFjayBVVEMgSVNPLTg2MDEuCmNsYXNzIFRpbWVzdGFtcENvbnZlcnRlciBpbXBsZW1lbnRzIEpzb25Db252ZXJ0ZXI8RGF0ZVRpbWUsIGR5bmFtaWM+IHsKICBjb25zdCBUaW1lc3RhbXBDb252ZXJ0ZXIoKTsKCiAgQG92ZXJyaWRlCiAgRGF0ZVRpbWUgZnJvbUpzb24oZHluYW1pYyBqc29uKSB7CiAgICBmaW5hbCBwYXJzZWQgPSBfdHJ5UGFyc2UoanNvbik7CiAgICBpZiAocGFyc2VkID09IG51bGwpIHsKICAgICAgdGhyb3cgQXJndW1lbnRFcnJvcignSW52YWxpZCB0aW1lc3RhbXAgZm9ybWF0OiAkanNvbicpOwogICAgfQogICAgcmV0dXJuIHBhcnNlZDsKICB9CgogIEBvdmVycmlkZQogIFN0cmluZz8gdG9Kc29uKERhdGVUaW1lPyBkYXRlKSA9PiBkYXRlPy50b1V0YygpLnRvSXNvODYwMVN0cmluZygpOwp9CgpjbGFzcyBUaW1lc3RhbXBDb252ZXJ0ZXJOdWxsYWJsZSBpbXBsZW1lbnRzIEpzb25Db252ZXJ0ZXI8RGF0ZVRpbWU/LCBkeW5hbWljPiB7CiAgY29uc3QgVGltZXN0YW1wQ29udmVydGVyTnVsbGFibGUoKTsKCiAgQG92ZXJyaWRlCiAgRGF0ZVRpbWU/IGZyb21Kc29uKGR5bmFtaWMganNvbikgPT4gX3RyeVBhcnNlKGpzb24pOwoKICBAb3ZlcnJpZGUKICBTdHJpbmc/IHRvSnNvbihEYXRlVGltZT8gZGF0ZSkgPT4gZGF0ZT8udG9VdGMoKS50b0lzbzg2MDFTdHJpbmcoKTsKfQoKRGF0ZVRpbWU/IF90cnlQYXJzZShkeW5hbWljIGpzb24pIHsKICBpZiAoanNvbiBpcyBTdHJpbmcgJiYganNvbi5pc05vdEVtcHR5KSByZXR1cm4gRGF0ZVRpbWUucGFyc2UoanNvbikudG9Mb2NhbCgpOwp7eyNmaXJlc3RvcmV9fSAgaWYgKGpzb24gaXMgVGltZXN0YW1wKSByZXR1cm4ganNvbi50b0RhdGUoKTsKe3svZmlyZXN0b3JlfX0gIGlmIChqc29uIGlzIE1hcDxTdHJpbmcsIGR5bmFtaWM+KSB7CiAgICBmaW5hbCBzZWNvbmRzID0ganNvblsnX3NlY29uZHMnXSBhcyBpbnQ7CiAgICBmaW5hbCBuYW5vc2Vjb25kcyA9IGpzb25bJ19uYW5vc2Vjb25kcyddIGFzIGludD8gPz8gMDsKICAgIHJldHVybiBEYXRlVGltZS5mcm9tTWlsbGlzZWNvbmRzU2luY2VFcG9jaCgKICAgICAgc2Vjb25kcyAqIDEwMDAgKyBuYW5vc2Vjb25kcyB+LyAxMDAwMDAwLAogICAgKTsKICB9CiAgcmV0dXJuIG51bGw7Cn0K", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/core.dart", + "data": + "bGlicmFyeTsKCmltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci9sb2dnZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKCmV4cG9ydCAnYmxvYy9pbmRleC5kYXJ0JzsKZXhwb3J0ICdjb25zdGFudHMvaW5kZXguZGFydCc7CmV4cG9ydCAnY29udmVydGVycy9pbmRleC5kYXJ0JzsKZXhwb3J0ICdlcnJvcnMvaW5kZXguZGFydCc7CmV4cG9ydCAnZXh0ZW5zaW9ucy9pbmRleC5kYXJ0JzsKZXhwb3J0ICdsb2dnZXIvbG9nZ2VyLmRhcnQnOwpleHBvcnQgJ29ic2VydmVyL2luZGV4LmRhcnQnOwpleHBvcnQgJ3JvdXRlci9jb3JlX3JvdXRlci5kYXJ0JzsKCkZ1dHVyZTx2b2lkPiBpbml0KCkgYXN5bmMgewogIHJlZ2lzdGVyTG9nZ2VyV2l0aERJKCk7CiAgZmluYWwgbG9nZ2VyID0gZGkuZ2V0PExvZ2dlcj4oKTsKICBsb2dnZXIuaSgnQ29yZSBtb2R1bGUgaW5pdGlhbGl6ZWQnKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/developer/emulators.dart", + "data": + "e3sjZmlyZWJhc2V9fXt7I2F1dGh9fWltcG9ydCAncGFja2FnZTpmaXJlYmFzZV9hdXRoL2ZpcmViYXNlX2F1dGguZGFydCc7Cnt7L2F1dGh9fXt7I2ZpcmVzdG9yZX19aW1wb3J0ICdwYWNrYWdlOmNsb3VkX2ZpcmVzdG9yZS9jbG91ZF9maXJlc3RvcmUuZGFydCc7CmltcG9ydCAncGFja2FnZTpjbG91ZF9mdW5jdGlvbnMvY2xvdWRfZnVuY3Rpb25zLmRhcnQnOwp7ey9maXJlc3RvcmV9fWltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci9sb2dnZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKCi8vLyBQb2ludHMgZXZlcnkgRmlyZWJhc2UgU0RLIGF0IHRoZSBsb2NhbCBlbXVsYXRvciBzdWl0ZS4KLy8vCi8vLyBQb3J0cyBtYXRjaCB0aGUgZGVmYXVsdHMgaW4gYGZpcmViYXNlLmpzb25gOyBjaGFuZ2UgYm90aCB0b2dldGhlci4KRnV0dXJlPHZvaWQ+IGluaXQoKSBhc3luYyB7CiAgZmluYWwgbG9nZ2VyID0gZGkuZ2V0PExvZ2dlcj4oKTsKICBsb2dnZXIuaSgnVXNpbmcgRmlyZWJhc2UgZW11bGF0b3JzJyk7CiAgdHJ5IHsKe3sjYXV0aH19ICAgIGF3YWl0IEZpcmViYXNlQXV0aC5pbnN0YW5jZS51c2VBdXRoRW11bGF0b3IoJ2xvY2FsaG9zdCcsIDkwOTkpOwp7ey9hdXRofX17eyNmaXJlc3RvcmV9fSAgICBGaXJlYmFzZUZpcmVzdG9yZS5pbnN0YW5jZS51c2VGaXJlc3RvcmVFbXVsYXRvcignbG9jYWxob3N0JywgODA4MCk7CiAgICBGaXJlYmFzZUZ1bmN0aW9ucy5pbnN0YW5jZS51c2VGdW5jdGlvbnNFbXVsYXRvcignbG9jYWxob3N0JywgNTAwMSk7Cnt7L2ZpcmVzdG9yZX19ICAgIGxvZ2dlci5pKCdTdWNjZXNzZnVsbHkgaW5pdGlhbGlzZWQgdGhlIEZpcmViYXNlIGVtdWxhdG9ycycpOwogIH0gY2F0Y2ggKGVycm9yLCBzdGFja1RyYWNlKSB7CiAgICBsb2dnZXIuZSgnRmFpbGVkIHRvIGluaXRpYWxpc2UgdGhlIEZpcmViYXNlIGVtdWxhdG9yczonLCBlcnJvciwgc3RhY2tUcmFjZSk7CiAgfQp9Cnt7L2ZpcmViYXNlfX17e15maXJlYmFzZX19Ly8vIE5vIEZpcmViYXNlIHNlcnZpY2VzIGFyZSBlbmFibGVkIGluIHRoaXMgcHJvamVjdCwgc28gdGhlcmUgaXMgbm90aGluZyB0bwovLy8gcG9pbnQgYXQgYW4gZW11bGF0b3IuIEtlcHQgYXMgYSBuby1vcCBzbyBib290c3RyYXAgc3RheXMgdW5pZm9ybS4KRnV0dXJlPHZvaWQ+IGluaXQoKSBhc3luYyB7fQp7ey9maXJlYmFzZX19", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/errors/exceptions.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CgpjbGFzcyBDb3JlRXhjZXB0aW9uIGltcGxlbWVudHMgRXhjZXB0aW9uIHsKICBmaW5hbCBTdHJpbmcgbWVzc2FnZTsKCiAgQ29yZUV4Y2VwdGlvbih7cmVxdWlyZWQgdGhpcy5tZXNzYWdlfSk7Cn0KCmNsYXNzIFVzZXJOb3RGb3VuZEV4Y2VwdGlvbiBleHRlbmRzIENvcmVFeGNlcHRpb24gewogIFVzZXJOb3RGb3VuZEV4Y2VwdGlvbigpIDogc3VwZXIobWVzc2FnZTogc3RyaW5ncy5lcnJvcnMudXNlcl9ub3RfZm91bmQpOwp9Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/errors/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdleGNlcHRpb25zLmRhcnQnOwo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/extensions/basic.dart", + "data": + "ZXh0ZW5zaW9uIEJhc2ljU3RyaW5nRXh0ZW5zaW9ucyBvbiBTdHJpbmc/IHsKICBib29sIGdldCBhc0Jvb2wgewogICAgaWYgKHRoaXMgPT0gbnVsbCkgcmV0dXJuIGZhbHNlOwogICAgZmluYWwgbG93ZXIgPSB0aGlzIS50b0xvd2VyQ2FzZSgpOwogICAgcmV0dXJuIGxvd2VyID09ICd0cnVlJyB8fCBsb3dlciA9PSAnMSc7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/extensions/index.dart", + "data": "ZXhwb3J0ICdiYXNpYy5kYXJ0JzsK", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/logger/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdsb2dnZXIuZGFydCc7CmV4cG9ydCAndGFsa2VyX2xvZ2dlcl9pbXBsLmRhcnQnOwo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/logger/logger.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL3RhbGtlcl9sb2dnZXJfaW1wbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6dGFsa2VyX2ZsdXR0ZXIvdGFsa2VyX2ZsdXR0ZXIuZGFydCc7Cgp2b2lkIHJlZ2lzdGVyTG9nZ2VyV2l0aERJKCkgewogIGZpbmFsIHRhbGtlciA9IFRhbGtlckZsdXR0ZXIuaW5pdCgpOwogIGZpbmFsIGxvZ2dlciA9IFRhbGtlckxvZ2dlckltcGwodGFsa2VyKTsKCiAgZGkucmVnaXN0ZXI8TG9nZ2VyPihsb2dnZXIpOwp9CgphYnN0cmFjdCBjbGFzcyBMb2dnZXIgewogIE9iamVjdCBnZXQgbG9nZ2VyOwoKICB2b2lkIGQoU3RyaW5nIG1lc3NhZ2UpOwoKICB2b2lkIGkoU3RyaW5nIG1lc3NhZ2UpOwoKICB2b2lkIHcoU3RyaW5nIG1lc3NhZ2UpOwoKICB2b2lkIGUoU3RyaW5nIG1lc3NhZ2UsIFtPYmplY3Q/IGVycm9yLCBTdGFja1RyYWNlPyBzdGFja1RyYWNlXSk7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/logger/talker_logger_impl.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL2xvZ2dlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnRhbGtlcl9mbHV0dGVyL3RhbGtlcl9mbHV0dGVyLmRhcnQnOwoKY2xhc3MgVGFsa2VyTG9nZ2VySW1wbCBpbXBsZW1lbnRzIExvZ2dlciB7CiAgZmluYWwgVGFsa2VyIF90YWxrZXI7CgogIFRhbGtlckxvZ2dlckltcGwodGhpcy5fdGFsa2VyKTsKCiAgQG92ZXJyaWRlCiAgdm9pZCBkKFN0cmluZyBtZXNzYWdlKSB7CiAgICBfdGFsa2VyLmRlYnVnKG1lc3NhZ2UpOwogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBpKFN0cmluZyBtZXNzYWdlKSB7CiAgICBfdGFsa2VyLmluZm8obWVzc2FnZSk7CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIHcoU3RyaW5nIG1lc3NhZ2UpIHsKICAgIF90YWxrZXIud2FybmluZyhtZXNzYWdlKTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgZShTdHJpbmcgbWVzc2FnZSwgW09iamVjdD8gZXJyb3IsIFN0YWNrVHJhY2U/IHN0YWNrVHJhY2VdKSB7CiAgICBfdGFsa2VyLmVycm9yKG1lc3NhZ2UsIGVycm9yLCBzdGFja1RyYWNlKTsKICB9CgogIEBvdmVycmlkZQogIE9iamVjdCBnZXQgbG9nZ2VyID0+IF90YWxrZXI7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/observer/core_bloc_observer.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmJsb2MvYmxvYy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6dGFsa2VyX2Jsb2NfbG9nZ2VyL3RhbGtlcl9ibG9jX2xvZ2dlcl9vYnNlcnZlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnRhbGtlcl9ibG9jX2xvZ2dlci90YWxrZXJfYmxvY19sb2dnZXJfc2V0dGluZ3MuZGFydCc7CmltcG9ydCAncGFja2FnZTp0YWxrZXJfZmx1dHRlci90YWxrZXJfZmx1dHRlci5kYXJ0JzsKCmNsYXNzIENvcmVCbG9jT2JzZXJ2ZXIgZXh0ZW5kcyBCbG9jT2JzZXJ2ZXIgewogIGZpbmFsIFRhbGtlckJsb2NPYnNlcnZlciBfb2JzZXJ2ZXI7CgogIENvcmVCbG9jT2JzZXJ2ZXIoKQogICAgOiBfb2JzZXJ2ZXIgPSBUYWxrZXJCbG9jT2JzZXJ2ZXIoCiAgICAgICAgdGFsa2VyOiBkaS5nZXQ8TG9nZ2VyPigpLmxvZ2dlciBhcyBUYWxrZXIsCiAgICAgICAgc2V0dGluZ3M6IFRhbGtlckJsb2NMb2dnZXJTZXR0aW5ncyhwcmludEV2ZW50RnVsbERhdGE6IHRydWUpLAogICAgICApOwoKICBAb3ZlcnJpZGUKICB2b2lkIG9uRXZlbnQoQmxvYyBibG9jLCBPYmplY3Q/IGV2ZW50KSB7CiAgICBzdXBlci5vbkV2ZW50KGJsb2MsIGV2ZW50KTsKICAgIF9vYnNlcnZlci5vbkV2ZW50KGJsb2MsIGV2ZW50KTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgb25UcmFuc2l0aW9uKEJsb2MgYmxvYywgVHJhbnNpdGlvbiB0cmFuc2l0aW9uKSB7CiAgICBzdXBlci5vblRyYW5zaXRpb24oYmxvYywgdHJhbnNpdGlvbik7CiAgICBfb2JzZXJ2ZXIub25UcmFuc2l0aW9uKGJsb2MsIHRyYW5zaXRpb24pOwogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBvbkVycm9yKEJsb2NCYXNlIGJsb2MsIE9iamVjdCBlcnJvciwgU3RhY2tUcmFjZSBzdGFja1RyYWNlKSB7CiAgICBzdXBlci5vbkVycm9yKGJsb2MsIGVycm9yLCBzdGFja1RyYWNlKTsKICAgIF9vYnNlcnZlci5vbkVycm9yKGJsb2MsIGVycm9yLCBzdGFja1RyYWNlKTsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5hdmlnYXRpb25faGlzdG9yeV9vYnNlcnZlci9uYXZpZ2F0aW9uX2hpc3Rvcnlfb2JzZXJ2ZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTp0YWxrZXJfZmx1dHRlci90YWxrZXJfZmx1dHRlci5kYXJ0JzsKCmNsYXNzIENvcmVSb3V0ZU9ic2VydmVyIGV4dGVuZHMgTmF2aWdhdG9yT2JzZXJ2ZXIgewogIENvcmVSb3V0ZU9ic2VydmVyKCkgOiBfb2JzZXJ2ZXIgPSBkaS5nZXQ8TG9nZ2VyPigpLmxvZ2dlciBhcyBUYWxrZXI7CgogIGZpbmFsIFRhbGtlciBfb2JzZXJ2ZXI7CgogIEBvdmVycmlkZQogIHZvaWQgZGlkUHVzaChSb3V0ZSByb3V0ZSwgUm91dGU/IHByZXZpb3VzUm91dGUpIHsKICAgIHN1cGVyLmRpZFB1c2gocm91dGUsIHByZXZpb3VzUm91dGUpOwogICAgaWYgKHJvdXRlLnNldHRpbmdzLm5hbWUgPT0gbnVsbCkgewogICAgICByZXR1cm47CiAgICB9CiAgICBfb2JzZXJ2ZXIubG9nQ3VzdG9tKFRhbGtlclJvdXRlTG9nKHJvdXRlOiByb3V0ZSwgdHlwZTogUm91dGVMb2dUeXBlLnB1c2gpKTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgZGlkUG9wKFJvdXRlIHJvdXRlLCBSb3V0ZT8gcHJldmlvdXNSb3V0ZSkgewogICAgc3VwZXIuZGlkUG9wKHJvdXRlLCBwcmV2aW91c1JvdXRlKTsKICAgIGlmIChyb3V0ZS5zZXR0aW5ncy5uYW1lID09IG51bGwpIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgX29ic2VydmVyLmxvZ0N1c3RvbShUYWxrZXJSb3V0ZUxvZyhyb3V0ZTogcm91dGUsIHR5cGU6IFJvdXRlTG9nVHlwZS5wb3ApKTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgZGlkUmVtb3ZlKFJvdXRlPGR5bmFtaWM+IHJvdXRlLCBSb3V0ZTxkeW5hbWljPj8gcHJldmlvdXNSb3V0ZSkgewogICAgc3VwZXIuZGlkUmVtb3ZlKHJvdXRlLCBwcmV2aW91c1JvdXRlKTsKICAgIGlmIChyb3V0ZS5zZXR0aW5ncy5uYW1lID09IG51bGwpIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgX29ic2VydmVyLmxvZ0N1c3RvbSgKICAgICAgVGFsa2VyUm91dGVMb2cocm91dGU6IHJvdXRlLCB0eXBlOiBSb3V0ZUxvZ1R5cGUucmVtb3ZlKSwKICAgICk7CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIGRpZFJlcGxhY2Uoe1JvdXRlPGR5bmFtaWM+PyBuZXdSb3V0ZSwgUm91dGU8ZHluYW1pYz4/IG9sZFJvdXRlfSkgewogICAgc3VwZXIuZGlkUmVwbGFjZShuZXdSb3V0ZTogbmV3Um91dGUsIG9sZFJvdXRlOiBvbGRSb3V0ZSk7CiAgICBpZiAobmV3Um91dGU/LnNldHRpbmdzLm5hbWUgPT0gbnVsbCkgewogICAgICByZXR1cm47CiAgICB9CiAgICBfb2JzZXJ2ZXIubG9nQ3VzdG9tKAogICAgICBUYWxrZXJSb3V0ZUxvZyhyb3V0ZTogbmV3Um91dGUhLCB0eXBlOiBSb3V0ZUxvZ1R5cGUucmVwbGFjZSksCiAgICApOwogIH0KfQoKZW51bSBSb3V0ZUxvZ1R5cGUgeyBwdXNoLCBwb3AsIHJlbW92ZSwgcmVwbGFjZSB9CgpjbGFzcyBUYWxrZXJSb3V0ZUxvZyBleHRlbmRzIFRhbGtlckxvZyB7CiAgVGFsa2VyUm91dGVMb2coe3JlcXVpcmVkIFJvdXRlIHJvdXRlLCByZXF1aXJlZCBSb3V0ZUxvZ1R5cGUgdHlwZX0pCiAgICA6IHN1cGVyKF9jcmVhdGVNZXNzYWdlKHJvdXRlLCB0eXBlKSk7CgogIEBvdmVycmlkZQogIEFuc2lQZW4gZ2V0IHBlbiA9PiBBbnNpUGVuKCkuLnh0ZXJtKDEzNSk7CgogIEBvdmVycmlkZQogIFN0cmluZyBnZXQga2V5ID0+IFRhbGtlckxvZ1R5cGUucm91dGUua2V5OwoKICBzdGF0aWMgU3RyaW5nIF9jcmVhdGVNZXNzYWdlKFJvdXRlPGR5bmFtaWM+IHJvdXRlLCBSb3V0ZUxvZ1R5cGUgdHlwZSkgewogICAgZmluYWwgYnVmZmVyID0gU3RyaW5nQnVmZmVyKCk7CiAgICBidWZmZXIud3JpdGUodHlwZS5uYW1lKTsKICAgIGJ1ZmZlci53cml0ZSgnIHJvdXRlIG5hbWVkICcpOwogICAgYnVmZmVyLndyaXRlKHJvdXRlLnNldHRpbmdzLm5hbWUgPz8gJ251bGwnKTsKCiAgICBmaW5hbCBhcmdzID0gcm91dGUuc2V0dGluZ3MuYXJndW1lbnRzOwogICAgaWYgKGFyZ3MgIT0gbnVsbCkgewogICAgICBidWZmZXIud3JpdGUoJ1xuQXJndW1lbnRzOiAkYXJncycpOwogICAgfQoKICAgIGZpbmFsIGhpc3RvcnlPYnNlcnZlciA9IE5hdmlnYXRpb25IaXN0b3J5T2JzZXJ2ZXIoKTsKICAgIGZpbmFsIGhpc3RvcnkgPSBoaXN0b3J5T2JzZXJ2ZXIuaGlzdG9yeTsKICAgIGlmIChoaXN0b3J5LmlzRW1wdHkpIHsKICAgICAgYnVmZmVyLndyaXRlKCdcbkhpc3Rvcnk6IG5hJyk7CiAgICB9IGVsc2UgewogICAgICBmaW5hbCBoaXN0b3J5QXNTdHJpbmcgPSBoaXN0b3J5T2JzZXJ2ZXIuaGlzdG9yeQogICAgICAgICAgLm1hcCgocikgPT4gci5zZXR0aW5ncy5uYW1lKQogICAgICAgICAgLmpvaW4oJyAtPiAnKTsKICAgICAgYnVmZmVyLndyaXRlKCdcbkhpc3Rvcnk6ICRoaXN0b3J5QXNTdHJpbmcnKTsKICAgIH0KCiAgICByZXR1cm4gYnVmZmVyLnRvU3RyaW5nKCk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/lib/observer/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdjb3JlX2Jsb2Nfb2JzZXJ2ZXIuZGFydCc7CmV4cG9ydCAnY29yZV9yb3V0ZV9vYnNlcnZlci5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/core/lib/router/core_router.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmdvX3JvdXRlci9nb19yb3V0ZXIuZGFydCc7CgphYnN0cmFjdCBjbGFzcyBDb3JlUm91dGVyIHsKICBMaXN0PFJvdXRlQmFzZT4gZ2V0IHJvdXRlczsKfQo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/pubspec.yaml", + "data": + "bmFtZTogY29yZQpkZXNjcmlwdGlvbjogIkNyb3NzLWN1dHRpbmcgZm91bmRhdGlvbiBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX06IGxvZ2dpbmcsIERJIHdpcmluZywgcm91dGluZyBjb250cmFjdHMsIGVycm9yIHR5cGVzIGFuZCBCTG9DIHBsdW1iaW5nLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgYmxvYzogXjkuMC4wCiAgZXF1YXRhYmxlOiBeMi4wLjUKICBmbHV0dGVyX2Jsb2M6IF45LjEuMQogIGdvX3JvdXRlcjogXjE1LjEuMwogIGludGw6IF4wLjIwLjIKICBqc29uX2Fubm90YXRpb246IF40LjkuMAogIG5hdmlnYXRpb25faGlzdG9yeV9vYnNlcnZlcjogXjEuMS4xCiAgdGFsa2VyX2Jsb2NfbG9nZ2VyOiBeNC45LjEKICB0YWxrZXJfZmx1dHRlcjogXjQuNC4xCiAgZGk6CiAgICBwYXRoOiAuLi9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uL2xvY2FsaXphdGlvbgp7eyNmaXJlYmFzZX19ICBmaXJlYmFzZV9jb3JlOiBeMy4xNC4wCnt7L2ZpcmViYXNlfX17eyNhdXRofX0gIGZpcmViYXNlX2F1dGg6IF41LjMuNAp7ey9hdXRofX17eyNmaXJlc3RvcmV9fSAgY2xvdWRfZmlyZXN0b3JlOiBeNS41LjMKICBjbG91ZF9mdW5jdGlvbnM6IF41LjIuMwp7ey9maXJlc3RvcmV9fQpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF41LjAuMAoKZGVwZW5kZW5jeV9vdmVycmlkZXM6CiAgaW50bDogXjAuMjAuMgoKZmx1dHRlcjoK", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/core/README.md", + "data": + "IyBDb3JlCgpBIE1lbG9zLW1hbmFnZWQgcHJvamVjdCBmb3IgbW9uby1yZXBvLCBjcmVhdGVkIHVzaW5nIE5vblN0b3AgQ0xJLgoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgojIyBJbnN0YWxsYXRpb24g8J+SuwoKKirinZcgSW4gb3JkZXIgdG8gc3RhcnQgdXNpbmcgQ29yZSB5b3UgbXVzdCBoYXZlIHRoZSBbRmx1dHRlciBTREtdW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXSBpbnN0YWxsZWQgb24geW91ciBtYWNoaW5lLioqCgpJbnN0YWxsIHZpYSBgZGFydCBwdWIgYWRkYDoKCmBgYHNoCmRhcnQgcHViIGFkZCBjb3JlCmBgYAoKW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXTogaHR0cHM6Ly9kb2NzLmZsdXR0ZXIuZGV2L2dldC1zdGFydGVkL2luc3RhbGw=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/components/asset_image.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3N2Zy9zdmcuZGFydCc7CgpjbGFzcyBBc3NldEltYWdlIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBmaW5hbCBTdHJpbmcgYXNzZXRQYXRoOwogIGZpbmFsIGRvdWJsZT8gd2lkdGg7CiAgZmluYWwgZG91YmxlPyBoZWlnaHQ7CiAgZmluYWwgQm94Rml0IGJveEZpdDsKICBmaW5hbCBTdHJpbmc/IHBhY2thZ2U7CgogIGNvbnN0IEFzc2V0SW1hZ2UoewogICAgc3VwZXIua2V5LAogICAgcmVxdWlyZWQgdGhpcy5hc3NldFBhdGgsCiAgICB0aGlzLndpZHRoLAogICAgdGhpcy5oZWlnaHQsCiAgICB0aGlzLmJveEZpdCA9IEJveEZpdC5jb3ZlciwKICAgIHRoaXMucGFja2FnZSwKICB9KTsKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIC8vIGlmIGFzc2V0IGlzIHN2ZywgdXNlIFN2Z1BpY3R1cmUuYXNzZXQKICAgIGlmIChhc3NldFBhdGguZW5kc1dpdGgoJy5zdmcnKSkgewogICAgICByZXR1cm4gU3ZnUGljdHVyZS5hc3NldCgKICAgICAgICBhc3NldFBhdGgsCiAgICAgICAgd2lkdGg6IHdpZHRoLAogICAgICAgIGhlaWdodDogaGVpZ2h0LAogICAgICAgIGZpdDogYm94Rml0LAogICAgICAgIHBhY2thZ2U6IHBhY2thZ2UsCiAgICAgICAgZXJyb3JCdWlsZGVyOiAoY29udGV4dCwgZXJyb3IsIHN0YWNrVHJhY2UpIHsKICAgICAgICAgIHJldHVybiBJY29uKAogICAgICAgICAgICBJY29ucy5pbWFnZSwKICAgICAgICAgICAgc2l6ZTogMzIsCiAgICAgICAgICAgIGNvbG9yOiBUaGVtZS5vZihjb250ZXh0KS5jb2xvclNjaGVtZS5vblN1cmZhY2UsCiAgICAgICAgICApOwogICAgICAgIH0sCiAgICAgICk7CiAgICB9CgogICAgLy8gb3RoZXJ3aXNlLCB1c2UgSW1hZ2UuYXNzZXQKICAgIHJldHVybiBJbWFnZS5hc3NldCgKICAgICAgYXNzZXRQYXRoLAogICAgICB3aWR0aDogd2lkdGgsCiAgICAgIGhlaWdodDogaGVpZ2h0LAogICAgICBmaXQ6IGJveEZpdCwKICAgICAgcGFja2FnZTogcGFja2FnZSwKICAgICAgZXJyb3JCdWlsZGVyOiAoY29udGV4dCwgZXJyb3IsIHN0YWNrVHJhY2UpIHsKICAgICAgICByZXR1cm4gSWNvbigKICAgICAgICAgIEljb25zLmltYWdlLAogICAgICAgICAgc2l6ZTogMzIsCiAgICAgICAgICBjb2xvcjogVGhlbWUub2YoY29udGV4dCkuY29sb3JTY2hlbWUub25TdXJmYWNlLAogICAgICAgICk7CiAgICAgIH0sCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/components/auth_headers_builder.dart", + "data": + "e3sjbmV0d29ya319aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvbmV0d29yay5kYXJ0JzsKCi8vLyBBIHdpZGdldCB0aGF0IHByb3ZpZGVzIGF1dGhlbnRpY2F0aW9uIGhlYWRlcnMgdG8gaXRzIGNoaWxkIGJ1aWxkZXIuCi8vLwovLy8gVGhpcyB3aWRnZXQgYXV0b21hdGljYWxseSBmZXRjaGVzIHRoZSBjdXJyZW50IGF1dGhlbnRpY2F0aW9uIHRva2VuCi8vLyB1c2luZyB0aGUgW0F1dGhUb2tlblByb3ZpZGVyXSBmcm9tIGRlcGVuZGVuY3kgaW5qZWN0aW9uIGFuZCBjb25zdHJ1Y3RzCi8vLyBIVFRQIGhlYWRlcnMgd2l0aCB0aGUgQmVhcmVyIHRva2VuLgovLy8KLy8vIEV4YW1wbGUgdXNhZ2U6Ci8vLyBgYGBkYXJ0Ci8vLyAvLyBXaXRob3V0IHRva2VuIHZhbGlkYXRpb24gKGRlZmF1bHQgLSBmYXN0ZXIsIHVzZXMgY2FjaGVkIHRva2VuKQovLy8gQXV0aEhlYWRlcnNCdWlsZGVyKAovLy8gICBidWlsZGVyOiAoY29udGV4dCwgaGVhZGVycykgewovLy8gICAgIHJldHVybiBDYWNoZWROZXR3b3JrSW1hZ2UoCi8vLyAgICAgICBpbWFnZVVybDogaW1hZ2VVcmwsCi8vLyAgICAgICBodHRwSGVhZGVyczogaGVhZGVycywKLy8vICAgICApOwovLy8gICB9LAovLy8gKQovLy8KLy8vIC8vIFdpdGggdG9rZW4gdmFsaWRhdGlvbiAoZW5zdXJlcyBmcmVzaCB0b2tlbikKLy8vIEF1dGhIZWFkZXJzQnVpbGRlcihkCi8vLyAgIHZhbGlkYXRlVG9rZW46IHRydWUsCi8vLyAgIGJ1aWxkZXI6IChjb250ZXh0LCBoZWFkZXJzKSB7Ci8vLyAgICAgcmV0dXJuIENhY2hlZE5ldHdvcmtJbWFnZSgKLy8vICAgICAgIGltYWdlVXJsOiBpbWFnZVVybCwKLy8vICAgICAgIGh0dHBIZWFkZXJzOiBoZWFkZXJzLAovLy8gICAgICk7Ci8vLyAgIH0sCi8vLyApCi8vLyBgYGAKY2xhc3MgQXV0aEhlYWRlcnNCdWlsZGVyIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBzdGF0aWMgZmluYWwgX2xvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CgogIC8vLyBDcmVhdGVzIGFuIFtBdXRoSGVhZGVyc0J1aWxkZXJdLgogIC8vLwogIC8vLyBUaGUgW2J1aWxkZXJdIGZ1bmN0aW9uIGlzIGNhbGxlZCB3aXRoIHRoZSBjdXJyZW50IFtCdWlsZENvbnRleHRdIGFuZAogIC8vLyBhIG1hcCBvZiBIVFRQIGhlYWRlcnMuIElmIGF1dGhlbnRpY2F0aW9uIGZhaWxzIG9yIG5vIHRva2VuIGlzIGF2YWlsYWJsZSwKICAvLy8gYW4gZW1wdHkgbWFwIGlzIHByb3ZpZGVkLgogIC8vLwogIC8vLyBJZiBbdmFsaWRhdGVUb2tlbl0gaXMgdHJ1ZSwgdGhlIHRva2VuIHdpbGwgYmUgdmFsaWRhdGVkIGFuZAogIC8vLyByZWZyZXNoZWQgaWYgbmVlZGVkLiBJZiBmYWxzZSAoZGVmYXVsdCksIHRoZSBjdXJyZW50IGNhY2hlZCB0b2tlbiB3aWxsIGJlIHVzZWQKICAvLy8gd2l0aG91dCB2YWxpZGF0aW9uLgogIGNvbnN0IEF1dGhIZWFkZXJzQnVpbGRlcih7CiAgICBzdXBlci5rZXksCiAgICByZXF1aXJlZCB0aGlzLmJ1aWxkZXIsCiAgICB0aGlzLnZhbGlkYXRlVG9rZW4gPSBmYWxzZSwKICB9KTsKCiAgLy8vIFRoZSBidWlsZGVyIGZ1bmN0aW9uIHRoYXQgcmVjZWl2ZXMgdGhlIGF1dGhlbnRpY2F0aW9uIGhlYWRlcnMuCiAgLy8vCiAgLy8vIFRoZSBoZWFkZXJzIG1hcCB3aWxsIGNvbnRhaW46CiAgLy8vIC0gJ0F1dGhvcml6YXRpb24nOiAnQmVhcmVyIHt0b2tlbn0nIChpZiBhdXRoZW50aWNhdGVkKQogIC8vLyAtICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicKICAvLy8KICAvLy8gSWYgbm8gYXV0aGVudGljYXRpb24gdG9rZW4gaXMgYXZhaWxhYmxlLCBhbiBlbXB0eSBtYXAge30gaXMgcHJvdmlkZWQuCiAgZmluYWwgV2lkZ2V0IEZ1bmN0aW9uKEJ1aWxkQ29udGV4dCBjb250ZXh0LCBNYXA8U3RyaW5nLCBTdHJpbmc+IGhlYWRlcnMpCiAgYnVpbGRlcjsKCiAgLy8vIFdoZXRoZXIgdG8gdmFsaWRhdGUgYW5kIHBvdGVudGlhbGx5IHJlZnJlc2ggdGhlIHRva2VuLgogIC8vLwogIC8vLyBJZiB0cnVlLCB1c2VzIFtBdXRoVG9rZW5Qcm92aWRlci5nZXRWYWxpZFRva2VuXSB3aGljaCB2YWxpZGF0ZXMKICAvLy8gdGhlIHRva2VuIGFuZCByZWZyZXNoZXMgaXQgaWYgbmVlZGVkLgogIC8vLyBJZiBmYWxzZSAoZGVmYXVsdCksIHVzZXMgW0F1dGhUb2tlblByb3ZpZGVyLmdldEN1cnJlbnRUb2tlbl0gd2hpY2ggcmV0dXJucyB0aGUKICAvLy8gY2FjaGVkIHRva2VuIHdpdGhvdXQgdmFsaWRhdGlvbi4KICBmaW5hbCBib29sIHZhbGlkYXRlVG9rZW47CgogIE1hcDxTdHJpbmcsIFN0cmluZz4gX2dldFN5bmNBdXRoSGVhZGVycygpIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIGF1dGhUb2tlblByb3ZpZGVyID0gZGkuZ2V0PEF1dGhUb2tlblByb3ZpZGVyPigpOwogICAgICBmaW5hbCB0b2tlbiA9IGF1dGhUb2tlblByb3ZpZGVyLmdldEN1cnJlbnRUb2tlbigpOwoKICAgICAgaWYgKHRva2VuICE9IG51bGwpIHsKICAgICAgICByZXR1cm4geydBdXRob3JpemF0aW9uJzogJ0JlYXJlciAkdG9rZW4nfTsKICAgICAgfQogICAgfSBjYXRjaCAoZSkgewogICAgICBfbG9nZ2VyLmUoJ0Vycm9yIGdldHRpbmcgYXV0aCBoZWFkZXJzOiAkZScpOwogICAgfQogICAgcmV0dXJuIHt9OwogIH0KCiAgRnV0dXJlPE1hcDxTdHJpbmcsIFN0cmluZz4+IF9nZXRBc3luY0F1dGhIZWFkZXJzKCkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgZmluYWwgYXV0aFRva2VuUHJvdmlkZXIgPSBkaS5nZXQ8QXV0aFRva2VuUHJvdmlkZXI+KCk7CiAgICAgIGZpbmFsIHRva2VuID0gYXdhaXQgYXV0aFRva2VuUHJvdmlkZXIuZ2V0VmFsaWRUb2tlbigpOwoKICAgICAgaWYgKHRva2VuICE9IG51bGwpIHsKICAgICAgICByZXR1cm4geydBdXRob3JpemF0aW9uJzogJ0JlYXJlciAkdG9rZW4nfTsKICAgICAgfQogICAgfSBjYXRjaCAoZSkgewogICAgICBfbG9nZ2VyLmUoJ0Vycm9yIGdldHRpbmcgYXV0aCBoZWFkZXJzOiAkZScpOwogICAgfQogICAgcmV0dXJuIHt9OwogIH0KCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKEJ1aWxkQ29udGV4dCBjb250ZXh0KSB7CiAgICAvLyBJZiB2YWxpZGF0aW9uIGlzIG5vdCBuZWVkZWQsIHJldHVybiBzeW5jaHJvbm91c2x5IHdpdGhvdXQgRnV0dXJlQnVpbGRlcgogICAgaWYgKCF2YWxpZGF0ZVRva2VuKSB7CiAgICAgIGZpbmFsIGhlYWRlcnMgPSBfZ2V0U3luY0F1dGhIZWFkZXJzKCk7CiAgICAgIHJldHVybiBidWlsZGVyKGNvbnRleHQsIGhlYWRlcnMpOwogICAgfQoKICAgIC8vIElmIHZhbGlkYXRpb24gaXMgbmVlZGVkLCB1c2UgRnV0dXJlQnVpbGRlcgogICAgcmV0dXJuIEZ1dHVyZUJ1aWxkZXI8TWFwPFN0cmluZywgU3RyaW5nPj4oCiAgICAgIGZ1dHVyZTogX2dldEFzeW5jQXV0aEhlYWRlcnMoKSwKICAgICAgYnVpbGRlcjogKGNvbnRleHQsIHNuYXBzaG90KSB7CiAgICAgICAgZmluYWwgaGVhZGVycyA9IHNuYXBzaG90LmRhdGEgPz8ge307CiAgICAgICAgcmV0dXJuIGJ1aWxkZXIoY29udGV4dCwgaGVhZGVycyk7CiAgICAgIH0sCiAgICApOwogIH0KfXt7L25ldHdvcmt9fQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/components/date_filter_chips.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2NhbGl6YXRpb24vbG9jYWxpemF0aW9uLmRhcnQnOwoKZW51bSBEYXRlRmlsdGVyVHlwZSB7CiAgd2VlaywKICBtb250aDsKCiAgU3RyaW5nIGdldCBkaXNwbGF5TmFtZSB7CiAgICBzd2l0Y2ggKHRoaXMpIHsKICAgICAgY2FzZSBEYXRlRmlsdGVyVHlwZS53ZWVrOgogICAgICAgIHJldHVybiBzdHJpbmdzLmNvbW1vbi53ZWVrOwogICAgICBjYXNlIERhdGVGaWx0ZXJUeXBlLm1vbnRoOgogICAgICAgIHJldHVybiBzdHJpbmdzLmNvbW1vbi5tb250aDsKICAgIH0KICB9Cn0KCmNsYXNzIERhdGVGaWx0ZXJDaGlwcyBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgZmluYWwgRGF0ZUZpbHRlclR5cGUgc2VsZWN0ZWRGaWx0ZXI7CiAgZmluYWwgVmFsdWVDaGFuZ2VkPERhdGVGaWx0ZXJUeXBlPiBvbkZpbHRlckNoYW5nZWQ7CgogIGNvbnN0IERhdGVGaWx0ZXJDaGlwcyh7CiAgICBzdXBlci5rZXksCiAgICByZXF1aXJlZCB0aGlzLnNlbGVjdGVkRmlsdGVyLAogICAgcmVxdWlyZWQgdGhpcy5vbkZpbHRlckNoYW5nZWQsCiAgfSk7CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgZmluYWwgY29sb3JTY2hlbWUgPSBUaGVtZS5vZihjb250ZXh0KS5jb2xvclNjaGVtZTsKCiAgICByZXR1cm4gUm93KAogICAgICBtYWluQXhpc0FsaWdubWVudDogTWFpbkF4aXNBbGlnbm1lbnQuY2VudGVyLAogICAgICBjaGlsZHJlbjogRGF0ZUZpbHRlclR5cGUudmFsdWVzLm1hcCgoZmlsdGVyKSB7CiAgICAgICAgZmluYWwgaXNTZWxlY3RlZCA9IGZpbHRlciA9PSBzZWxlY3RlZEZpbHRlcjsKCiAgICAgICAgcmV0dXJuIFBhZGRpbmcoCiAgICAgICAgICBwYWRkaW5nOiBjb25zdCBFZGdlSW5zZXRzLnN5bW1ldHJpYyhob3Jpem9udGFsOiA0KSwKICAgICAgICAgIGNoaWxkOiBGaWx0ZXJDaGlwKAogICAgICAgICAgICBsYWJlbDogVGV4dChmaWx0ZXIuZGlzcGxheU5hbWUpLAogICAgICAgICAgICBzZWxlY3RlZDogaXNTZWxlY3RlZCwKICAgICAgICAgICAgb25TZWxlY3RlZDogKHNlbGVjdGVkKSB7CiAgICAgICAgICAgICAgaWYgKHNlbGVjdGVkKSB7CiAgICAgICAgICAgICAgICBvbkZpbHRlckNoYW5nZWQoZmlsdGVyKTsKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIGJhY2tncm91bmRDb2xvcjogY29sb3JTY2hlbWUuc3VyZmFjZSwKICAgICAgICAgICAgc2VsZWN0ZWRDb2xvcjogY29sb3JTY2hlbWUucHJpbWFyeSwKICAgICAgICAgICAgY2hlY2ttYXJrQ29sb3I6IGNvbG9yU2NoZW1lLm9uUHJpbWFyeSwKICAgICAgICAgICAgbGFiZWxTdHlsZTogVGV4dFN0eWxlKAogICAgICAgICAgICAgIGNvbG9yOiBpc1NlbGVjdGVkID8gY29sb3JTY2hlbWUub25QcmltYXJ5IDogY29sb3JTY2hlbWUub25TdXJmYWNlLAogICAgICAgICAgICAgIGZvbnRXZWlnaHQ6IGlzU2VsZWN0ZWQgPyBGb250V2VpZ2h0Lnc2MDAgOiBGb250V2VpZ2h0Lm5vcm1hbCwKICAgICAgICAgICAgKSwKICAgICAgICAgICAgc2hhcGU6IFJvdW5kZWRSZWN0YW5nbGVCb3JkZXIoCiAgICAgICAgICAgICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoOCksCiAgICAgICAgICAgICAgc2lkZTogQm9yZGVyU2lkZSgKICAgICAgICAgICAgICAgIGNvbG9yOiBpc1NlbGVjdGVkCiAgICAgICAgICAgICAgICAgICAgPyBjb2xvclNjaGVtZS5wcmltYXJ5CiAgICAgICAgICAgICAgICAgICAgOiBjb2xvclNjaGVtZS5vdXRsaW5lLndpdGhWYWx1ZXMoYWxwaGE6IDAuNSksCiAgICAgICAgICAgICAgICB3aWR0aDogaXNTZWxlY3RlZCA/IDEuNSA6IDEsCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgKSwKICAgICAgICAgICksCiAgICAgICAgKTsKICAgICAgfSkudG9MaXN0KCksCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/components/default_error_view.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvZm91bmRhdGlvbi5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2NhbGl6YXRpb24vbG9jYWxpemF0aW9uLmRhcnQnOwoKY2xhc3MgRGVmYXVsdEVycm9yVmlldyBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgY29uc3QgRGVmYXVsdEVycm9yVmlldyh7CiAgICBzdXBlci5rZXksCiAgICB0aGlzLmVycm9yLAogICAgdGhpcy5zdGFja1RyYWNlLAogICAgdGhpcy5vblJldHJ5LAogICAgdGhpcy5kZXNjcmlwdGlvbkJ1aWxkZXIsCiAgICB0aGlzLnBhZGRpbmcsCiAgfSk7CgogIGZpbmFsIE9iamVjdD8gZXJyb3I7CiAgZmluYWwgU3RhY2tUcmFjZT8gc3RhY2tUcmFjZTsKICBmaW5hbCBWb2lkQ2FsbGJhY2s/IG9uUmV0cnk7CiAgZmluYWwgU3RyaW5nPyBGdW5jdGlvbihPYmplY3Q/KT8gZGVzY3JpcHRpb25CdWlsZGVyOwogIGZpbmFsIEVkZ2VJbnNldHNHZW9tZXRyeT8gcGFkZGluZzsKCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKEJ1aWxkQ29udGV4dCBjb250ZXh0KSB7CiAgICBmaW5hbCB0aGVtZSA9IFRoZW1lLm9mKGNvbnRleHQpOwogICAgZmluYWwgY29sb3JTY2hlbWUgPSB0aGVtZS5jb2xvclNjaGVtZTsKICAgIGZpbmFsIHRleHRUaGVtZSA9IHRoZW1lLnRleHRUaGVtZTsKCiAgICBmaW5hbCB0aXRsZSA9IHN0cmluZ3MuZXJyb3JzLmRlZmF1bHRfZXJyb3JfbWVzc2FnZTsKICAgIGZpbmFsIGRlc2NyaXB0aW9uID0gZGVzY3JpcHRpb25CdWlsZGVyPy5jYWxsKGVycm9yKTsKCiAgICByZXR1cm4gU2FmZUFyZWEoCiAgICAgIGNoaWxkOiBQYWRkaW5nKAogICAgICAgIHBhZGRpbmc6IHBhZGRpbmcgPz8gY29uc3QgRWRnZUluc2V0cy5hbGwoNDAuMCksCiAgICAgICAgY2hpbGQ6IENlbnRlcigKICAgICAgICAgIGNoaWxkOiBDb2x1bW4oCiAgICAgICAgICAgIG1haW5BeGlzQWxpZ25tZW50OiBNYWluQXhpc0FsaWdubWVudC5jZW50ZXIsCiAgICAgICAgICAgIGNoaWxkcmVuOiBbCiAgICAgICAgICAgICAgQ29udGFpbmVyKAogICAgICAgICAgICAgICAgd2lkdGg6IDgwLAogICAgICAgICAgICAgICAgaGVpZ2h0OiA4MCwKICAgICAgICAgICAgICAgIGRlY29yYXRpb246IEJveERlY29yYXRpb24oCiAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5lcnJvckNvbnRhaW5lci53aXRoVmFsdWVzKGFscGhhOiAwLjE1KSwKICAgICAgICAgICAgICAgICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoMTYpLAogICAgICAgICAgICAgICAgICBib3JkZXI6IEJvcmRlci5hbGwoCiAgICAgICAgICAgICAgICAgICAgY29sb3I6IGNvbG9yU2NoZW1lLmVycm9yLndpdGhWYWx1ZXMoYWxwaGE6IDAuMiksCiAgICAgICAgICAgICAgICAgICAgd2lkdGg6IDEsCiAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgY2hpbGQ6IEljb24oCiAgICAgICAgICAgICAgICAgIEljb25zLmVycm9yX291dGxpbmVfcm91bmRlZCwKICAgICAgICAgICAgICAgICAgc2l6ZTogNDAsCiAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5lcnJvciwKICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICBjb25zdCBTaXplZEJveChoZWlnaHQ6IDI0KSwKICAgICAgICAgICAgICBUZXh0KAogICAgICAgICAgICAgICAgdGl0bGUsCiAgICAgICAgICAgICAgICBzdHlsZTogdGV4dFRoZW1lLnRpdGxlTWVkaXVtPy5jb3B5V2l0aCgKICAgICAgICAgICAgICAgICAgY29sb3I6IGNvbG9yU2NoZW1lLm9uU3VyZmFjZSwKICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICB0ZXh0QWxpZ246IFRleHRBbGlnbi5jZW50ZXIsCiAgICAgICAgICAgICAgICBtYXhMaW5lczogNSwKICAgICAgICAgICAgICAgIG92ZXJmbG93OiBUZXh0T3ZlcmZsb3cuZWxsaXBzaXMsCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICBpZiAoZGVzY3JpcHRpb24gIT0gbnVsbCkgLi4uWwogICAgICAgICAgICAgICAgY29uc3QgU2l6ZWRCb3goaGVpZ2h0OiA4KSwKICAgICAgICAgICAgICAgIFRleHQoCiAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uLAogICAgICAgICAgICAgICAgICBzdHlsZTogdGV4dFRoZW1lLmxhYmVsTWVkaXVtPy5jb3B5V2l0aCgKICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUub25TdXJmYWNlVmFyaWFudCwKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgdGV4dEFsaWduOiBUZXh0QWxpZ24uY2VudGVyLAogICAgICAgICAgICAgICAgICBtYXhMaW5lczogMjAsCiAgICAgICAgICAgICAgICAgIG92ZXJmbG93OiBUZXh0T3ZlcmZsb3cuZWxsaXBzaXMsCiAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgaWYgKGVycm9yICE9IG51bGwgJiYga0RlYnVnTW9kZSkgLi4uWwogICAgICAgICAgICAgICAgY29uc3QgU2l6ZWRCb3goaGVpZ2h0OiAxNiksCiAgICAgICAgICAgICAgICBDb250YWluZXIoCiAgICAgICAgICAgICAgICAgIHdpZHRoOiBkb3VibGUuaW5maW5pdHksCiAgICAgICAgICAgICAgICAgIHBhZGRpbmc6IGNvbnN0IEVkZ2VJbnNldHMuYWxsKDEyKSwKICAgICAgICAgICAgICAgICAgZGVjb3JhdGlvbjogQm94RGVjb3JhdGlvbigKICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUuc3VyZmFjZUNvbnRhaW5lckhpZ2gsCiAgICAgICAgICAgICAgICAgICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoMTIpLAogICAgICAgICAgICAgICAgICAgIGJvcmRlcjogQm9yZGVyLmFsbCgKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5vdXRsaW5lLndpdGhWYWx1ZXMoYWxwaGE6IDAuMyksCiAgICAgICAgICAgICAgICAgICAgICB3aWR0aDogMSwKICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICBjaGlsZDogU2VsZWN0YWJsZVRleHQoCiAgICAgICAgICAgICAgICAgICAgJ3R5cGU6JHtlcnJvci5ydW50aW1lVHlwZX06XG4kZXJyb3JcbiR7c3RhY2tUcmFjZT8udG9TdHJpbmcoKSA/PyAnJ30nLAogICAgICAgICAgICAgICAgICAgIHN0eWxlOiB0ZXh0VGhlbWUuYm9keVNtYWxsPy5jb3B5V2l0aCgKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5vblN1cmZhY2UsCiAgICAgICAgICAgICAgICAgICAgICBmb250RmFtaWx5OiAnbW9ub3NwYWNlJywKICAgICAgICAgICAgICAgICAgICAgIGZvbnRTaXplOiAxMiwKICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgIHRleHRBbGlnbjogVGV4dEFsaWduLmxlZnQsCiAgICAgICAgICAgICAgICAgICAgbWF4TGluZXM6IDEwLAogICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KGhlaWdodDogMzIpLAogICAgICAgICAgICAgIFdyYXAoCiAgICAgICAgICAgICAgICBzcGFjaW5nOiAxMiwKICAgICAgICAgICAgICAgIHJ1blNwYWNpbmc6IDgsCiAgICAgICAgICAgICAgICBhbGlnbm1lbnQ6IFdyYXBBbGlnbm1lbnQuY2VudGVyLAogICAgICAgICAgICAgICAgY2hpbGRyZW46IFsKICAgICAgICAgICAgICAgICAgaWYgKG9uUmV0cnkgIT0gbnVsbCkKICAgICAgICAgICAgICAgICAgICBPdXRsaW5lZEJ1dHRvbigKICAgICAgICAgICAgICAgICAgICAgIG9uUHJlc3NlZDogb25SZXRyeSwKICAgICAgICAgICAgICAgICAgICAgIHN0eWxlOiBGaWxsZWRCdXR0b24uc3R5bGVGcm9tKAogICAgICAgICAgICAgICAgICAgICAgICBwYWRkaW5nOiBjb25zdCBFZGdlSW5zZXRzLnN5bW1ldHJpYygKICAgICAgICAgICAgICAgICAgICAgICAgICBob3Jpem9udGFsOiAyNCwKICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbDogMTIsCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbmltdW1TaXplOiBjb25zdCBTaXplKDg4LCA0NCksCiAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgY2hpbGQ6IFRleHQoc3RyaW5ncy5nZW5lcmljLnRyeV9hZ2FpbiksCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgXSwKICAgICAgICAgICksCiAgICAgICAgKSwKICAgICAgKSwKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/components/header.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vY29tcG9uZW50cy9uZXR3b3JrX3VybF9pbWFnZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vdXRpbHMvaW5kZXguZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwoKZW51bSBIZWFkZXJUeXBlIHsKICBpbWFnZSwKICBhc3NldDsKCiAgZG91YmxlIGdldCByYXRpbyA9PiBzd2l0Y2ggKHRoaXMpIHsKICAgIEhlYWRlclR5cGUuaW1hZ2UgPT4gNCAvIDEsCiAgICBIZWFkZXJUeXBlLmFzc2V0ID0+IDEgLyAxLAogIH07Cn0KCmNsYXNzIEhlYWRlciBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgY29uc3QgSGVhZGVyKHsKICAgIHN1cGVyLmtleSwKICAgIHRoaXMudHlwZSA9IEhlYWRlclR5cGUuaW1hZ2UsCiAgICB0aGlzLmFzc2V0UGF0aCwKICAgIHRoaXMuaW1hZ2VVcmwsCiAgfSk7CgogIGZpbmFsIEhlYWRlclR5cGUgdHlwZTsKICBmaW5hbCBTdHJpbmc/IGFzc2V0UGF0aDsKICBmaW5hbCBTdHJpbmc/IGltYWdlVXJsOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBBc3BlY3RSYXRpbyhhc3BlY3RSYXRpbzogdHlwZS5yYXRpbywgY2hpbGQ6IF9idWlsZEltYWdlKGNvbnRleHQpKTsKICB9CgogIFdpZGdldCBfYnVpbGRJbWFnZShCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgc3dpdGNoICh0eXBlKSB7CiAgICAgIGNhc2UgSGVhZGVyVHlwZS5hc3NldDoKICAgICAgICByZXR1cm4gSGVybygKICAgICAgICAgIHRhZzogYXNzZXRQYXRoISwKICAgICAgICAgIGNoaWxkOiBJbWFnZS5hc3NldCgKICAgICAgICAgICAgYXNzZXRQYXRoISwKICAgICAgICAgICAgZml0OiBCb3hGaXQuY29udGFpbiwKICAgICAgICAgICAgYWxpZ25tZW50OiBBbGlnbm1lbnQuY2VudGVyLAogICAgICAgICAgKSwKICAgICAgICApOwogICAgICBjYXNlIEhlYWRlclR5cGUuaW1hZ2U6CiAgICAgICAgcmV0dXJuIE5ldHdvcmtVcmxJbWFnZSh1cmw6IGltYWdlVXJsLmFzRW1wdHlJZk51bGwpOwogICAgfQogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/components/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdhc3NldF9pbWFnZS5kYXJ0JzsKZXhwb3J0ICdhdXRoX2hlYWRlcnNfYnVpbGRlci5kYXJ0JzsKZXhwb3J0ICdkYXRlX2ZpbHRlcl9jaGlwcy5kYXJ0JzsKZXhwb3J0ICdkZWZhdWx0X2Vycm9yX3ZpZXcuZGFydCc7CmV4cG9ydCAnaGVhZGVyLmRhcnQnOwpleHBvcnQgJ2xvYWRlcnMuZGFydCc7CmV4cG9ydCAnbmV0d29ya191cmxfaW1hZ2UuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/components/loaders.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CgpjbGFzcyBEZWZhdWx0TG9hZGVyIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBjb25zdCBEZWZhdWx0TG9hZGVyKHtzdXBlci5rZXksIHRoaXMuc2l6ZSA9IDIwfSk7CgogIGZpbmFsIGRvdWJsZSBzaXplOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBDZW50ZXIoCiAgICAgIGNoaWxkOiBTaXplZEJveCgKICAgICAgICB3aWR0aDogc2l6ZSwKICAgICAgICBoZWlnaHQ6IHNpemUsCiAgICAgICAgY2hpbGQ6IENpcmN1bGFyUHJvZ3Jlc3NJbmRpY2F0b3IuYWRhcHRpdmUoc3Ryb2tlV2lkdGg6IDQpLAogICAgICApLAogICAgKTsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart", + "data": + "aW1wb3J0ICdkYXJ0Om1hdGgnOwoKaW1wb3J0ICdwYWNrYWdlOmNhY2hlZF9uZXR3b3JrX2ltYWdlL2NhY2hlZF9uZXR3b3JrX2ltYWdlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKCmNsYXNzIE5ldHdvcmtVcmxJbWFnZSBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgZmluYWwgU3RyaW5nIHVybDsKICBmaW5hbCBkb3VibGU/IHdpZHRoOwogIGZpbmFsIGRvdWJsZT8gaGVpZ2h0OwogIGZpbmFsIEJveEZpdD8gYm94Rml0OwoKICBjb25zdCBOZXR3b3JrVXJsSW1hZ2UoewogICAgc3VwZXIua2V5LAogICAgcmVxdWlyZWQgdGhpcy51cmwsCiAgICB0aGlzLndpZHRoLAogICAgdGhpcy5oZWlnaHQsCiAgICB0aGlzLmJveEZpdCA9IEJveEZpdC5jb3ZlciwKICB9KTsKCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKGZpbmFsIEJ1aWxkQ29udGV4dCBjb250ZXh0KSA9PiBDYWNoZWROZXR3b3JrSW1hZ2UoCiAgICBpbWFnZVVybDogdXJsLAogICAgcGxhY2Vob2xkZXI6IChmaW5hbCBjb250ZXh0LCBmaW5hbCBfXykgewogICAgICBmaW5hbCB0aGVtZSA9IFRoZW1lLm9mKGNvbnRleHQpOwoKICAgICAgcmV0dXJuIENvbnRhaW5lcigKICAgICAgICBjb2xvcjogdGhlbWUuY29sb3JTY2hlbWUuc3VyZmFjZSwKICAgICAgICB3aWR0aDogd2lkdGgsCiAgICAgICAgaGVpZ2h0OiBoZWlnaHQsCiAgICAgICAgY2hpbGQ6IEljb24oCiAgICAgICAgICBJY29ucy5pbWFnZSwKICAgICAgICAgIHNpemU6IG1pbih3aWR0aCA/PyAzMiwgaGVpZ2h0ID8/IDMyKSwKICAgICAgICAgIGNvbG9yOiB0aGVtZS5jYXJkQ29sb3IsCiAgICAgICAgKSwKICAgICAgKTsKICAgIH0sCiAgICB3aWR0aDogd2lkdGgsCiAgICBoZWlnaHQ6IGhlaWdodCwKICAgIGZpdDogYm94Rml0LAogICAgZmFkZU91dER1cmF0aW9uOiBjb25zdCBEdXJhdGlvbigpLAogICAgZmFkZUluRHVyYXRpb246IGNvbnN0IER1cmF0aW9uKCksCiAgICBlcnJvcldpZGdldDogKGZpbmFsIGNvbnRleHQsIGZpbmFsIHVybCwgZmluYWwgZXJyb3IpID0+CiAgICAgICAgSWNvbihJY29ucy5pbWFnZSwgc2l6ZTogMzIpLAogICk7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/constants/navigation_icons.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CgovLy8gSWNvbnMgdXNlZCBieSB0aGUgYXBwJ3MgcHJpbWFyeSBuYXZpZ2F0aW9uLgovLy8KLy8vIFN3YXAgdGhlc2UgZm9yIHlvdXIgcHJvZHVjdCdzIGljb24gc2V0IC0gZXZlcnkgbmF2aWdhdGlvbiBzdXJmYWNlIHJlYWRzCi8vLyBmcm9tIGhlcmUgc28gYSBzaW5nbGUgZWRpdCByZS1za2lucyB0aGUgd2hvbGUgc2hlbGwuCmNsYXNzIE5hdmlnYXRpb25JY29ucyB7CiAgY29uc3QgTmF2aWdhdGlvbkljb25zLl8oKTsKCiAgc3RhdGljIGNvbnN0IEljb25EYXRhIGhvbWUgPSBJY29ucy5ob21lX291dGxpbmVkOwogIHN0YXRpYyBjb25zdCBJY29uRGF0YSBob21lU2VsZWN0ZWQgPSBJY29ucy5ob21lOwoKICBzdGF0aWMgY29uc3QgSWNvbkRhdGEgZXhwbG9yZSA9IEljb25zLmV4cGxvcmVfb3V0bGluZWQ7CiAgc3RhdGljIGNvbnN0IEljb25EYXRhIGV4cGxvcmVTZWxlY3RlZCA9IEljb25zLmV4cGxvcmU7CgogIHN0YXRpYyBjb25zdCBJY29uRGF0YSBkYXNoYm9hcmQgPSBJY29ucy5kYXNoYm9hcmRfb3V0bGluZWQ7CiAgc3RhdGljIGNvbnN0IEljb25EYXRhIGRhc2hib2FyZFNlbGVjdGVkID0gSWNvbnMuZGFzaGJvYXJkOwoKICBzdGF0aWMgY29uc3QgSWNvbkRhdGEgaW5zaWdodHMgPSBJY29ucy50cmVuZGluZ191cF9vdXRsaW5lZDsKICBzdGF0aWMgY29uc3QgSWNvbkRhdGEgaW5zaWdodHNTZWxlY3RlZCA9IEljb25zLnRyZW5kaW5nX3VwOwoKICBzdGF0aWMgY29uc3QgSWNvbkRhdGEgcHJvZmlsZSA9IEljb25zLnBlcnNvbl9vdXRsaW5lOwogIHN0YXRpYyBjb25zdCBJY29uRGF0YSBwcm9maWxlU2VsZWN0ZWQgPSBJY29ucy5wZXJzb247CgogIHN0YXRpYyBjb25zdCBJY29uRGF0YSBzZXR0aW5ncyA9IEljb25zLnNldHRpbmdzX291dGxpbmVkOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/design_system.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vZ2VuZXJhdGVkL3RoZW1lLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGVzaWduX3N5c3RlbS9nZW5lcmF0ZWQvdXRpbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CgpleHBvcnQgJ2NvbXBvbmVudHMvaW5kZXguZGFydCc7CmV4cG9ydCAnY29uc3RhbnRzL25hdmlnYXRpb25faWNvbnMuZGFydCc7CmV4cG9ydCAnZGlhbG9ncy9pbmRleC5kYXJ0JzsKZXhwb3J0ICdnZW5lcmF0ZWQvdGhlbWUuZGFydCc7CmV4cG9ydCAnZ2VuZXJhdGVkL3V0aWwuZGFydCc7CmV4cG9ydCAnbG9hZGVyL2xvYWRlci5kYXJ0JzsKZXhwb3J0ICdzY3JlZW5zL2luZGV4LmRhcnQnOwpleHBvcnQgJ3dyYXBwZXIvd3JhcHBlcnMuZGFydCc7CgpjbGFzcyBEZXNpZ25TeXN0ZW0gewogIGZpbmFsIE1hdGVyaWFsVGhlbWUgdGhlbWU7CgogIERlc2lnblN5c3RlbSgKICAgIEJ1aWxkQ29udGV4dCBjb250ZXh0LCB7CiAgICBTdHJpbmcgYm9keUZvbnQgPSAiT3BlbiBTYW5zIiwKICAgIFN0cmluZyBkaXNwbGF5Rm9udCA9ICJPcGVuIFNhbnMiLAogIH0pIDogdGhlbWUgPSBidWlsZFRoZW1lKAogICAgICAgICBjb250ZXh0LAogICAgICAgICBib2R5Rm9udDogYm9keUZvbnQsCiAgICAgICAgIGRpc3BsYXlGb250OiBkaXNwbGF5Rm9udCwKICAgICAgICk7CgogIHN0YXRpYyBNYXRlcmlhbFRoZW1lIGJ1aWxkVGhlbWUoCiAgICBCdWlsZENvbnRleHQgY29udGV4dCwgewogICAgU3RyaW5nIGJvZHlGb250ID0gIk9wZW4gU2FucyIsCiAgICBTdHJpbmcgZGlzcGxheUZvbnQgPSAiT3BlbiBTYW5zIiwKICB9KSB7CiAgICBmaW5hbCB0ZXh0VGhlbWUgPSBjcmVhdGVUZXh0VGhlbWUoY29udGV4dCwgYm9keUZvbnQsIGRpc3BsYXlGb250KTsKICAgIHJldHVybiBNYXRlcmlhbFRoZW1lKHRleHRUaGVtZSk7CiAgfQoKICBUaGVtZURhdGEgX2Jhc2VUaGVtZSh7cmVxdWlyZWQgYm9vbCBpc0Rhcmt9KSB7CiAgICBmaW5hbCBiYXNlID0gaXNEYXJrID8gdGhlbWUuZGFyaygpIDogdGhlbWUubGlnaHQoKTsKICAgIHJldHVybiBiYXNlLmNvcHlXaXRoKAogICAgICBpbnB1dERlY29yYXRpb25UaGVtZTogYmFzZS5pbnB1dERlY29yYXRpb25UaGVtZS5jb3B5V2l0aCgKICAgICAgICBib3JkZXI6IFVuZGVybGluZUlucHV0Qm9yZGVyKAogICAgICAgICAgYm9yZGVyU2lkZTogQm9yZGVyU2lkZShjb2xvcjogQ29sb3JzLmdyZXkuc2hhZGUzMDApLAogICAgICAgICksCiAgICAgICAgaGludFN0eWxlOiBiYXNlLnRleHRUaGVtZS5sYWJlbFNtYWxsPy5jb3B5V2l0aCgKICAgICAgICAgIGNvbG9yOiBiYXNlLmNvbG9yU2NoZW1lLm9uU3VyZmFjZS53aXRoVmFsdWVzKGFscGhhOiAwLjYpLAogICAgICAgICksCiAgICAgICAgbGFiZWxTdHlsZTogYmFzZS50ZXh0VGhlbWUubGFiZWxNZWRpdW0/LmNvcHlXaXRoKAogICAgICAgICAgY29sb3I6IGJhc2UuY29sb3JTY2hlbWUub25TdXJmYWNlLndpdGhWYWx1ZXMoYWxwaGE6IDAuNiksCiAgICAgICAgKSwKICAgICAgKSwKICAgICAgYXBwQmFyVGhlbWU6IEFwcEJhclRoZW1lKAogICAgICAgIGJhY2tncm91bmRDb2xvcjogYmFzZS5jb2xvclNjaGVtZS5zdXJmYWNlLAogICAgICAgIHN1cmZhY2VUaW50Q29sb3I6IGJhc2UuY29sb3JTY2hlbWUuc3VyZmFjZVRpbnQsCiAgICAgICAgZm9yZWdyb3VuZENvbG9yOiBiYXNlLmNvbG9yU2NoZW1lLm9uU3VyZmFjZSwKICAgICAgICBlbGV2YXRpb246IDAsCiAgICAgICAgY2VudGVyVGl0bGU6IHRydWUsCiAgICAgICAgdGl0bGVUZXh0U3R5bGU6IGJhc2UudGV4dFRoZW1lLnRpdGxlTGFyZ2U/LmNvcHlXaXRoKAogICAgICAgICAgY29sb3I6IGJhc2UuY29sb3JTY2hlbWUub25TdXJmYWNlLAogICAgICAgICAgZm9udFdlaWdodDogRm9udFdlaWdodC53NjAwLAogICAgICAgICksCiAgICAgICAgaWNvblRoZW1lOiBJY29uVGhlbWVEYXRhKGNvbG9yOiBiYXNlLmNvbG9yU2NoZW1lLm9uU3VyZmFjZSksCiAgICAgICAgYWN0aW9uc0ljb25UaGVtZTogSWNvblRoZW1lRGF0YShjb2xvcjogYmFzZS5jb2xvclNjaGVtZS5vblN1cmZhY2UpLAogICAgICApLAogICAgICBuYXZpZ2F0aW9uQmFyVGhlbWU6IE5hdmlnYXRpb25CYXJUaGVtZURhdGEoCiAgICAgICAgYmFja2dyb3VuZENvbG9yOiBiYXNlLmNvbG9yU2NoZW1lLnN1cmZhY2UsCiAgICAgICAgc3VyZmFjZVRpbnRDb2xvcjogQ29sb3JzLnRyYW5zcGFyZW50LAogICAgICAgIGVsZXZhdGlvbjogMCwKICAgICAgICBoZWlnaHQ6IDcwLAogICAgICAgIGxhYmVsQmVoYXZpb3I6IE5hdmlnYXRpb25EZXN0aW5hdGlvbkxhYmVsQmVoYXZpb3IuYWx3YXlzU2hvdywKICAgICAgICBsYWJlbFRleHRTdHlsZTogV2lkZ2V0U3RhdGVQcm9wZXJ0eS5yZXNvbHZlV2l0aDxUZXh0U3R5bGU/Pigoc3RhdGVzKSB7CiAgICAgICAgICBpZiAoc3RhdGVzLmNvbnRhaW5zKFdpZGdldFN0YXRlLnNlbGVjdGVkKSkgewogICAgICAgICAgICByZXR1cm4gYmFzZS50ZXh0VGhlbWUubGFiZWxTbWFsbD8uY29weVdpdGgoCiAgICAgICAgICAgICAgY29sb3I6IGJhc2UuY29sb3JTY2hlbWUub25TdXJmYWNlLAogICAgICAgICAgICAgIGZvbnRXZWlnaHQ6IEZvbnRXZWlnaHQudzYwMCwKICAgICAgICAgICAgICBmb250U2l6ZTogMTIsCiAgICAgICAgICAgICk7CiAgICAgICAgICB9CiAgICAgICAgICByZXR1cm4gYmFzZS50ZXh0VGhlbWUubGFiZWxTbWFsbD8uY29weVdpdGgoCiAgICAgICAgICAgIGNvbG9yOiBiYXNlLmNvbG9yU2NoZW1lLm9uU3VyZmFjZVZhcmlhbnQsCiAgICAgICAgICAgIGZvbnRXZWlnaHQ6IEZvbnRXZWlnaHQudzUwMCwKICAgICAgICAgICAgZm9udFNpemU6IDEyLAogICAgICAgICAgKTsKICAgICAgICB9KSwKICAgICAgICBpY29uVGhlbWU6IFdpZGdldFN0YXRlUHJvcGVydHkucmVzb2x2ZVdpdGg8SWNvblRoZW1lRGF0YT8+KChzdGF0ZXMpIHsKICAgICAgICAgIGlmIChzdGF0ZXMuY29udGFpbnMoV2lkZ2V0U3RhdGUuc2VsZWN0ZWQpKSB7CiAgICAgICAgICAgIHJldHVybiBJY29uVGhlbWVEYXRhKGNvbG9yOiBiYXNlLmNvbG9yU2NoZW1lLm9uUHJpbWFyeSwgc2l6ZTogMjApOwogICAgICAgICAgfQogICAgICAgICAgcmV0dXJuIEljb25UaGVtZURhdGEoCiAgICAgICAgICAgIGNvbG9yOiBiYXNlLmNvbG9yU2NoZW1lLm9uU3VyZmFjZVZhcmlhbnQsCiAgICAgICAgICAgIHNpemU6IDIwLAogICAgICAgICAgKTsKICAgICAgICB9KSwKICAgICAgICBpbmRpY2F0b3JDb2xvcjogYmFzZS5jb2xvclNjaGVtZS5wcmltYXJ5LAogICAgICAgIGluZGljYXRvclNoYXBlOiBjb25zdCBSb3VuZGVkUmVjdGFuZ2xlQm9yZGVyKAogICAgICAgICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuYWxsKFJhZGl1cy5jaXJjdWxhcig0KSksCiAgICAgICAgKSwKICAgICAgKSwKICAgICAgZmlsbGVkQnV0dG9uVGhlbWU6IEZpbGxlZEJ1dHRvblRoZW1lRGF0YSgKICAgICAgICBzdHlsZTogRmlsbGVkQnV0dG9uLnN0eWxlRnJvbSgKICAgICAgICAgIHNoYXBlOiBSb3VuZGVkUmVjdGFuZ2xlQm9yZGVyKGJvcmRlclJhZGl1czogQm9yZGVyUmFkaXVzLmNpcmN1bGFyKDQpKSwKICAgICAgICAgIHRleHRTdHlsZTogYmFzZS50ZXh0VGhlbWUubGFiZWxMYXJnZT8uY29weVdpdGgoCiAgICAgICAgICAgIGZvbnRXZWlnaHQ6IEZvbnRXZWlnaHQudzYwMCwKICAgICAgICAgICksCiAgICAgICAgKSwKICAgICAgKSwKICAgICAgdGV4dEJ1dHRvblRoZW1lOiBUZXh0QnV0dG9uVGhlbWVEYXRhKAogICAgICAgIHN0eWxlOiBUZXh0QnV0dG9uLnN0eWxlRnJvbSgKICAgICAgICAgIGZvcmVncm91bmRDb2xvcjogYmFzZS5jb2xvclNjaGVtZS5wcmltYXJ5LAogICAgICAgICAgdGV4dFN0eWxlOiBiYXNlLnRleHRUaGVtZS5sYWJlbExhcmdlPy5jb3B5V2l0aCgKICAgICAgICAgICAgZm9udFdlaWdodDogRm9udFdlaWdodC53NjAwLAogICAgICAgICAgKSwKICAgICAgICAgIHNoYXBlOiBSb3VuZGVkUmVjdGFuZ2xlQm9yZGVyKGJvcmRlclJhZGl1czogQm9yZGVyUmFkaXVzLmNpcmN1bGFyKDQpKSwKICAgICAgICApLAogICAgICApLAogICAgICBlbGV2YXRlZEJ1dHRvblRoZW1lOiBFbGV2YXRlZEJ1dHRvblRoZW1lRGF0YSgKICAgICAgICBzdHlsZTogRWxldmF0ZWRCdXR0b24uc3R5bGVGcm9tKAogICAgICAgICAgc2hhcGU6IFJvdW5kZWRSZWN0YW5nbGVCb3JkZXIoYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoNCkpLAogICAgICAgICAgdGV4dFN0eWxlOiBiYXNlLnRleHRUaGVtZS5sYWJlbExhcmdlPy5jb3B5V2l0aCgKICAgICAgICAgICAgZm9udFdlaWdodDogRm9udFdlaWdodC53NjAwLAogICAgICAgICAgKSwKICAgICAgICApLAogICAgICApLAogICAgICBvdXRsaW5lZEJ1dHRvblRoZW1lOiBPdXRsaW5lZEJ1dHRvblRoZW1lRGF0YSgKICAgICAgICBzdHlsZTogT3V0bGluZWRCdXR0b24uc3R5bGVGcm9tKAogICAgICAgICAgZm9yZWdyb3VuZENvbG9yOiBiYXNlLmNvbG9yU2NoZW1lLnByaW1hcnksCiAgICAgICAgICB0ZXh0U3R5bGU6IGJhc2UudGV4dFRoZW1lLmxhYmVsTGFyZ2U/LmNvcHlXaXRoKAogICAgICAgICAgICBmb250V2VpZ2h0OiBGb250V2VpZ2h0Lnc2MDAsCiAgICAgICAgICApLAogICAgICAgICAgc2hhcGU6IFJvdW5kZWRSZWN0YW5nbGVCb3JkZXIoYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoNCkpLAogICAgICAgICAgc2lkZTogQm9yZGVyU2lkZShjb2xvcjogYmFzZS5jb2xvclNjaGVtZS5wcmltYXJ5LCB3aWR0aDogMS41KSwKICAgICAgICApLAogICAgICApLAogICAgKTsKICB9CgogIFRoZW1lRGF0YSBsaWdodCgpID0+IF9iYXNlVGhlbWUoaXNEYXJrOiBmYWxzZSk7CgogIFRoZW1lRGF0YSBkYXJrKCkgPT4gX2Jhc2VUaGVtZShpc0Rhcms6IHRydWUpOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/dialogs/file_info_dialog.dart", + "data": + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9zZXJ2aWNlcy5kYXJ0JzsKCmNsYXNzIEZpbGVJbmZvRGlhbG9nIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBmaW5hbCBGaWxlIGZpbGU7CgogIGNvbnN0IEZpbGVJbmZvRGlhbG9nKHtzdXBlci5rZXksIHJlcXVpcmVkIHRoaXMuZmlsZX0pOwoKICBzdGF0aWMgRnV0dXJlPHZvaWQ+IHNob3coQnVpbGRDb250ZXh0IGNvbnRleHQsIEZpbGUgZmlsZSkgewogICAgcmV0dXJuIHNob3dEaWFsb2c8dm9pZD4oCiAgICAgIGNvbnRleHQ6IGNvbnRleHQsCiAgICAgIGJ1aWxkZXI6IChjb250ZXh0KSA9PiBGaWxlSW5mb0RpYWxvZyhmaWxlOiBmaWxlKSwKICAgICk7CiAgfQoKICBTdHJpbmcgX2dldEZpbGVUeXBlKFN0cmluZyBleHRlbnNpb24pIHsKICAgIHN3aXRjaCAoZXh0ZW5zaW9uLnRvTG93ZXJDYXNlKCkpIHsKICAgICAgY2FzZSAnanBnJzoKICAgICAgY2FzZSAnanBlZyc6CiAgICAgICAgcmV0dXJuICdKUEVHIEltYWdlJzsKICAgICAgY2FzZSAncG5nJzoKICAgICAgICByZXR1cm4gJ1BORyBJbWFnZSc7CiAgICAgIGNhc2UgJ2dpZic6CiAgICAgICAgcmV0dXJuICdHSUYgSW1hZ2UnOwogICAgICBjYXNlICd3ZWJwJzoKICAgICAgICByZXR1cm4gJ1dlYlAgSW1hZ2UnOwogICAgICBjYXNlICdibXAnOgogICAgICAgIHJldHVybiAnQk1QIEltYWdlJzsKICAgICAgY2FzZSAndGlmZic6CiAgICAgIGNhc2UgJ3RpZic6CiAgICAgICAgcmV0dXJuICdUSUZGIEltYWdlJzsKICAgICAgY2FzZSAnaGVpYyc6CiAgICAgIGNhc2UgJ2hlaWYnOgogICAgICAgIHJldHVybiAnSEVJRiBJbWFnZSc7CiAgICAgIGRlZmF1bHQ6CiAgICAgICAgcmV0dXJuICdJbWFnZSBGaWxlJzsKICAgIH0KICB9CgogIFN0cmluZyBfZm9ybWF0RmlsZVNpemUoaW50IGJ5dGVzKSB7CiAgICBpZiAoYnl0ZXMgPCAxMDI0KSByZXR1cm4gJyRieXRlcyBCJzsKICAgIGlmIChieXRlcyA8IDEwMjQgKiAxMDI0KSByZXR1cm4gJyR7KGJ5dGVzIC8gMTAyNCkudG9TdHJpbmdBc0ZpeGVkKDEpfSBLQic7CiAgICByZXR1cm4gJyR7KGJ5dGVzIC8gKDEwMjQgKiAxMDI0KSkudG9TdHJpbmdBc0ZpeGVkKDEpfSBNQic7CiAgfQoKICBXaWRnZXQgX2J1aWxkSW5mb1Jvdyh7CiAgICByZXF1aXJlZCBJY29uRGF0YSBpY29uLAogICAgcmVxdWlyZWQgU3RyaW5nIGxhYmVsLAogICAgcmVxdWlyZWQgU3RyaW5nIHZhbHVlLAogICAgcmVxdWlyZWQgQ29sb3JTY2hlbWUgY29sb3JTY2hlbWUsCiAgICByZXF1aXJlZCBUZXh0VGhlbWUgdGV4dFRoZW1lLAogICAgYm9vbCBpc1BhdGggPSBmYWxzZSwKICAgIFZvaWRDYWxsYmFjaz8gb25UYXAsCiAgfSkgewogICAgcmV0dXJuIElua1dlbGwoCiAgICAgIG9uVGFwOiBvblRhcCwKICAgICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoOCksCiAgICAgIGNoaWxkOiBQYWRkaW5nKAogICAgICAgIHBhZGRpbmc6IGNvbnN0IEVkZ2VJbnNldHMuc3ltbWV0cmljKHZlcnRpY2FsOiA4LCBob3Jpem9udGFsOiA0KSwKICAgICAgICBjaGlsZDogUm93KAogICAgICAgICAgY3Jvc3NBeGlzQWxpZ25tZW50OiBDcm9zc0F4aXNBbGlnbm1lbnQuc3RhcnQsCiAgICAgICAgICBjaGlsZHJlbjogWwogICAgICAgICAgICBJY29uKGljb24sIHNpemU6IDE4LCBjb2xvcjogY29sb3JTY2hlbWUub25TdXJmYWNlVmFyaWFudCksCiAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KHdpZHRoOiAxMiksCiAgICAgICAgICAgIEV4cGFuZGVkKAogICAgICAgICAgICAgIGNoaWxkOiBDb2x1bW4oCiAgICAgICAgICAgICAgICBjcm9zc0F4aXNBbGlnbm1lbnQ6IENyb3NzQXhpc0FsaWdubWVudC5zdGFydCwKICAgICAgICAgICAgICAgIGNoaWxkcmVuOiBbCiAgICAgICAgICAgICAgICAgIFRleHQoCiAgICAgICAgICAgICAgICAgICAgbGFiZWwsCiAgICAgICAgICAgICAgICAgICAgc3R5bGU6IHRleHRUaGVtZS5ib2R5U21hbGw/LmNvcHlXaXRoKAogICAgICAgICAgICAgICAgICAgICAgY29sb3I6IGNvbG9yU2NoZW1lLm9uU3VyZmFjZVZhcmlhbnQsCiAgICAgICAgICAgICAgICAgICAgICBmb250V2VpZ2h0OiBGb250V2VpZ2h0Lnc1MDAsCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgY29uc3QgU2l6ZWRCb3goaGVpZ2h0OiA0KSwKICAgICAgICAgICAgICAgICAgU2VsZWN0YWJsZVRleHQoCiAgICAgICAgICAgICAgICAgICAgdmFsdWUsCiAgICAgICAgICAgICAgICAgICAgc3R5bGU6IHRleHRUaGVtZS5ib2R5TWVkaXVtPy5jb3B5V2l0aCgKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5vblN1cmZhY2UsCiAgICAgICAgICAgICAgICAgICAgICBmb250RmFtaWx5OiBpc1BhdGggPyAnbW9ub3NwYWNlJyA6IG51bGwsCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgKSwKICAgICAgICAgICAgaWYgKG9uVGFwICE9IG51bGwpCiAgICAgICAgICAgICAgSWNvbigKICAgICAgICAgICAgICAgIEljb25zLmNvcHksCiAgICAgICAgICAgICAgICBzaXplOiAxNiwKICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5vblN1cmZhY2VWYXJpYW50LndpdGhWYWx1ZXMoYWxwaGE6IDAuNyksCiAgICAgICAgICAgICAgKSwKICAgICAgICAgIF0sCiAgICAgICAgKSwKICAgICAgKSwKICAgICk7CiAgfQoKICB2b2lkIF9jb3B5VG9DbGlwYm9hcmQoQnVpbGRDb250ZXh0IGNvbnRleHQsIFN0cmluZyB2YWx1ZSwgU3RyaW5nIGxhYmVsKSB7CiAgICBDbGlwYm9hcmQuc2V0RGF0YShDbGlwYm9hcmREYXRhKHRleHQ6IHZhbHVlKSk7CiAgICBTY2FmZm9sZE1lc3Nlbmdlci5vZihjb250ZXh0KS5zaG93U25hY2tCYXIoCiAgICAgIFNuYWNrQmFyKAogICAgICAgIGNvbnRlbnQ6IFRleHQoJyRsYWJlbCBjb3BpZWQgdG8gY2xpcGJvYXJkJyksCiAgICAgICAgZHVyYXRpb246IGNvbnN0IER1cmF0aW9uKHNlY29uZHM6IDIpLAogICAgICAgIGJlaGF2aW9yOiBTbmFja0JhckJlaGF2aW9yLmZsb2F0aW5nLAogICAgICApLAogICAgKTsKICB9CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgZmluYWwgdGhlbWUgPSBUaGVtZS5vZihjb250ZXh0KTsKICAgIGZpbmFsIGNvbG9yU2NoZW1lID0gdGhlbWUuY29sb3JTY2hlbWU7CiAgICBmaW5hbCB0ZXh0VGhlbWUgPSB0aGVtZS50ZXh0VGhlbWU7CgogICAgLy8gR2V0IGZpbGUgaW5mbwogICAgZmluYWwgZmlsZVN0YXQgPSBmaWxlLnN0YXRTeW5jKCk7CiAgICBmaW5hbCBmaWxlU2l6ZSA9IGZpbGVTdGF0LnNpemU7CiAgICBmaW5hbCBmaWxlTmFtZSA9IGZpbGUucGF0aC5zcGxpdCgnLycpLmxhc3Q7CiAgICBmaW5hbCBmaWxlRXh0ZW5zaW9uID0gZmlsZU5hbWUuc3BsaXQoJy4nKS5sYXN0LnRvTG93ZXJDYXNlKCk7CiAgICBmaW5hbCBmaWxlVHlwZSA9IF9nZXRGaWxlVHlwZShmaWxlRXh0ZW5zaW9uKTsKCiAgICByZXR1cm4gRGlhbG9nKAogICAgICBzaGFwZTogUm91bmRlZFJlY3RhbmdsZUJvcmRlcihib3JkZXJSYWRpdXM6IEJvcmRlclJhZGl1cy5jaXJjdWxhcigxNikpLAogICAgICBjaGlsZDogQ29udGFpbmVyKAogICAgICAgIGNvbnN0cmFpbnRzOiBjb25zdCBCb3hDb25zdHJhaW50cyhtYXhXaWR0aDogNDAwLCBtYXhIZWlnaHQ6IDYwMCksCiAgICAgICAgY2hpbGQ6IENvbHVtbigKICAgICAgICAgIG1haW5BeGlzU2l6ZTogTWFpbkF4aXNTaXplLm1pbiwKICAgICAgICAgIGNoaWxkcmVuOiBbCiAgICAgICAgICAgIC8vIEhlYWRlcgogICAgICAgICAgICBDb250YWluZXIoCiAgICAgICAgICAgICAgcGFkZGluZzogY29uc3QgRWRnZUluc2V0cy5hbGwoMjApLAogICAgICAgICAgICAgIGRlY29yYXRpb246IEJveERlY29yYXRpb24oCiAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUucHJpbWFyeUNvbnRhaW5lciwKICAgICAgICAgICAgICAgIGJvcmRlclJhZGl1czogY29uc3QgQm9yZGVyUmFkaXVzLnZlcnRpY2FsKAogICAgICAgICAgICAgICAgICB0b3A6IFJhZGl1cy5jaXJjdWxhcigxNiksCiAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgY2hpbGQ6IFJvdygKICAgICAgICAgICAgICAgIGNoaWxkcmVuOiBbCiAgICAgICAgICAgICAgICAgIEljb24oCiAgICAgICAgICAgICAgICAgICAgSWNvbnMuaW5mb19vdXRsaW5lLAogICAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5vblByaW1hcnlDb250YWluZXIsCiAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KHdpZHRoOiAxMiksCiAgICAgICAgICAgICAgICAgIEV4cGFuZGVkKAogICAgICAgICAgICAgICAgICAgIGNoaWxkOiBUZXh0KAogICAgICAgICAgICAgICAgICAgICAgJ0ZpbGUgSW5mb3JtYXRpb24nLAogICAgICAgICAgICAgICAgICAgICAgc3R5bGU6IHRleHRUaGVtZS50aXRsZUxhcmdlPy5jb3B5V2l0aCgKICAgICAgICAgICAgICAgICAgICAgICAgZm9udFdlaWdodDogRm9udFdlaWdodC53NjAwLAogICAgICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUub25QcmltYXJ5Q29udGFpbmVyLAogICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICBJY29uQnV0dG9uKAogICAgICAgICAgICAgICAgICAgIG9uUHJlc3NlZDogKCkgPT4gTmF2aWdhdG9yLm9mKGNvbnRleHQpLnBvcCgpLAogICAgICAgICAgICAgICAgICAgIGljb246IEljb24oCiAgICAgICAgICAgICAgICAgICAgICBJY29ucy5jbG9zZSwKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5vblByaW1hcnlDb250YWluZXIsCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgKSwKCiAgICAgICAgICAgIC8vIENvbnRlbnQKICAgICAgICAgICAgRmxleGlibGUoCiAgICAgICAgICAgICAgY2hpbGQ6IFNpbmdsZUNoaWxkU2Nyb2xsVmlldygKICAgICAgICAgICAgICAgIHBhZGRpbmc6IGNvbnN0IEVkZ2VJbnNldHMuYWxsKDIwKSwKICAgICAgICAgICAgICAgIGNoaWxkOiBDb2x1bW4oCiAgICAgICAgICAgICAgICAgIGNoaWxkcmVuOiBbCiAgICAgICAgICAgICAgICAgICAgX2J1aWxkSW5mb1JvdygKICAgICAgICAgICAgICAgICAgICAgIGljb246IEljb25zLmltYWdlX291dGxpbmVkLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWw6ICdGaWxlIE5hbWUnLAogICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgY29sb3JTY2hlbWU6IGNvbG9yU2NoZW1lLAogICAgICAgICAgICAgICAgICAgICAgdGV4dFRoZW1lOiB0ZXh0VGhlbWUsCiAgICAgICAgICAgICAgICAgICAgICBvblRhcDogKCkgPT4KICAgICAgICAgICAgICAgICAgICAgICAgICBfY29weVRvQ2xpcGJvYXJkKGNvbnRleHQsIGZpbGVOYW1lLCAnRmlsZSBuYW1lJyksCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICBfYnVpbGRJbmZvUm93KAogICAgICAgICAgICAgICAgICAgICAgaWNvbjogSWNvbnMuY2F0ZWdvcnlfb3V0bGluZWQsCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbDogJ0ZpbGUgVHlwZScsCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZTogZmlsZVR5cGUsCiAgICAgICAgICAgICAgICAgICAgICBjb2xvclNjaGVtZTogY29sb3JTY2hlbWUsCiAgICAgICAgICAgICAgICAgICAgICB0ZXh0VGhlbWU6IHRleHRUaGVtZSwKICAgICAgICAgICAgICAgICAgICAgIG9uVGFwOiAoKSA9PgogICAgICAgICAgICAgICAgICAgICAgICAgIF9jb3B5VG9DbGlwYm9hcmQoY29udGV4dCwgZmlsZVR5cGUsICdGaWxlIHR5cGUnKSwKICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgIF9idWlsZEluZm9Sb3coCiAgICAgICAgICAgICAgICAgICAgICBpY29uOiBJY29ucy5leHRlbnNpb25fb3V0bGluZWQsCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbDogJ0ZpbGUgRXh0ZW5zaW9uJywKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlOiAnLiRmaWxlRXh0ZW5zaW9uJywKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yU2NoZW1lOiBjb2xvclNjaGVtZSwKICAgICAgICAgICAgICAgICAgICAgIHRleHRUaGVtZTogdGV4dFRoZW1lLAogICAgICAgICAgICAgICAgICAgICAgb25UYXA6ICgpID0+IF9jb3B5VG9DbGlwYm9hcmQoCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRleHQsCiAgICAgICAgICAgICAgICAgICAgICAgICcuJGZpbGVFeHRlbnNpb24nLAogICAgICAgICAgICAgICAgICAgICAgICAnRmlsZSBleHRlbnNpb24nLAogICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgIF9idWlsZEluZm9Sb3coCiAgICAgICAgICAgICAgICAgICAgICBpY29uOiBJY29ucy5zdG9yYWdlX291dGxpbmVkLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWw6ICdGaWxlIFNpemUnLAogICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IF9mb3JtYXRGaWxlU2l6ZShmaWxlU2l6ZSksCiAgICAgICAgICAgICAgICAgICAgICBjb2xvclNjaGVtZTogY29sb3JTY2hlbWUsCiAgICAgICAgICAgICAgICAgICAgICB0ZXh0VGhlbWU6IHRleHRUaGVtZSwKICAgICAgICAgICAgICAgICAgICAgIG9uVGFwOiAoKSA9PiBfY29weVRvQ2xpcGJvYXJkKAogICAgICAgICAgICAgICAgICAgICAgICBjb250ZXh0LAogICAgICAgICAgICAgICAgICAgICAgICBfZm9ybWF0RmlsZVNpemUoZmlsZVNpemUpLAogICAgICAgICAgICAgICAgICAgICAgICAnRmlsZSBzaXplJywKICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICBfYnVpbGRJbmZvUm93KAogICAgICAgICAgICAgICAgICAgICAgaWNvbjogSWNvbnMuZm9sZGVyX291dGxpbmVkLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWw6ICdGaWxlIFBhdGgnLAogICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IGZpbGUucGF0aCwKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yU2NoZW1lOiBjb2xvclNjaGVtZSwKICAgICAgICAgICAgICAgICAgICAgIHRleHRUaGVtZTogdGV4dFRoZW1lLAogICAgICAgICAgICAgICAgICAgICAgaXNQYXRoOiB0cnVlLAogICAgICAgICAgICAgICAgICAgICAgb25UYXA6ICgpID0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgX2NvcHlUb0NsaXBib2FyZChjb250ZXh0LCBmaWxlLnBhdGgsICdGaWxlIHBhdGgnKSwKICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KGhlaWdodDogOCksCiAgICAgICAgICAgICAgICAgICAgQ29udGFpbmVyKAogICAgICAgICAgICAgICAgICAgICAgcGFkZGluZzogY29uc3QgRWRnZUluc2V0cy5hbGwoMTIpLAogICAgICAgICAgICAgICAgICAgICAgZGVjb3JhdGlvbjogQm94RGVjb3JhdGlvbigKICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I6IGNvbG9yU2NoZW1lLnN1cmZhY2VDb250YWluZXJIaWdoZXN0LndpdGhWYWx1ZXMoCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGE6IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoOCksCiAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgY2hpbGQ6IFJvdygKICAgICAgICAgICAgICAgICAgICAgICAgY2hpbGRyZW46IFsKICAgICAgICAgICAgICAgICAgICAgICAgICBJY29uKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgSWNvbnMudG91Y2hfYXBwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZTogMTYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUub25TdXJmYWNlVmFyaWFudCwKICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KHdpZHRoOiA4KSwKICAgICAgICAgICAgICAgICAgICAgICAgICBFeHBhbmRlZCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNoaWxkOiBUZXh0KAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnVGFwIGFueSBmaWVsZCB0byBjb3B5IHRvIGNsaXBib2FyZCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlOiB0ZXh0VGhlbWUuYm9keVNtYWxsPy5jb3B5V2l0aCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUub25TdXJmYWNlVmFyaWFudCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250U3R5bGU6IEZvbnRTdHlsZS5pdGFsaWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICksCiAgICAgICAgICAgICksCiAgICAgICAgICBdLAogICAgICAgICksCiAgICAgICksCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/dialogs/index.dart", + "data": "ZXhwb3J0ICdmaWxlX2luZm9fZGlhbG9nLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/generated/theme.dart", + "data": + "aW1wb3J0ICJwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCI7CgpjbGFzcyBNYXRlcmlhbFRoZW1lIHsKICBmaW5hbCBUZXh0VGhlbWUgdGV4dFRoZW1lOwoKICBjb25zdCBNYXRlcmlhbFRoZW1lKHRoaXMudGV4dFRoZW1lKTsKCiAgc3RhdGljIENvbG9yU2NoZW1lIGxpZ2h0U2NoZW1lKCkgewogICAgcmV0dXJuIGNvbnN0IENvbG9yU2NoZW1lKAogICAgICBicmlnaHRuZXNzOiBCcmlnaHRuZXNzLmxpZ2h0LAogICAgICBwcmltYXJ5OiBDb2xvcigweGZmNDQ2NzMyKSwKICAgICAgc3VyZmFjZVRpbnQ6IENvbG9yKDB4ZmY0NDY3MzIpLAogICAgICBvblByaW1hcnk6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBwcmltYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmYzVlZmFiKSwKICAgICAgb25QcmltYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmMmQ0ZjFjKSwKICAgICAgc2Vjb25kYXJ5OiBDb2xvcigweGZmNTU2MjRjKSwKICAgICAgb25TZWNvbmRhcnk6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBzZWNvbmRhcnlDb250YWluZXI6IENvbG9yKDB4ZmZkOWU3Y2IpLAogICAgICBvblNlY29uZGFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjNlNGEzNSksCiAgICAgIHRlcnRpYXJ5OiBDb2xvcigweGZmMzg2NjY2KSwKICAgICAgb25UZXJ0aWFyeTogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHRlcnRpYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmYmJlYmVjKSwKICAgICAgb25UZXJ0aWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjFlNGU0ZSksCiAgICAgIGVycm9yOiBDb2xvcigweGZmYmExYTFhKSwKICAgICAgb25FcnJvcjogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIGVycm9yQ29udGFpbmVyOiBDb2xvcigweGZmZmZkYWQ2KSwKICAgICAgb25FcnJvckNvbnRhaW5lcjogQ29sb3IoMHhmZjkzMDAwYSksCiAgICAgIHN1cmZhY2U6IENvbG9yKDB4ZmZmOGZhZjApLAogICAgICBvblN1cmZhY2U6IENvbG9yKDB4ZmYxOTFkMTYpLAogICAgICBvblN1cmZhY2VWYXJpYW50OiBDb2xvcigweGZmNDM0ODNlKSwKICAgICAgb3V0bGluZTogQ29sb3IoMHhmZjc0Nzk2ZCksCiAgICAgIG91dGxpbmVWYXJpYW50OiBDb2xvcigweGZmYzNjOGJiKSwKICAgICAgc2hhZG93OiBDb2xvcigweGZmMDAwMDAwKSwKICAgICAgc2NyaW06IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICBpbnZlcnNlU3VyZmFjZTogQ29sb3IoMHhmZjJlMzEyYiksCiAgICAgIGludmVyc2VQcmltYXJ5OiBDb2xvcigweGZmYWFkMjkxKSwKICAgICAgcHJpbWFyeUZpeGVkOiBDb2xvcigweGZmYzVlZmFiKSwKICAgICAgb25QcmltYXJ5Rml4ZWQ6IENvbG9yKDB4ZmYwNzIxMDApLAogICAgICBwcmltYXJ5Rml4ZWREaW06IENvbG9yKDB4ZmZhYWQyOTEpLAogICAgICBvblByaW1hcnlGaXhlZFZhcmlhbnQ6IENvbG9yKDB4ZmYyZDRmMWMpLAogICAgICBzZWNvbmRhcnlGaXhlZDogQ29sb3IoMHhmZmQ5ZTdjYiksCiAgICAgIG9uU2Vjb25kYXJ5Rml4ZWQ6IENvbG9yKDB4ZmYxMzFmMGQpLAogICAgICBzZWNvbmRhcnlGaXhlZERpbTogQ29sb3IoMHhmZmJkY2JiMCksCiAgICAgIG9uU2Vjb25kYXJ5Rml4ZWRWYXJpYW50OiBDb2xvcigweGZmM2U0YTM1KSwKICAgICAgdGVydGlhcnlGaXhlZDogQ29sb3IoMHhmZmJiZWJlYyksCiAgICAgIG9uVGVydGlhcnlGaXhlZDogQ29sb3IoMHhmZjAwMjAyMCksCiAgICAgIHRlcnRpYXJ5Rml4ZWREaW06IENvbG9yKDB4ZmZhMGNmY2YpLAogICAgICBvblRlcnRpYXJ5Rml4ZWRWYXJpYW50OiBDb2xvcigweGZmMWU0ZTRlKSwKICAgICAgc3VyZmFjZURpbTogQ29sb3IoMHhmZmQ5ZGJkMSksCiAgICAgIHN1cmZhY2VCcmlnaHQ6IENvbG9yKDB4ZmZmOGZhZjApLAogICAgICBzdXJmYWNlQ29udGFpbmVyTG93ZXN0OiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckxvdzogQ29sb3IoMHhmZmYyZjVlYSksCiAgICAgIHN1cmZhY2VDb250YWluZXI6IENvbG9yKDB4ZmZlZGVmZTUpLAogICAgICBzdXJmYWNlQ29udGFpbmVySGlnaDogQ29sb3IoMHhmZmU3ZTlkZiksCiAgICAgIHN1cmZhY2VDb250YWluZXJIaWdoZXN0OiBDb2xvcigweGZmZTFlNGQ5KSwKICAgICk7CiAgfQoKICBUaGVtZURhdGEgbGlnaHQoKSB7CiAgICByZXR1cm4gdGhlbWUobGlnaHRTY2hlbWUoKSk7CiAgfQoKICBzdGF0aWMgQ29sb3JTY2hlbWUgbGlnaHRNZWRpdW1Db250cmFzdFNjaGVtZSgpIHsKICAgIHJldHVybiBjb25zdCBDb2xvclNjaGVtZSgKICAgICAgYnJpZ2h0bmVzczogQnJpZ2h0bmVzcy5saWdodCwKICAgICAgcHJpbWFyeTogQ29sb3IoMHhmZjFkM2UwYyksCiAgICAgIHN1cmZhY2VUaW50OiBDb2xvcigweGZmNDQ2NzMyKSwKICAgICAgb25QcmltYXJ5OiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgcHJpbWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjUzNzczZiksCiAgICAgIG9uUHJpbWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHNlY29uZGFyeTogQ29sb3IoMHhmZjJkM2EyNiksCiAgICAgIG9uU2Vjb25kYXJ5OiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgc2Vjb25kYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmNjQ3MTVhKSwKICAgICAgb25TZWNvbmRhcnlDb250YWluZXI6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICB0ZXJ0aWFyeTogQ29sb3IoMHhmZjA3M2QzZSksCiAgICAgIG9uVGVydGlhcnk6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICB0ZXJ0aWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjQ3NzU3NSksCiAgICAgIG9uVGVydGlhcnlDb250YWluZXI6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBlcnJvcjogQ29sb3IoMHhmZjc0MDAwNiksCiAgICAgIG9uRXJyb3I6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBlcnJvckNvbnRhaW5lcjogQ29sb3IoMHhmZmNmMmMyNyksCiAgICAgIG9uRXJyb3JDb250YWluZXI6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBzdXJmYWNlOiBDb2xvcigweGZmZjhmYWYwKSwKICAgICAgb25TdXJmYWNlOiBDb2xvcigweGZmMGYxMjBjKSwKICAgICAgb25TdXJmYWNlVmFyaWFudDogQ29sb3IoMHhmZjMzMzgyZSksCiAgICAgIG91dGxpbmU6IENvbG9yKDB4ZmY0ZjU0NGEpLAogICAgICBvdXRsaW5lVmFyaWFudDogQ29sb3IoMHhmZjZhNmY2NCksCiAgICAgIHNoYWRvdzogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIHNjcmltOiBDb2xvcigweGZmMDAwMDAwKSwKICAgICAgaW52ZXJzZVN1cmZhY2U6IENvbG9yKDB4ZmYyZTMxMmIpLAogICAgICBpbnZlcnNlUHJpbWFyeTogQ29sb3IoMHhmZmFhZDI5MSksCiAgICAgIHByaW1hcnlGaXhlZDogQ29sb3IoMHhmZjUzNzczZiksCiAgICAgIG9uUHJpbWFyeUZpeGVkOiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgcHJpbWFyeUZpeGVkRGltOiBDb2xvcigweGZmM2I1ZDI5KSwKICAgICAgb25QcmltYXJ5Rml4ZWRWYXJpYW50OiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgc2Vjb25kYXJ5Rml4ZWQ6IENvbG9yKDB4ZmY2NDcxNWEpLAogICAgICBvblNlY29uZGFyeUZpeGVkOiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgc2Vjb25kYXJ5Rml4ZWREaW06IENvbG9yKDB4ZmY0YzU5NDMpLAogICAgICBvblNlY29uZGFyeUZpeGVkVmFyaWFudDogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHRlcnRpYXJ5Rml4ZWQ6IENvbG9yKDB4ZmY0Nzc1NzUpLAogICAgICBvblRlcnRpYXJ5Rml4ZWQ6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICB0ZXJ0aWFyeUZpeGVkRGltOiBDb2xvcigweGZmMmU1YzVkKSwKICAgICAgb25UZXJ0aWFyeUZpeGVkVmFyaWFudDogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHN1cmZhY2VEaW06IENvbG9yKDB4ZmZjNWM4YmUpLAogICAgICBzdXJmYWNlQnJpZ2h0OiBDb2xvcigweGZmZjhmYWYwKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckxvd2VzdDogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHN1cmZhY2VDb250YWluZXJMb3c6IENvbG9yKDB4ZmZmMmY1ZWEpLAogICAgICBzdXJmYWNlQ29udGFpbmVyOiBDb2xvcigweGZmZTdlOWRmKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckhpZ2g6IENvbG9yKDB4ZmZkYmRlZDQpLAogICAgICBzdXJmYWNlQ29udGFpbmVySGlnaGVzdDogQ29sb3IoMHhmZmQwZDNjOSksCiAgICApOwogIH0KCiAgVGhlbWVEYXRhIGxpZ2h0TWVkaXVtQ29udHJhc3QoKSB7CiAgICByZXR1cm4gdGhlbWUobGlnaHRNZWRpdW1Db250cmFzdFNjaGVtZSgpKTsKICB9CgogIHN0YXRpYyBDb2xvclNjaGVtZSBsaWdodEhpZ2hDb250cmFzdFNjaGVtZSgpIHsKICAgIHJldHVybiBjb25zdCBDb2xvclNjaGVtZSgKICAgICAgYnJpZ2h0bmVzczogQnJpZ2h0bmVzcy5saWdodCwKICAgICAgcHJpbWFyeTogQ29sb3IoMHhmZjEzMzMwNCksCiAgICAgIHN1cmZhY2VUaW50OiBDb2xvcigweGZmNDQ2NzMyKSwKICAgICAgb25QcmltYXJ5OiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgcHJpbWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjMwNTExZSksCiAgICAgIG9uUHJpbWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHNlY29uZGFyeTogQ29sb3IoMHhmZjI0MmYxYyksCiAgICAgIG9uU2Vjb25kYXJ5OiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgc2Vjb25kYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmNDA0ZDM4KSwKICAgICAgb25TZWNvbmRhcnlDb250YWluZXI6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICB0ZXJ0aWFyeTogQ29sb3IoMHhmZjAwMzIzMyksCiAgICAgIG9uVGVydGlhcnk6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICB0ZXJ0aWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjIxNTA1MSksCiAgICAgIG9uVGVydGlhcnlDb250YWluZXI6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBlcnJvcjogQ29sb3IoMHhmZjYwMDAwNCksCiAgICAgIG9uRXJyb3I6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBlcnJvckNvbnRhaW5lcjogQ29sb3IoMHhmZjk4MDAwYSksCiAgICAgIG9uRXJyb3JDb250YWluZXI6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBzdXJmYWNlOiBDb2xvcigweGZmZjhmYWYwKSwKICAgICAgb25TdXJmYWNlOiBDb2xvcigweGZmMDAwMDAwKSwKICAgICAgb25TdXJmYWNlVmFyaWFudDogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIG91dGxpbmU6IENvbG9yKDB4ZmYyOTJlMjUpLAogICAgICBvdXRsaW5lVmFyaWFudDogQ29sb3IoMHhmZjQ2NGI0MSksCiAgICAgIHNoYWRvdzogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIHNjcmltOiBDb2xvcigweGZmMDAwMDAwKSwKICAgICAgaW52ZXJzZVN1cmZhY2U6IENvbG9yKDB4ZmYyZTMxMmIpLAogICAgICBpbnZlcnNlUHJpbWFyeTogQ29sb3IoMHhmZmFhZDI5MSksCiAgICAgIHByaW1hcnlGaXhlZDogQ29sb3IoMHhmZjMwNTExZSksCiAgICAgIG9uUHJpbWFyeUZpeGVkOiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgcHJpbWFyeUZpeGVkRGltOiBDb2xvcigweGZmMWEzYTA5KSwKICAgICAgb25QcmltYXJ5Rml4ZWRWYXJpYW50OiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgc2Vjb25kYXJ5Rml4ZWQ6IENvbG9yKDB4ZmY0MDRkMzgpLAogICAgICBvblNlY29uZGFyeUZpeGVkOiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgc2Vjb25kYXJ5Rml4ZWREaW06IENvbG9yKDB4ZmYyYTM2MjIpLAogICAgICBvblNlY29uZGFyeUZpeGVkVmFyaWFudDogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHRlcnRpYXJ5Rml4ZWQ6IENvbG9yKDB4ZmYyMTUwNTEpLAogICAgICBvblRlcnRpYXJ5Rml4ZWQ6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICB0ZXJ0aWFyeUZpeGVkRGltOiBDb2xvcigweGZmMDIzOTNhKSwKICAgICAgb25UZXJ0aWFyeUZpeGVkVmFyaWFudDogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHN1cmZhY2VEaW06IENvbG9yKDB4ZmZiN2JhYjApLAogICAgICBzdXJmYWNlQnJpZ2h0OiBDb2xvcigweGZmZjhmYWYwKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckxvd2VzdDogQ29sb3IoMHhmZmZmZmZmZiksCiAgICAgIHN1cmZhY2VDb250YWluZXJMb3c6IENvbG9yKDB4ZmZmMGYyZTcpLAogICAgICBzdXJmYWNlQ29udGFpbmVyOiBDb2xvcigweGZmZTFlNGQ5KSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckhpZ2g6IENvbG9yKDB4ZmZkM2Q1Y2IpLAogICAgICBzdXJmYWNlQ29udGFpbmVySGlnaGVzdDogQ29sb3IoMHhmZmM1YzhiZSksCiAgICApOwogIH0KCiAgVGhlbWVEYXRhIGxpZ2h0SGlnaENvbnRyYXN0KCkgewogICAgcmV0dXJuIHRoZW1lKGxpZ2h0SGlnaENvbnRyYXN0U2NoZW1lKCkpOwogIH0KCiAgc3RhdGljIENvbG9yU2NoZW1lIGRhcmtTY2hlbWUoKSB7CiAgICByZXR1cm4gY29uc3QgQ29sb3JTY2hlbWUoCiAgICAgIGJyaWdodG5lc3M6IEJyaWdodG5lc3MuZGFyaywKICAgICAgcHJpbWFyeTogQ29sb3IoMHhmZmFhZDI5MSksCiAgICAgIHN1cmZhY2VUaW50OiBDb2xvcigweGZmYWFkMjkxKSwKICAgICAgb25QcmltYXJ5OiBDb2xvcigweGZmMTczODA3KSwKICAgICAgcHJpbWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjJkNGYxYyksCiAgICAgIG9uUHJpbWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZmM1ZWZhYiksCiAgICAgIHNlY29uZGFyeTogQ29sb3IoMHhmZmJkY2JiMCksCiAgICAgIG9uU2Vjb25kYXJ5OiBDb2xvcigweGZmMjgzNDIwKSwKICAgICAgc2Vjb25kYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmM2U0YTM1KSwKICAgICAgb25TZWNvbmRhcnlDb250YWluZXI6IENvbG9yKDB4ZmZkOWU3Y2IpLAogICAgICB0ZXJ0aWFyeTogQ29sb3IoMHhmZmEwY2ZjZiksCiAgICAgIG9uVGVydGlhcnk6IENvbG9yKDB4ZmYwMDM3MzgpLAogICAgICB0ZXJ0aWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjFlNGU0ZSksCiAgICAgIG9uVGVydGlhcnlDb250YWluZXI6IENvbG9yKDB4ZmZiYmViZWMpLAogICAgICBlcnJvcjogQ29sb3IoMHhmZmZmYjRhYiksCiAgICAgIG9uRXJyb3I6IENvbG9yKDB4ZmY2OTAwMDUpLAogICAgICBlcnJvckNvbnRhaW5lcjogQ29sb3IoMHhmZjkzMDAwYSksCiAgICAgIG9uRXJyb3JDb250YWluZXI6IENvbG9yKDB4ZmZmZmRhZDYpLAogICAgICBzdXJmYWNlOiBDb2xvcigweGZmMTExNDBlKSwKICAgICAgb25TdXJmYWNlOiBDb2xvcigweGZmZTFlNGQ5KSwKICAgICAgb25TdXJmYWNlVmFyaWFudDogQ29sb3IoMHhmZmMzYzhiYiksCiAgICAgIG91dGxpbmU6IENvbG9yKDB4ZmY4ZDkyODYpLAogICAgICBvdXRsaW5lVmFyaWFudDogQ29sb3IoMHhmZjQzNDgzZSksCiAgICAgIHNoYWRvdzogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIHNjcmltOiBDb2xvcigweGZmMDAwMDAwKSwKICAgICAgaW52ZXJzZVN1cmZhY2U6IENvbG9yKDB4ZmZlMWU0ZDkpLAogICAgICBpbnZlcnNlUHJpbWFyeTogQ29sb3IoMHhmZjQ0NjczMiksCiAgICAgIHByaW1hcnlGaXhlZDogQ29sb3IoMHhmZmM1ZWZhYiksCiAgICAgIG9uUHJpbWFyeUZpeGVkOiBDb2xvcigweGZmMDcyMTAwKSwKICAgICAgcHJpbWFyeUZpeGVkRGltOiBDb2xvcigweGZmYWFkMjkxKSwKICAgICAgb25QcmltYXJ5Rml4ZWRWYXJpYW50OiBDb2xvcigweGZmMmQ0ZjFjKSwKICAgICAgc2Vjb25kYXJ5Rml4ZWQ6IENvbG9yKDB4ZmZkOWU3Y2IpLAogICAgICBvblNlY29uZGFyeUZpeGVkOiBDb2xvcigweGZmMTMxZjBkKSwKICAgICAgc2Vjb25kYXJ5Rml4ZWREaW06IENvbG9yKDB4ZmZiZGNiYjApLAogICAgICBvblNlY29uZGFyeUZpeGVkVmFyaWFudDogQ29sb3IoMHhmZjNlNGEzNSksCiAgICAgIHRlcnRpYXJ5Rml4ZWQ6IENvbG9yKDB4ZmZiYmViZWMpLAogICAgICBvblRlcnRpYXJ5Rml4ZWQ6IENvbG9yKDB4ZmYwMDIwMjApLAogICAgICB0ZXJ0aWFyeUZpeGVkRGltOiBDb2xvcigweGZmYTBjZmNmKSwKICAgICAgb25UZXJ0aWFyeUZpeGVkVmFyaWFudDogQ29sb3IoMHhmZjFlNGU0ZSksCiAgICAgIHN1cmZhY2VEaW06IENvbG9yKDB4ZmYxMTE0MGUpLAogICAgICBzdXJmYWNlQnJpZ2h0OiBDb2xvcigweGZmMzczYTMzKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckxvd2VzdDogQ29sb3IoMHhmZjBjMGYwOSksCiAgICAgIHN1cmZhY2VDb250YWluZXJMb3c6IENvbG9yKDB4ZmYxOTFkMTYpLAogICAgICBzdXJmYWNlQ29udGFpbmVyOiBDb2xvcigweGZmMWQyMTFhKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckhpZ2g6IENvbG9yKDB4ZmYyODJiMjQpLAogICAgICBzdXJmYWNlQ29udGFpbmVySGlnaGVzdDogQ29sb3IoMHhmZjMyMzYyZiksCiAgICApOwogIH0KCiAgVGhlbWVEYXRhIGRhcmsoKSB7CiAgICByZXR1cm4gdGhlbWUoZGFya1NjaGVtZSgpKTsKICB9CgogIHN0YXRpYyBDb2xvclNjaGVtZSBkYXJrTWVkaXVtQ29udHJhc3RTY2hlbWUoKSB7CiAgICByZXR1cm4gY29uc3QgQ29sb3JTY2hlbWUoCiAgICAgIGJyaWdodG5lc3M6IEJyaWdodG5lc3MuZGFyaywKICAgICAgcHJpbWFyeTogQ29sb3IoMHhmZmJmZThhNSksCiAgICAgIHN1cmZhY2VUaW50OiBDb2xvcigweGZmYWFkMjkxKSwKICAgICAgb25QcmltYXJ5OiBDb2xvcigweGZmMGMyYzAwKSwKICAgICAgcHJpbWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjc1OWI1ZiksCiAgICAgIG9uUHJpbWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIHNlY29uZGFyeTogQ29sb3IoMHhmZmQyZTFjNSksCiAgICAgIG9uU2Vjb25kYXJ5OiBDb2xvcigweGZmMWQyOTE2KSwKICAgICAgc2Vjb25kYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmODc5NTdjKSwKICAgICAgb25TZWNvbmRhcnlDb250YWluZXI6IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICB0ZXJ0aWFyeTogQ29sb3IoMHhmZmI1ZTVlNSksCiAgICAgIG9uVGVydGlhcnk6IENvbG9yKDB4ZmYwMDJiMmMpLAogICAgICB0ZXJ0aWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjZiOTk5OSksCiAgICAgIG9uVGVydGlhcnlDb250YWluZXI6IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICBlcnJvcjogQ29sb3IoMHhmZmZmZDJjYyksCiAgICAgIG9uRXJyb3I6IENvbG9yKDB4ZmY1NDAwMDMpLAogICAgICBlcnJvckNvbnRhaW5lcjogQ29sb3IoMHhmZmZmNTQ0OSksCiAgICAgIG9uRXJyb3JDb250YWluZXI6IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICBzdXJmYWNlOiBDb2xvcigweGZmMTExNDBlKSwKICAgICAgb25TdXJmYWNlOiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgb25TdXJmYWNlVmFyaWFudDogQ29sb3IoMHhmZmQ5ZGVkMCksCiAgICAgIG91dGxpbmU6IENvbG9yKDB4ZmZhZmI0YTcpLAogICAgICBvdXRsaW5lVmFyaWFudDogQ29sb3IoMHhmZjhkOTI4NiksCiAgICAgIHNoYWRvdzogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIHNjcmltOiBDb2xvcigweGZmMDAwMDAwKSwKICAgICAgaW52ZXJzZVN1cmZhY2U6IENvbG9yKDB4ZmZlMWU0ZDkpLAogICAgICBpbnZlcnNlUHJpbWFyeTogQ29sb3IoMHhmZjJmNTAxZCksCiAgICAgIHByaW1hcnlGaXhlZDogQ29sb3IoMHhmZmM1ZWZhYiksCiAgICAgIG9uUHJpbWFyeUZpeGVkOiBDb2xvcigweGZmMDQxNTAwKSwKICAgICAgcHJpbWFyeUZpeGVkRGltOiBDb2xvcigweGZmYWFkMjkxKSwKICAgICAgb25QcmltYXJ5Rml4ZWRWYXJpYW50OiBDb2xvcigweGZmMWQzZTBjKSwKICAgICAgc2Vjb25kYXJ5Rml4ZWQ6IENvbG9yKDB4ZmZkOWU3Y2IpLAogICAgICBvblNlY29uZGFyeUZpeGVkOiBDb2xvcigweGZmMDkxNDA1KSwKICAgICAgc2Vjb25kYXJ5Rml4ZWREaW06IENvbG9yKDB4ZmZiZGNiYjApLAogICAgICBvblNlY29uZGFyeUZpeGVkVmFyaWFudDogQ29sb3IoMHhmZjJkM2EyNiksCiAgICAgIHRlcnRpYXJ5Rml4ZWQ6IENvbG9yKDB4ZmZiYmViZWMpLAogICAgICBvblRlcnRpYXJ5Rml4ZWQ6IENvbG9yKDB4ZmYwMDE0MTUpLAogICAgICB0ZXJ0aWFyeUZpeGVkRGltOiBDb2xvcigweGZmYTBjZmNmKSwKICAgICAgb25UZXJ0aWFyeUZpeGVkVmFyaWFudDogQ29sb3IoMHhmZjA3M2QzZSksCiAgICAgIHN1cmZhY2VEaW06IENvbG9yKDB4ZmYxMTE0MGUpLAogICAgICBzdXJmYWNlQnJpZ2h0OiBDb2xvcigweGZmNDI0NjNlKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckxvd2VzdDogQ29sb3IoMHhmZjA2MDgwNCksCiAgICAgIHN1cmZhY2VDb250YWluZXJMb3c6IENvbG9yKDB4ZmYxYjFmMTgpLAogICAgICBzdXJmYWNlQ29udGFpbmVyOiBDb2xvcigweGZmMjYyOTIyKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckhpZ2g6IENvbG9yKDB4ZmYzMDM0MmQpLAogICAgICBzdXJmYWNlQ29udGFpbmVySGlnaGVzdDogQ29sb3IoMHhmZjNiM2YzOCksCiAgICApOwogIH0KCiAgVGhlbWVEYXRhIGRhcmtNZWRpdW1Db250cmFzdCgpIHsKICAgIHJldHVybiB0aGVtZShkYXJrTWVkaXVtQ29udHJhc3RTY2hlbWUoKSk7CiAgfQoKICBzdGF0aWMgQ29sb3JTY2hlbWUgZGFya0hpZ2hDb250cmFzdFNjaGVtZSgpIHsKICAgIHJldHVybiBjb25zdCBDb2xvclNjaGVtZSgKICAgICAgYnJpZ2h0bmVzczogQnJpZ2h0bmVzcy5kYXJrLAogICAgICBwcmltYXJ5OiBDb2xvcigweGZmZDJmY2I3KSwKICAgICAgc3VyZmFjZVRpbnQ6IENvbG9yKDB4ZmZhYWQyOTEpLAogICAgICBvblByaW1hcnk6IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICBwcmltYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmYTZjZThkKSwKICAgICAgb25QcmltYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmMDIwZTAwKSwKICAgICAgc2Vjb25kYXJ5OiBDb2xvcigweGZmZTZmNWQ4KSwKICAgICAgb25TZWNvbmRhcnk6IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICBzZWNvbmRhcnlDb250YWluZXI6IENvbG9yKDB4ZmZiOWM3YWMpLAogICAgICBvblNlY29uZGFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjA0MGUwMiksCiAgICAgIHRlcnRpYXJ5OiBDb2xvcigweGZmYzlmOWY5KSwKICAgICAgb25UZXJ0aWFyeTogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIHRlcnRpYXJ5Q29udGFpbmVyOiBDb2xvcigweGZmOWNjYmNiKSwKICAgICAgb25UZXJ0aWFyeUNvbnRhaW5lcjogQ29sb3IoMHhmZjAwMGUwZSksCiAgICAgIGVycm9yOiBDb2xvcigweGZmZmZlY2U5KSwKICAgICAgb25FcnJvcjogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIGVycm9yQ29udGFpbmVyOiBDb2xvcigweGZmZmZhZWE0KSwKICAgICAgb25FcnJvckNvbnRhaW5lcjogQ29sb3IoMHhmZjIyMDAwMSksCiAgICAgIHN1cmZhY2U6IENvbG9yKDB4ZmYxMTE0MGUpLAogICAgICBvblN1cmZhY2U6IENvbG9yKDB4ZmZmZmZmZmYpLAogICAgICBvblN1cmZhY2VWYXJpYW50OiBDb2xvcigweGZmZmZmZmZmKSwKICAgICAgb3V0bGluZTogQ29sb3IoMHhmZmVkZjJlNCksCiAgICAgIG91dGxpbmVWYXJpYW50OiBDb2xvcigweGZmYmZjNGI3KSwKICAgICAgc2hhZG93OiBDb2xvcigweGZmMDAwMDAwKSwKICAgICAgc2NyaW06IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICBpbnZlcnNlU3VyZmFjZTogQ29sb3IoMHhmZmUxZTRkOSksCiAgICAgIGludmVyc2VQcmltYXJ5OiBDb2xvcigweGZmMmY1MDFkKSwKICAgICAgcHJpbWFyeUZpeGVkOiBDb2xvcigweGZmYzVlZmFiKSwKICAgICAgb25QcmltYXJ5Rml4ZWQ6IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICBwcmltYXJ5Rml4ZWREaW06IENvbG9yKDB4ZmZhYWQyOTEpLAogICAgICBvblByaW1hcnlGaXhlZFZhcmlhbnQ6IENvbG9yKDB4ZmYwNDE1MDApLAogICAgICBzZWNvbmRhcnlGaXhlZDogQ29sb3IoMHhmZmQ5ZTdjYiksCiAgICAgIG9uU2Vjb25kYXJ5Rml4ZWQ6IENvbG9yKDB4ZmYwMDAwMDApLAogICAgICBzZWNvbmRhcnlGaXhlZERpbTogQ29sb3IoMHhmZmJkY2JiMCksCiAgICAgIG9uU2Vjb25kYXJ5Rml4ZWRWYXJpYW50OiBDb2xvcigweGZmMDkxNDA1KSwKICAgICAgdGVydGlhcnlGaXhlZDogQ29sb3IoMHhmZmJiZWJlYyksCiAgICAgIG9uVGVydGlhcnlGaXhlZDogQ29sb3IoMHhmZjAwMDAwMCksCiAgICAgIHRlcnRpYXJ5Rml4ZWREaW06IENvbG9yKDB4ZmZhMGNmY2YpLAogICAgICBvblRlcnRpYXJ5Rml4ZWRWYXJpYW50OiBDb2xvcigweGZmMDAxNDE1KSwKICAgICAgc3VyZmFjZURpbTogQ29sb3IoMHhmZjExMTQwZSksCiAgICAgIHN1cmZhY2VCcmlnaHQ6IENvbG9yKDB4ZmY0ZTUxNGEpLAogICAgICBzdXJmYWNlQ29udGFpbmVyTG93ZXN0OiBDb2xvcigweGZmMDAwMDAwKSwKICAgICAgc3VyZmFjZUNvbnRhaW5lckxvdzogQ29sb3IoMHhmZjFkMjExYSksCiAgICAgIHN1cmZhY2VDb250YWluZXI6IENvbG9yKDB4ZmYyZTMxMmIpLAogICAgICBzdXJmYWNlQ29udGFpbmVySGlnaDogQ29sb3IoMHhmZjM5M2MzNSksCiAgICAgIHN1cmZhY2VDb250YWluZXJIaWdoZXN0OiBDb2xvcigweGZmNDQ0ODQwKSwKICAgICk7CiAgfQoKICBUaGVtZURhdGEgZGFya0hpZ2hDb250cmFzdCgpIHsKICAgIHJldHVybiB0aGVtZShkYXJrSGlnaENvbnRyYXN0U2NoZW1lKCkpOwogIH0KCiAgVGhlbWVEYXRhIHRoZW1lKENvbG9yU2NoZW1lIGNvbG9yU2NoZW1lKSA9PiBUaGVtZURhdGEoCiAgICB1c2VNYXRlcmlhbDM6IHRydWUsCiAgICBicmlnaHRuZXNzOiBjb2xvclNjaGVtZS5icmlnaHRuZXNzLAogICAgY29sb3JTY2hlbWU6IGNvbG9yU2NoZW1lLAogICAgdGV4dFRoZW1lOiB0ZXh0VGhlbWUuYXBwbHkoCiAgICAgIGJvZHlDb2xvcjogY29sb3JTY2hlbWUub25TdXJmYWNlLAogICAgICBkaXNwbGF5Q29sb3I6IGNvbG9yU2NoZW1lLm9uU3VyZmFjZSwKICAgICksCiAgICBzY2FmZm9sZEJhY2tncm91bmRDb2xvcjogY29sb3JTY2hlbWUuc3VyZmFjZSwKICAgIGNhbnZhc0NvbG9yOiBjb2xvclNjaGVtZS5zdXJmYWNlLAogICk7CgogIExpc3Q8RXh0ZW5kZWRDb2xvcj4gZ2V0IGV4dGVuZGVkQ29sb3JzID0+IFtdOwp9CgpjbGFzcyBFeHRlbmRlZENvbG9yIHsKICBmaW5hbCBDb2xvciBzZWVkLCB2YWx1ZTsKICBmaW5hbCBDb2xvckZhbWlseSBsaWdodDsKICBmaW5hbCBDb2xvckZhbWlseSBsaWdodEhpZ2hDb250cmFzdDsKICBmaW5hbCBDb2xvckZhbWlseSBsaWdodE1lZGl1bUNvbnRyYXN0OwogIGZpbmFsIENvbG9yRmFtaWx5IGRhcms7CiAgZmluYWwgQ29sb3JGYW1pbHkgZGFya0hpZ2hDb250cmFzdDsKICBmaW5hbCBDb2xvckZhbWlseSBkYXJrTWVkaXVtQ29udHJhc3Q7CgogIGNvbnN0IEV4dGVuZGVkQ29sb3IoewogICAgcmVxdWlyZWQgdGhpcy5zZWVkLAogICAgcmVxdWlyZWQgdGhpcy52YWx1ZSwKICAgIHJlcXVpcmVkIHRoaXMubGlnaHQsCiAgICByZXF1aXJlZCB0aGlzLmxpZ2h0SGlnaENvbnRyYXN0LAogICAgcmVxdWlyZWQgdGhpcy5saWdodE1lZGl1bUNvbnRyYXN0LAogICAgcmVxdWlyZWQgdGhpcy5kYXJrLAogICAgcmVxdWlyZWQgdGhpcy5kYXJrSGlnaENvbnRyYXN0LAogICAgcmVxdWlyZWQgdGhpcy5kYXJrTWVkaXVtQ29udHJhc3QsCiAgfSk7Cn0KCmNsYXNzIENvbG9yRmFtaWx5IHsKICBjb25zdCBDb2xvckZhbWlseSh7CiAgICByZXF1aXJlZCB0aGlzLmNvbG9yLAogICAgcmVxdWlyZWQgdGhpcy5vbkNvbG9yLAogICAgcmVxdWlyZWQgdGhpcy5jb2xvckNvbnRhaW5lciwKICAgIHJlcXVpcmVkIHRoaXMub25Db2xvckNvbnRhaW5lciwKICB9KTsKCiAgZmluYWwgQ29sb3IgY29sb3I7CiAgZmluYWwgQ29sb3Igb25Db2xvcjsKICBmaW5hbCBDb2xvciBjb2xvckNvbnRhaW5lcjsKICBmaW5hbCBDb2xvciBvbkNvbG9yQ29udGFpbmVyOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/generated/util.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpnb29nbGVfZm9udHMvZ29vZ2xlX2ZvbnRzLmRhcnQnOwoKVGV4dFRoZW1lIGNyZWF0ZVRleHRUaGVtZSgKICBCdWlsZENvbnRleHQgY29udGV4dCwKICBTdHJpbmcgYm9keUZvbnRTdHJpbmcsCiAgU3RyaW5nIGRpc3BsYXlGb250U3RyaW5nLAopIHsKICBUZXh0VGhlbWUgYmFzZVRleHRUaGVtZSA9IFRoZW1lLm9mKGNvbnRleHQpLnRleHRUaGVtZTsKICBUZXh0VGhlbWUgYm9keVRleHRUaGVtZSA9IEdvb2dsZUZvbnRzLmdldFRleHRUaGVtZSgKICAgIGJvZHlGb250U3RyaW5nLAogICAgYmFzZVRleHRUaGVtZSwKICApOwogIFRleHRUaGVtZSBkaXNwbGF5VGV4dFRoZW1lID0gR29vZ2xlRm9udHMuZ2V0VGV4dFRoZW1lKAogICAgZGlzcGxheUZvbnRTdHJpbmcsCiAgICBiYXNlVGV4dFRoZW1lLAogICk7CiAgVGV4dFRoZW1lIHRleHRUaGVtZSA9IGRpc3BsYXlUZXh0VGhlbWUuY29weVdpdGgoCiAgICBib2R5TGFyZ2U6IGJvZHlUZXh0VGhlbWUuYm9keUxhcmdlLAogICAgYm9keU1lZGl1bTogYm9keVRleHRUaGVtZS5ib2R5TWVkaXVtLAogICAgYm9keVNtYWxsOiBib2R5VGV4dFRoZW1lLmJvZHlTbWFsbCwKICAgIGxhYmVsTGFyZ2U6IGJvZHlUZXh0VGhlbWUubGFiZWxMYXJnZSwKICAgIGxhYmVsTWVkaXVtOiBib2R5VGV4dFRoZW1lLmxhYmVsTWVkaXVtLAogICAgbGFiZWxTbWFsbDogYm9keVRleHRUaGVtZS5sYWJlbFNtYWxsLAogICk7CiAgcmV0dXJuIHRleHRUaGVtZTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/loader/loader.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vY29tcG9uZW50cy9pbmRleC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2FkZXJfb3ZlcmxheS9sb2FkZXJfb3ZlcmxheS5kYXJ0JzsKCmNsYXNzIExvYWRlciB7CiAgc3RhdGljIHZvaWQgc2hvdyhCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgY29udGV4dC5sb2FkZXJPdmVybGF5LnNob3coCiAgICAgIHdpZGdldEJ1aWxkZXI6IChfKSA9PiBjb25zdCBDZW50ZXIoY2hpbGQ6IERlZmF1bHRMb2FkZXIoKSksCiAgICApOwogIH0KCiAgc3RhdGljIHZvaWQgaGlkZShCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgY29udGV4dC5sb2FkZXJPdmVybGF5LmhpZGUoKTsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/screens/error_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2NhbGl6YXRpb24vbG9jYWxpemF0aW9uLmRhcnQnOwoKY2xhc3MgRXJyb3JTY3JlZW4gZXh0ZW5kcyBTdGF0ZWxlc3NXaWRnZXQgewogIGNvbnN0IEVycm9yU2NyZWVuKHsKICAgIHN1cGVyLmtleSwKICAgIHRoaXMudGl0bGUsCiAgICB0aGlzLm1lc3NhZ2UsCiAgICB0aGlzLm9uUmV0cnksCiAgICB0aGlzLm9uR29Ib21lLAogICAgdGhpcy5lcnJvciwKICB9KTsKCiAgZmluYWwgU3RyaW5nPyB0aXRsZTsKICBmaW5hbCBTdHJpbmc/IG1lc3NhZ2U7CiAgZmluYWwgVm9pZENhbGxiYWNrPyBvblJldHJ5OwogIGZpbmFsIFZvaWRDYWxsYmFjaz8gb25Hb0hvbWU7CiAgZmluYWwgU3RyaW5nPyBlcnJvcjsKCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKEJ1aWxkQ29udGV4dCBjb250ZXh0KSB7CiAgICBmaW5hbCB0aGVtZSA9IFRoZW1lLm9mKGNvbnRleHQpOwogICAgZmluYWwgY29sb3JTY2hlbWUgPSB0aGVtZS5jb2xvclNjaGVtZTsKICAgIGZpbmFsIHRleHRUaGVtZSA9IHRoZW1lLnRleHRUaGVtZTsKCiAgICBmaW5hbCByZXNvbHZlZFRpdGxlID0gdGl0bGUgPz8gc3RyaW5ncy5lcnJvcnMuZGVmYXVsdF9lcnJvcl9tZXNzYWdlOwogICAgZmluYWwgcmVzb2x2ZWRNZXNzYWdlID0gbWVzc2FnZSA/PyBzdHJpbmdzLmVycm9ycy5kZWZhdWx0X2Vycm9yX2Rlc2NyaXB0aW9uOwoKICAgIHJldHVybiBTY2FmZm9sZCgKICAgICAgYmFja2dyb3VuZENvbG9yOiBjb2xvclNjaGVtZS5zdXJmYWNlLAogICAgICBhcHBCYXI6IEFwcEJhcigKICAgICAgICBiYWNrZ3JvdW5kQ29sb3I6IGNvbG9yU2NoZW1lLnN1cmZhY2UsCiAgICAgICAgZm9yZWdyb3VuZENvbG9yOiBjb2xvclNjaGVtZS5vblN1cmZhY2UsCiAgICAgICAgZWxldmF0aW9uOiAwLAogICAgICAgIHN1cmZhY2VUaW50Q29sb3I6IGNvbG9yU2NoZW1lLnN1cmZhY2VUaW50LAogICAgICApLAogICAgICBib2R5OiBTYWZlQXJlYSgKICAgICAgICBjaGlsZDogUGFkZGluZygKICAgICAgICAgIHBhZGRpbmc6IGNvbnN0IEVkZ2VJbnNldHMuYWxsKDE2LjApLAogICAgICAgICAgY2hpbGQ6IENlbnRlcigKICAgICAgICAgICAgY2hpbGQ6IFNpbmdsZUNoaWxkU2Nyb2xsVmlldygKICAgICAgICAgICAgICBjaGlsZDogQ29sdW1uKAogICAgICAgICAgICAgICAgbWFpbkF4aXNBbGlnbm1lbnQ6IE1haW5BeGlzQWxpZ25tZW50LmNlbnRlciwKICAgICAgICAgICAgICAgIGNoaWxkcmVuOiBbCiAgICAgICAgICAgICAgICAgIENvbnRhaW5lcigKICAgICAgICAgICAgICAgICAgICB3aWR0aDogODAsCiAgICAgICAgICAgICAgICAgICAgaGVpZ2h0OiA4MCwKICAgICAgICAgICAgICAgICAgICBkZWNvcmF0aW9uOiBCb3hEZWNvcmF0aW9uKAogICAgICAgICAgICAgICAgICAgICAgY29sb3I6IGNvbG9yU2NoZW1lLmVycm9yQ29udGFpbmVyLndpdGhWYWx1ZXMoYWxwaGE6IDAuMTUpLAogICAgICAgICAgICAgICAgICAgICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoMTYpLAogICAgICAgICAgICAgICAgICAgICAgYm9yZGVyOiBCb3JkZXIuYWxsKAogICAgICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUuZXJyb3Iud2l0aFZhbHVlcyhhbHBoYTogMC4yKSwKICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGg6IDEsCiAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgY2hpbGQ6IEljb24oCiAgICAgICAgICAgICAgICAgICAgICBJY29ucy5lcnJvcl9vdXRsaW5lX3JvdW5kZWQsCiAgICAgICAgICAgICAgICAgICAgICBzaXplOiA0MCwKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5lcnJvciwKICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICBjb25zdCBTaXplZEJveChoZWlnaHQ6IDI0KSwKICAgICAgICAgICAgICAgICAgVGV4dCgKICAgICAgICAgICAgICAgICAgICByZXNvbHZlZFRpdGxlLAogICAgICAgICAgICAgICAgICAgIHN0eWxlOiB0ZXh0VGhlbWUudGl0bGVMYXJnZT8uY29weVdpdGgoCiAgICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUub25TdXJmYWNlLAogICAgICAgICAgICAgICAgICAgICAgZm9udFdlaWdodDogRm9udFdlaWdodC53NjAwLAogICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgdGV4dEFsaWduOiBUZXh0QWxpZ24uY2VudGVyLAogICAgICAgICAgICAgICAgICAgIG1heExpbmVzOiAyLAogICAgICAgICAgICAgICAgICAgIG92ZXJmbG93OiBUZXh0T3ZlcmZsb3cuZWxsaXBzaXMsCiAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KGhlaWdodDogOCksCiAgICAgICAgICAgICAgICAgIFRleHQoCiAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZWRNZXNzYWdlLAogICAgICAgICAgICAgICAgICAgIHN0eWxlOiB0ZXh0VGhlbWUuYm9keU1lZGl1bT8uY29weVdpdGgoCiAgICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUub25TdXJmYWNlVmFyaWFudCwKICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgIHRleHRBbGlnbjogVGV4dEFsaWduLmNlbnRlciwKICAgICAgICAgICAgICAgICAgICBtYXhMaW5lczogMywKICAgICAgICAgICAgICAgICAgICBvdmVyZmxvdzogVGV4dE92ZXJmbG93LmVsbGlwc2lzLAogICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICBpZiAoZXJyb3IgIT0gbnVsbCkgLi4uWwogICAgICAgICAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KGhlaWdodDogMTYpLAogICAgICAgICAgICAgICAgICAgIENvbnRhaW5lcigKICAgICAgICAgICAgICAgICAgICAgIHdpZHRoOiBkb3VibGUuaW5maW5pdHksCiAgICAgICAgICAgICAgICAgICAgICBwYWRkaW5nOiBjb25zdCBFZGdlSW5zZXRzLmFsbCgxMiksCiAgICAgICAgICAgICAgICAgICAgICBkZWNvcmF0aW9uOiBCb3hEZWNvcmF0aW9uKAogICAgICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUuc3VyZmFjZUNvbnRhaW5lckhpZ2gsCiAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlclJhZGl1czogQm9yZGVyUmFkaXVzLmNpcmN1bGFyKDEyKSwKICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyOiBCb3JkZXIuYWxsKAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yOiBjb2xvclNjaGVtZS5vdXRsaW5lLndpdGhWYWx1ZXMoYWxwaGE6IDAuMyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGg6IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgY2hpbGQ6IFNlbGVjdGFibGVUZXh0KAogICAgICAgICAgICAgICAgICAgICAgICBlcnJvciEsCiAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlOiB0ZXh0VGhlbWUuYm9keVNtYWxsPy5jb3B5V2l0aCgKICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcjogY29sb3JTY2hlbWUub25TdXJmYWNlLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnRGYW1pbHk6ICdtb25vc3BhY2UnLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnRTaXplOiAxMiwKICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dEFsaWduOiBUZXh0QWxpZ24ubGVmdCwKICAgICAgICAgICAgICAgICAgICAgICAgbWF4TGluZXM6IDQsCiAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgIGNvbnN0IFNpemVkQm94KGhlaWdodDogMzIpLAogICAgICAgICAgICAgICAgICBXcmFwKAogICAgICAgICAgICAgICAgICAgIHNwYWNpbmc6IDEyLAogICAgICAgICAgICAgICAgICAgIHJ1blNwYWNpbmc6IDgsCiAgICAgICAgICAgICAgICAgICAgYWxpZ25tZW50OiBXcmFwQWxpZ25tZW50LmNlbnRlciwKICAgICAgICAgICAgICAgICAgICBjaGlsZHJlbjogWwogICAgICAgICAgICAgICAgICAgICAgaWYgKG9uUmV0cnkgIT0gbnVsbCkKICAgICAgICAgICAgICAgICAgICAgICAgRmlsbGVkQnV0dG9uKAogICAgICAgICAgICAgICAgICAgICAgICAgIG9uUHJlc3NlZDogb25SZXRyeSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZTogRmlsbGVkQnV0dG9uLnN0eWxlRnJvbSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhZGRpbmc6IGNvbnN0IEVkZ2VJbnNldHMuc3ltbWV0cmljKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3Jpem9udGFsOiAyNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVydGljYWw6IDEyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbmltdW1TaXplOiBjb25zdCBTaXplKDg4LCA0NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBjaGlsZDogVGV4dChzdHJpbmdzLmdlbmVyaWMudHJ5X2FnYWluKSwKICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgIGlmIChvbkdvSG9tZSAhPSBudWxsKQogICAgICAgICAgICAgICAgICAgICAgICBPdXRsaW5lZEJ1dHRvbigKICAgICAgICAgICAgICAgICAgICAgICAgICBvblByZXNzZWQ6IG9uR29Ib21lLAogICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlOiBPdXRsaW5lZEJ1dHRvbi5zdHlsZUZyb20oCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWRkaW5nOiBjb25zdCBFZGdlSW5zZXRzLnN5bW1ldHJpYygKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG9yaXpvbnRhbDogMjQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcnRpY2FsOiAxMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5pbXVtU2l6ZTogY29uc3QgU2l6ZSg4OCwgNDQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2lkZTogQm9yZGVyU2lkZSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I6IGNvbG9yU2NoZW1lLm91dGxpbmUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoOiAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNoaWxkOiBUZXh0KHN0cmluZ3MuZ2VuZXJpYy5yZXRyeSksCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgKSwKICAgICAgICAgICksCiAgICAgICAgKSwKICAgICAgKSwKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/screens/index.dart", + "data": "ZXhwb3J0ICdlcnJvcl9zY3JlZW4uZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/toast/toasts.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTppY29uc2F4X2ZsdXR0ZXIvaWNvbnNheF9mbHV0dGVyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6dG9hc3RpZmljYXRpb24vdG9hc3RpZmljYXRpb24uZGFydCc7CgpjbGFzcyBUb2FzdCB7CiAgc3RhdGljIHZvaWQgbm90aWZpY2F0aW9uKHtyZXF1aXJlZCBTdHJpbmcgdGl0bGUsIHJlcXVpcmVkIFN0cmluZyBib2R5fSkgPT4KICAgICAgdG9hc3RpZmljYXRpb24uc2hvdygKICAgICAgICB0eXBlOiBUb2FzdGlmaWNhdGlvblR5cGUuaW5mbywKICAgICAgICBzdHlsZTogVG9hc3RpZmljYXRpb25TdHlsZS5maWxsQ29sb3JlZCwKICAgICAgICB0aXRsZTogVGV4dCh0aXRsZSksCiAgICAgICAgZGVzY3JpcHRpb246IFRleHQoYm9keSksCiAgICAgICAgYWxpZ25tZW50OiBBbGlnbm1lbnQudG9wQ2VudGVyLAogICAgICAgIGF1dG9DbG9zZUR1cmF0aW9uOiBjb25zdCBEdXJhdGlvbihzZWNvbmRzOiAzLCBtaWxsaXNlY29uZHM6IDUwMCksCiAgICAgICAgYW5pbWF0aW9uQnVpbGRlcjogKGNvbnRleHQsIGFuaW1hdGlvbiwgYWxpZ25tZW50LCBjaGlsZCkgewogICAgICAgICAgcmV0dXJuIFNjYWxlVHJhbnNpdGlvbihzY2FsZTogYW5pbWF0aW9uLCBjaGlsZDogY2hpbGQpOwogICAgICAgIH0sCiAgICAgICAgaWNvbjogSWNvbihJY29uc2F4Lm5vdGlmaWNhdGlvbiksCiAgICAgICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoMTIuMCksCiAgICAgICAgc2hvd1Byb2dyZXNzQmFyOiB0cnVlLAogICAgICAgIGRyYWdUb0Nsb3NlOiB0cnVlLAogICAgICAgIGFwcGx5Qmx1ckVmZmVjdDogdHJ1ZSwKICAgICAgKTsKCiAgc3RhdGljIHZvaWQgZXJyb3IoCiAgICBCdWlsZENvbnRleHQgY29udGV4dCwgewogICAgcmVxdWlyZWQgU3RyaW5nIG1lc3NhZ2UsCiAgICBTdHJpbmc/IGRlc2NyaXB0aW9uLAogIH0pID0+IHRvYXN0aWZpY2F0aW9uLnNob3coCiAgICBjb250ZXh0OiBjb250ZXh0LAogICAgdHlwZTogVG9hc3RpZmljYXRpb25UeXBlLmVycm9yLAogICAgc3R5bGU6IFRvYXN0aWZpY2F0aW9uU3R5bGUuZmlsbENvbG9yZWQsCiAgICB0aXRsZTogVGV4dChtZXNzYWdlKSwKICAgIGRlc2NyaXB0aW9uOiBkZXNjcmlwdGlvbiAhPSBudWxsID8gVGV4dChkZXNjcmlwdGlvbikgOiBudWxsLAogICAgYWxpZ25tZW50OiBBbGlnbm1lbnQudG9wQ2VudGVyLAogICAgYXV0b0Nsb3NlRHVyYXRpb246IGNvbnN0IER1cmF0aW9uKHNlY29uZHM6IDMsIG1pbGxpc2Vjb25kczogNTAwKSwKICAgIGFuaW1hdGlvbkJ1aWxkZXI6IChjb250ZXh0LCBhbmltYXRpb24sIGFsaWdubWVudCwgY2hpbGQpIHsKICAgICAgcmV0dXJuIFNjYWxlVHJhbnNpdGlvbihzY2FsZTogYW5pbWF0aW9uLCBjaGlsZDogY2hpbGQpOwogICAgfSwKICAgIGljb246IEljb24oSWNvbnNheC53YXJuaW5nXzIpLAogICAgYm9yZGVyUmFkaXVzOiBCb3JkZXJSYWRpdXMuY2lyY3VsYXIoMTIuMCksCiAgICBzaG93UHJvZ3Jlc3NCYXI6IHRydWUsCiAgICBkcmFnVG9DbG9zZTogdHJ1ZSwKICAgIGFwcGx5Qmx1ckVmZmVjdDogdHJ1ZSwKICApOwoKICBzdGF0aWMgdm9pZCBzdWNjZXNzKAogICAgQnVpbGRDb250ZXh0IGNvbnRleHQsIHsKICAgIHJlcXVpcmVkIFN0cmluZyBtZXNzYWdlLAogICAgU3RyaW5nPyBkZXNjcmlwdGlvbiwKICB9KSA9PiB0b2FzdGlmaWNhdGlvbi5zaG93KAogICAgY29udGV4dDogY29udGV4dCwKICAgIHR5cGU6IFRvYXN0aWZpY2F0aW9uVHlwZS5zdWNjZXNzLAogICAgc3R5bGU6IFRvYXN0aWZpY2F0aW9uU3R5bGUuZmlsbENvbG9yZWQsCiAgICB0aXRsZTogVGV4dChtZXNzYWdlKSwKICAgIGRlc2NyaXB0aW9uOiBkZXNjcmlwdGlvbiAhPSBudWxsID8gVGV4dChkZXNjcmlwdGlvbikgOiBudWxsLAogICAgYWxpZ25tZW50OiBBbGlnbm1lbnQudG9wQ2VudGVyLAogICAgYXV0b0Nsb3NlRHVyYXRpb246IGNvbnN0IER1cmF0aW9uKHNlY29uZHM6IDMsIG1pbGxpc2Vjb25kczogNTAwKSwKICAgIGFuaW1hdGlvbkJ1aWxkZXI6IChjb250ZXh0LCBhbmltYXRpb24sIGFsaWdubWVudCwgY2hpbGQpIHsKICAgICAgcmV0dXJuIFNjYWxlVHJhbnNpdGlvbihzY2FsZTogYW5pbWF0aW9uLCBjaGlsZDogY2hpbGQpOwogICAgfSwKICAgIGljb246IEljb24oSWNvbnNheC50aWNrX2NpcmNsZSksCiAgICBib3JkZXJSYWRpdXM6IEJvcmRlclJhZGl1cy5jaXJjdWxhcigxMi4wKSwKICAgIHNob3dQcm9ncmVzc0JhcjogdHJ1ZSwKICAgIGRyYWdUb0Nsb3NlOiB0cnVlLAogICAgYXBwbHlCbHVyRWZmZWN0OiB0cnVlLAogICk7CgogIHN0YXRpYyB2b2lkIHdhcm5pbmcoCiAgICBCdWlsZENvbnRleHQgY29udGV4dCwgewogICAgcmVxdWlyZWQgU3RyaW5nIG1lc3NhZ2UsCiAgICBTdHJpbmc/IGRlc2NyaXB0aW9uLAogIH0pID0+IHRvYXN0aWZpY2F0aW9uLnNob3coCiAgICBjb250ZXh0OiBjb250ZXh0LAogICAgdHlwZTogVG9hc3RpZmljYXRpb25UeXBlLndhcm5pbmcsCiAgICBzdHlsZTogVG9hc3RpZmljYXRpb25TdHlsZS5maWxsQ29sb3JlZCwKICAgIHRpdGxlOiBUZXh0KG1lc3NhZ2UpLAogICAgZGVzY3JpcHRpb246IGRlc2NyaXB0aW9uICE9IG51bGwgPyBUZXh0KGRlc2NyaXB0aW9uKSA6IG51bGwsCiAgICBhbGlnbm1lbnQ6IEFsaWdubWVudC50b3BDZW50ZXIsCiAgICBhdXRvQ2xvc2VEdXJhdGlvbjogY29uc3QgRHVyYXRpb24oc2Vjb25kczogMywgbWlsbGlzZWNvbmRzOiA1MDApLAogICAgYW5pbWF0aW9uQnVpbGRlcjogKGNvbnRleHQsIGFuaW1hdGlvbiwgYWxpZ25tZW50LCBjaGlsZCkgewogICAgICByZXR1cm4gU2NhbGVUcmFuc2l0aW9uKHNjYWxlOiBhbmltYXRpb24sIGNoaWxkOiBjaGlsZCk7CiAgICB9LAogICAgaWNvbjogSWNvbihJY29uc2F4LmRhbmdlciksCiAgICBib3JkZXJSYWRpdXM6IEJvcmRlclJhZGl1cy5jaXJjdWxhcigxMi4wKSwKICAgIHNob3dQcm9ncmVzc0JhcjogdHJ1ZSwKICAgIGRyYWdUb0Nsb3NlOiB0cnVlLAogICAgYXBwbHlCbHVyRWZmZWN0OiB0cnVlLAogICk7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/utils/extensions/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdzdHJpbmcuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/utils/extensions/string.dart", + "data": + "Ly8vIFN0cmluZyBleHRlbnNpb25zIGZvciBjb21tb24gdXRpbGl0eSBvcGVyYXRpb25zCmV4dGVuc2lvbiBTdHJpbmdFeHRlbnNpb25zIG9uIFN0cmluZz8gewogIC8vLyBSZXR1cm5zIHRydWUgaWYgdGhlIHN0cmluZyBpcyBub3QgbnVsbCBhbmQgbm90IGVtcHR5CiAgLy8vCiAgLy8vIEV4YW1wbGU6CiAgLy8vIGBgYGRhcnQKICAvLy8gU3RyaW5nPyB0ZXh0ID0gIkhlbGxvIjsKICAvLy8gcHJpbnQodGV4dC5pc05vdE51bGxBbmROb3RFbXB0eSk7IC8vIHRydWUKICAvLy8KICAvLy8gU3RyaW5nPyBlbXB0eVRleHQgPSAiIjsKICAvLy8gcHJpbnQoZW1wdHlUZXh0LmlzTm90TnVsbEFuZE5vdEVtcHR5KTsgLy8gZmFsc2UKICAvLy8KICAvLy8gU3RyaW5nPyBudWxsVGV4dCA9IG51bGw7CiAgLy8vIHByaW50KG51bGxUZXh0LmlzTm90TnVsbEFuZE5vdEVtcHR5KTsgLy8gZmFsc2UKICAvLy8gYGBgCiAgYm9vbCBnZXQgaXNOb3ROdWxsQW5kTm90RW1wdHkgewogICAgcmV0dXJuIHRoaXMgIT0gbnVsbCAmJiB0aGlzIS5pc05vdEVtcHR5OwogIH0KCiAgLy8vIFJldHVybnMgYW4gZW1wdHkgc3RyaW5nIGlmIHRoZSBzdHJpbmcgaXMgbnVsbCwgb3RoZXJ3aXNlIHJldHVybnMgdGhlIHN0cmluZyBpdHNlbGYKICAvLy8KICAvLy8gRXhhbXBsZToKICAvLy8gYGBgZGFydAogIC8vLyBTdHJpbmc/IHRleHQgPSAiSGVsbG8iOwogIC8vLyBwcmludCh0ZXh0LmFzRW1wdHlJZk51bGwpOyAvLyAiSGVsbG8iCiAgLy8vCiAgLy8vIFN0cmluZz8gbnVsbFRleHQgPSBudWxsOwogIC8vLyBwcmludChudWxsVGV4dC5hc0VtcHR5SWZOdWxsKTsgLy8gIiIKICAvLy8gYGBgCiAgU3RyaW5nIGdldCBhc0VtcHR5SWZOdWxsIHsKICAgIHJldHVybiB0aGlzID8/ICcnOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/utils/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICdleHRlbnNpb25zL2luZGV4LmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/lib/wrapper/wrappers.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vZGVzaWduX3N5c3RlbS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2FkZXJfb3ZlcmxheS9sb2FkZXJfb3ZlcmxheS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnRvYXN0aWZpY2F0aW9uL3RvYXN0aWZpY2F0aW9uLmRhcnQnOwoKY2xhc3MgRGVzaWduU3lzdGVtV3JhcHBlciBleHRlbmRzIFN0YXRlZnVsV2lkZ2V0IHsKICBjb25zdCBEZXNpZ25TeXN0ZW1XcmFwcGVyKHtzdXBlci5rZXksIHJlcXVpcmVkIHRoaXMuYnVpbGRlcn0pOwoKICBmaW5hbCBXaWRnZXQgRnVuY3Rpb24oQnVpbGRDb250ZXh0IGNvbnRleHQsIFRoZW1lRGF0YSB0aGVtZSkgYnVpbGRlcjsKCiAgQG92ZXJyaWRlCiAgU3RhdGU8RGVzaWduU3lzdGVtV3JhcHBlcj4gY3JlYXRlU3RhdGUoKSA9PiBfRGVzaWduU3lzdGVtV3JhcHBlclN0YXRlKCk7Cn0KCmNsYXNzIF9EZXNpZ25TeXN0ZW1XcmFwcGVyU3RhdGUgZXh0ZW5kcyBTdGF0ZTxEZXNpZ25TeXN0ZW1XcmFwcGVyPgogICAgd2l0aCBXaWRnZXRzQmluZGluZ09ic2VydmVyIHsKICBAb3ZlcnJpZGUKICB2b2lkIGluaXRTdGF0ZSgpIHsKICAgIHN1cGVyLmluaXRTdGF0ZSgpOwogICAgV2lkZ2V0c0JpbmRpbmcuaW5zdGFuY2UuYWRkT2JzZXJ2ZXIodGhpcyk7CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIGRpc3Bvc2UoKSB7CiAgICBXaWRnZXRzQmluZGluZy5pbnN0YW5jZS5yZW1vdmVPYnNlcnZlcih0aGlzKTsKICAgIHN1cGVyLmRpc3Bvc2UoKTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgZGlkQ2hhbmdlUGxhdGZvcm1CcmlnaHRuZXNzKCkgewogICAgc2V0U3RhdGUoKCkge30pOwogIH0KCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKEJ1aWxkQ29udGV4dCBjb250ZXh0KSB7CiAgICBmaW5hbCBicmlnaHRuZXNzID0gTWVkaWFRdWVyeS5wbGF0Zm9ybUJyaWdodG5lc3NPZihjb250ZXh0KTsKICAgIGZpbmFsIG1hdGVyaWFsVGhlbWUgPSBEZXNpZ25TeXN0ZW0oY29udGV4dCk7CiAgICBmaW5hbCB0aGVtZSA9IGJyaWdodG5lc3MgIT0gQnJpZ2h0bmVzcy5kYXJrCiAgICAgICAgPyBtYXRlcmlhbFRoZW1lLmxpZ2h0KCkKICAgICAgICA6IG1hdGVyaWFsVGhlbWUuZGFyaygpOwoKICAgIHJldHVybiBUaGVtZSgKICAgICAgZGF0YTogdGhlbWUsCiAgICAgIGNoaWxkOiBHbG9iYWxMb2FkZXJPdmVybGF5KAogICAgICAgIG92ZXJsYXlDb2xvcjogdGhlbWUuY29sb3JTY2hlbWUuc3VyZmFjZS53aXRoVmFsdWVzKGFscGhhOiAwLjgpLAogICAgICAgIGR1cmF0aW9uOiBjb25zdCBEdXJhdGlvbihtaWxsaXNlY29uZHM6IDMwMCksCiAgICAgICAgcmV2ZXJzZUR1cmF0aW9uOiBjb25zdCBEdXJhdGlvbihtaWxsaXNlY29uZHM6IDIwMCksCiAgICAgICAgc3dpdGNoSW5DdXJ2ZTogQ3VydmVzLmVhc2VJbk91dCwKICAgICAgICBzd2l0Y2hPdXRDdXJ2ZTogQ3VydmVzLmVhc2VJbk91dCwKICAgICAgICBjaGlsZDogVG9hc3RpZmljYXRpb25XcmFwcGVyKGNoaWxkOiB3aWRnZXQuYnVpbGRlcihjb250ZXh0LCB0aGVtZSkpLAogICAgICApLAogICAgKTsKICB9Cn0K", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/design_system/pubspec.yaml", + "data": + "bmFtZTogZGVzaWduX3N5c3RlbQpkZXNjcmlwdGlvbjogIlRoZW1lLCBjb21wb25lbnRzIGFuZCBkaWFsb2dzIHNoYXJlZCBhY3Jvc3Mge3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBjYWNoZWRfbmV0d29ya19pbWFnZTogXjMuNC4xCiAgZmx1dHRlcl9zdmc6IF4yLjIuMAogIGdvb2dsZV9mb250czogXjYuMi4xCiAgaWNvbnNheF9mbHV0dGVyOiBeMS4wLjEKICBsb2FkZXJfb3ZlcmxheTogXjUuMC4wCiAgdG9hc3RpZmljYXRpb246IF4zLjAuMwogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uL2xvY2FsaXphdGlvbgp7eyNuZXR3b3JrfX0gIG5ldHdvcms6CiAgICBwYXRoOiAuLi9uZXR3b3JrCnt7L25ldHdvcmt9fQpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF41LjAuMAoKZmx1dHRlcjoK", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/design_system/README.md", + "data": + "IyBEZXNpZ24gU3lzdGVtCgpBIE1lbG9zLW1hbmFnZWQgcHJvamVjdCBmb3IgbW9uby1yZXBvLCBjcmVhdGVkIHVzaW5nIE5vblN0b3AgQ0xJLgoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgojIyBJbnN0YWxsYXRpb24g8J+SuwoKKirinZcgSW4gb3JkZXIgdG8gc3RhcnQgdXNpbmcgRGVzaWduIFN5c3RlbSB5b3UgbXVzdCBoYXZlIHRoZSBbRmx1dHRlciBTREtdW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXSBpbnN0YWxsZWQgb24geW91ciBtYWNoaW5lLioqCgpJbnN0YWxsIHZpYSBgZGFydCBwdWIgYWRkYDoKCmBgYHNoCmRhcnQgcHViIGFkZCBkZXNpZ25fc3lzdGVtCmBgYAoKW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXTogaHR0cHM6Ly9kb2NzLmZsdXR0ZXIuZGV2L2dldC1zdGFydGVkL2luc3RhbGw=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/design_system/test/design_system_test.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vZGVzaWduX3N5c3RlbS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3Rlc3QvZmx1dHRlcl90ZXN0LmRhcnQnOwoKdm9pZCBtYWluKCkgewogIHRlc3RXaWRnZXRzKCdEZXNpZ25TeXN0ZW1XcmFwcGVyIGhhbmRzIHRoZSBidWlsZGVyIGEgdGhlbWVkIGNvbnRleHQnLCAoCiAgICB0ZXN0ZXIsCiAgKSBhc3luYyB7CiAgICBsYXRlIFRoZW1lRGF0YSBidWlsdDsKCiAgICBhd2FpdCB0ZXN0ZXIucHVtcFdpZGdldCgKICAgICAgTWF0ZXJpYWxBcHAoCiAgICAgICAgaG9tZTogRGVzaWduU3lzdGVtV3JhcHBlcigKICAgICAgICAgIGJ1aWxkZXI6IChjb250ZXh0LCB0aGVtZSkgewogICAgICAgICAgICBidWlsdCA9IHRoZW1lOwogICAgICAgICAgICByZXR1cm4gY29uc3QgU2l6ZWRCb3guc2hyaW5rKCk7CiAgICAgICAgICB9LAogICAgICAgICksCiAgICAgICksCiAgICApOwoKICAgIGV4cGVjdChidWlsdC51c2VNYXRlcmlhbDMsIGlzVHJ1ZSk7CiAgICBleHBlY3QoYnVpbHQuY29sb3JTY2hlbWUucHJpbWFyeSwgaXNOb3QoQ29sb3JzLnRyYW5zcGFyZW50KSk7CiAgfSk7Cn0K", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/di/lib/di.dart", + "data": + "Ly8vIERlcGVuZGVuY3kgSW5qZWN0aW9uIHBhY2thZ2UgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19Ci8vLwovLy8gVGhpcyBwYWNrYWdlIHByb3ZpZGVzIGEgY2xlYW4gYWJzdHJhY3Rpb24gb3ZlciBkZXBlbmRlbmN5IGluamVjdGlvbgovLy8gdXNpbmcgR2V0SXQgYXMgdGhlIHVuZGVybHlpbmcgaW1wbGVtZW50YXRpb24uCmxpYnJhcnk7CgppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CgovLyBDb3JlIGludGVyZmFjZXMKZXhwb3J0ICdzcmMvZGVwZW5kZW5jeV9pbmplY3Rpb24uZGFydCc7Ci8vIEdldEl0IGltcGxlbWVudGF0aW9uCmV4cG9ydCAnc3JjL2RlcGVuZGVuY3lfaW5qZWN0aW9uX2ltcC5kYXJ0JzsKCmZpbmFsIGRpID0gR2V0SXREZXBlbmRlbmN5SW5qZWN0aW9uKCk7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/di/lib/src/dependency_injection.dart", + "data": + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCnR5cGVkZWYgRGlzcG9zZUZ1bmM8VD4gPSBGdXR1cmVPciBGdW5jdGlvbihUIHBhcmFtKTsKCmFic3RyYWN0IGNsYXNzIERlcGVuZGVuY3lJbmplY3Rpb24gewogIEZ1dHVyZTx2b2lkPiBpbml0KCk7CgogIEZ1dHVyZTx2b2lkPiBkaXNwb3NlKCk7CgogIHZvaWQgcmVnaXN0ZXI8VCBleHRlbmRzIE9iamVjdD4oCiAgICBmaW5hbCBUIGluc3RhbmNlLCB7CiAgICBmaW5hbCBEaXNwb3NlRnVuYz8gZGlzcG9zZSwKICB9KTsKCiAgRnV0dXJlPHZvaWQ+IHVucmVnaXN0ZXI8VCBleHRlbmRzIE9iamVjdD4oZmluYWwgVD8gaW5zdGFuY2UpOwoKICBUIGdldDxUIGV4dGVuZHMgT2JqZWN0PigpOwoKICBib29sIGhhczxUIGV4dGVuZHMgT2JqZWN0PigpOwoKICBGdXR1cmU8dm9pZD4gcmVzZXQoKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/di/lib/src/dependency_injection_imp.dart", + "data": + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAnZGFydDpkZXZlbG9wZXInIGFzIGRldjsKCmltcG9ydCAncGFja2FnZTpkaS9zcmMvZGVwZW5kZW5jeV9pbmplY3Rpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpnZXRfaXQvZ2V0X2l0LmRhcnQnOwoKLy8vIEltcGxlbWVudGF0aW9uIG9mIERlcGVuZGVuY3lJbmplY3Rpb24gdXNpbmcgR2V0SXQKY2xhc3MgR2V0SXREZXBlbmRlbmN5SW5qZWN0aW9uIGltcGxlbWVudHMgRGVwZW5kZW5jeUluamVjdGlvbiB7CiAgZmluYWwgR2V0SXQgX2dldEl0OwogIGZpbmFsIE1hcDxUeXBlLCBEaXNwb3NlRnVuYz4gX2Rpc3Bvc2VGdW5jdGlvbnMgPSB7fTsKICBmaW5hbCBNYXA8VHlwZSwgT2JqZWN0PiBfcmVnaXN0ZXJlZEluc3RhbmNlcyA9IHt9OwoKICAvLy8gQ3JlYXRlcyBhIG5ldyBpbnN0YW5jZSB3aXRoIG9wdGlvbmFsIEdldEl0IGluc3RhbmNlCiAgLy8vIElmIG5vIGluc3RhbmNlIGlzIHByb3ZpZGVkLCB1c2VzIEdldEl0Lmluc3RhbmNlCiAgR2V0SXREZXBlbmRlbmN5SW5qZWN0aW9uKHtHZXRJdD8gZ2V0SXR9KSA6IF9nZXRJdCA9IGdldEl0ID8/IEdldEl0Lmluc3RhbmNlOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gaW5pdCgpIGFzeW5jIHsKICAgIC8vIEdldEl0IGRvZXNuJ3QgcmVxdWlyZSBleHBsaWNpdCBpbml0aWFsaXphdGlvbgogICAgLy8gYnV0IHdlIGNhbiB1c2UgdGhpcyB0byBzZXQgdXAgYW55IGdsb2JhbCBjb25maWd1cmF0aW9ucwogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGRpc3Bvc2UoKSBhc3luYyB7CiAgICAvLyBDYWxsIGRpc3Bvc2UgZnVuY3Rpb25zIGZvciBhbGwgcmVnaXN0ZXJlZCBpbnN0YW5jZXMKICAgIGZvciAoZmluYWwgZW50cnkgaW4gX2Rpc3Bvc2VGdW5jdGlvbnMuZW50cmllcykgewogICAgICBmaW5hbCB0eXBlID0gZW50cnkua2V5OwogICAgICBmaW5hbCBkaXNwb3NlRnVuYyA9IGVudHJ5LnZhbHVlOwogICAgICBmaW5hbCBpbnN0YW5jZSA9IF9yZWdpc3RlcmVkSW5zdGFuY2VzW3R5cGVdOwoKICAgICAgaWYgKGluc3RhbmNlICE9IG51bGwpIHsKICAgICAgICB0cnkgewogICAgICAgICAgYXdhaXQgZGlzcG9zZUZ1bmMoaW5zdGFuY2UpOwogICAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICAgIC8vIExvZyBlcnJvciBidXQgY29udGludWUgZGlzcG9zaW5nIG90aGVyIGluc3RhbmNlcwogICAgICAgICAgZGV2LmxvZygKICAgICAgICAgICAgJ0Vycm9yIGRpc3Bvc2luZyBpbnN0YW5jZSBvZiB0eXBlICR0eXBlJywKICAgICAgICAgICAgbmFtZTogJ2RpJywKICAgICAgICAgICAgZXJyb3I6IGUsCiAgICAgICAgICApOwogICAgICAgIH0KICAgICAgfQogICAgfQoKICAgIC8vIENsZWFyIGRpc3Bvc2UgZnVuY3Rpb25zIGFuZCByZWdpc3RlcmVkIGluc3RhbmNlcyBtYXBzCiAgICBfZGlzcG9zZUZ1bmN0aW9ucy5jbGVhcigpOwogICAgX3JlZ2lzdGVyZWRJbnN0YW5jZXMuY2xlYXIoKTsKCiAgICAvLyBSZXNldCBHZXRJdCBpbnN0YW5jZQogICAgYXdhaXQgX2dldEl0LnJlc2V0KCk7CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIHJlZ2lzdGVyPFQgZXh0ZW5kcyBPYmplY3Q+KAogICAgZmluYWwgVCBpbnN0YW5jZSwgewogICAgZmluYWwgRGlzcG9zZUZ1bmM/IGRpc3Bvc2UsCiAgfSkgewogICAgLy8gUmVnaXN0ZXIgdGhlIGluc3RhbmNlIGFzIGEgc2luZ2xldG9uCiAgICBfZ2V0SXQucmVnaXN0ZXJTaW5nbGV0b248VD4oaW5zdGFuY2UpOwoKICAgIC8vIFN0b3JlIHRoZSBpbnN0YW5jZSBhbmQgZGlzcG9zZSBmdW5jdGlvbiBpZiBwcm92aWRlZAogICAgX3JlZ2lzdGVyZWRJbnN0YW5jZXNbVF0gPSBpbnN0YW5jZTsKICAgIGlmIChkaXNwb3NlICE9IG51bGwpIHsKICAgICAgX2Rpc3Bvc2VGdW5jdGlvbnNbVF0gPSBkaXNwb3NlOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHVucmVnaXN0ZXI8VCBleHRlbmRzIE9iamVjdD4oZmluYWwgVD8gaW5zdGFuY2UpIGFzeW5jIHsKICAgIGlmICghX2dldEl0LmlzUmVnaXN0ZXJlZDxUPigpKSB7CiAgICAgIHJldHVybjsKICAgIH0KCiAgICAvLyBDYWxsIGRpc3Bvc2UgZnVuY3Rpb24gaWYgaXQgZXhpc3RzCiAgICBmaW5hbCBkaXNwb3NlRnVuYyA9IF9kaXNwb3NlRnVuY3Rpb25zW1RdOwogICAgaWYgKGRpc3Bvc2VGdW5jICE9IG51bGwpIHsKICAgICAgdHJ5IHsKICAgICAgICBmaW5hbCByZWdpc3RlcmVkSW5zdGFuY2UgPSBpbnN0YW5jZSA/PyBfcmVnaXN0ZXJlZEluc3RhbmNlc1tUXTsKICAgICAgICBpZiAocmVnaXN0ZXJlZEluc3RhbmNlICE9IG51bGwpIHsKICAgICAgICAgIGF3YWl0IGRpc3Bvc2VGdW5jKHJlZ2lzdGVyZWRJbnN0YW5jZSk7CiAgICAgICAgfQogICAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgLy8gTG9nIGVycm9yIGJ1dCBjb250aW51ZSB3aXRoIHVucmVnaXN0cmF0aW9uCiAgICAgICAgZGV2LmxvZygnRXJyb3IgZGlzcG9zaW5nIGluc3RhbmNlIG9mIHR5cGUgJFQnLCBuYW1lOiAnZGknLCBlcnJvcjogZSk7CiAgICAgIH0KICAgICAgX2Rpc3Bvc2VGdW5jdGlvbnMucmVtb3ZlKFQpOwogICAgfQoKICAgIC8vIFJlbW92ZSBmcm9tIG91ciB0cmFja2luZwogICAgX3JlZ2lzdGVyZWRJbnN0YW5jZXMucmVtb3ZlKFQpOwoKICAgIC8vIFVucmVnaXN0ZXIgZnJvbSBHZXRJdAogICAgYXdhaXQgX2dldEl0LnVucmVnaXN0ZXI8VD4oaW5zdGFuY2U6IGluc3RhbmNlKTsKICB9CgogIEBvdmVycmlkZQogIFQgZ2V0PFQgZXh0ZW5kcyBPYmplY3Q+KCkgewogICAgcmV0dXJuIF9nZXRJdC5nZXQ8VD4oKTsKICB9CgogIEBvdmVycmlkZQogIGJvb2wgaGFzPFQgZXh0ZW5kcyBPYmplY3Q+KCkgewogICAgcmV0dXJuIF9nZXRJdC5pc1JlZ2lzdGVyZWQ8VD4oKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiByZXNldCgpIGFzeW5jIHsKICAgIGF3YWl0IGRpc3Bvc2UoKTsKICAgIGF3YWl0IGluaXQoKTsKICB9CgogIC8vLyBHZXQgdGhlIHVuZGVybHlpbmcgR2V0SXQgaW5zdGFuY2UgKGZvciBhZHZhbmNlZCB1c2FnZSkKICBHZXRJdCBnZXQgZ2V0SXQgPT4gX2dldEl0Owp9Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/di/pubspec.yaml", + "data": + "bmFtZTogZGkKZGVzY3JpcHRpb246ICJTZXJ2aWNlIGxvY2F0b3IgYWJzdHJhY3Rpb24gZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LCBiYWNrZWQgYnkgR2V0SXQuIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBnZXRfaXQ6IF44LjAuMwoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmZsdXR0ZXI6Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/di/README.md", + "data": + "IyBEZXBlbmRlbmN5IEluamVjdGlvbiAoREkpIFBhY2thZ2UKCkEgY2xlYW4gZGVwZW5kZW5jeSBpbmplY3Rpb24gYWJzdHJhY3Rpb24gZm9yIHRoZSB7e25hbWUudGl0bGVDYXNlKCl9fSBtb25vcmVwbywgYnVpbHQgb24gdG9wIG9mIEdldEl0LgoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/di/test/di_test.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlcl90ZXN0L2ZsdXR0ZXJfdGVzdC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmdldF9pdC9nZXRfaXQuZGFydCc7CgovLyBUZXN0IGNsYXNzZXMgZm9yIGRlcGVuZGVuY3kgaW5qZWN0aW9uCmNsYXNzIFRlc3RTZXJ2aWNlIHsKICBmaW5hbCBTdHJpbmcgbmFtZTsKICBib29sIGlzRGlzcG9zZWQgPSBmYWxzZTsKCiAgVGVzdFNlcnZpY2UodGhpcy5uYW1lKTsKCiAgdm9pZCBkaXNwb3NlKCkgewogICAgaXNEaXNwb3NlZCA9IHRydWU7CiAgfQp9CgpjbGFzcyBUZXN0UmVwb3NpdG9yeSB7CiAgZmluYWwgVGVzdFNlcnZpY2Ugc2VydmljZTsKICBib29sIGlzRGlzcG9zZWQgPSBmYWxzZTsKCiAgVGVzdFJlcG9zaXRvcnkodGhpcy5zZXJ2aWNlKTsKCiAgdm9pZCBkaXNwb3NlKCkgewogICAgaXNEaXNwb3NlZCA9IHRydWU7CiAgfQp9Cgp2b2lkIG1haW4oKSB7CiAgZ3JvdXAoJ0dldEl0RGVwZW5kZW5jeUluamVjdGlvbicsICgpIHsKICAgIGxhdGUgR2V0SXREZXBlbmRlbmN5SW5qZWN0aW9uIGRpOwogICAgbGF0ZSBHZXRJdCB0ZXN0R2V0SXQ7CgogICAgc2V0VXAoKCkgewogICAgICAvLyBDcmVhdGUgYSBzZXBhcmF0ZSBHZXRJdCBpbnN0YW5jZSBmb3IgdGVzdGluZwogICAgICB0ZXN0R2V0SXQgPSBHZXRJdC5hc05ld0luc3RhbmNlKCk7CiAgICAgIGRpID0gR2V0SXREZXBlbmRlbmN5SW5qZWN0aW9uKGdldEl0OiB0ZXN0R2V0SXQpOwogICAgfSk7CgogICAgdGVhckRvd24oKCkgYXN5bmMgewogICAgICBhd2FpdCBkaS5kaXNwb3NlKCk7CiAgICB9KTsKCiAgICBncm91cCgnUmVnaXN0cmF0aW9uJywgKCkgewogICAgICBzZXRVcCgoKSBhc3luYyB7CiAgICAgICAgYXdhaXQgZGkuaW5pdCgpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3Nob3VsZCByZWdpc3RlciBhbmQgcmV0cmlldmUgaW5zdGFuY2VzJywgKCkgYXN5bmMgewogICAgICAgIGZpbmFsIHNlcnZpY2UgPSBUZXN0U2VydmljZSgndGVzdCcpOwoKICAgICAgICBkaS5yZWdpc3RlcjxUZXN0U2VydmljZT4oc2VydmljZSk7CgogICAgICAgIGV4cGVjdChkaS5oYXM8VGVzdFNlcnZpY2U+KCksIHRydWUpOwogICAgICAgIGZpbmFsIHJldHJpZXZlZCA9IGRpLmdldDxUZXN0U2VydmljZT4oKTsKICAgICAgICBleHBlY3QocmV0cmlldmVkLCBzYW1lKHNlcnZpY2UpKTsKICAgICAgICBleHBlY3QocmV0cmlldmVkLm5hbWUsICd0ZXN0Jyk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnc2hvdWxkIHJlZ2lzdGVyIHdpdGggZGlzcG9zZSBmdW5jdGlvbicsICgpIGFzeW5jIHsKICAgICAgICBmaW5hbCBzZXJ2aWNlID0gVGVzdFNlcnZpY2UoJ3Rlc3QnKTsKICAgICAgICBib29sIGRpc3Bvc2VDYWxsZWQgPSBmYWxzZTsKCiAgICAgICAgZGkucmVnaXN0ZXI8VGVzdFNlcnZpY2U+KAogICAgICAgICAgc2VydmljZSwKICAgICAgICAgIGRpc3Bvc2U6IChpbnN0YW5jZSkgewogICAgICAgICAgICBkaXNwb3NlQ2FsbGVkID0gdHJ1ZTsKICAgICAgICAgICAgaW5zdGFuY2UuZGlzcG9zZSgpOwogICAgICAgICAgfSwKICAgICAgICApOwoKICAgICAgICBleHBlY3QoZGkuaGFzPFRlc3RTZXJ2aWNlPigpLCB0cnVlKTsKICAgICAgICBhd2FpdCBkaS51bnJlZ2lzdGVyPFRlc3RTZXJ2aWNlPihudWxsKTsKCiAgICAgICAgZXhwZWN0KGRpc3Bvc2VDYWxsZWQsIHRydWUpOwogICAgICAgIGV4cGVjdChzZXJ2aWNlLmlzRGlzcG9zZWQsIHRydWUpOwogICAgICAgIGV4cGVjdChkaS5oYXM8VGVzdFNlcnZpY2U+KCksIGZhbHNlKTsKICAgICAgfSk7CiAgICB9KTsKCiAgICBncm91cCgnRXJyb3IgaGFuZGxpbmcnLCAoKSB7CiAgICAgIHRlc3QoJ3JlZ2lzdGVyaW5nIGJlZm9yZSBpbml0KCkgaXMgYWxsb3dlZCcsICgpIHsKICAgICAgICAvLyBpbml0KCkgZXhpc3RzIGZvciBzeW1tZXRyeSB3aXRoIGRpc3Bvc2UoKTsgR2V0SXQgbmVlZHMgbm8gc2V0dXAsIHNvCiAgICAgICAgLy8gcmVnaXN0cmF0aW9uIHdvcmtzIHN0cmFpZ2h0IGF3YXkuCiAgICAgICAgZmluYWwgc2VydmljZSA9IFRlc3RTZXJ2aWNlKCd0ZXN0Jyk7CgogICAgICAgIGRpLnJlZ2lzdGVyPFRlc3RTZXJ2aWNlPihzZXJ2aWNlKTsKCiAgICAgICAgZXhwZWN0KGRpLmdldDxUZXN0U2VydmljZT4oKSwgc2FtZShzZXJ2aWNlKSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnc2hvdWxkIHRocm93IHdoZW4gZ2V0dGluZyBiZWZvcmUgaW5pdGlhbGl6YXRpb24nLCAoKSB7CiAgICAgICAgZXhwZWN0KCgpID0+IGRpLmdldDxUZXN0U2VydmljZT4oKSwgdGhyb3dzQShpc0E8U3RhdGVFcnJvcj4oKSkpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3Nob3VsZCByZXR1cm4gZmFsc2Ugd2hlbiBub3QgaW5pdGlhbGl6ZWQnLCAoKSB7CiAgICAgICAgZXhwZWN0KGRpLmhhczxUZXN0U2VydmljZT4oKSwgZmFsc2UpOwogICAgICB9KTsKICAgIH0pOwoKICAgIGdyb3VwKCdSZXRyaWV2YWwnLCAoKSB7CiAgICAgIHNldFVwKCgpIGFzeW5jIHsKICAgICAgICBhd2FpdCBkaS5pbml0KCk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnc2hvdWxkIGdldCByZWdpc3RlcmVkIGluc3RhbmNlcycsICgpIHsKICAgICAgICBmaW5hbCBzZXJ2aWNlID0gVGVzdFNlcnZpY2UoJ3Rlc3QnKTsKICAgICAgICBkaS5yZWdpc3RlcjxUZXN0U2VydmljZT4oc2VydmljZSk7CgogICAgICAgIGZpbmFsIHJldHJpZXZlZCA9IGRpLmdldDxUZXN0U2VydmljZT4oKTsKICAgICAgICBleHBlY3QocmV0cmlldmVkLCBzYW1lKHNlcnZpY2UpKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdzaG91bGQgdGhyb3cgd2hlbiBnZXR0aW5nIHVucmVnaXN0ZXJlZCB0eXBlJywgKCkgewogICAgICAgIGV4cGVjdCgoKSA9PiBkaS5nZXQ8VGVzdFNlcnZpY2U+KCksIHRocm93c0EoaXNBPE9iamVjdD4oKSkpOwogICAgICB9KTsKICAgIH0pOwoKICAgIGdyb3VwKCdIYXMvQ29udGFpbnMnLCAoKSB7CiAgICAgIHNldFVwKCgpIGFzeW5jIHsKICAgICAgICBhd2FpdCBkaS5pbml0KCk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnc2hvdWxkIHJldHVybiB0cnVlIGZvciByZWdpc3RlcmVkIHR5cGVzJywgKCkgewogICAgICAgIGZpbmFsIHNlcnZpY2UgPSBUZXN0U2VydmljZSgndGVzdCcpOwogICAgICAgIGRpLnJlZ2lzdGVyPFRlc3RTZXJ2aWNlPihzZXJ2aWNlKTsKCiAgICAgICAgZXhwZWN0KGRpLmhhczxUZXN0U2VydmljZT4oKSwgdHJ1ZSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnc2hvdWxkIHJldHVybiBmYWxzZSBmb3IgdW5yZWdpc3RlcmVkIHR5cGVzJywgKCkgewogICAgICAgIGV4cGVjdChkaS5oYXM8VGVzdFNlcnZpY2U+KCksIGZhbHNlKTsKICAgICAgfSk7CiAgICB9KTsKCiAgICBncm91cCgnVW5yZWdpc3RyYXRpb24nLCAoKSB7CiAgICAgIHNldFVwKCgpIGFzeW5jIHsKICAgICAgICBhd2FpdCBkaS5pbml0KCk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnc2hvdWxkIHVucmVnaXN0ZXIgaW5zdGFuY2VzJywgKCkgYXN5bmMgewogICAgICAgIGZpbmFsIHNlcnZpY2UgPSBUZXN0U2VydmljZSgndGVzdCcpOwogICAgICAgIGRpLnJlZ2lzdGVyPFRlc3RTZXJ2aWNlPihzZXJ2aWNlKTsKCiAgICAgICAgZXhwZWN0KGRpLmhhczxUZXN0U2VydmljZT4oKSwgdHJ1ZSk7CiAgICAgICAgYXdhaXQgZGkudW5yZWdpc3RlcjxUZXN0U2VydmljZT4obnVsbCk7CiAgICAgICAgZXhwZWN0KGRpLmhhczxUZXN0U2VydmljZT4oKSwgZmFsc2UpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3Nob3VsZCBjYWxsIGRpc3Bvc2UgZnVuY3Rpb24gd2hlbiB1bnJlZ2lzdGVyaW5nJywgKCkgYXN5bmMgewogICAgICAgIGZpbmFsIHNlcnZpY2UgPSBUZXN0U2VydmljZSgndGVzdCcpOwogICAgICAgIGJvb2wgZGlzcG9zZUNhbGxlZCA9IGZhbHNlOwoKICAgICAgICBkaS5yZWdpc3RlcjxUZXN0U2VydmljZT4oCiAgICAgICAgICBzZXJ2aWNlLAogICAgICAgICAgZGlzcG9zZTogKGluc3RhbmNlKSB7CiAgICAgICAgICAgIGRpc3Bvc2VDYWxsZWQgPSB0cnVlOwogICAgICAgICAgICBpbnN0YW5jZS5kaXNwb3NlKCk7CiAgICAgICAgICB9LAogICAgICAgICk7CgogICAgICAgIGF3YWl0IGRpLnVucmVnaXN0ZXI8VGVzdFNlcnZpY2U+KG51bGwpOwoKICAgICAgICBleHBlY3QoZGlzcG9zZUNhbGxlZCwgdHJ1ZSk7CiAgICAgICAgZXhwZWN0KHNlcnZpY2UuaXNEaXNwb3NlZCwgdHJ1ZSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgKICAgICAgICAnc2hvdWxkIGhhbmRsZSB1bnJlZ2lzdGVyaW5nIG5vbi1leGlzdGVudCB0eXBlcyBncmFjZWZ1bGx5JywKICAgICAgICAoKSBhc3luYyB7CiAgICAgICAgICAvLyBTaG91bGQgbm90IHRocm93CiAgICAgICAgICBhd2FpdCBkaS51bnJlZ2lzdGVyPFRlc3RTZXJ2aWNlPihudWxsKTsKICAgICAgICAgIGV4cGVjdChkaS5oYXM8VGVzdFNlcnZpY2U+KCksIGZhbHNlKTsKICAgICAgICB9LAogICAgICApOwoKICAgICAgdGVzdCgnc2hvdWxkIGhhbmRsZSB1bnJlZ2lzdGVyaW5nIHdoZW4gbm90IGluaXRpYWxpemVkJywgKCkgYXN5bmMgewogICAgICAgIC8vIFNob3VsZCBub3QgdGhyb3cKICAgICAgICBhd2FpdCBkaS51bnJlZ2lzdGVyPFRlc3RTZXJ2aWNlPihudWxsKTsKICAgICAgfSk7CiAgICB9KTsKCiAgICBncm91cCgnRGlzcG9zYWwnLCAoKSB7CiAgICAgIHNldFVwKCgpIGFzeW5jIHsKICAgICAgICBhd2FpdCBkaS5pbml0KCk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnc2hvdWxkIGRpc3Bvc2UgYWxsIHJlZ2lzdGVyZWQgaW5zdGFuY2VzJywgKCkgYXN5bmMgewogICAgICAgIGZpbmFsIHNlcnZpY2UgPSBUZXN0U2VydmljZSgnc2VydmljZScpOwogICAgICAgIGZpbmFsIHJlcG9zaXRvcnkgPSBUZXN0UmVwb3NpdG9yeShzZXJ2aWNlKTsKCiAgICAgICAgZGkucmVnaXN0ZXI8VGVzdFNlcnZpY2U+KAogICAgICAgICAgc2VydmljZSwKICAgICAgICAgIGRpc3Bvc2U6IChpbnN0YW5jZSkgPT4gaW5zdGFuY2UuZGlzcG9zZSgpLAogICAgICAgICk7CgogICAgICAgIGRpLnJlZ2lzdGVyPFRlc3RSZXBvc2l0b3J5PigKICAgICAgICAgIHJlcG9zaXRvcnksCiAgICAgICAgICBkaXNwb3NlOiAoaW5zdGFuY2UpID0+IGluc3RhbmNlLmRpc3Bvc2UoKSwKICAgICAgICApOwoKICAgICAgICBhd2FpdCBkaS5kaXNwb3NlKCk7CgogICAgICAgIGV4cGVjdChzZXJ2aWNlLmlzRGlzcG9zZWQsIHRydWUpOwogICAgICAgIGV4cGVjdChyZXBvc2l0b3J5LmlzRGlzcG9zZWQsIHRydWUpOwogICAgICAgIGV4cGVjdChkaS5oYXM8VGVzdFNlcnZpY2U+KCksIGZhbHNlKTsKICAgICAgICBleHBlY3QoZGkuaGFzPFRlc3RSZXBvc2l0b3J5PigpLCBmYWxzZSk7CiAgICAgIH0pOwogICAgfSk7CgogICAgZ3JvdXAoJ1Jlc2V0JywgKCkgewogICAgICB0ZXN0KCdzaG91bGQgcmVzZXQgYW5kIHJlaW5pdGlhbGl6ZScsICgpIGFzeW5jIHsKICAgICAgICBhd2FpdCBkaS5pbml0KCk7CgogICAgICAgIGZpbmFsIHNlcnZpY2UgPSBUZXN0U2VydmljZSgndGVzdCcpOwogICAgICAgIGRpLnJlZ2lzdGVyPFRlc3RTZXJ2aWNlPigKICAgICAgICAgIHNlcnZpY2UsCiAgICAgICAgICBkaXNwb3NlOiAoaW5zdGFuY2UpID0+IGluc3RhbmNlLmRpc3Bvc2UoKSwKICAgICAgICApOwoKICAgICAgICBleHBlY3QoZGkuaGFzPFRlc3RTZXJ2aWNlPigpLCB0cnVlKTsKCiAgICAgICAgYXdhaXQgZGkucmVzZXQoKTsKCiAgICAgICAgZXhwZWN0KGRpLmhhczxUZXN0U2VydmljZT4oKSwgZmFsc2UpOwogICAgICAgIGV4cGVjdChzZXJ2aWNlLmlzRGlzcG9zZWQsIHRydWUpOwogICAgICB9KTsKICAgIH0pOwoKICAgIGdyb3VwKCdDb21wbGV4IHNjZW5hcmlvcycsICgpIHsKICAgICAgc2V0VXAoKCkgYXN5bmMgewogICAgICAgIGF3YWl0IGRpLmluaXQoKTsKICAgICAgfSk7CgogICAgICB0ZXN0KCdzaG91bGQgaGFuZGxlIG11bHRpcGxlIGRlcGVuZGVuY2llcycsICgpIHsKICAgICAgICBmaW5hbCBzZXJ2aWNlID0gVGVzdFNlcnZpY2UoJ3NlcnZpY2UnKTsKICAgICAgICBmaW5hbCByZXBvc2l0b3J5ID0gVGVzdFJlcG9zaXRvcnkoc2VydmljZSk7CgogICAgICAgIGRpLnJlZ2lzdGVyPFRlc3RTZXJ2aWNlPihzZXJ2aWNlKTsKICAgICAgICBkaS5yZWdpc3RlcjxUZXN0UmVwb3NpdG9yeT4ocmVwb3NpdG9yeSk7CgogICAgICAgIGV4cGVjdChkaS5oYXM8VGVzdFNlcnZpY2U+KCksIHRydWUpOwogICAgICAgIGV4cGVjdChkaS5oYXM8VGVzdFJlcG9zaXRvcnk+KCksIHRydWUpOwoKICAgICAgICBmaW5hbCByZXRyaWV2ZWRTZXJ2aWNlID0gZGkuZ2V0PFRlc3RTZXJ2aWNlPigpOwogICAgICAgIGZpbmFsIHJldHJpZXZlZFJlcG9zaXRvcnkgPSBkaS5nZXQ8VGVzdFJlcG9zaXRvcnk+KCk7CgogICAgICAgIGV4cGVjdChyZXRyaWV2ZWRTZXJ2aWNlLCBzYW1lKHNlcnZpY2UpKTsKICAgICAgICBleHBlY3QocmV0cmlldmVkUmVwb3NpdG9yeSwgc2FtZShyZXBvc2l0b3J5KSk7CiAgICAgICAgZXhwZWN0KHJldHJpZXZlZFJlcG9zaXRvcnkuc2VydmljZSwgc2FtZShzZXJ2aWNlKSk7CiAgICAgIH0pOwogICAgfSk7CiAgfSk7Cn0K", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/localization/build.yaml", + "data": + "dGFyZ2V0czoKICAkZGVmYXVsdDoKICAgIGJ1aWxkZXJzOgogICAgICBpNjluOgogICAgICAgIG9wdGlvbnM6CiAgICAgICAgICBkZWZhdWx0X2xvY2FsZTogZW4KICAgICAgICAgIGxvY2FsZXM6CiAgICAgICAgICAgIC0gZW4KICAgICAgICAgICAgLSBlcwogICAgICAgICAgICAtIGZyCiAgICAgICAgICAgIC0gZGUKICAgICAgICBnZW5lcmF0ZV9mb3I6CiAgICAgICAgICAtIGxpYi9tZXNzYWdlcy5pNjluLnlhbWw=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/localization/lib/localization.dart", + "data": + "Ly8vIHt7bmFtZS50aXRsZUNhc2UoKX19IExvY2FsaXphdGlvbiBQYWNrYWdlCi8vLwovLy8gVGhpcyBwYWNrYWdlIHByb3ZpZGVzIGludGVybmF0aW9uYWxpemF0aW9uIHN1cHBvcnQgdXNpbmcgaTY5bi4KLy8vIEl0IGNvbnRhaW5zIGdlbmVyYXRlZCBtZXNzYWdlIGNsYXNzZXMgYW5kIHV0aWxpdGllcyBmb3IgbWFuYWdpbmcgdHJhbnNsYXRpb25zLgpsaWJyYXJ5OwoKLy8gRXhwb3J0IGdlbmVyYXRlZCBpNjluIG1lc3NhZ2VzCmV4cG9ydCAnbWVzc2FnZXMuaTY5bi5kYXJ0JzsKCi8vIEV4cG9ydCBsb2NhbGl6YXRpb24gdXRpbGl0aWVzCmV4cG9ydCAnc3JjL2xvY2FsaXphdGlvbl9wcm92aWRlci5kYXJ0JzsKCi8vIEV4cG9ydCBnbG9iYWwgc3RyaW5ncyBpbnN0YW5jZSBmb3IgZWFzeSBhY2Nlc3MKZXhwb3J0ICdzcmMvc3RyaW5ncy5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/localization/lib/messages.i69n.dart", + "data": + "Ly8gaWdub3JlX2Zvcl9maWxlOiB1bnVzZWRfZWxlbWVudCwgdW51c2VkX2ZpZWxkLCBjYW1lbF9jYXNlX3R5cGVzLCBhbm5vdGF0ZV9vdmVycmlkZXMsIHByZWZlcl9zaW5nbGVfcXVvdGVzCi8vIEdFTkVSQVRFRCBGSUxFLCBkbyBub3QgZWRpdCEKLy8gZGFydCBmb3JtYXQgb2ZmCmltcG9ydCAncGFja2FnZTppNjluL2k2OW4uZGFydCcgYXMgaTY5bjsKClN0cmluZyBnZXQgX2xhbmd1YWdlQ29kZSA9PiAnZW4nOwpTdHJpbmcgZ2V0IF9sb2NhbGVOYW1lID0+ICdlbic7CgpTdHJpbmcgX3BsdXJhbChpbnQgY291bnQsCiAgICAgICAge1N0cmluZz8gemVybywKICAgICAgICBTdHJpbmc/IG9uZSwKICAgICAgICBTdHJpbmc/IHR3bywKICAgICAgICBTdHJpbmc/IGZldywKICAgICAgICBTdHJpbmc/IG1hbnksCiAgICAgICAgU3RyaW5nPyBvdGhlcn0pID0+CiAgICBpNjluLnBsdXJhbChjb3VudCwgX2xhbmd1YWdlQ29kZSwKICAgICAgICB6ZXJvOiB6ZXJvLCBvbmU6IG9uZSwgdHdvOiB0d28sIGZldzogZmV3LCBtYW55OiBtYW55LCBvdGhlcjogb3RoZXIpOwpTdHJpbmcgX29yZGluYWwoaW50IGNvdW50LAogICAgICAgIHtTdHJpbmc/IHplcm8sCiAgICAgICAgU3RyaW5nPyBvbmUsCiAgICAgICAgU3RyaW5nPyB0d28sCiAgICAgICAgU3RyaW5nPyBmZXcsCiAgICAgICAgU3RyaW5nPyBtYW55LAogICAgICAgIFN0cmluZz8gb3RoZXJ9KSA9PgogICAgaTY5bi5vcmRpbmFsKGNvdW50LCBfbGFuZ3VhZ2VDb2RlLAogICAgICAgIHplcm86IHplcm8sIG9uZTogb25lLCB0d286IHR3bywgZmV3OiBmZXcsIG1hbnk6IG1hbnksIG90aGVyOiBvdGhlcik7ClN0cmluZyBfY2FyZGluYWwoaW50IGNvdW50LAogICAgICAgIHtTdHJpbmc/IHplcm8sCiAgICAgICAgU3RyaW5nPyBvbmUsCiAgICAgICAgU3RyaW5nPyB0d28sCiAgICAgICAgU3RyaW5nPyBmZXcsCiAgICAgICAgU3RyaW5nPyBtYW55LAogICAgICAgIFN0cmluZz8gb3RoZXJ9KSA9PgogICAgaTY5bi5jYXJkaW5hbChjb3VudCwgX2xhbmd1YWdlQ29kZSwKICAgICAgICB6ZXJvOiB6ZXJvLCBvbmU6IG9uZSwgdHdvOiB0d28sIGZldzogZmV3LCBtYW55OiBtYW55LCBvdGhlcjogb3RoZXIpOwoKY2xhc3MgTWVzc2FnZXMgaW1wbGVtZW50cyBpNjluLkk2OW5NZXNzYWdlQnVuZGxlIHsKICBjb25zdCBNZXNzYWdlcygpOwogIEFwcE1lc3NhZ2VzIGdldCBhcHAgPT4gQXBwTWVzc2FnZXModGhpcyk7CiAgR2VuZXJpY01lc3NhZ2VzIGdldCBnZW5lcmljID0+IEdlbmVyaWNNZXNzYWdlcyh0aGlzKTsKICBDb21tb25NZXNzYWdlcyBnZXQgY29tbW9uID0+IENvbW1vbk1lc3NhZ2VzKHRoaXMpOwogIEF1dGhNZXNzYWdlcyBnZXQgYXV0aCA9PiBBdXRoTWVzc2FnZXModGhpcyk7CiAgUHJvZmlsZU1lc3NhZ2VzIGdldCBwcm9maWxlID0+IFByb2ZpbGVNZXNzYWdlcyh0aGlzKTsKICBOYXZNZXNzYWdlcyBnZXQgbmF2ID0+IE5hdk1lc3NhZ2VzKHRoaXMpOwogIE5vdGlmaWNhdGlvbnNNZXNzYWdlcyBnZXQgbm90aWZpY2F0aW9ucyA9PiBOb3RpZmljYXRpb25zTWVzc2FnZXModGhpcyk7CiAgRXJyb3JzTWVzc2FnZXMgZ2V0IGVycm9ycyA9PiBFcnJvcnNNZXNzYWdlcyh0aGlzKTsKICBWYWxpZGF0aW9uTWVzc2FnZXMgZ2V0IHZhbGlkYXRpb24gPT4gVmFsaWRhdGlvbk1lc3NhZ2VzKHRoaXMpOwogIE9iamVjdCBvcGVyYXRvciBbXShTdHJpbmcga2V5KSB7CiAgICB2YXIgaW5kZXggPSBrZXkuaW5kZXhPZignLicpOwogICAgaWYgKGluZGV4ID4gMCkgewogICAgICByZXR1cm4gKHRoaXNba2V5LnN1YnN0cmluZygwLCBpbmRleCldCiAgICAgICAgICBhcyBpNjluLkk2OW5NZXNzYWdlQnVuZGxlKVtrZXkuc3Vic3RyaW5nKGluZGV4ICsgMSldOwogICAgfQogICAgc3dpdGNoIChrZXkpIHsKICAgICAgY2FzZSAnYXBwJzoKICAgICAgICByZXR1cm4gYXBwOwogICAgICBjYXNlICdnZW5lcmljJzoKICAgICAgICByZXR1cm4gZ2VuZXJpYzsKICAgICAgY2FzZSAnY29tbW9uJzoKICAgICAgICByZXR1cm4gY29tbW9uOwogICAgICBjYXNlICdhdXRoJzoKICAgICAgICByZXR1cm4gYXV0aDsKICAgICAgY2FzZSAncHJvZmlsZSc6CiAgICAgICAgcmV0dXJuIHByb2ZpbGU7CiAgICAgIGNhc2UgJ25hdic6CiAgICAgICAgcmV0dXJuIG5hdjsKICAgICAgY2FzZSAnbm90aWZpY2F0aW9ucyc6CiAgICAgICAgcmV0dXJuIG5vdGlmaWNhdGlvbnM7CiAgICAgIGNhc2UgJ2Vycm9ycyc6CiAgICAgICAgcmV0dXJuIGVycm9yczsKICAgICAgY2FzZSAndmFsaWRhdGlvbic6CiAgICAgICAgcmV0dXJuIHZhbGlkYXRpb247CiAgICAgIGRlZmF1bHQ6CiAgICAgICAgcmV0dXJuIGtleTsKICAgIH0KICB9Cn0KCmNsYXNzIEFwcE1lc3NhZ2VzIGltcGxlbWVudHMgaTY5bi5JNjluTWVzc2FnZUJ1bmRsZSB7CiAgZmluYWwgTWVzc2FnZXMgX3BhcmVudDsKICBjb25zdCBBcHBNZXNzYWdlcyh0aGlzLl9wYXJlbnQpOwogIFN0cmluZyBnZXQgbmFtZSA9PiAie3tuYW1lLnRpdGxlQ2FzZSgpfX0iOwogIFN0cmluZyBnZXQgZGVzY3JpcHRpb24gPT4gInt7e2Rlc2NyaXB0aW9ufX19IjsKICBTdHJpbmcgZ2V0IHdlbGNvbWVfdG9fYXBwID0+ICJXZWxjb21lIHRvIHt7bmFtZS50aXRsZUNhc2UoKX19ISI7CiAgT2JqZWN0IG9wZXJhdG9yIFtdKFN0cmluZyBrZXkpIHsKICAgIHZhciBpbmRleCA9IGtleS5pbmRleE9mKCcuJyk7CiAgICBpZiAoaW5kZXggPiAwKSB7CiAgICAgIHJldHVybiAodGhpc1trZXkuc3Vic3RyaW5nKDAsIGluZGV4KV0KICAgICAgICAgIGFzIGk2OW4uSTY5bk1lc3NhZ2VCdW5kbGUpW2tleS5zdWJzdHJpbmcoaW5kZXggKyAxKV07CiAgICB9CiAgICBzd2l0Y2ggKGtleSkgewogICAgICBjYXNlICduYW1lJzoKICAgICAgICByZXR1cm4gbmFtZTsKICAgICAgY2FzZSAnZGVzY3JpcHRpb24nOgogICAgICAgIHJldHVybiBkZXNjcmlwdGlvbjsKICAgICAgY2FzZSAnd2VsY29tZV90b19hcHAnOgogICAgICAgIHJldHVybiB3ZWxjb21lX3RvX2FwcDsKICAgICAgZGVmYXVsdDoKICAgICAgICByZXR1cm4ga2V5OwogICAgfQogIH0KfQoKY2xhc3MgR2VuZXJpY01lc3NhZ2VzIGltcGxlbWVudHMgaTY5bi5JNjluTWVzc2FnZUJ1bmRsZSB7CiAgZmluYWwgTWVzc2FnZXMgX3BhcmVudDsKICBjb25zdCBHZW5lcmljTWVzc2FnZXModGhpcy5fcGFyZW50KTsKICBTdHJpbmcgZ2V0IG9rID0+ICJPSyI7CiAgU3RyaW5nIGdldCBjYW5jZWwgPT4gIkNhbmNlbCI7CiAgU3RyaW5nIGdldCBzYXZlID0+ICJTYXZlIjsKICBTdHJpbmcgZ2V0IGRlbGV0ZSA9PiAiRGVsZXRlIjsKICBTdHJpbmcgZ2V0IGVkaXQgPT4gIkVkaXQiOwogIFN0cmluZyBnZXQgdXBkYXRlID0+ICJVcGRhdGUiOwogIFN0cmluZyBnZXQgc3VibWl0ID0+ICJTdWJtaXQiOwogIFN0cmluZyBnZXQgY2xvc2UgPT4gIkNsb3NlIjsKICBTdHJpbmcgZ2V0IGJhY2sgPT4gIkJhY2siOwogIFN0cmluZyBnZXQgbmV4dCA9PiAiTmV4dCI7CiAgU3RyaW5nIGdldCBwcmV2aW91cyA9PiAiUHJldmlvdXMiOwogIFN0cmluZyBnZXQgZG9uZSA9PiAiRG9uZSI7CiAgU3RyaW5nIGdldCBsb2FkaW5nID0+ICJMb2FkaW5nLi4uIjsKICBTdHJpbmcgZ2V0IGVycm9yID0+ICJFcnJvciI7CiAgU3RyaW5nIGdldCBzdWNjZXNzID0+ICJTdWNjZXNzIjsKICBTdHJpbmcgZ2V0IHdhcm5pbmcgPT4gIldhcm5pbmciOwogIFN0cmluZyBnZXQgaW5mbyA9PiAiSW5mbyI7CiAgU3RyaW5nIGdldCByZXRyeSA9PiAiUmV0cnkiOwogIFN0cmluZyBnZXQgcmVmcmVzaCA9PiAiUmVmcmVzaCI7CiAgU3RyaW5nIGdldCB5ZXMgPT4gIlllcyI7CiAgU3RyaW5nIGdldCBubyA9PiAiTm8iOwogIFN0cmluZyBnZXQgYWRkID0+ICIrIEFkZCI7CiAgU3RyaW5nIGdldCB0cnlfYWdhaW4gPT4gIlRyeSBBZ2FpbiI7CiAgT2JqZWN0IG9wZXJhdG9yIFtdKFN0cmluZyBrZXkpIHsKICAgIHZhciBpbmRleCA9IGtleS5pbmRleE9mKCcuJyk7CiAgICBpZiAoaW5kZXggPiAwKSB7CiAgICAgIHJldHVybiAodGhpc1trZXkuc3Vic3RyaW5nKDAsIGluZGV4KV0KICAgICAgICAgIGFzIGk2OW4uSTY5bk1lc3NhZ2VCdW5kbGUpW2tleS5zdWJzdHJpbmcoaW5kZXggKyAxKV07CiAgICB9CiAgICBzd2l0Y2ggKGtleSkgewogICAgICBjYXNlICdvayc6CiAgICAgICAgcmV0dXJuIG9rOwogICAgICBjYXNlICdjYW5jZWwnOgogICAgICAgIHJldHVybiBjYW5jZWw7CiAgICAgIGNhc2UgJ3NhdmUnOgogICAgICAgIHJldHVybiBzYXZlOwogICAgICBjYXNlICdkZWxldGUnOgogICAgICAgIHJldHVybiBkZWxldGU7CiAgICAgIGNhc2UgJ2VkaXQnOgogICAgICAgIHJldHVybiBlZGl0OwogICAgICBjYXNlICd1cGRhdGUnOgogICAgICAgIHJldHVybiB1cGRhdGU7CiAgICAgIGNhc2UgJ3N1Ym1pdCc6CiAgICAgICAgcmV0dXJuIHN1Ym1pdDsKICAgICAgY2FzZSAnY2xvc2UnOgogICAgICAgIHJldHVybiBjbG9zZTsKICAgICAgY2FzZSAnYmFjayc6CiAgICAgICAgcmV0dXJuIGJhY2s7CiAgICAgIGNhc2UgJ25leHQnOgogICAgICAgIHJldHVybiBuZXh0OwogICAgICBjYXNlICdwcmV2aW91cyc6CiAgICAgICAgcmV0dXJuIHByZXZpb3VzOwogICAgICBjYXNlICdkb25lJzoKICAgICAgICByZXR1cm4gZG9uZTsKICAgICAgY2FzZSAnbG9hZGluZyc6CiAgICAgICAgcmV0dXJuIGxvYWRpbmc7CiAgICAgIGNhc2UgJ2Vycm9yJzoKICAgICAgICByZXR1cm4gZXJyb3I7CiAgICAgIGNhc2UgJ3N1Y2Nlc3MnOgogICAgICAgIHJldHVybiBzdWNjZXNzOwogICAgICBjYXNlICd3YXJuaW5nJzoKICAgICAgICByZXR1cm4gd2FybmluZzsKICAgICAgY2FzZSAnaW5mbyc6CiAgICAgICAgcmV0dXJuIGluZm87CiAgICAgIGNhc2UgJ3JldHJ5JzoKICAgICAgICByZXR1cm4gcmV0cnk7CiAgICAgIGNhc2UgJ3JlZnJlc2gnOgogICAgICAgIHJldHVybiByZWZyZXNoOwogICAgICBjYXNlICd5ZXMnOgogICAgICAgIHJldHVybiB5ZXM7CiAgICAgIGNhc2UgJ25vJzoKICAgICAgICByZXR1cm4gbm87CiAgICAgIGNhc2UgJ2FkZCc6CiAgICAgICAgcmV0dXJuIGFkZDsKICAgICAgY2FzZSAndHJ5X2FnYWluJzoKICAgICAgICByZXR1cm4gdHJ5X2FnYWluOwogICAgICBkZWZhdWx0OgogICAgICAgIHJldHVybiBrZXk7CiAgICB9CiAgfQp9CgpjbGFzcyBDb21tb25NZXNzYWdlcyBpbXBsZW1lbnRzIGk2OW4uSTY5bk1lc3NhZ2VCdW5kbGUgewogIGZpbmFsIE1lc3NhZ2VzIF9wYXJlbnQ7CiAgY29uc3QgQ29tbW9uTWVzc2FnZXModGhpcy5fcGFyZW50KTsKICBTdHJpbmcgZ2V0IGNhbmNlbCA9PiAiQ2FuY2VsIjsKICBTdHJpbmcgZ2V0IG9rID0+ICJPSyI7CiAgU3RyaW5nIGdldCBzYXZlID0+ICJTYXZlIjsKICBTdHJpbmcgZ2V0IGRlbGV0ZSA9PiAiRGVsZXRlIjsKICBTdHJpbmcgZ2V0IGVkaXQgPT4gIkVkaXQiOwogIFN0cmluZyBnZXQgdHJ5X2FnYWluID0+ICJUcnkgQWdhaW4iOwogIFN0cmluZyBnZXQgd2VlayA9PiAiV2VlayI7CiAgU3RyaW5nIGdldCBtb250aCA9PiAiTW9udGgiOwogIE9iamVjdCBvcGVyYXRvciBbXShTdHJpbmcga2V5KSB7CiAgICB2YXIgaW5kZXggPSBrZXkuaW5kZXhPZignLicpOwogICAgaWYgKGluZGV4ID4gMCkgewogICAgICByZXR1cm4gKHRoaXNba2V5LnN1YnN0cmluZygwLCBpbmRleCldCiAgICAgICAgICBhcyBpNjluLkk2OW5NZXNzYWdlQnVuZGxlKVtrZXkuc3Vic3RyaW5nKGluZGV4ICsgMSldOwogICAgfQogICAgc3dpdGNoIChrZXkpIHsKICAgICAgY2FzZSAnY2FuY2VsJzoKICAgICAgICByZXR1cm4gY2FuY2VsOwogICAgICBjYXNlICdvayc6CiAgICAgICAgcmV0dXJuIG9rOwogICAgICBjYXNlICdzYXZlJzoKICAgICAgICByZXR1cm4gc2F2ZTsKICAgICAgY2FzZSAnZGVsZXRlJzoKICAgICAgICByZXR1cm4gZGVsZXRlOwogICAgICBjYXNlICdlZGl0JzoKICAgICAgICByZXR1cm4gZWRpdDsKICAgICAgY2FzZSAndHJ5X2FnYWluJzoKICAgICAgICByZXR1cm4gdHJ5X2FnYWluOwogICAgICBjYXNlICd3ZWVrJzoKICAgICAgICByZXR1cm4gd2VlazsKICAgICAgY2FzZSAnbW9udGgnOgogICAgICAgIHJldHVybiBtb250aDsKICAgICAgZGVmYXVsdDoKICAgICAgICByZXR1cm4ga2V5OwogICAgfQogIH0KfQoKY2xhc3MgQXV0aE1lc3NhZ2VzIGltcGxlbWVudHMgaTY5bi5JNjluTWVzc2FnZUJ1bmRsZSB7CiAgZmluYWwgTWVzc2FnZXMgX3BhcmVudDsKICBjb25zdCBBdXRoTWVzc2FnZXModGhpcy5fcGFyZW50KTsKICBTdHJpbmcgZ2V0IHJlZ2lzdGVyID0+ICJSZWdpc3RlciI7CiAgU3RyaW5nIGdldCBzaWduX2luID0+ICJTaWduIEluIjsKICBTdHJpbmcgZ2V0IHNpZ25fb3V0ID0+ICJTaWduIE91dCI7CiAgU3RyaW5nIGdldCBkb250X2hhdmVfYWNjb3VudCA9PiAiRG9uJ3QgaGF2ZSBhbiBhY2NvdW50PyAiOwogIFN0cmluZyBnZXQgYWxyZWFkeV9oYXZlX2FjY291bnQgPT4gIkFscmVhZHkgaGF2ZSBhbiBhY2NvdW50PyAiOwogIFN0cmluZyBnZXQgc2lnbl9vdXRfY29uZmlybWF0aW9uID0+ICJBcmUgeW91IHN1cmUgeW91IHdhbnQgdG8gc2lnbiBvdXQ/IjsKICBPYmplY3Qgb3BlcmF0b3IgW10oU3RyaW5nIGtleSkgewogICAgdmFyIGluZGV4ID0ga2V5LmluZGV4T2YoJy4nKTsKICAgIGlmIChpbmRleCA+IDApIHsKICAgICAgcmV0dXJuICh0aGlzW2tleS5zdWJzdHJpbmcoMCwgaW5kZXgpXQogICAgICAgICAgYXMgaTY5bi5JNjluTWVzc2FnZUJ1bmRsZSlba2V5LnN1YnN0cmluZyhpbmRleCArIDEpXTsKICAgIH0KICAgIHN3aXRjaCAoa2V5KSB7CiAgICAgIGNhc2UgJ3JlZ2lzdGVyJzoKICAgICAgICByZXR1cm4gcmVnaXN0ZXI7CiAgICAgIGNhc2UgJ3NpZ25faW4nOgogICAgICAgIHJldHVybiBzaWduX2luOwogICAgICBjYXNlICdzaWduX291dCc6CiAgICAgICAgcmV0dXJuIHNpZ25fb3V0OwogICAgICBjYXNlICdkb250X2hhdmVfYWNjb3VudCc6CiAgICAgICAgcmV0dXJuIGRvbnRfaGF2ZV9hY2NvdW50OwogICAgICBjYXNlICdhbHJlYWR5X2hhdmVfYWNjb3VudCc6CiAgICAgICAgcmV0dXJuIGFscmVhZHlfaGF2ZV9hY2NvdW50OwogICAgICBjYXNlICdzaWduX291dF9jb25maXJtYXRpb24nOgogICAgICAgIHJldHVybiBzaWduX291dF9jb25maXJtYXRpb247CiAgICAgIGRlZmF1bHQ6CiAgICAgICAgcmV0dXJuIGtleTsKICAgIH0KICB9Cn0KCmNsYXNzIFByb2ZpbGVNZXNzYWdlcyBpbXBsZW1lbnRzIGk2OW4uSTY5bk1lc3NhZ2VCdW5kbGUgewogIGZpbmFsIE1lc3NhZ2VzIF9wYXJlbnQ7CiAgY29uc3QgUHJvZmlsZU1lc3NhZ2VzKHRoaXMuX3BhcmVudCk7CiAgU3RyaW5nIGdldCBwcm9maWxlID0+ICJQcm9maWxlIjsKICBTdHJpbmcgZ2V0IHNldHRpbmdzID0+ICJTZXR0aW5ncyI7CiAgU3RyaW5nIGdldCBhY2NvdW50ID0+ICJBY2NvdW50IjsKICBTdHJpbmcgZ2V0IHBlcnNvbmFsX2luZm8gPT4gIlBlcnNvbmFsIEluZm9ybWF0aW9uIjsKICBTdHJpbmcgZ2V0IHByaXZhY3lfc2V0dGluZ3MgPT4gIlByaXZhY3kgU2V0dGluZ3MiOwogIFN0cmluZyBnZXQgbmFtZSA9PiAiTmFtZSI7CiAgU3RyaW5nIGdldCBlbWFpbF9hZGRyZXNzID0+ICJFbWFpbCBBZGRyZXNzIjsKICBTdHJpbmcgZ2V0IHBob25lX251bWJlciA9PiAiUGhvbmUgTnVtYmVyIjsKICBTdHJpbmcgZ2V0IGRhdGVfb2ZfYmlydGggPT4gIkRhdGUgb2YgQmlydGgiOwogIFN0cmluZyBnZXQgZGVsZXRlX2NvbmZpcm1hdGlvbiA9PiAiRGVsZXRlIENvbmZpcm1hdGlvbiI7CiAgU3RyaW5nIGdldCBkZWxldGVfY29uZmlybWF0aW9uX21lc3NhZ2UgPT4KICAgICAgIkFyZSB5b3Ugc3VyZSB5b3Ugd2FudCB0byBkZWxldGUgdGhpcyBpdGVtPyBUaGlzIGFjdGlvbiBjYW5ub3QgYmUgdW5kb25lLiI7CiAgT2JqZWN0IG9wZXJhdG9yIFtdKFN0cmluZyBrZXkpIHsKICAgIHZhciBpbmRleCA9IGtleS5pbmRleE9mKCcuJyk7CiAgICBpZiAoaW5kZXggPiAwKSB7CiAgICAgIHJldHVybiAodGhpc1trZXkuc3Vic3RyaW5nKDAsIGluZGV4KV0KICAgICAgICAgIGFzIGk2OW4uSTY5bk1lc3NhZ2VCdW5kbGUpW2tleS5zdWJzdHJpbmcoaW5kZXggKyAxKV07CiAgICB9CiAgICBzd2l0Y2ggKGtleSkgewogICAgICBjYXNlICdwcm9maWxlJzoKICAgICAgICByZXR1cm4gcHJvZmlsZTsKICAgICAgY2FzZSAnc2V0dGluZ3MnOgogICAgICAgIHJldHVybiBzZXR0aW5nczsKICAgICAgY2FzZSAnYWNjb3VudCc6CiAgICAgICAgcmV0dXJuIGFjY291bnQ7CiAgICAgIGNhc2UgJ3BlcnNvbmFsX2luZm8nOgogICAgICAgIHJldHVybiBwZXJzb25hbF9pbmZvOwogICAgICBjYXNlICdwcml2YWN5X3NldHRpbmdzJzoKICAgICAgICByZXR1cm4gcHJpdmFjeV9zZXR0aW5nczsKICAgICAgY2FzZSAnbmFtZSc6CiAgICAgICAgcmV0dXJuIG5hbWU7CiAgICAgIGNhc2UgJ2VtYWlsX2FkZHJlc3MnOgogICAgICAgIHJldHVybiBlbWFpbF9hZGRyZXNzOwogICAgICBjYXNlICdwaG9uZV9udW1iZXInOgogICAgICAgIHJldHVybiBwaG9uZV9udW1iZXI7CiAgICAgIGNhc2UgJ2RhdGVfb2ZfYmlydGgnOgogICAgICAgIHJldHVybiBkYXRlX29mX2JpcnRoOwogICAgICBjYXNlICdkZWxldGVfY29uZmlybWF0aW9uJzoKICAgICAgICByZXR1cm4gZGVsZXRlX2NvbmZpcm1hdGlvbjsKICAgICAgY2FzZSAnZGVsZXRlX2NvbmZpcm1hdGlvbl9tZXNzYWdlJzoKICAgICAgICByZXR1cm4gZGVsZXRlX2NvbmZpcm1hdGlvbl9tZXNzYWdlOwogICAgICBkZWZhdWx0OgogICAgICAgIHJldHVybiBrZXk7CiAgICB9CiAgfQp9CgpjbGFzcyBOYXZNZXNzYWdlcyBpbXBsZW1lbnRzIGk2OW4uSTY5bk1lc3NhZ2VCdW5kbGUgewogIGZpbmFsIE1lc3NhZ2VzIF9wYXJlbnQ7CiAgY29uc3QgTmF2TWVzc2FnZXModGhpcy5fcGFyZW50KTsKICBTdHJpbmcgZ2V0IGhvbWUgPT4gIkhvbWUiOwogIFN0cmluZyBnZXQgZGFzaGJvYXJkID0+ICJEYXNoYm9hcmQiOwogIFN0cmluZyBnZXQgcHJvZmlsZSA9PiAiUHJvZmlsZSI7CiAgU3RyaW5nIGdldCBzZXR0aW5ncyA9PiAiU2V0dGluZ3MiOwogIFN0cmluZyBnZXQgbm90aWZpY2F0aW9ucyA9PiAiTm90aWZpY2F0aW9ucyI7CiAgT2JqZWN0IG9wZXJhdG9yIFtdKFN0cmluZyBrZXkpIHsKICAgIHZhciBpbmRleCA9IGtleS5pbmRleE9mKCcuJyk7CiAgICBpZiAoaW5kZXggPiAwKSB7CiAgICAgIHJldHVybiAodGhpc1trZXkuc3Vic3RyaW5nKDAsIGluZGV4KV0KICAgICAgICAgIGFzIGk2OW4uSTY5bk1lc3NhZ2VCdW5kbGUpW2tleS5zdWJzdHJpbmcoaW5kZXggKyAxKV07CiAgICB9CiAgICBzd2l0Y2ggKGtleSkgewogICAgICBjYXNlICdob21lJzoKICAgICAgICByZXR1cm4gaG9tZTsKICAgICAgY2FzZSAnZGFzaGJvYXJkJzoKICAgICAgICByZXR1cm4gZGFzaGJvYXJkOwogICAgICBjYXNlICdwcm9maWxlJzoKICAgICAgICByZXR1cm4gcHJvZmlsZTsKICAgICAgY2FzZSAnc2V0dGluZ3MnOgogICAgICAgIHJldHVybiBzZXR0aW5nczsKICAgICAgY2FzZSAnbm90aWZpY2F0aW9ucyc6CiAgICAgICAgcmV0dXJuIG5vdGlmaWNhdGlvbnM7CiAgICAgIGRlZmF1bHQ6CiAgICAgICAgcmV0dXJuIGtleTsKICAgIH0KICB9Cn0KCmNsYXNzIE5vdGlmaWNhdGlvbnNNZXNzYWdlcyBpbXBsZW1lbnRzIGk2OW4uSTY5bk1lc3NhZ2VCdW5kbGUgewogIGZpbmFsIE1lc3NhZ2VzIF9wYXJlbnQ7CiAgY29uc3QgTm90aWZpY2F0aW9uc01lc3NhZ2VzKHRoaXMuX3BhcmVudCk7CiAgU3RyaW5nIGdldCB0aXRsZSA9PiAiTm90aWZpY2F0aW9ucyI7CiAgU3RyaW5nIGdldCBtYXJrX2FzX3JlYWQgPT4gIk1hcmsgYXMgcmVhZCI7CiAgU3RyaW5nIGdldCBtYXJrX2FsbF9yZWFkID0+ICJNYXJrIGFsbCBhcyByZWFkIjsKICBTdHJpbmcgZ2V0IGRlbGV0ZSA9PiAiRGVsZXRlIjsKICBTdHJpbmcgZ2V0IGZpbHRlcl9hbGwgPT4gIkFsbCI7CiAgU3RyaW5nIGdldCBmaWx0ZXJfdW5yZWFkID0+ICJVbnJlYWQiOwogIFN0cmluZyBnZXQgZmlsdGVyX3JlYWQgPT4gIlJlYWQiOwogIFN0cmluZyBnZXQgdHlwZV9yZW1pbmRlciA9PiAiUmVtaW5kZXIiOwogIFN0cmluZyBnZXQgdHlwZV9hbGVydCA9PiAiQWxlcnQiOwogIFN0cmluZyBnZXQgdHlwZV9wcm9tb3Rpb24gPT4gIlByb21vdGlvbiI7CiAgU3RyaW5nIGdldCB0eXBlX3N5c3RlbSA9PiAiU3lzdGVtIjsKICBTdHJpbmcgZ2V0IHR5cGVfY3VzdG9tID0+ICJDdXN0b20iOwogIFN0cmluZyBnZXQgZW1wdHlfdGl0bGUgPT4gIk5vIG5vdGlmaWNhdGlvbnMiOwogIFN0cmluZyBnZXQgZW1wdHlfZGVzY3JpcHRpb24gPT4KICAgICAgIllvdSdyZSBhbGwgY2F1Z2h0IHVwISBOZXcgbm90aWZpY2F0aW9ucyB3aWxsIGFwcGVhciBoZXJlLiI7CiAgU3RyaW5nIGdldCBkZWxldGVfY29uZmlybWF0aW9uX3RpdGxlID0+ICJEZWxldGUgbm90aWZpY2F0aW9uPyI7CiAgU3RyaW5nIGdldCBkZWxldGVfY29uZmlybWF0aW9uX21lc3NhZ2UgPT4KICAgICAgIlRoaXMgbm90aWZpY2F0aW9uIHdpbGwgYmUgcGVybWFuZW50bHkgcmVtb3ZlZCBmcm9tIHlvdXIgbGlzdC4iOwogIE9iamVjdCBvcGVyYXRvciBbXShTdHJpbmcga2V5KSB7CiAgICB2YXIgaW5kZXggPSBrZXkuaW5kZXhPZignLicpOwogICAgaWYgKGluZGV4ID4gMCkgewogICAgICByZXR1cm4gKHRoaXNba2V5LnN1YnN0cmluZygwLCBpbmRleCldCiAgICAgICAgICBhcyBpNjluLkk2OW5NZXNzYWdlQnVuZGxlKVtrZXkuc3Vic3RyaW5nKGluZGV4ICsgMSldOwogICAgfQogICAgc3dpdGNoIChrZXkpIHsKICAgICAgY2FzZSAndGl0bGUnOgogICAgICAgIHJldHVybiB0aXRsZTsKICAgICAgY2FzZSAnbWFya19hc19yZWFkJzoKICAgICAgICByZXR1cm4gbWFya19hc19yZWFkOwogICAgICBjYXNlICdtYXJrX2FsbF9yZWFkJzoKICAgICAgICByZXR1cm4gbWFya19hbGxfcmVhZDsKICAgICAgY2FzZSAnZGVsZXRlJzoKICAgICAgICByZXR1cm4gZGVsZXRlOwogICAgICBjYXNlICdmaWx0ZXJfYWxsJzoKICAgICAgICByZXR1cm4gZmlsdGVyX2FsbDsKICAgICAgY2FzZSAnZmlsdGVyX3VucmVhZCc6CiAgICAgICAgcmV0dXJuIGZpbHRlcl91bnJlYWQ7CiAgICAgIGNhc2UgJ2ZpbHRlcl9yZWFkJzoKICAgICAgICByZXR1cm4gZmlsdGVyX3JlYWQ7CiAgICAgIGNhc2UgJ3R5cGVfcmVtaW5kZXInOgogICAgICAgIHJldHVybiB0eXBlX3JlbWluZGVyOwogICAgICBjYXNlICd0eXBlX2FsZXJ0JzoKICAgICAgICByZXR1cm4gdHlwZV9hbGVydDsKICAgICAgY2FzZSAndHlwZV9wcm9tb3Rpb24nOgogICAgICAgIHJldHVybiB0eXBlX3Byb21vdGlvbjsKICAgICAgY2FzZSAndHlwZV9zeXN0ZW0nOgogICAgICAgIHJldHVybiB0eXBlX3N5c3RlbTsKICAgICAgY2FzZSAndHlwZV9jdXN0b20nOgogICAgICAgIHJldHVybiB0eXBlX2N1c3RvbTsKICAgICAgY2FzZSAnZW1wdHlfdGl0bGUnOgogICAgICAgIHJldHVybiBlbXB0eV90aXRsZTsKICAgICAgY2FzZSAnZW1wdHlfZGVzY3JpcHRpb24nOgogICAgICAgIHJldHVybiBlbXB0eV9kZXNjcmlwdGlvbjsKICAgICAgY2FzZSAnZGVsZXRlX2NvbmZpcm1hdGlvbl90aXRsZSc6CiAgICAgICAgcmV0dXJuIGRlbGV0ZV9jb25maXJtYXRpb25fdGl0bGU7CiAgICAgIGNhc2UgJ2RlbGV0ZV9jb25maXJtYXRpb25fbWVzc2FnZSc6CiAgICAgICAgcmV0dXJuIGRlbGV0ZV9jb25maXJtYXRpb25fbWVzc2FnZTsKICAgICAgZGVmYXVsdDoKICAgICAgICByZXR1cm4ga2V5OwogICAgfQogIH0KfQoKY2xhc3MgRXJyb3JzTWVzc2FnZXMgaW1wbGVtZW50cyBpNjluLkk2OW5NZXNzYWdlQnVuZGxlIHsKICBmaW5hbCBNZXNzYWdlcyBfcGFyZW50OwogIGNvbnN0IEVycm9yc01lc3NhZ2VzKHRoaXMuX3BhcmVudCk7CiAgU3RyaW5nIGdldCBuZXR3b3JrX2Vycm9yID0+ICJOZXR3b3JrIGVycm9yLiBQbGVhc2UgY2hlY2sgeW91ciBjb25uZWN0aW9uLiI7CiAgU3RyaW5nIGdldCB1bmtub3duX2Vycm9yID0+ICJBbiB1bmtub3duIGVycm9yIG9jY3VycmVkLiI7CiAgU3RyaW5nIGdldCB2YWxpZGF0aW9uX2Vycm9yID0+ICJQbGVhc2UgY2hlY2sgeW91ciBpbnB1dCBhbmQgdHJ5IGFnYWluLiI7CiAgU3RyaW5nIGdldCBzZXJ2ZXJfZXJyb3IgPT4gIlNlcnZlciBlcnJvci4gUGxlYXNlIHRyeSBhZ2FpbiBsYXRlci4iOwogIFN0cmluZyBnZXQgZGVmYXVsdF9lcnJvcl9tZXNzYWdlID0+CiAgICAgICJPb3BzISBTb21ldGhpbmcgd2VudCB3cm9uZy4gUGxlYXNlIHRyeSBhZ2Fpbi4iOwogIFN0cmluZyBnZXQgdXNlcl9ub3RfZm91bmQgPT4gIlVzZXIgbm90IGZvdW5kLiBQbGVhc2UgY2hlY2sgeW91ciBjcmVkZW50aWFscy4iOwogIFN0cmluZyBnZXQgZGVmYXVsdF9lcnJvcl9kZXNjcmlwdGlvbiA9PgogICAgICAiV2UgZW5jb3VudGVyZWQgYW4gZXJyb3Igd2hpbGUgcHJvY2Vzc2luZyB5b3VyIHJlcXVlc3QuIFdlIGFwb2xvZ2l6ZSBmb3IgdGhlIGluY29udmVuaWVuY2UuIFBsZWFzZSB0cnkgYWdhaW4gbGF0ZXIgb3IgY29udGFjdCBzdXBwb3J0IGlmIHRoZSBpc3N1ZSBwZXJzaXN0cy4iOwogIFN0cmluZyBnZXQgcGFnZV9ub3RfZm91bmQgPT4gIlBhZ2UgTm90IEZvdW5kIjsKICBTdHJpbmcgZ2V0IHBhZ2Vfbm90X2ZvdW5kX2Rlc2NyaXB0aW9uID0+CiAgICAgICJUaGUgcGFnZSB5b3UgYXJlIGxvb2tpbmcgZm9yIGRvZXMgbm90IGV4aXN0LiI7CiAgU3RyaW5nIGdldCB1bmV4cGVjdGVkX2Vycm9yID0+ICJBbiB1bmV4cGVjdGVkIGVycm9yIG9jY3VycmVkLiI7CiAgU3RyaW5nIGdldCByZWRpcmVjdF9lcnJvciA9PiAiUmVkaXJlY3QgRXJyb3IiOwogIFN0cmluZyBnZXQgYmFkX3JlcXVlc3QgPT4gIkludmFsaWQgcmVxdWVzdC4gUGxlYXNlIGNoZWNrIHlvdXIgaW5wdXQuIjsKICBTdHJpbmcgZ2V0IHVuYXV0aG9yaXplZCA9PiAiQXV0aGVudGljYXRpb24gcmVxdWlyZWQuIFBsZWFzZSBzaWduIGluIGFnYWluLiI7CiAgU3RyaW5nIGdldCBmb3JiaWRkZW4gPT4gIkFjY2VzcyBkZW5pZWQuIFlvdSBkb24ndCBoYXZlIHBlcm1pc3Npb24uIjsKICBTdHJpbmcgZ2V0IG5vdF9mb3VuZCA9PiAiUmVxdWVzdGVkIHJlc291cmNlIG5vdCBmb3VuZC4iOwogIFN0cmluZyBnZXQgY29uZmxpY3QgPT4gIkRhdGEgY29uZmxpY3QuIFBsZWFzZSByZWZyZXNoIGFuZCB0cnkgYWdhaW4uIjsKICBTdHJpbmcgZ2V0IHVucHJvY2Vzc2FibGVfZW50aXR5ID0+CiAgICAgICJJbnZhbGlkIGRhdGEgZm9ybWF0LiBQbGVhc2UgY2hlY2sgeW91ciBpbnB1dC4iOwogIFN0cmluZyBnZXQgaW50ZXJuYWxfc2VydmVyX2Vycm9yID0+ICJTZXJ2ZXIgZXJyb3IuIFBsZWFzZSB0cnkgYWdhaW4gbGF0ZXIuIjsKICBTdHJpbmcgZ2V0IGNvbm5lY3Rpb25fdGltZW91dCA9PgogICAgICAiQ29ubmVjdGlvbiB0aW1lb3V0LiBQbGVhc2UgY2hlY2sgeW91ciBpbnRlcm5ldC4iOwogIFN0cmluZyBnZXQgcmVjZWl2ZV90aW1lb3V0ID0+ICJSZXF1ZXN0IHRpbWVvdXQuIFBsZWFzZSB0cnkgYWdhaW4uIjsKICBTdHJpbmcgZ2V0IHNlbmRfdGltZW91dCA9PiAiVXBsb2FkIHRpbWVvdXQuIFBsZWFzZSB0cnkgYWdhaW4uIjsKICBTdHJpbmcgZ2V0IG5vX2ludGVybmV0ID0+CiAgICAgICJObyBpbnRlcm5ldCBjb25uZWN0aW9uLiBQbGVhc2UgY2hlY2sgeW91ciBuZXR3b3JrLiI7CiAgU3RyaW5nIGdldCB1bmtub3duX25ldHdvcmsgPT4gIk5ldHdvcmsgZXJyb3Igb2NjdXJyZWQuIFBsZWFzZSB0cnkgYWdhaW4uIjsKICBTdHJpbmcgZm9ybWF0X2V4Y2VwdGlvbl9tZXNzYWdlKFN0cmluZyBjb2RlLCBTdHJpbmcgcG9zdGZpeCkgPT4KICAgICAgIlRoaXMgZGF0YSBpcyB3ZWFyaW5nIHRoZSB3cm9uZyBjb3N0dW1lLCBJIGRvbid0IHJlY29nbml6ZSBpdCBbJGNvZGVdICRwb3N0Zml4IjsKICBTdHJpbmcgdHlwZV9lcnJvcl9tZXNzYWdlKFN0cmluZyBjb2RlLCBTdHJpbmcgcG9zdGZpeCkgPT4KICAgICAgIlRoaXMgZGF0YSBpcyBub3Qgd2hhdCBJIGV4cGVjdGVkLCBJIGNhbid0IHByb2Nlc3MgaXQgWyRjb2RlXSAkcG9zdGZpeCI7CiAgU3RyaW5nIGluZGV4X2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KSA9PgogICAgICAiSG1tLCBJIGNhbid0IHNlZW0gdG8gZmluZCB0aGF0IGl0ZW0gaW4gdGhlIGxpc3QgWyRjb2RlXSAkcG9zdGZpeCI7CiAgU3RyaW5nIHJhbmdlX2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KSA9PgogICAgICAiT29wcyEgVGhhdCBudW1iZXIgaXMgd2F5IG91dCBvZiBteSBjb21mb3J0IHpvbmUgWyRjb2RlXSAkcG9zdGZpeCI7CiAgU3RyaW5nIGFyZ3VtZW50X2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KSA9PgogICAgICAiSGV5ISBTb21ldGhpbmcncyBub3QgcmlnaHQgd2l0aCB3aGF0IHlvdSBnYXZlIG1lIFskY29kZV0gJHBvc3RmaXgiOwogIFN0cmluZyBzdGF0ZV9lcnJvcl9tZXNzYWdlKFN0cmluZyBjb2RlLCBTdHJpbmcgcG9zdGZpeCkgPT4KICAgICAgIkknbSBhIGJpdCBjb25mdXNlZCBhYm91dCB3aGF0IEkgc2hvdWxkIGJlIGRvaW5nIHJpZ2h0IG5vdyBbJGNvZGVdICRwb3N0Zml4IjsKICBTdHJpbmcgdW5pbXBsZW1lbnRlZF9lcnJvcl9tZXNzYWdlKFN0cmluZyBjb2RlLCBTdHJpbmcgcG9zdGZpeCkgPT4KICAgICAgIlRoaXMgZmVhdHVyZSBpcyBzdGlsbCB1bmRlciBjb25zdHJ1Y3Rpb24gWyRjb2RlXSAkcG9zdGZpeCI7CiAgU3RyaW5nIHVuc3VwcG9ydGVkX2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KSA9PgogICAgICAiU29ycnksIEkgZG9uJ3Qga25vdyBob3cgdG8gZG8gdGhhdCB5ZXQgWyRjb2RlXSAkcG9zdGZpeCI7CiAgU3RyaW5nIGNvbmN1cnJlbnRfbW9kaWZpY2F0aW9uX2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KSA9PgogICAgICAiV2hvYSEgVG9vIG1hbnkgdGhpbmdzIGhhcHBlbmluZyBhdCBvbmNlIFskY29kZV0gJHBvc3RmaXgiOwogIFN0cmluZyBvdXRfb2ZfbWVtb3J5X2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KSA9PgogICAgICAiTXkgYnJhaW4gaXMgZnVsbCEgTmVlZCB0byBjbGVhciBzb21lIHNwYWNlIFskY29kZV0gJHBvc3RmaXgiOwogIFN0cmluZyBzdGFja19vdmVyZmxvd19lcnJvcl9tZXNzYWdlKFN0cmluZyBjb2RlLCBTdHJpbmcgcG9zdGZpeCkgPT4KICAgICAgIkknbSBzdHVjayBpbiBhIGxvb3AgYW5kIGdldHRpbmcgZGl6enkgWyRjb2RlXSAkcG9zdGZpeCI7CiAgU3RyaW5nIHVua25vd25fZXJyb3JfbWVzc2FnZShTdHJpbmcgY29kZSwgU3RyaW5nIHBvc3RmaXgpID0+CiAgICAgICJTb21ldGhpbmcgdW5leHBlY3RlZCBoYXBwZW5lZCwgYnV0IGRvbid0IHdvcnJ5IFskY29kZV0gJHBvc3RmaXgiOwogIE9iamVjdCBvcGVyYXRvciBbXShTdHJpbmcga2V5KSB7CiAgICB2YXIgaW5kZXggPSBrZXkuaW5kZXhPZignLicpOwogICAgaWYgKGluZGV4ID4gMCkgewogICAgICByZXR1cm4gKHRoaXNba2V5LnN1YnN0cmluZygwLCBpbmRleCldCiAgICAgICAgICBhcyBpNjluLkk2OW5NZXNzYWdlQnVuZGxlKVtrZXkuc3Vic3RyaW5nKGluZGV4ICsgMSldOwogICAgfQogICAgc3dpdGNoIChrZXkpIHsKICAgICAgY2FzZSAnbmV0d29ya19lcnJvcic6CiAgICAgICAgcmV0dXJuIG5ldHdvcmtfZXJyb3I7CiAgICAgIGNhc2UgJ3Vua25vd25fZXJyb3InOgogICAgICAgIHJldHVybiB1bmtub3duX2Vycm9yOwogICAgICBjYXNlICd2YWxpZGF0aW9uX2Vycm9yJzoKICAgICAgICByZXR1cm4gdmFsaWRhdGlvbl9lcnJvcjsKICAgICAgY2FzZSAnc2VydmVyX2Vycm9yJzoKICAgICAgICByZXR1cm4gc2VydmVyX2Vycm9yOwogICAgICBjYXNlICdkZWZhdWx0X2Vycm9yX21lc3NhZ2UnOgogICAgICAgIHJldHVybiBkZWZhdWx0X2Vycm9yX21lc3NhZ2U7CiAgICAgIGNhc2UgJ3VzZXJfbm90X2ZvdW5kJzoKICAgICAgICByZXR1cm4gdXNlcl9ub3RfZm91bmQ7CiAgICAgIGNhc2UgJ2RlZmF1bHRfZXJyb3JfZGVzY3JpcHRpb24nOgogICAgICAgIHJldHVybiBkZWZhdWx0X2Vycm9yX2Rlc2NyaXB0aW9uOwogICAgICBjYXNlICdwYWdlX25vdF9mb3VuZCc6CiAgICAgICAgcmV0dXJuIHBhZ2Vfbm90X2ZvdW5kOwogICAgICBjYXNlICdwYWdlX25vdF9mb3VuZF9kZXNjcmlwdGlvbic6CiAgICAgICAgcmV0dXJuIHBhZ2Vfbm90X2ZvdW5kX2Rlc2NyaXB0aW9uOwogICAgICBjYXNlICd1bmV4cGVjdGVkX2Vycm9yJzoKICAgICAgICByZXR1cm4gdW5leHBlY3RlZF9lcnJvcjsKICAgICAgY2FzZSAncmVkaXJlY3RfZXJyb3InOgogICAgICAgIHJldHVybiByZWRpcmVjdF9lcnJvcjsKICAgICAgY2FzZSAnYmFkX3JlcXVlc3QnOgogICAgICAgIHJldHVybiBiYWRfcmVxdWVzdDsKICAgICAgY2FzZSAndW5hdXRob3JpemVkJzoKICAgICAgICByZXR1cm4gdW5hdXRob3JpemVkOwogICAgICBjYXNlICdmb3JiaWRkZW4nOgogICAgICAgIHJldHVybiBmb3JiaWRkZW47CiAgICAgIGNhc2UgJ25vdF9mb3VuZCc6CiAgICAgICAgcmV0dXJuIG5vdF9mb3VuZDsKICAgICAgY2FzZSAnY29uZmxpY3QnOgogICAgICAgIHJldHVybiBjb25mbGljdDsKICAgICAgY2FzZSAndW5wcm9jZXNzYWJsZV9lbnRpdHknOgogICAgICAgIHJldHVybiB1bnByb2Nlc3NhYmxlX2VudGl0eTsKICAgICAgY2FzZSAnaW50ZXJuYWxfc2VydmVyX2Vycm9yJzoKICAgICAgICByZXR1cm4gaW50ZXJuYWxfc2VydmVyX2Vycm9yOwogICAgICBjYXNlICdjb25uZWN0aW9uX3RpbWVvdXQnOgogICAgICAgIHJldHVybiBjb25uZWN0aW9uX3RpbWVvdXQ7CiAgICAgIGNhc2UgJ3JlY2VpdmVfdGltZW91dCc6CiAgICAgICAgcmV0dXJuIHJlY2VpdmVfdGltZW91dDsKICAgICAgY2FzZSAnc2VuZF90aW1lb3V0JzoKICAgICAgICByZXR1cm4gc2VuZF90aW1lb3V0OwogICAgICBjYXNlICdub19pbnRlcm5ldCc6CiAgICAgICAgcmV0dXJuIG5vX2ludGVybmV0OwogICAgICBjYXNlICd1bmtub3duX25ldHdvcmsnOgogICAgICAgIHJldHVybiB1bmtub3duX25ldHdvcms7CiAgICAgIGNhc2UgJ2Zvcm1hdF9leGNlcHRpb25fbWVzc2FnZSc6CiAgICAgICAgcmV0dXJuIGZvcm1hdF9leGNlcHRpb25fbWVzc2FnZTsKICAgICAgY2FzZSAndHlwZV9lcnJvcl9tZXNzYWdlJzoKICAgICAgICByZXR1cm4gdHlwZV9lcnJvcl9tZXNzYWdlOwogICAgICBjYXNlICdpbmRleF9lcnJvcl9tZXNzYWdlJzoKICAgICAgICByZXR1cm4gaW5kZXhfZXJyb3JfbWVzc2FnZTsKICAgICAgY2FzZSAncmFuZ2VfZXJyb3JfbWVzc2FnZSc6CiAgICAgICAgcmV0dXJuIHJhbmdlX2Vycm9yX21lc3NhZ2U7CiAgICAgIGNhc2UgJ2FyZ3VtZW50X2Vycm9yX21lc3NhZ2UnOgogICAgICAgIHJldHVybiBhcmd1bWVudF9lcnJvcl9tZXNzYWdlOwogICAgICBjYXNlICdzdGF0ZV9lcnJvcl9tZXNzYWdlJzoKICAgICAgICByZXR1cm4gc3RhdGVfZXJyb3JfbWVzc2FnZTsKICAgICAgY2FzZSAndW5pbXBsZW1lbnRlZF9lcnJvcl9tZXNzYWdlJzoKICAgICAgICByZXR1cm4gdW5pbXBsZW1lbnRlZF9lcnJvcl9tZXNzYWdlOwogICAgICBjYXNlICd1bnN1cHBvcnRlZF9lcnJvcl9tZXNzYWdlJzoKICAgICAgICByZXR1cm4gdW5zdXBwb3J0ZWRfZXJyb3JfbWVzc2FnZTsKICAgICAgY2FzZSAnY29uY3VycmVudF9tb2RpZmljYXRpb25fZXJyb3JfbWVzc2FnZSc6CiAgICAgICAgcmV0dXJuIGNvbmN1cnJlbnRfbW9kaWZpY2F0aW9uX2Vycm9yX21lc3NhZ2U7CiAgICAgIGNhc2UgJ291dF9vZl9tZW1vcnlfZXJyb3JfbWVzc2FnZSc6CiAgICAgICAgcmV0dXJuIG91dF9vZl9tZW1vcnlfZXJyb3JfbWVzc2FnZTsKICAgICAgY2FzZSAnc3RhY2tfb3ZlcmZsb3dfZXJyb3JfbWVzc2FnZSc6CiAgICAgICAgcmV0dXJuIHN0YWNrX292ZXJmbG93X2Vycm9yX21lc3NhZ2U7CiAgICAgIGNhc2UgJ3Vua25vd25fZXJyb3JfbWVzc2FnZSc6CiAgICAgICAgcmV0dXJuIHVua25vd25fZXJyb3JfbWVzc2FnZTsKICAgICAgZGVmYXVsdDoKICAgICAgICByZXR1cm4ga2V5OwogICAgfQogIH0KfQoKY2xhc3MgVmFsaWRhdGlvbk1lc3NhZ2VzIGltcGxlbWVudHMgaTY5bi5JNjluTWVzc2FnZUJ1bmRsZSB7CiAgZmluYWwgTWVzc2FnZXMgX3BhcmVudDsKICBjb25zdCBWYWxpZGF0aW9uTWVzc2FnZXModGhpcy5fcGFyZW50KTsKICBTdHJpbmcgZ2V0IHJlcXVpcmVkX2ZpZWxkID0+ICJUaGlzIGZpZWxkIGlzIHJlcXVpcmVkIjsKICBTdHJpbmcgZ2V0IGludmFsaWRfZW1haWwgPT4gIlBsZWFzZSBlbnRlciBhIHZhbGlkIGVtYWlsIGFkZHJlc3MiOwogIFN0cmluZyBnZXQgcGFzc3dvcmRfdG9vX3Nob3J0ID0+ICJQYXNzd29yZCBtdXN0IGJlIGF0IGxlYXN0IDggY2hhcmFjdGVycyI7CiAgU3RyaW5nIGdldCBwYXNzd29yZHNfZG9udF9tYXRjaCA9PiAiUGFzc3dvcmRzIGRvIG5vdCBtYXRjaCI7CiAgU3RyaW5nIGludmFsaWRfa2V5X2NvbmZpZyhTdHJpbmcgb2YsIFN0cmluZyBrZXkpID0+CiAgICAgICJJbnZhbGlkIGNvbmZpZ3VyYXRpb24gZm9yICRrZXkgaW4gJG9mLiBQbGVhc2UgY2hlY2sgeW91ciBzZXR0aW5ncy4iOwogIE9iamVjdCBvcGVyYXRvciBbXShTdHJpbmcga2V5KSB7CiAgICB2YXIgaW5kZXggPSBrZXkuaW5kZXhPZignLicpOwogICAgaWYgKGluZGV4ID4gMCkgewogICAgICByZXR1cm4gKHRoaXNba2V5LnN1YnN0cmluZygwLCBpbmRleCldCiAgICAgICAgICBhcyBpNjluLkk2OW5NZXNzYWdlQnVuZGxlKVtrZXkuc3Vic3RyaW5nKGluZGV4ICsgMSldOwogICAgfQogICAgc3dpdGNoIChrZXkpIHsKICAgICAgY2FzZSAncmVxdWlyZWRfZmllbGQnOgogICAgICAgIHJldHVybiByZXF1aXJlZF9maWVsZDsKICAgICAgY2FzZSAnaW52YWxpZF9lbWFpbCc6CiAgICAgICAgcmV0dXJuIGludmFsaWRfZW1haWw7CiAgICAgIGNhc2UgJ3Bhc3N3b3JkX3Rvb19zaG9ydCc6CiAgICAgICAgcmV0dXJuIHBhc3N3b3JkX3Rvb19zaG9ydDsKICAgICAgY2FzZSAncGFzc3dvcmRzX2RvbnRfbWF0Y2gnOgogICAgICAgIHJldHVybiBwYXNzd29yZHNfZG9udF9tYXRjaDsKICAgICAgY2FzZSAnaW52YWxpZF9rZXlfY29uZmlnJzoKICAgICAgICByZXR1cm4gaW52YWxpZF9rZXlfY29uZmlnOwogICAgICBkZWZhdWx0OgogICAgICAgIHJldHVybiBrZXk7CiAgICB9CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/localization/lib/messages.i69n.yaml", + "data": + "IyB7e25hbWUudGl0bGVDYXNlKCl9fSAtIEludGVybmF0aW9uYWxpemF0aW9uIE1lc3NhZ2VzCiMgQmFzZSB0cmFuc2xhdGlvbnMgZm9yIHRoZSBhcHBsaWNhdGlvbgoKIyBhcHBsaWNhdGlvbiBtZXRhZGF0YQphcHA6CiAgbmFtZTogInt7bmFtZS50aXRsZUNhc2UoKX19IgogIGRlc2NyaXB0aW9uOiAie3t7ZGVzY3JpcHRpb259fX0iCiAgd2VsY29tZV90b19hcHA6ICJXZWxjb21lIHRvIHt7bmFtZS50aXRsZUNhc2UoKX19ISIKCiMgQ29tbW9uL0dlbmVyaWMgbWVzc2FnZXMKZ2VuZXJpYzoKICBvazogIk9LIgogIGNhbmNlbDogIkNhbmNlbCIKICBzYXZlOiAiU2F2ZSIKICBkZWxldGU6ICJEZWxldGUiCiAgZWRpdDogIkVkaXQiCiAgdXBkYXRlOiAiVXBkYXRlIgogIHN1Ym1pdDogIlN1Ym1pdCIKICBjbG9zZTogIkNsb3NlIgogIGJhY2s6ICJCYWNrIgogIG5leHQ6ICJOZXh0IgogIHByZXZpb3VzOiAiUHJldmlvdXMiCiAgZG9uZTogIkRvbmUiCiAgbG9hZGluZzogIkxvYWRpbmcuLi4iCiAgZXJyb3I6ICJFcnJvciIKICBzdWNjZXNzOiAiU3VjY2VzcyIKICB3YXJuaW5nOiAiV2FybmluZyIKICBpbmZvOiAiSW5mbyIKICByZXRyeTogIlJldHJ5IgogIHJlZnJlc2g6ICJSZWZyZXNoIgogIHllczogIlllcyIKICBubzogIk5vIgogIGFkZDogIisgQWRkIgogIHRyeV9hZ2FpbjogIlRyeSBBZ2FpbiIKCiMgQ29tbW9uIG1lc3NhZ2VzICh1c2VkIGZvciBjb25zaXN0ZW5jeSBhY3Jvc3MgbW9kdWxlcykKY29tbW9uOgogIGNhbmNlbDogIkNhbmNlbCIKICBvazogIk9LIgogIHNhdmU6ICJTYXZlIgogIGRlbGV0ZTogIkRlbGV0ZSIKICBlZGl0OiAiRWRpdCIKICB0cnlfYWdhaW46ICJUcnkgQWdhaW4iCiAgd2VlazogIldlZWsiCiAgbW9udGg6ICJNb250aCIKCiMgQXV0aGVudGljYXRpb24KYXV0aDoKICByZWdpc3RlcjogIlJlZ2lzdGVyIgogIHNpZ25faW46ICJTaWduIEluIgogIHNpZ25fb3V0OiAiU2lnbiBPdXQiCiAgZG9udF9oYXZlX2FjY291bnQ6ICJEb24ndCBoYXZlIGFuIGFjY291bnQ/ICIKICBhbHJlYWR5X2hhdmVfYWNjb3VudDogIkFscmVhZHkgaGF2ZSBhbiBhY2NvdW50PyAiCiAgc2lnbl9vdXRfY29uZmlybWF0aW9uOiAiQXJlIHlvdSBzdXJlIHlvdSB3YW50IHRvIHNpZ24gb3V0PyIKCiMgUHJvZmlsZQojIFByb2ZpbGUKcHJvZmlsZToKICBwcm9maWxlOiAiUHJvZmlsZSIKICBzZXR0aW5nczogIlNldHRpbmdzIgogIGFjY291bnQ6ICJBY2NvdW50IgogIHBlcnNvbmFsX2luZm86ICJQZXJzb25hbCBJbmZvcm1hdGlvbiIKICBwcml2YWN5X3NldHRpbmdzOiAiUHJpdmFjeSBTZXR0aW5ncyIKICBuYW1lOiAiTmFtZSIKICBlbWFpbF9hZGRyZXNzOiAiRW1haWwgQWRkcmVzcyIKICBwaG9uZV9udW1iZXI6ICJQaG9uZSBOdW1iZXIiCiAgZGF0ZV9vZl9iaXJ0aDogIkRhdGUgb2YgQmlydGgiCiAgZGVsZXRlX2NvbmZpcm1hdGlvbjogIkRlbGV0ZSBDb25maXJtYXRpb24iCiAgZGVsZXRlX2NvbmZpcm1hdGlvbl9tZXNzYWdlOiAiQXJlIHlvdSBzdXJlIHlvdSB3YW50IHRvIGRlbGV0ZSB0aGlzIGl0ZW0/IFRoaXMgYWN0aW9uIGNhbm5vdCBiZSB1bmRvbmUuIgoKbmF2OgogIGhvbWU6ICJIb21lIgogIGRhc2hib2FyZDogIkRhc2hib2FyZCIKICBwcm9maWxlOiAiUHJvZmlsZSIKICBzZXR0aW5nczogIlNldHRpbmdzIgogIG5vdGlmaWNhdGlvbnM6ICJOb3RpZmljYXRpb25zIgoKIyBOb3RpZmljYXRpb25zCm5vdGlmaWNhdGlvbnM6CiAgdGl0bGU6ICJOb3RpZmljYXRpb25zIgogIG1hcmtfYXNfcmVhZDogIk1hcmsgYXMgcmVhZCIKICBtYXJrX2FsbF9yZWFkOiAiTWFyayBhbGwgYXMgcmVhZCIKICBkZWxldGU6ICJEZWxldGUiCiAgCiAgIyBGaWx0ZXIgdGFicwogIGZpbHRlcl9hbGw6ICJBbGwiCiAgZmlsdGVyX3VucmVhZDogIlVucmVhZCIKICBmaWx0ZXJfcmVhZDogIlJlYWQiCiAgCiAgIyBOb3RpZmljYXRpb24gdHlwZXMKICB0eXBlX3JlbWluZGVyOiAiUmVtaW5kZXIiCiAgdHlwZV9hbGVydDogIkFsZXJ0IgogIHR5cGVfcHJvbW90aW9uOiAiUHJvbW90aW9uIgogIHR5cGVfc3lzdGVtOiAiU3lzdGVtIgogIHR5cGVfY3VzdG9tOiAiQ3VzdG9tIgogIAogICMgRW1wdHkgc3RhdGVzCiAgZW1wdHlfdGl0bGU6ICJObyBub3RpZmljYXRpb25zIgogIGVtcHR5X2Rlc2NyaXB0aW9uOiAiWW91J3JlIGFsbCBjYXVnaHQgdXAhIE5ldyBub3RpZmljYXRpb25zIHdpbGwgYXBwZWFyIGhlcmUuIgogIAogICMgRGVsZXRlIGNvbmZpcm1hdGlvbgogIGRlbGV0ZV9jb25maXJtYXRpb25fdGl0bGU6ICJEZWxldGUgbm90aWZpY2F0aW9uPyIKICBkZWxldGVfY29uZmlybWF0aW9uX21lc3NhZ2U6ICJUaGlzIG5vdGlmaWNhdGlvbiB3aWxsIGJlIHBlcm1hbmVudGx5IHJlbW92ZWQgZnJvbSB5b3VyIGxpc3QuIgoKIyBFcnJvcnMKZXJyb3JzOgogIG5ldHdvcmtfZXJyb3I6ICJOZXR3b3JrIGVycm9yLiBQbGVhc2UgY2hlY2sgeW91ciBjb25uZWN0aW9uLiIKICB1bmtub3duX2Vycm9yOiAiQW4gdW5rbm93biBlcnJvciBvY2N1cnJlZC4iCiAgdmFsaWRhdGlvbl9lcnJvcjogIlBsZWFzZSBjaGVjayB5b3VyIGlucHV0IGFuZCB0cnkgYWdhaW4uIgogIHNlcnZlcl9lcnJvcjogIlNlcnZlciBlcnJvci4gUGxlYXNlIHRyeSBhZ2FpbiBsYXRlci4iCiAgZGVmYXVsdF9lcnJvcl9tZXNzYWdlOiAiT29wcyEgU29tZXRoaW5nIHdlbnQgd3JvbmcuIFBsZWFzZSB0cnkgYWdhaW4uIgogIHVzZXJfbm90X2ZvdW5kOiAiVXNlciBub3QgZm91bmQuIFBsZWFzZSBjaGVjayB5b3VyIGNyZWRlbnRpYWxzLiIKICBkZWZhdWx0X2Vycm9yX2Rlc2NyaXB0aW9uOiAiV2UgZW5jb3VudGVyZWQgYW4gZXJyb3Igd2hpbGUgcHJvY2Vzc2luZyB5b3VyIHJlcXVlc3QuIFdlIGFwb2xvZ2l6ZSBmb3IgdGhlIGluY29udmVuaWVuY2UuIFBsZWFzZSB0cnkgYWdhaW4gbGF0ZXIgb3IgY29udGFjdCBzdXBwb3J0IGlmIHRoZSBpc3N1ZSBwZXJzaXN0cy4iCiAgcGFnZV9ub3RfZm91bmQ6ICJQYWdlIE5vdCBGb3VuZCIKICBwYWdlX25vdF9mb3VuZF9kZXNjcmlwdGlvbjogIlRoZSBwYWdlIHlvdSBhcmUgbG9va2luZyBmb3IgZG9lcyBub3QgZXhpc3QuIgogIHVuZXhwZWN0ZWRfZXJyb3I6ICJBbiB1bmV4cGVjdGVkIGVycm9yIG9jY3VycmVkLiIKICByZWRpcmVjdF9lcnJvcjogIlJlZGlyZWN0IEVycm9yIgogIGJhZF9yZXF1ZXN0OiAiSW52YWxpZCByZXF1ZXN0LiBQbGVhc2UgY2hlY2sgeW91ciBpbnB1dC4iCiAgdW5hdXRob3JpemVkOiAiQXV0aGVudGljYXRpb24gcmVxdWlyZWQuIFBsZWFzZSBzaWduIGluIGFnYWluLiIKICBmb3JiaWRkZW46ICJBY2Nlc3MgZGVuaWVkLiBZb3UgZG9uJ3QgaGF2ZSBwZXJtaXNzaW9uLiIKICBub3RfZm91bmQ6ICJSZXF1ZXN0ZWQgcmVzb3VyY2Ugbm90IGZvdW5kLiIKICBjb25mbGljdDogIkRhdGEgY29uZmxpY3QuIFBsZWFzZSByZWZyZXNoIGFuZCB0cnkgYWdhaW4uIgogIHVucHJvY2Vzc2FibGVfZW50aXR5OiAiSW52YWxpZCBkYXRhIGZvcm1hdC4gUGxlYXNlIGNoZWNrIHlvdXIgaW5wdXQuIgogIGludGVybmFsX3NlcnZlcl9lcnJvcjogIlNlcnZlciBlcnJvci4gUGxlYXNlIHRyeSBhZ2FpbiBsYXRlci4iCiAgY29ubmVjdGlvbl90aW1lb3V0OiAiQ29ubmVjdGlvbiB0aW1lb3V0LiBQbGVhc2UgY2hlY2sgeW91ciBpbnRlcm5ldC4iCiAgcmVjZWl2ZV90aW1lb3V0OiAiUmVxdWVzdCB0aW1lb3V0LiBQbGVhc2UgdHJ5IGFnYWluLiIKICBzZW5kX3RpbWVvdXQ6ICJVcGxvYWQgdGltZW91dC4gUGxlYXNlIHRyeSBhZ2Fpbi4iCiAgbm9faW50ZXJuZXQ6ICJObyBpbnRlcm5ldCBjb25uZWN0aW9uLiBQbGVhc2UgY2hlY2sgeW91ciBuZXR3b3JrLiIKICB1bmtub3duX25ldHdvcms6ICJOZXR3b3JrIGVycm9yIG9jY3VycmVkLiBQbGVhc2UgdHJ5IGFnYWluLiIKICBmb3JtYXRfZXhjZXB0aW9uX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KTogIlRoaXMgZGF0YSBpcyB3ZWFyaW5nIHRoZSB3cm9uZyBjb3N0dW1lLCBJIGRvbid0IHJlY29nbml6ZSBpdCBbJGNvZGVdICRwb3N0Zml4IgogIHR5cGVfZXJyb3JfbWVzc2FnZShTdHJpbmcgY29kZSwgU3RyaW5nIHBvc3RmaXgpOiAiVGhpcyBkYXRhIGlzIG5vdCB3aGF0IEkgZXhwZWN0ZWQsIEkgY2FuJ3QgcHJvY2VzcyBpdCBbJGNvZGVdICRwb3N0Zml4IgogIGluZGV4X2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KTogIkhtbSwgSSBjYW4ndCBzZWVtIHRvIGZpbmQgdGhhdCBpdGVtIGluIHRoZSBsaXN0IFskY29kZV0gJHBvc3RmaXgiCiAgcmFuZ2VfZXJyb3JfbWVzc2FnZShTdHJpbmcgY29kZSwgU3RyaW5nIHBvc3RmaXgpOiAiT29wcyEgVGhhdCBudW1iZXIgaXMgd2F5IG91dCBvZiBteSBjb21mb3J0IHpvbmUgWyRjb2RlXSAkcG9zdGZpeCIKICBhcmd1bWVudF9lcnJvcl9tZXNzYWdlKFN0cmluZyBjb2RlLCBTdHJpbmcgcG9zdGZpeCk6ICJIZXkhIFNvbWV0aGluZydzIG5vdCByaWdodCB3aXRoIHdoYXQgeW91IGdhdmUgbWUgWyRjb2RlXSAkcG9zdGZpeCIKICBzdGF0ZV9lcnJvcl9tZXNzYWdlKFN0cmluZyBjb2RlLCBTdHJpbmcgcG9zdGZpeCk6ICJJJ20gYSBiaXQgY29uZnVzZWQgYWJvdXQgd2hhdCBJIHNob3VsZCBiZSBkb2luZyByaWdodCBub3cgWyRjb2RlXSAkcG9zdGZpeCIKICB1bmltcGxlbWVudGVkX2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KTogIlRoaXMgZmVhdHVyZSBpcyBzdGlsbCB1bmRlciBjb25zdHJ1Y3Rpb24gWyRjb2RlXSAkcG9zdGZpeCIKICB1bnN1cHBvcnRlZF9lcnJvcl9tZXNzYWdlKFN0cmluZyBjb2RlLCBTdHJpbmcgcG9zdGZpeCk6ICJTb3JyeSwgSSBkb24ndCBrbm93IGhvdyB0byBkbyB0aGF0IHlldCBbJGNvZGVdICRwb3N0Zml4IgogIGNvbmN1cnJlbnRfbW9kaWZpY2F0aW9uX2Vycm9yX21lc3NhZ2UoU3RyaW5nIGNvZGUsIFN0cmluZyBwb3N0Zml4KTogIldob2EhIFRvbyBtYW55IHRoaW5ncyBoYXBwZW5pbmcgYXQgb25jZSBbJGNvZGVdICRwb3N0Zml4IgogIG91dF9vZl9tZW1vcnlfZXJyb3JfbWVzc2FnZShTdHJpbmcgY29kZSwgU3RyaW5nIHBvc3RmaXgpOiAiTXkgYnJhaW4gaXMgZnVsbCEgTmVlZCB0byBjbGVhciBzb21lIHNwYWNlIFskY29kZV0gJHBvc3RmaXgiCiAgc3RhY2tfb3ZlcmZsb3dfZXJyb3JfbWVzc2FnZShTdHJpbmcgY29kZSwgU3RyaW5nIHBvc3RmaXgpOiAiSSdtIHN0dWNrIGluIGEgbG9vcCBhbmQgZ2V0dGluZyBkaXp6eSBbJGNvZGVdICRwb3N0Zml4IgogIHVua25vd25fZXJyb3JfbWVzc2FnZShTdHJpbmcgY29kZSwgU3RyaW5nIHBvc3RmaXgpOiAiU29tZXRoaW5nIHVuZXhwZWN0ZWQgaGFwcGVuZWQsIGJ1dCBkb24ndCB3b3JyeSBbJGNvZGVdICRwb3N0Zml4IgojIFZhbGlkYXRpb24gbWVzc2FnZXMKdmFsaWRhdGlvbjoKICByZXF1aXJlZF9maWVsZDogIlRoaXMgZmllbGQgaXMgcmVxdWlyZWQiCiAgaW52YWxpZF9lbWFpbDogIlBsZWFzZSBlbnRlciBhIHZhbGlkIGVtYWlsIGFkZHJlc3MiCiAgcGFzc3dvcmRfdG9vX3Nob3J0OiAiUGFzc3dvcmQgbXVzdCBiZSBhdCBsZWFzdCA4IGNoYXJhY3RlcnMiCiAgcGFzc3dvcmRzX2RvbnRfbWF0Y2g6ICJQYXNzd29yZHMgZG8gbm90IG1hdGNoIgogIGludmFsaWRfa2V5X2NvbmZpZyhTdHJpbmcgb2YsIFN0cmluZyBrZXkpOiAiSW52YWxpZCBjb25maWd1cmF0aW9uIGZvciAka2V5IGluICRvZi4gUGxlYXNlIGNoZWNrIHlvdXIgc2V0dGluZ3MuIgo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/localization/lib/src/localization_provider.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2NhbGl6YXRpb24vbWVzc2FnZXMuaTY5bi5kYXJ0JzsKCi8vLyBQcm92aWRlcyBsb2NhbGl6YXRpb24gdXRpbGl0aWVzIGZvciB0aGUge3tuYW1lLnRpdGxlQ2FzZSgpfX0gYXBwbGljYXRpb24KY2xhc3MgTG9jYWxpemF0aW9uUHJvdmlkZXIgewogIHN0YXRpYyBNZXNzYWdlcyBfbWVzc2FnZXMgPSBjb25zdCBNZXNzYWdlcygpOwoKICAvLy8gR2V0IHRoZSBjdXJyZW50IG1lc3NhZ2VzIGluc3RhbmNlCiAgc3RhdGljIE1lc3NhZ2VzIGdldCBtZXNzYWdlcyA9PiBfbWVzc2FnZXM7CgogIC8vLyBJbml0aWFsaXplIGxvY2FsaXphdGlvbiAoY3VycmVudGx5IG9ubHkgc3VwcG9ydHMgRW5nbGlzaCkKICBzdGF0aWMgdm9pZCBpbml0aWFsaXplKHtTdHJpbmcgbG9jYWxlID0gJ2VuJ30pIHsKICAgIC8vIEN1cnJlbnRseSBvbmx5IHN1cHBvcnRzIEVuZ2xpc2gsIGZ1dHVyZSB2ZXJzaW9ucyB3aWxsIHN1cHBvcnQgbXVsdGlwbGUgbG9jYWxlcwogICAgX21lc3NhZ2VzID0gY29uc3QgTWVzc2FnZXMoKTsKICB9CgogIC8vLyBHZXQgbWVzc2FnZXMgKGN1cnJlbnRseSBvbmx5IHN1cHBvcnRzIEVuZ2xpc2gpCiAgc3RhdGljIE1lc3NhZ2VzIGdldE1lc3NhZ2VzKFN0cmluZyBsb2NhbGUpIHsKICAgIC8vIEN1cnJlbnRseSBvbmx5IHN1cHBvcnRzIEVuZ2xpc2gsIGZ1dHVyZSB2ZXJzaW9ucyB3aWxsIHN1cHBvcnQgbXVsdGlwbGUgbG9jYWxlcwogICAgcmV0dXJuIGNvbnN0IE1lc3NhZ2VzKCk7CiAgfQoKICAvLy8gR2V0IHRoZSBjdXJyZW50IGxvY2FsZQogIHN0YXRpYyBTdHJpbmcgZ2V0IGN1cnJlbnRMb2NhbGUgPT4gJ2VuJzsKCiAgLy8vIEdldCBzdXBwb3J0ZWQgbG9jYWxlcyAoY3VycmVudGx5IG9ubHkgRW5nbGlzaCkKICBzdGF0aWMgTGlzdDxTdHJpbmc+IGdldCBzdXBwb3J0ZWRMb2NhbGVzID0+IFsnZW4nXTsKCiAgLy8vIENoZWNrIGlmIGEgbG9jYWxlIGlzIHN1cHBvcnRlZAogIHN0YXRpYyBib29sIGlzTG9jYWxlU3VwcG9ydGVkKFN0cmluZyBsb2NhbGUpIHsKICAgIHJldHVybiBzdXBwb3J0ZWRMb2NhbGVzLmNvbnRhaW5zKGxvY2FsZSk7CiAgfQoKICAvLy8gR2V0IHRoZSBiZXN0IG1hdGNoaW5nIGxvY2FsZSBmcm9tIGEgbGlzdCBvZiBwcmVmZXJyZWQgbG9jYWxlcwogIHN0YXRpYyBTdHJpbmcgZ2V0QmVzdE1hdGNoaW5nTG9jYWxlKExpc3Q8TG9jYWxlPiBwcmVmZXJyZWRMb2NhbGVzKSB7CiAgICBmb3IgKGZpbmFsIGxvY2FsZSBpbiBwcmVmZXJyZWRMb2NhbGVzKSB7CiAgICAgIGlmIChpc0xvY2FsZVN1cHBvcnRlZChsb2NhbGUubGFuZ3VhZ2VDb2RlKSkgewogICAgICAgIHJldHVybiBsb2NhbGUubGFuZ3VhZ2VDb2RlOwogICAgICB9CiAgICB9CiAgICByZXR1cm4gJ2VuJzsgLy8gRGVmYXVsdCB0byBFbmdsaXNoCiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/localization/lib/src/strings.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CgovLy8gR2xvYmFsIHN0cmluZ3MgaW5zdGFuY2UgZm9yIGVhc3kgYWNjZXNzIHRocm91Z2hvdXQgdGhlIGFwcGxpY2F0aW9uCi8vLwovLy8gVXNhZ2U6Ci8vLyBgYGBkYXJ0Ci8vLyBpbXBvcnQgJ3BhY2thZ2U6bG9jYWxpemF0aW9uL2xvY2FsaXphdGlvbi5kYXJ0JzsKLy8vCi8vLyAvLyBBY2Nlc3Mgc3RyaW5ncyBlYXNpbHkKLy8vIFRleHQoc3RyaW5ncy5hdXRoLmxvZ2luKTsKLy8vIFRleHQoc3RyaW5ncy5nZW5lcmljLm9rKTsKLy8vIFRleHQoc3RyaW5ncy5wcm9maWxlLm5hbWUpOwovLy8gYGBgCk1lc3NhZ2VzIGdldCBzdHJpbmdzID0+IExvY2FsaXphdGlvblByb3ZpZGVyLm1lc3NhZ2VzOwo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/localization/pubspec.yaml", + "data": + "bmFtZTogbG9jYWxpemF0aW9uCmRlc2NyaXB0aW9uOiAiVHlwZS1zYWZlIHN0cmluZ3MgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LCBnZW5lcmF0ZWQgZnJvbSBZQU1MIHdpdGggaTY5bi4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjI5LjAiCgpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcgogIGk2OW46IF4zLjEuMAoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKICBidWlsZF9ydW5uZXI6IF4yLjQuMTUKCmZsdXR0ZXI6Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/localization/README.md", + "data": + "IyBMb2NhbGl6YXRpb24KCkxvY2FsaXphdGlvbiBwYWNrYWdlIGZvciB7e25hbWUudGl0bGVDYXNlKCl9fSB1c2luZyBpNjluIGZvciB0eXBlLXNhZmUgaW50ZXJuYXRpb25hbGl6YXRpb24uCgpbIVtub25zdG9wX2NsaV0oaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9iYWRnZS9zdGFydGVkJTIwd2l0aC1ub25zdG9wX2NsaS0xNjZDNEUuc3ZnP3N0eWxlPWZsYXQtc3F1YXJlKV0oaHR0cHM6Ly9wdWIuZGV2L3BhY2thZ2VzL25vbnN0b3BfY2xpKQpbIVttZWxvc10oaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9iYWRnZS9tYWludGFpbmVkJTIwd2l0aC1tZWxvcy1mNzAwZmYuc3ZnP3N0eWxlPWZsYXQtc3F1YXJlKV0oaHR0cHM6Ly9naXRodWIuY29tL2ludmVydGFzZS9tZWxvcykKCiMjIEZlYXR1cmVzIOKcqAoKLSAqKlR5cGUtc2FmZSB0cmFuc2xhdGlvbnMqKiAtIEdlbmVyYXRlZCBjbGFzc2VzIHdpdGggY29tcGlsZS10aW1lIHN0cmluZyB2YWxpZGF0aW9uCi0gKipFYXN5IGludGVncmF0aW9uKiogLSBTaW1wbGUgQVBJIHdpdGggZ2xvYmFsIGBzdHJpbmdzYCBhY2Nlc3MKLSAqKk9yZ2FuaXplZCBzdHJ1Y3R1cmUqKiAtIENhdGVnb3JpemVkIG1lc3NhZ2VzIChhdXRoLCBwcm9maWxlLCBnZW5lcmljLCBldGMuKQotICoqRnV0dXJlLXJlYWR5KiogLSBCdWlsdCBmb3IgbXVsdGktbGFuZ3VhZ2Ugc3VwcG9ydAotICoqTWVsb3MgaW50ZWdyYXRpb24qKiAtIFNlYW1sZXNzIG1vbm9yZXBvIHdvcmtmbG93CgojIyBJbnN0YWxsYXRpb24g8J+SuwoKQWRkIHRvIHlvdXIgYHB1YnNwZWMueWFtbGA6CgpgYGB5YW1sCmRlcGVuZGVuY2llczoKICBsb2NhbGl6YXRpb246CiAgICBwYXRoOiAuLi9wYWNrYWdlcy9sb2NhbGl6YXRpb24KYGBgCgojIyBVc2FnZSDwn5qACgojIyMgQmFzaWMgVXNhZ2UKCmBgYGRhcnQKaW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CgovLyBTaW1wbGUgYWNjZXNzIHRvIGFueSBzdHJpbmcKVGV4dChzdHJpbmdzLmF1dGgubG9naW4pOyAgICAgICAgICAgLy8gIkxvZ2luIgpUZXh0KHN0cmluZ3MuZ2VuZXJpYy5vayk7ICAgICAgICAgICAvLyAiT0siClRleHQoc3RyaW5ncy5wcm9maWxlLm5hbWUpOyAgICAgICAgIC8vICJOYW1lIgpUZXh0KHN0cmluZ3MudmFsaWRhdGlvbi5yZXF1aXJlZF9maWVsZCk7IC8vICJUaGlzIGZpZWxkIGlzIHJlcXVpcmVkIgpgYGAKCiMjIyBJbiBXaWRnZXRzCgpgYGBkYXJ0CmNsYXNzIExvZ2luQnV0dG9uIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBFbGV2YXRlZEJ1dHRvbigKICAgICAgb25QcmVzc2VkOiAoKSA9PiBfbG9naW4oKSwKICAgICAgY2hpbGQ6IFRleHQoc3RyaW5ncy5hdXRoLmxvZ2luKSwKICAgICk7CiAgfQp9CmBgYAoKIyMjIEVycm9yIEhhbmRsaW5nCgpgYGBkYXJ0CnZvaWQgc2hvd0Vycm9yKCkgewogIFNjYWZmb2xkTWVzc2VuZ2VyLm9mKGNvbnRleHQpLnNob3dTbmFja0JhcigKICAgIFNuYWNrQmFyKGNvbnRlbnQ6IFRleHQoc3RyaW5ncy5lcnJvcnMubmV0d29ya19lcnJvcikpLAogICk7Cn0KYGBgCgojIyMgRm9ybSBWYWxpZGF0aW9uCgpgYGBkYXJ0ClN0cmluZz8gdmFsaWRhdGVFbWFpbChTdHJpbmc/IHZhbHVlKSB7CiAgaWYgKHZhbHVlPy5pc0VtcHR5ID8/IHRydWUpIHsKICAgIHJldHVybiBzdHJpbmdzLnZhbGlkYXRpb24ucmVxdWlyZWRfZmllbGQ7CiAgfQogIGlmICghdmFsdWUhLmNvbnRhaW5zKCdAJykpIHsKICAgIHJldHVybiBzdHJpbmdzLnZhbGlkYXRpb24uaW52YWxpZF9lbWFpbDsKICB9CiAgcmV0dXJuIG51bGw7Cn0KYGBgCgojIyBEZXZlbG9wbWVudCDwn5ug77iPCgojIyMgQWRkaW5nIE5ldyBTdHJpbmdzCgoxLiBFZGl0IGBsaWIvbWVzc2FnZXMuaTY5bi55YW1sYDoKYGBgeWFtbApuZXdfY2F0ZWdvcnk6CiAgbmV3X21lc3NhZ2U6ICJOZXcgTWVzc2FnZSBUZXh0IgogIGFub3RoZXJfbWVzc2FnZTogIkFub3RoZXIgTWVzc2FnZSIKYGBgCgoyLiBHZW5lcmF0ZSBjb2RlOgpgYGBiYXNoCiMgRnJvbSB0aGUgbW9ub3JlcG8gcm9vdAptZWxvcyBydW4gZ2VuZXJhdGU6aTY5bgoKIyBPciBmcm9tIHRoaXMgcGFja2FnZQpkYXJ0IHJ1biBidWlsZF9ydW5uZXIgYnVpbGQgLS1kZWxldGUtY29uZmxpY3Rpbmctb3V0cHV0cwpgYGAKCjMuIFVzZSB0aGUgbmV3IHN0cmluZ3M6CmBgYGRhcnQKVGV4dChzdHJpbmdzLm5ld19jYXRlZ29yeS5uZXdfbWVzc2FnZSk7IC8vICJOZXcgTWVzc2FnZSBUZXh0IgpgYGAKCiMjIyBUZXN0aW5nCgpgYGBiYXNoCiMgUnVuIHRlc3RzCmZsdXR0ZXIgdGVzdAoKIyBSdW4gd2l0aCBjb3ZlcmFnZQpmbHV0dGVyIHRlc3QgLS1jb3ZlcmFnZQpgYGAKCiMjIyBMaW50aW5nCgpgYGBiYXNoCiMgRnJvbSBtb25vcmVwbyByb290Cm1lbG9zIHJ1biBsaW50CgojIE9yIGZyb20gdGhpcyBwYWNrYWdlCmRhcnQgZm9ybWF0IC4gJiYgZGFydCBhbmFseXplIC0tZmF0YWwtaW5mb3MgLgpgYGAKCiMjIEFkdmFuY2VkIFVzYWdlIPCflKcKCiMjIyBVc2luZyBMb2NhbGl6YXRpb25Qcm92aWRlcgoKYGBgZGFydAppbXBvcnQgJ3BhY2thZ2U6bG9jYWxpemF0aW9uL2xvY2FsaXphdGlvbi5kYXJ0JzsKCi8vIEluaXRpYWxpemUgKGN1cnJlbnRseSBvbmx5IHN1cHBvcnRzIEVuZ2xpc2gpCkxvY2FsaXphdGlvblByb3ZpZGVyLmluaXRpYWxpemUoKTsKCi8vIENoZWNrIHN1cHBvcnRlZCBsb2NhbGVzCmlmIChMb2NhbGl6YXRpb25Qcm92aWRlci5pc0xvY2FsZVN1cHBvcnRlZCgnZW4nKSkgewogIC8vIEhhbmRsZSBzdXBwb3J0ZWQgbG9jYWxlCn0KCi8vIFNpbXBsZSBhY2Nlc3MgdG8gYW55IHN0cmluZwpUZXh0KHN0cmluZ3MuYXV0aC5sb2dpbik7ICAgICAgICAgICAvLyAiTG9naW4iCgpgYGAKCiMjIyBGdXR1cmUgTXVsdGktbGFuZ3VhZ2UgU3VwcG9ydAoKVGhpcyBwYWNrYWdlIGlzIGRlc2lnbmVkIHRvIHN1cHBvcnQgbXVsdGlwbGUgbGFuZ3VhZ2VzIGluIHRoZSBmdXR1cmUuIFRoZSBjdXJyZW50IGltcGxlbWVudGF0aW9uIHN1cHBvcnRzIEVuZ2xpc2ggb25seSwgYnV0IHRoZSBzdHJ1Y3R1cmUgaXMgcmVhZHkgZm9yOgoKLSBTcGFuaXNoIChgZXNgKQotIEZyZW5jaCAoYGZyYCkKLSBHZXJtYW4gKGBkZWApCi0gQWRkaXRpb25hbCBsYW5ndWFnZXMgYXMgbmVlZGVkCgojIyBQYWNrYWdlIFN0cnVjdHVyZSDwn5OBCgpgYGAKcGFja2FnZXMvbG9jYWxpemF0aW9uLwrilJzilIDilIAgbGliLwrilIIgICDilJzilIDilIAgbG9jYWxpemF0aW9uLmRhcnQgICAgICAgICAgIyBNYWluIGV4cG9ydCBmaWxlCuKUgiAgIOKUnOKUgOKUgCBtZXNzYWdlcy5pNjluLmRhcnQgICAgICAgICAjIEdlbmVyYXRlZCBpNjluIGNsYXNzZXMK4pSCICAg4pSc4pSA4pSAIG1lc3NhZ2VzLmk2OW4ueWFtbCAgICAgICAgICMgVHJhbnNsYXRpb24gc291cmNlCuKUgiAgIOKUlOKUgOKUgCBzcmMvCuKUgiAgICAgICDilJzilIDilIAgbG9jYWxpemF0aW9uX3Byb3ZpZGVyLmRhcnQgIyBBZHZhbmNlZCB1dGlsaXRpZXMK4pSCICAgICAgIOKUlOKUgOKUgCBzdHJpbmdzLmRhcnQgICAgICAgICAgICMgR2xvYmFsIHN0cmluZ3MgaW5zdGFuY2UK4pSc4pSA4pSAIHRlc3QvCuKUgiAgIOKUlOKUgOKUgCBsb2NhbGl6YXRpb25fdGVzdC5kYXJ0ICAgICAjIENvbXByZWhlbnNpdmUgdGVzdHMK4pSc4pSA4pSAIGJ1aWxkLnlhbWwgICAgICAgICAgICAgICAgICAgICAjIGk2OW4gYnVpbGQgY29uZmlndXJhdGlvbgrilJTilIDilIAgcHVic3BlYy55YW1sICAgICAgICAgICAgICAgICAgICMgUGFja2FnZSBkZXBlbmRlbmNpZXMKYGBgCgpbZmx1dHRlcl9pbnN0YWxsX2xpbmtdOiBodHRwczovL2RvY3MuZmx1dHRlci5kZXYvZ2V0LXN0YXJ0ZWQvaW5zdGFsbAo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/localization/test/localization_test.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXJfdGVzdC9mbHV0dGVyX3Rlc3QuZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2NhbGl6YXRpb24vbG9jYWxpemF0aW9uLmRhcnQnOwoKdm9pZCBtYWluKCkgewogIGdyb3VwKCdMb2NhbGl6YXRpb25Qcm92aWRlciBUZXN0cycsICgpIHsKICAgIHRlc3QoJ3Nob3VsZCBpbml0aWFsaXplIHdpdGggZGVmYXVsdCBsb2NhbGUnLCAoKSB7CiAgICAgIExvY2FsaXphdGlvblByb3ZpZGVyLmluaXRpYWxpemUoKTsKICAgICAgZXhwZWN0KExvY2FsaXphdGlvblByb3ZpZGVyLmN1cnJlbnRMb2NhbGUsICdlbicpOwogICAgfSk7CgogICAgdGVzdCgnc2hvdWxkIHJldHVybiBNZXNzYWdlcyBpbnN0YW5jZScsICgpIHsKICAgICAgZmluYWwgbWVzc2FnZXMgPSBMb2NhbGl6YXRpb25Qcm92aWRlci5tZXNzYWdlczsKICAgICAgZXhwZWN0KG1lc3NhZ2VzLCBpc0E8TWVzc2FnZXM+KCkpOwogICAgfSk7CgogICAgdGVzdCgnc2hvdWxkIHJldHVybiBzdXBwb3J0ZWQgbG9jYWxlcycsICgpIHsKICAgICAgZmluYWwgbG9jYWxlcyA9IExvY2FsaXphdGlvblByb3ZpZGVyLnN1cHBvcnRlZExvY2FsZXM7CiAgICAgIGV4cGVjdChsb2NhbGVzLCBjb250YWlucygnZW4nKSk7CiAgICB9KTsKCiAgICB0ZXN0KCdzaG91bGQgY2hlY2sgaWYgbG9jYWxlIGlzIHN1cHBvcnRlZCcsICgpIHsKICAgICAgZXhwZWN0KExvY2FsaXphdGlvblByb3ZpZGVyLmlzTG9jYWxlU3VwcG9ydGVkKCdlbicpLCB0cnVlKTsKICAgICAgZXhwZWN0KExvY2FsaXphdGlvblByb3ZpZGVyLmlzTG9jYWxlU3VwcG9ydGVkKCdlcycpLCBmYWxzZSk7CiAgICB9KTsKICB9KTsKfQo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/README.md", + "data": + "IyBQYWNrYWdlcwoKSG9yaXpvbnRhbCBjYXBhYmlsaXRpZXMgc2hhcmVkIGFjcm9zcyBmZWF0dXJlczogbmV0d29ya2luZywgZGVzaWduIHN5c3RlbSwKbG9nZ2luZywgYW5hbHl0aWNzLCBkZXBlbmRlbmN5IGluamVjdGlvbi4KCkEgcGFja2FnZSBoZXJlIG11c3Qgbm90IGRlcGVuZCBvbiBhIGBmZWF0dXJlcy9gIHBhY2thZ2UgLSBkZXBlbmRlbmNpZXMgcG9pbnQKaW53YXJkLCBmcm9tIGZlYXR1cmVzIHRvIHBhY2thZ2VzLgoKQWRkIG9uZSB3aXRoOgoKYGBgc2gKbm9uc3RvcCBjcmVhdGUgcGFja2FnZSBteV9wYWNrYWdlIC1vIHBhY2thZ2VzCmBgYAo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/utils/lib/src/number_formatter.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmludGwvaW50bC5kYXJ0JzsKCi8vLyBFeHRlbnNpb24gZm9yIGZvcm1hdHRpbmcgZGVjaW1hbCBudW1iZXJzIHdpdGggY2xlYW4sIHByb2Zlc3Npb25hbCBkaXNwbGF5CmV4dGVuc2lvbiBEb3VibGVGb3JtYXR0aW5nIG9uIGRvdWJsZSB7CiAgLy8vIEZvcm1hdHMgZGVjaW1hbCB2YWx1ZXMsIHJlbW92aW5nIHVubmVjZXNzYXJ5IC4wIGZvciB3aG9sZSBudW1iZXJzCiAgU3RyaW5nIGZvcm1hdCh7aW50IGRlY2ltYWxQbGFjZXMgPSAxfSkgewogICAgZmluYWwgZm9ybWF0dGVyID0gTnVtYmVyRm9ybWF0KCcjLiR7JyMnICogZGVjaW1hbFBsYWNlc30nKTsKICAgIHJldHVybiBmb3JtYXR0ZXIuZm9ybWF0KHRoaXMpOwogIH0KCiAgLy8vIEZvcm1hdHMgc2NvcmUgdmFsdWVzIHdpdGggY2xlYW4gZGVjaW1hbCBoYW5kbGluZwogIFN0cmluZyBhc1Njb3JlKCkgPT4gZm9ybWF0KGRlY2ltYWxQbGFjZXM6IDEpOwoKICAvLy8gRm9ybWF0cyBIUlYgdmFsdWVzIHdpdGggc21hcnQgZGVjaW1hbCBoYW5kbGluZwogIFN0cmluZyBhc0hydigpID0+IGZvcm1hdChkZWNpbWFsUGxhY2VzOiAxKTsKCiAgLy8vIEZvcm1hdHMgc2xlZXAgaG91cnMgd2l0aCBzbWFydCBkZWNpbWFsIGhhbmRsaW5nCiAgU3RyaW5nIGFzU2xlZXBIb3VycygpID0+IGZvcm1hdChkZWNpbWFsUGxhY2VzOiAxKTsKCiAgLy8vIEZvcm1hdHMgcmVzcGlyYXRvcnkgcmF0ZSB3aXRoIHNtYXJ0IGRlY2ltYWwgaGFuZGxpbmcKICBTdHJpbmcgYXNSZXNwaXJhdG9yeVJhdGUoKSA9PiBmb3JtYXQoZGVjaW1hbFBsYWNlczogMSk7Cn0KCi8vLyBFeHRlbnNpb24gZm9yIGZvcm1hdHRpbmcgYW55IG51bWVyaWMgdmFsdWUKZXh0ZW5zaW9uIE51bUZvcm1hdHRpbmcgb24gbnVtIHsKICAvLy8gRm9ybWF0cyBsYXJnZSBudW1iZXJzIHdpdGggSywgTSwgQiBzdWZmaXhlcyBhbmQgc21hcnQgZGVjaW1hbCBoYW5kbGluZwogIFN0cmluZyBjb21wYWN0KCkgewogICAgZmluYWwgTnVtYmVyRm9ybWF0IGZvcm1hdHRlciA9IE51bWJlckZvcm1hdC5jb21wYWN0KGxvY2FsZTogJ2VuX1VTJyk7CiAgICByZXR1cm4gZm9ybWF0dGVyLmZvcm1hdCh0aGlzKTsKICB9CgogIC8vLyBGb3JtYXRzIGhlYXJ0IHJhdGUgdmFsdWVzIChhbHdheXMgd2hvbGUgbnVtYmVycykKICBTdHJpbmcgYXNIZWFydFJhdGUoKSA9PiByb3VuZCgpLnRvU3RyaW5nKCk7Cn0KCi8vLyBFeHRlbnNpb24gZm9yIGZvcm1hdHRpbmcgdGltZSBkdXJhdGlvbiBmcm9tIHNlY29uZHMKZXh0ZW5zaW9uIEludFRpbWVGb3JtYXR0aW5nIG9uIGludCB7CiAgLy8vIEZvcm1hdCB0aW1lIGZyb20gc2Vjb25kcyB0byAxIHNlYyBvciAxbWluIDFzZWMgb3IgMWhyIDFtaW4KICBTdHJpbmcgYXNUaW1lKCkgewogICAgaWYgKHRoaXMgPCA2MCkgewogICAgICByZXR1cm4gJyR0aGlzIHNlYyc7CiAgICB9IGVsc2UgaWYgKHRoaXMgPCAzNjAwKSB7CiAgICAgIGZpbmFsIG1pbnV0ZXMgPSB0aGlzIH4vIDYwOwogICAgICBmaW5hbCByZW1haW5pbmdTZWNvbmRzID0gdGhpcyAlIDYwOwogICAgICBpZiAocmVtYWluaW5nU2Vjb25kcyA9PSAwKSB7CiAgICAgICAgcmV0dXJuICckbWludXRlcyBtaW4nOwogICAgICB9CiAgICAgIHJldHVybiAnJG1pbnV0ZXMgbWluICRyZW1haW5pbmdTZWNvbmRzIHNlYyc7CiAgICB9IGVsc2UgewogICAgICBmaW5hbCBob3VycyA9IHRoaXMgfi8gMzYwMDsKICAgICAgZmluYWwgcmVtYWluaW5nTWludXRlcyA9ICh0aGlzICUgMzYwMCkgfi8gNjA7CiAgICAgIGlmIChyZW1haW5pbmdNaW51dGVzID09IDApIHsKICAgICAgICByZXR1cm4gJyRob3VycyBocic7CiAgICAgIH0KICAgICAgcmV0dXJuICckaG91cnMgaHIgJHJlbWFpbmluZ01pbnV0ZXMgbWluJzsKICAgIH0KICB9Cn0KCmV4dGVuc2lvbiBEYXRlVGltZUZvcm1hdHRpbmcgb24gRGF0ZVRpbWUgewogIFN0cmluZyBmb3JtYXRSZWxhdGl2ZSgpIHsKICAgIGZpbmFsIG5vdyA9IERhdGVUaW1lLm5vdygpOwogICAgZmluYWwgZGlmZmVyZW5jZSA9IG5vdy5kaWZmZXJlbmNlKHRoaXMpOwoKICAgIGlmIChkaWZmZXJlbmNlLmluTWludXRlcyA8IDEpIHsKICAgICAgcmV0dXJuICdKdXN0IG5vdyc7CiAgICB9IGVsc2UgaWYgKGRpZmZlcmVuY2UuaW5NaW51dGVzIDwgNjApIHsKICAgICAgcmV0dXJuICcke2RpZmZlcmVuY2UuaW5NaW51dGVzfW0gYWdvJzsKICAgIH0gZWxzZSBpZiAoZGlmZmVyZW5jZS5pbkhvdXJzIDwgMjQpIHsKICAgICAgcmV0dXJuICcke2RpZmZlcmVuY2UuaW5Ib3Vyc31oIGFnbyc7CiAgICB9IGVsc2UgaWYgKGRpZmZlcmVuY2UuaW5EYXlzID09IDEpIHsKICAgICAgcmV0dXJuICdZZXN0ZXJkYXknOwogICAgfSBlbHNlIGlmIChkaWZmZXJlbmNlLmluRGF5cyA8IDcpIHsKICAgICAgcmV0dXJuICcke2RpZmZlcmVuY2UuaW5EYXlzfWQgYWdvJzsKICAgIH0gZWxzZSB7CiAgICAgIHJldHVybiAnJGRheS8kbW9udGgvJHllYXInOwogICAgfQogIH0KfQo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/utils/lib/utils.dart", + "data": + "bGlicmFyeTsKCmV4cG9ydCAnc3JjL251bWJlcl9mb3JtYXR0ZXIuZGFydCc7Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/utils/pubspec.yaml", + "data": + "bmFtZTogdXRpbHMKZGVzY3JpcHRpb246ICJTaGFyZWQsIGRlcGVuZGVuY3ktbGlnaHQgaGVscGVycyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBpbnRsOiBeMC4yMC4yCgpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF41LjAuMAoKZmx1dHRlcjoK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/analytics.dart", + "data": + "bGlicmFyeTsKCmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL2NsaWVudC9pbmRleC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9zcmMvY29uZmlnL2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CgpleHBvcnQgJ3NyYy9jbGllbnQvaW5kZXguZGFydCc7CmV4cG9ydCAnc3JjL2NvbmZpZy9pbmRleC5kYXJ0JzsKZXhwb3J0ICdzcmMvbW9kZWxzL2luZGV4LmRhcnQnOwpleHBvcnQgJ3NyYy9vYnNlcnZlci9pbmRleC5kYXJ0JzsKZXhwb3J0ICdzcmMvdXRpbHMvaW5kZXguZGFydCc7CgpGdXR1cmU8dm9pZD4gaW5pdCh7CiAgQW5hbHl0aWNzQ29uZmlnIGNvbmZpZyA9IGNvbnN0IERlZmF1bHRBbmFseXRpY3NDb25maWcoKSwKfSkgYXN5bmMgewogIGF3YWl0IHJlZ2lzdGVyQW5hbHl0aWNzV2l0aERJKGNvbmZpZyk7CiAgZmluYWwgbG9nZ2VyID0gZGkuZ2V0PExvZ2dlcj4oKTsKICBsb2dnZXIuaSgnQW5hbHl0aWNzIG1vZHVsZSBpbml0aWFsaXplZCB3aXRoIEZpcmViYXNlIEFuYWx5dGljcycpOwp9CgpGdXR1cmU8dm9pZD4gcmVnaXN0ZXJBbmFseXRpY3NXaXRoREkoQW5hbHl0aWNzQ29uZmlnIGNvbmZpZykgYXN5bmMgewogIGZpbmFsIGxvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CgogIGZpbmFsIGFuYWx5dGljc0NsaWVudCA9IEZpcmViYXNlQW5hbHl0aWNzQ2xpZW50KGNvbmZpZyk7CiAgZGkucmVnaXN0ZXI8QW5hbHl0aWNzQ2xpZW50PigKICAgIGFuYWx5dGljc0NsaWVudCwKICAgIGRpc3Bvc2U6IChjbGllbnQpID0+IGNsaWVudC5kaXNwb3NlKCksCiAgKTsKICBkaS5yZWdpc3RlcjxBbmFseXRpY3NDb25maWc+KGNvbmZpZyk7CgogIGlmIChjb25maWcuZW5hYmxlQW5hbHl0aWNzKSB7CiAgICBsb2dnZXIuaSgn8J+UjSBBbmFseXRpY3MgY2xpZW50IHJlZ2lzdGVyZWQgd2l0aCBjb2xsZWN0aW9uIGVuYWJsZWQnKTsKICB9IGVsc2UgewogICAgbG9nZ2VyLmkoJ/CflI0gQW5hbHl0aWNzIGNsaWVudCByZWdpc3RlcmVkIHdpdGggY29sbGVjdGlvbiBkaXNhYmxlZCcpOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/analytics_client.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9zcmMvbW9kZWxzL2FuYWx5dGljc19ldmVudC5kYXJ0JzsKCmFic3RyYWN0IGNsYXNzIEFuYWx5dGljc0NsaWVudCB7CiAgRnV0dXJlPHZvaWQ+IGxvZ0V2ZW50KHsKICAgIHJlcXVpcmVkIFN0cmluZyBuYW1lLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSk7CgogIEZ1dHVyZTx2b2lkPiBsb2dDdXN0b21FdmVudChBbmFseXRpY3NFdmVudCBldmVudCk7CgogIEZ1dHVyZTx2b2lkPiBzZXRVc2VySWQoU3RyaW5nPyB1c2VySWQpOwoKICBGdXR1cmU8dm9pZD4gc2V0VXNlclByb3BlcnR5KHtyZXF1aXJlZCBTdHJpbmcgbmFtZSwgcmVxdWlyZWQgU3RyaW5nPyB2YWx1ZX0pOwoKICBGdXR1cmU8dm9pZD4gcmVzZXRBbmFseXRpY3NEYXRhKCk7CgogIEZ1dHVyZTx2b2lkPiBzZXRBbmFseXRpY3NDb2xsZWN0aW9uRW5hYmxlZChib29sIGVuYWJsZWQpOwoKICBGdXR1cmU8dm9pZD4gbG9nU2NyZWVuVmlldyh7CiAgICByZXF1aXJlZCBTdHJpbmcgc2NyZWVuTmFtZSwKICAgIFN0cmluZz8gc2NyZWVuQ2xhc3MsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KTsKCiAgRnV0dXJlPHZvaWQ+IGxvZ0FwcE9wZW4oe01hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzfSk7CgogIEZ1dHVyZTx2b2lkPiBsb2dMb2dpbih7CiAgICBTdHJpbmc/IGxvZ2luTWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSk7CgogIEZ1dHVyZTx2b2lkPiBsb2dTaWduVXAoewogICAgU3RyaW5nPyBzaWduVXBNZXRob2QsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KTsKCiAgRnV0dXJlPHZvaWQ+IGxvZ1B1cmNoYXNlKHsKICAgIHJlcXVpcmVkIFN0cmluZyBjdXJyZW5jeSwKICAgIHJlcXVpcmVkIGRvdWJsZSB2YWx1ZSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzLAogIH0pOwoKICB2b2lkIGRpc3Bvc2UoKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9zcmMvY2xpZW50L2FuYWx5dGljc19jbGllbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL2NvbmZpZy9hbmFseXRpY3NfY29uZmlnLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6YW5hbHl0aWNzL3NyYy9tb2RlbHMvYW5hbHl0aWNzX2V2ZW50LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CmltcG9ydCAncGFja2FnZTpmaXJlYmFzZV9hbmFseXRpY3MvZmlyZWJhc2VfYW5hbHl0aWNzLmRhcnQnOwoKY2xhc3MgRmlyZWJhc2VBbmFseXRpY3NDbGllbnQgaW1wbGVtZW50cyBBbmFseXRpY3NDbGllbnQgewogIEZpcmViYXNlQW5hbHl0aWNzQ2xpZW50KHRoaXMuX2NvbmZpZykKICAgIDogX2FuYWx5dGljcyA9IEZpcmViYXNlQW5hbHl0aWNzLmluc3RhbmNlIHsKICAgIF9zZXR1cEFuYWx5dGljcygpOwogIH0KCiAgZmluYWwgQW5hbHl0aWNzQ29uZmlnIF9jb25maWc7CiAgZmluYWwgRmlyZWJhc2VBbmFseXRpY3MgX2FuYWx5dGljczsKICBmaW5hbCBMb2dnZXIgX2xvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CgogIHZvaWQgX3NldHVwQW5hbHl0aWNzKCkgewogICAgX2FuYWx5dGljcy5zZXRBbmFseXRpY3NDb2xsZWN0aW9uRW5hYmxlZChfY29uZmlnLmVuYWJsZUFuYWx5dGljcyk7CgogICAgaWYgKF9jb25maWcudXNlcklkICE9IG51bGwpIHsKICAgICAgX2FuYWx5dGljcy5zZXRVc2VySWQoaWQ6IF9jb25maWcudXNlcklkKTsKICAgICAgX2xvZ2dlci5kKCfwn5SNIEFuYWx5dGljcyB1c2VyIElEIHNldDogJHtfY29uZmlnLnVzZXJJZH0nKTsKICAgIH0KCiAgICBpZiAoX2NvbmZpZy5kZWZhdWx0VXNlclByb3BlcnRpZXMgIT0gbnVsbCkgewogICAgICBmb3IgKGZpbmFsIGVudHJ5IGluIF9jb25maWcuZGVmYXVsdFVzZXJQcm9wZXJ0aWVzIS5lbnRyaWVzKSB7CiAgICAgICAgX2FuYWx5dGljcy5zZXRVc2VyUHJvcGVydHkobmFtZTogZW50cnkua2V5LCB2YWx1ZTogZW50cnkudmFsdWUpOwogICAgICB9CiAgICAgIF9sb2dnZXIuZCgn8J+UjSBBbmFseXRpY3MgZGVmYXVsdCB1c2VyIHByb3BlcnRpZXMgc2V0Jyk7CiAgICB9CgogICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgIF9sb2dnZXIuZCgn8J+UjSBGaXJlYmFzZSBBbmFseXRpY3MgaW5pdGlhbGl6ZWQgd2l0aCBkZWJ1ZyBsb2dnaW5nIGVuYWJsZWQnKTsKICAgIH0gZWxzZSB7CiAgICAgIF9sb2dnZXIuaSgn8J+UjSBGaXJlYmFzZSBBbmFseXRpY3MgaW5pdGlhbGl6ZWQnKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dFdmVudCh7CiAgICByZXF1aXJlZCBTdHJpbmcgbmFtZSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzLAogIH0pIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGlmICghX2NvbmZpZy5lbmFibGVBbmFseXRpY3MpIHsKICAgICAgICBpZiAoX2NvbmZpZy5lbmFibGVEZWJ1Z0xvZ2dpbmcpIHsKICAgICAgICAgIF9sb2dnZXIuZCgn8J+UjSBBbmFseXRpY3MgZGlzYWJsZWQsIHNraXBwaW5nIGV2ZW50OiAkbmFtZScpOwogICAgICAgIH0KICAgICAgICByZXR1cm47CiAgICAgIH0KCiAgICAgIGF3YWl0IF9hbmFseXRpY3MubG9nRXZlbnQoCiAgICAgICAgbmFtZTogbmFtZSwKICAgICAgICBwYXJhbWV0ZXJzOiBwYXJhbWV0ZXJzPy5jYXN0PFN0cmluZywgT2JqZWN0PigpLAogICAgICApOwoKICAgICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgICAgX2xvZ2dlci5kKAogICAgICAgICAgJ/CflI0gQW5hbHl0aWNzIGV2ZW50IGxvZ2dlZDogJG5hbWUgd2l0aCBwYXJhbWV0ZXJzOiAkcGFyYW1ldGVycycsCiAgICAgICAgKTsKICAgICAgfQogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byBsb2cgYW5hbHl0aWNzIGV2ZW50OiAkbmFtZScsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ0N1c3RvbUV2ZW50KEFuYWx5dGljc0V2ZW50IGV2ZW50KSBhc3luYyB7CiAgICBhd2FpdCBsb2dFdmVudChuYW1lOiBldmVudC5uYW1lLCBwYXJhbWV0ZXJzOiBldmVudC5wYXJhbWV0ZXJzKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBzZXRVc2VySWQoU3RyaW5nPyB1c2VySWQpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0VXNlcklkKGlkOiB1c2VySWQpOwoKICAgICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgICAgX2xvZ2dlci5kKCfwn5SNIEFuYWx5dGljcyB1c2VyIElEIHNldDogJHVzZXJJZCcpOwogICAgICB9CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnRmFpbGVkIHRvIHNldCBhbmFseXRpY3MgdXNlciBJRDogJHVzZXJJZCcsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHNldFVzZXJQcm9wZXJ0eSh7CiAgICByZXF1aXJlZCBTdHJpbmcgbmFtZSwKICAgIHJlcXVpcmVkIFN0cmluZz8gdmFsdWUsCiAgfSkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgYXdhaXQgX2FuYWx5dGljcy5zZXRVc2VyUHJvcGVydHkobmFtZTogbmFtZSwgdmFsdWU6IHZhbHVlKTsKCiAgICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICAgIF9sb2dnZXIuZCgn8J+UjSBBbmFseXRpY3MgdXNlciBwcm9wZXJ0eSBzZXQ6ICRuYW1lID0gJHZhbHVlJyk7CiAgICAgIH0KICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCdGYWlsZWQgdG8gc2V0IGFuYWx5dGljcyB1c2VyIHByb3BlcnR5OiAkbmFtZScsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJlc2V0QW5hbHl0aWNzRGF0YSgpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9hbmFseXRpY3MucmVzZXRBbmFseXRpY3NEYXRhKCk7CgogICAgICBpZiAoX2NvbmZpZy5lbmFibGVEZWJ1Z0xvZ2dpbmcpIHsKICAgICAgICBfbG9nZ2VyLmQoJ/CflI0gQW5hbHl0aWNzIGRhdGEgcmVzZXQnKTsKICAgICAgfQogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byByZXNldCBhbmFseXRpY3MgZGF0YScsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHNldEFuYWx5dGljc0NvbGxlY3Rpb25FbmFibGVkKGJvb2wgZW5hYmxlZCkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgYXdhaXQgX2FuYWx5dGljcy5zZXRBbmFseXRpY3NDb2xsZWN0aW9uRW5hYmxlZChlbmFibGVkKTsKCiAgICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICAgIF9sb2dnZXIuZCgKICAgICAgICAgICfwn5SNIEFuYWx5dGljcyBjb2xsZWN0aW9uICR7ZW5hYmxlZCA/ICdlbmFibGVkJyA6ICdkaXNhYmxlZCd9JywKICAgICAgICApOwogICAgICB9CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnRmFpbGVkIHRvIHNldCBhbmFseXRpY3MgY29sbGVjdGlvbiBlbmFibGVkOiAkZW5hYmxlZCcsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ1NjcmVlblZpZXcoewogICAgcmVxdWlyZWQgU3RyaW5nIHNjcmVlbk5hbWUsCiAgICBTdHJpbmc/IHNjcmVlbkNsYXNzLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgZXZlbnRQYXJhbWV0ZXJzID0gPFN0cmluZywgZHluYW1pYz57CiAgICAgIFByZWRlZmluZWRQYXJhbWV0ZXJzLnNjcmVlbk5hbWU6IHNjcmVlbk5hbWUsCiAgICAgIGlmIChzY3JlZW5DbGFzcyAhPSBudWxsKSBQcmVkZWZpbmVkUGFyYW1ldGVycy5zY3JlZW5DbGFzczogc2NyZWVuQ2xhc3MsCiAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgfTsKCiAgICBhd2FpdCBsb2dFdmVudCgKICAgICAgbmFtZTogUHJlZGVmaW5lZEV2ZW50cy5zY3JlZW5WaWV3LAogICAgICBwYXJhbWV0ZXJzOiBldmVudFBhcmFtZXRlcnMsCiAgICApOwogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ0FwcE9wZW4oe01hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzfSkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogUHJlZGVmaW5lZEV2ZW50cy5hcHBPcGVuLCBwYXJhbWV0ZXJzOiBwYXJhbWV0ZXJzKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dMb2dpbih7CiAgICBTdHJpbmc/IGxvZ2luTWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgZXZlbnRQYXJhbWV0ZXJzID0gPFN0cmluZywgZHluYW1pYz57CiAgICAgIGlmIChsb2dpbk1ldGhvZCAhPSBudWxsKSBQcmVkZWZpbmVkUGFyYW1ldGVycy5tZXRob2Q6IGxvZ2luTWV0aG9kLAogICAgICAuLi4/cGFyYW1ldGVycywKICAgIH07CgogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogUHJlZGVmaW5lZEV2ZW50cy5sb2dpbiwgcGFyYW1ldGVyczogZXZlbnRQYXJhbWV0ZXJzKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dTaWduVXAoewogICAgU3RyaW5nPyBzaWduVXBNZXRob2QsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KSBhc3luYyB7CiAgICBmaW5hbCBldmVudFBhcmFtZXRlcnMgPSA8U3RyaW5nLCBkeW5hbWljPnsKICAgICAgaWYgKHNpZ25VcE1ldGhvZCAhPSBudWxsKSBQcmVkZWZpbmVkUGFyYW1ldGVycy5tZXRob2Q6IHNpZ25VcE1ldGhvZCwKICAgICAgLi4uP3BhcmFtZXRlcnMsCiAgICB9OwoKICAgIGF3YWl0IGxvZ0V2ZW50KG5hbWU6IFByZWRlZmluZWRFdmVudHMuc2lnblVwLCBwYXJhbWV0ZXJzOiBldmVudFBhcmFtZXRlcnMpOwogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ1B1cmNoYXNlKHsKICAgIHJlcXVpcmVkIFN0cmluZyBjdXJyZW5jeSwKICAgIHJlcXVpcmVkIGRvdWJsZSB2YWx1ZSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzLAogIH0pIGFzeW5jIHsKICAgIGZpbmFsIGV2ZW50UGFyYW1ldGVycyA9IDxTdHJpbmcsIGR5bmFtaWM+ewogICAgICBQcmVkZWZpbmVkUGFyYW1ldGVycy5jdXJyZW5jeTogY3VycmVuY3ksCiAgICAgIFByZWRlZmluZWRQYXJhbWV0ZXJzLnZhbHVlOiB2YWx1ZSwKICAgICAgLi4uP3BhcmFtZXRlcnMsCiAgICB9OwoKICAgIGF3YWl0IGxvZ0V2ZW50KAogICAgICBuYW1lOiBQcmVkZWZpbmVkRXZlbnRzLnB1cmNoYXNlLAogICAgICBwYXJhbWV0ZXJzOiBldmVudFBhcmFtZXRlcnMsCiAgICApOwogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBkaXNwb3NlKCkgewogICAgLy8gRmlyZWJhc2UgQW5hbHl0aWNzIGRvZXNuJ3QgbmVlZCBleHBsaWNpdCBkaXNwb3NhbAogICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgIF9sb2dnZXIuZCgn8J+UjSBGaXJlYmFzZSBBbmFseXRpY3MgY2xpZW50IGRpc3Bvc2VkJyk7CiAgICB9CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/index.dart", + "data": + "ZXhwb3J0ICdhbmFseXRpY3NfY2xpZW50LmRhcnQnOwpleHBvcnQgJ2ZpcmViYXNlX2FuYWx5dGljc19jbGllbnQuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/config/analytics_config.dart", + "data": + "YWJzdHJhY3QgY2xhc3MgQW5hbHl0aWNzQ29uZmlnIHsKICBib29sIGdldCBlbmFibGVBbmFseXRpY3M7CiAgYm9vbCBnZXQgZW5hYmxlRGVidWdMb2dnaW5nOwogIFN0cmluZz8gZ2V0IHVzZXJJZDsKICBNYXA8U3RyaW5nLCBTdHJpbmc+PyBnZXQgZGVmYXVsdFVzZXJQcm9wZXJ0aWVzOwp9CgpjbGFzcyBEZWZhdWx0QW5hbHl0aWNzQ29uZmlnIGltcGxlbWVudHMgQW5hbHl0aWNzQ29uZmlnIHsKICBjb25zdCBEZWZhdWx0QW5hbHl0aWNzQ29uZmlnKHsKICAgIHRoaXMuZW5hYmxlQW5hbHl0aWNzID0gdHJ1ZSwKICAgIHRoaXMuZW5hYmxlRGVidWdMb2dnaW5nID0gZmFsc2UsCiAgICB0aGlzLnVzZXJJZCwKICAgIHRoaXMuZGVmYXVsdFVzZXJQcm9wZXJ0aWVzLAogIH0pOwoKICBAb3ZlcnJpZGUKICBmaW5hbCBib29sIGVuYWJsZUFuYWx5dGljczsKCiAgQG92ZXJyaWRlCiAgZmluYWwgYm9vbCBlbmFibGVEZWJ1Z0xvZ2dpbmc7CgogIEBvdmVycmlkZQogIGZpbmFsIFN0cmluZz8gdXNlcklkOwoKICBAb3ZlcnJpZGUKICBmaW5hbCBNYXA8U3RyaW5nLCBTdHJpbmc+PyBkZWZhdWx0VXNlclByb3BlcnRpZXM7CgogIERlZmF1bHRBbmFseXRpY3NDb25maWcgY29weVdpdGgoewogICAgYm9vbD8gZW5hYmxlQW5hbHl0aWNzLAogICAgYm9vbD8gZW5hYmxlRGVidWdMb2dnaW5nLAogICAgU3RyaW5nPyB1c2VySWQsCiAgICBNYXA8U3RyaW5nLCBTdHJpbmc+PyBkZWZhdWx0VXNlclByb3BlcnRpZXMsCiAgfSkgewogICAgcmV0dXJuIERlZmF1bHRBbmFseXRpY3NDb25maWcoCiAgICAgIGVuYWJsZUFuYWx5dGljczogZW5hYmxlQW5hbHl0aWNzID8/IHRoaXMuZW5hYmxlQW5hbHl0aWNzLAogICAgICBlbmFibGVEZWJ1Z0xvZ2dpbmc6IGVuYWJsZURlYnVnTG9nZ2luZyA/PyB0aGlzLmVuYWJsZURlYnVnTG9nZ2luZywKICAgICAgdXNlcklkOiB1c2VySWQgPz8gdGhpcy51c2VySWQsCiAgICAgIGRlZmF1bHRVc2VyUHJvcGVydGllczoKICAgICAgICAgIGRlZmF1bHRVc2VyUHJvcGVydGllcyA/PyB0aGlzLmRlZmF1bHRVc2VyUHJvcGVydGllcywKICAgICk7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/config/index.dart", + "data": "ZXhwb3J0ICdhbmFseXRpY3NfY29uZmlnLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/analytics_event.dart", + "data": + "Y2xhc3MgQW5hbHl0aWNzRXZlbnQgewogIGNvbnN0IEFuYWx5dGljc0V2ZW50KHtyZXF1aXJlZCB0aGlzLm5hbWUsIHRoaXMucGFyYW1ldGVyc30pOwoKICBmaW5hbCBTdHJpbmcgbmFtZTsKICBmaW5hbCBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVyczsKCiAgTWFwPFN0cmluZywgZHluYW1pYz4gdG9NYXAoKSB7CiAgICByZXR1cm4geyduYW1lJzogbmFtZSwgaWYgKHBhcmFtZXRlcnMgIT0gbnVsbCkgJ3BhcmFtZXRlcnMnOiBwYXJhbWV0ZXJzfTsKICB9CgogIEBvdmVycmlkZQogIFN0cmluZyB0b1N0cmluZygpID0+ICdBbmFseXRpY3NFdmVudChuYW1lOiAkbmFtZSwgcGFyYW1ldGVyczogJHBhcmFtZXRlcnMpJzsKCiAgQG92ZXJyaWRlCiAgYm9vbCBvcGVyYXRvciA9PShPYmplY3Qgb3RoZXIpIHsKICAgIGlmIChpZGVudGljYWwodGhpcywgb3RoZXIpKSByZXR1cm4gdHJ1ZTsKICAgIHJldHVybiBvdGhlciBpcyBBbmFseXRpY3NFdmVudCAmJgogICAgICAgIG90aGVyLm5hbWUgPT0gbmFtZSAmJgogICAgICAgIF9tYXBFcXVhbHMob3RoZXIucGFyYW1ldGVycywgcGFyYW1ldGVycyk7CiAgfQoKICBAb3ZlcnJpZGUKICBpbnQgZ2V0IGhhc2hDb2RlID0+IG5hbWUuaGFzaENvZGUgXiBwYXJhbWV0ZXJzLmhhc2hDb2RlOwoKICBib29sIF9tYXBFcXVhbHMoTWFwPFN0cmluZywgZHluYW1pYz4/IGEsIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBiKSB7CiAgICBpZiAoYSA9PSBudWxsKSByZXR1cm4gYiA9PSBudWxsOwogICAgaWYgKGIgPT0gbnVsbCB8fCBhLmxlbmd0aCAhPSBiLmxlbmd0aCkgcmV0dXJuIGZhbHNlOwogICAgZm9yIChmaW5hbCBrZXkgaW4gYS5rZXlzKSB7CiAgICAgIGlmICghYi5jb250YWluc0tleShrZXkpIHx8IGFba2V5XSAhPSBiW2tleV0pIHJldHVybiBmYWxzZTsKICAgIH0KICAgIHJldHVybiB0cnVlOwogIH0KfQoKY2xhc3MgUHJlZGVmaW5lZEV2ZW50cyB7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBhcHBPcGVuID0gJ2FwcF9vcGVuJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGxvZ2luID0gJ2xvZ2luJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHNpZ25VcCA9ICdzaWduX3VwJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHNjcmVlblZpZXcgPSAnc2NyZWVuX3ZpZXcnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgcHVyY2hhc2UgPSAncHVyY2hhc2UnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgc2VsZWN0Q29udGVudCA9ICdzZWxlY3RfY29udGVudCc7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBzaGFyZSA9ICdzaGFyZSc7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBzZWFyY2ggPSAnc2VhcmNoJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGJlZ2luQ2hlY2tvdXQgPSAnYmVnaW5fY2hlY2tvdXQnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgYWRkVG9DYXJ0ID0gJ2FkZF90b19jYXJ0JzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHJlbW92ZUZyb21DYXJ0ID0gJ3JlbW92ZV9mcm9tX2NhcnQnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgdmlld0l0ZW0gPSAndmlld19pdGVtJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHZpZXdJdGVtTGlzdCA9ICd2aWV3X2l0ZW1fbGlzdCc7Cn0KCmNsYXNzIFByZWRlZmluZWRQYXJhbWV0ZXJzIHsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHNjcmVlbk5hbWUgPSAnc2NyZWVuX25hbWUnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgc2NyZWVuQ2xhc3MgPSAnc2NyZWVuX2NsYXNzJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIG1ldGhvZCA9ICdtZXRob2QnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgY3VycmVuY3kgPSAnY3VycmVuY3knOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgdmFsdWUgPSAndmFsdWUnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgaXRlbUlkID0gJ2l0ZW1faWQnOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgaXRlbU5hbWUgPSAnaXRlbV9uYW1lJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGl0ZW1DYXRlZ29yeSA9ICdpdGVtX2NhdGVnb3J5JzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGNvbnRlbnRUeXBlID0gJ2NvbnRlbnRfdHlwZSc7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBzZWFyY2hUZXJtID0gJ3NlYXJjaF90ZXJtJzsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHVzZXJJZCA9ICd1c2VyX2lkJzsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/analytics_events.dart", + "data": + "Ly8vIFN0cnVjdHVyZWQgYW5hbHl0aWNzIGV2ZW50IG5hbWVzIG9yZ2FuaXplZCBieSBjYXRlZ29yeS4KLy8vCi8vLyBVc2FnZTogYEFuYWx5dGljc0V2ZW50cy51c2VyLmF1dGhlbnRpY2F0ZWRSZWRpcmVjdGAuIE5hbWluZyBldmVudHMgaGVyZQovLy8gaW5zdGVhZCBvZiBpbmxpbmUga2VlcHMgdGhlbSBkaXNjb3ZlcmFibGUgYW5kIHR5cG8tZnJlZSwgYW5kIGdpdmVzIHlvdSBvbmUKLy8vIHBsYWNlIHRvIGFkZCB0aGUgZ3JvdXBzIHlvdXIgb3duIHByb2R1Y3QgbmVlZHMuCmNsYXNzIEFuYWx5dGljc0V2ZW50cyB7CiAgQW5hbHl0aWNzRXZlbnRzLl8oKTsKCiAgLy8vIFVzZXItcmVsYXRlZCBldmVudHMKICBzdGF0aWMgY29uc3QgdXNlciA9IF9Vc2VyRXZlbnRzKCk7CgogIC8vLyBBdXRoZW50aWNhdGlvbi1yZWxhdGVkIGV2ZW50cwogIHN0YXRpYyBjb25zdCBhdXRoID0gX0F1dGhFdmVudHMoKTsKCiAgLy8vIE5hdmlnYXRpb24tcmVsYXRlZCBldmVudHMKICBzdGF0aWMgY29uc3QgbmF2aWdhdGlvbiA9IF9OYXZpZ2F0aW9uRXZlbnRzKCk7CgogIC8vLyBGZWF0dXJlIHVzYWdlIGV2ZW50cwogIHN0YXRpYyBjb25zdCBmZWF0dXJlID0gX0ZlYXR1cmVFdmVudHMoKTsKCiAgLy8vIEVycm9yIGFuZCBkZWJ1Z2dpbmcgZXZlbnRzCiAgc3RhdGljIGNvbnN0IGVycm9yID0gX0Vycm9yRXZlbnRzKCk7CgogIC8vLyBBcHAgbGlmZWN5Y2xlIGV2ZW50cwogIHN0YXRpYyBjb25zdCBhcHAgPSBfQXBwRXZlbnRzKCk7CgogIC8vLyBQcm9maWxlLXJlbGF0ZWQgZXZlbnRzCiAgc3RhdGljIGNvbnN0IHByb2ZpbGUgPSBfUHJvZmlsZUV2ZW50cygpOwp9CgpjbGFzcyBfVXNlckV2ZW50cyB7CiAgY29uc3QgX1VzZXJFdmVudHMoKTsKCiAgLy8vIFVzZXIgc3VjY2Vzc2Z1bGx5IGF1dGhlbnRpY2F0ZWQgYW5kIHJlZGlyZWN0ZWQKICBTdHJpbmcgZ2V0IGF1dGhlbnRpY2F0ZWRSZWRpcmVjdCA9PiAndXNlcl9hdXRoZW50aWNhdGVkX3JlZGlyZWN0JzsKCiAgLy8vIFVzZXIgcHJvZmlsZSB1cGRhdGVkCiAgU3RyaW5nIGdldCBwcm9maWxlVXBkYXRlZCA9PiAndXNlcl9wcm9maWxlX3VwZGF0ZWQnOwoKICAvLy8gVXNlciBwcmVmZXJlbmNlcyBjaGFuZ2VkCiAgU3RyaW5nIGdldCBwcmVmZXJlbmNlc0NoYW5nZWQgPT4gJ3VzZXJfcHJlZmVyZW5jZXNfY2hhbmdlZCc7CgogIC8vLyBVc2VyIGNvbXBsZXRlZCBvbmJvYXJkaW5nCiAgU3RyaW5nIGdldCBvbmJvYXJkaW5nQ29tcGxldGVkID0+ICd1c2VyX29uYm9hcmRpbmdfY29tcGxldGVkJzsKCiAgLy8vIFVzZXIgYWNjb3VudCBkZWxldGVkCiAgU3RyaW5nIGdldCBhY2NvdW50RGVsZXRlZCA9PiAndXNlcl9hY2NvdW50X2RlbGV0ZWQnOwp9CgpjbGFzcyBfQXV0aEV2ZW50cyB7CiAgY29uc3QgX0F1dGhFdmVudHMoKTsKCiAgLy8vIFVzZXIgc2lnbmVkIGluCiAgU3RyaW5nIGdldCBzaWduSW4gPT4gJ2F1dGhfc2lnbl9pbic7CgogIC8vLyBVc2VyIHNpZ25lZCB1cAogIFN0cmluZyBnZXQgc2lnblVwID0+ICdhdXRoX3NpZ25fdXAnOwoKICAvLy8gVXNlciBzaWduZWQgb3V0CiAgU3RyaW5nIGdldCBzaWduT3V0ID0+ICdhdXRoX3NpZ25fb3V0JzsKCiAgLy8vIFBhc3N3b3JkIHJlc2V0IHJlcXVlc3RlZAogIFN0cmluZyBnZXQgcGFzc3dvcmRSZXNldCA9PiAnYXV0aF9wYXNzd29yZF9yZXNldCc7CgogIC8vLyBBdXRoZW50aWNhdGlvbiBmYWlsZWQKICBTdHJpbmcgZ2V0IGZhaWxlZCA9PiAnYXV0aF9mYWlsZWQnOwoKICAvLy8gU29jaWFsIGF1dGhlbnRpY2F0aW9uIChHb29nbGUsIEFwcGxlLCBldGMuKQogIFN0cmluZyBnZXQgc29jaWFsTG9naW4gPT4gJ2F1dGhfc29jaWFsX2xvZ2luJzsKfQoKY2xhc3MgX05hdmlnYXRpb25FdmVudHMgewogIGNvbnN0IF9OYXZpZ2F0aW9uRXZlbnRzKCk7CgogIC8vLyBTY3JlZW4gdmlld2VkIChhdXRvbWF0aWNhbGx5IHRyYWNrZWQpCiAgU3RyaW5nIGdldCBzY3JlZW5WaWV3ID0+ICdzY3JlZW5fdmlldyc7CgogIC8vLyBUYWIgc3dpdGNoZWQgaW4gYm90dG9tIG5hdmlnYXRpb24KICBTdHJpbmcgZ2V0IHRhYlN3aXRjaGVkID0+ICduYXZpZ2F0aW9uX3RhYl9zd2l0Y2hlZCc7CgogIC8vLyBCYWNrIGJ1dHRvbiBwcmVzc2VkCiAgU3RyaW5nIGdldCBiYWNrUHJlc3NlZCA9PiAnbmF2aWdhdGlvbl9iYWNrX3ByZXNzZWQnOwoKICAvLy8gRGVlcCBsaW5rIG9wZW5lZAogIFN0cmluZyBnZXQgZGVlcExpbmtPcGVuZWQgPT4gJ25hdmlnYXRpb25fZGVlcF9saW5rX29wZW5lZCc7Cn0KCmNsYXNzIF9GZWF0dXJlRXZlbnRzIHsKICBjb25zdCBfRmVhdHVyZUV2ZW50cygpOwoKICAvLy8gRmVhdHVyZSB3YXMgdXNlZC9hY2Nlc3NlZAogIFN0cmluZyBnZXQgdXNlZCA9PiAnZmVhdHVyZV91c2VkJzsKCiAgLy8vIEZlYXR1cmUgdHV0b3JpYWwgc3RhcnRlZAogIFN0cmluZyBnZXQgdHV0b3JpYWxTdGFydGVkID0+ICdmZWF0dXJlX3R1dG9yaWFsX3N0YXJ0ZWQnOwoKICAvLy8gRmVhdHVyZSB0dXRvcmlhbCBjb21wbGV0ZWQKICBTdHJpbmcgZ2V0IHR1dG9yaWFsQ29tcGxldGVkID0+ICdmZWF0dXJlX3R1dG9yaWFsX2NvbXBsZXRlZCc7CgogIC8vLyBGZWF0dXJlIGZlZWRiYWNrIGdpdmVuCiAgU3RyaW5nIGdldCBmZWVkYmFja0dpdmVuID0+ICdmZWF0dXJlX2ZlZWRiYWNrX2dpdmVuJzsKCiAgLy8vIEJ1dHRvbiBwcmVzc2VkCiAgU3RyaW5nIGdldCBidXR0b25QcmVzc2VkID0+ICdidXR0b25fcHJlc3NlZCc7Cn0KCmNsYXNzIF9FcnJvckV2ZW50cyB7CiAgY29uc3QgX0Vycm9yRXZlbnRzKCk7CgogIC8vLyBHZW5lcmFsIGFwcCBlcnJvciBvY2N1cnJlZAogIFN0cmluZyBnZXQgYXBwRXJyb3IgPT4gJ2FwcF9lcnJvcic7CgogIC8vLyBOZXR3b3JrIGVycm9yIG9jY3VycmVkCiAgU3RyaW5nIGdldCBuZXR3b3JrRXJyb3IgPT4gJ25ldHdvcmtfZXJyb3InOwoKICAvLy8gQ3Jhc2ggb2NjdXJyZWQKICBTdHJpbmcgZ2V0IGNyYXNoID0+ICdhcHBfY3Jhc2gnOwoKICAvLy8gQVBJIGVycm9yIG9jY3VycmVkCiAgU3RyaW5nIGdldCBhcGlFcnJvciA9PiAnYXBpX2Vycm9yJzsKCiAgLy8vIFZhbGlkYXRpb24gZXJyb3IKICBTdHJpbmcgZ2V0IHZhbGlkYXRpb25FcnJvciA9PiAndmFsaWRhdGlvbl9lcnJvcic7Cn0KCmNsYXNzIF9BcHBFdmVudHMgewogIGNvbnN0IF9BcHBFdmVudHMoKTsKCiAgLy8vIEFwcCBvcGVuZWQvbGF1bmNoZWQKICBTdHJpbmcgZ2V0IG9wZW4gPT4gJ2FwcF9vcGVuJzsKCiAgLy8vIEFwcCB3ZW50IHRvIGJhY2tncm91bmQKICBTdHJpbmcgZ2V0IGJhY2tncm91bmQgPT4gJ2FwcF9iYWNrZ3JvdW5kJzsKCiAgLy8vIEFwcCBjYW1lIHRvIGZvcmVncm91bmQKICBTdHJpbmcgZ2V0IGZvcmVncm91bmQgPT4gJ2FwcF9mb3JlZ3JvdW5kJzsKCiAgLy8vIEFwcCB1cGRhdGVkIHRvIG5ldyB2ZXJzaW9uCiAgU3RyaW5nIGdldCB1cGRhdGVkID0+ICdhcHBfdXBkYXRlZCc7CgogIC8vLyBBcHAgaW5zdGFsbGVkIGZvciBmaXJzdCB0aW1lCiAgU3RyaW5nIGdldCBpbnN0YWxsZWQgPT4gJ2FwcF9pbnN0YWxsZWQnOwoKICAvLy8gQXBwIHVuaW5zdGFsbGVkCiAgU3RyaW5nIGdldCB1bmluc3RhbGxlZCA9PiAnYXBwX3VuaW5zdGFsbGVkJzsKfQoKCgpjbGFzcyBfUHJvZmlsZUV2ZW50cyB7CiAgY29uc3QgX1Byb2ZpbGVFdmVudHMoKTsKCiAgLy8vIFByb2ZpbGUgdmlld2VkCiAgU3RyaW5nIGdldCB2aWV3ZWQgPT4gJ3Byb2ZpbGVfdmlld2VkJzsKCiAgLy8vIFByb2ZpbGUgZWRpdGVkCiAgU3RyaW5nIGdldCBlZGl0ZWQgPT4gJ3Byb2ZpbGVfZWRpdGVkJzsKCiAgLy8vIFByb2ZpbGUgcGhvdG8gdXBkYXRlZAogIFN0cmluZyBnZXQgcGhvdG9VcGRhdGVkID0+ICdwcm9maWxlX3Bob3RvX3VwZGF0ZWQnOwoKICAvLy8gUHJvZmlsZSBzZXR0aW5ncyBjaGFuZ2VkCiAgU3RyaW5nIGdldCBzZXR0aW5nc0NoYW5nZWQgPT4gJ3Byb2ZpbGVfc2V0dGluZ3NfY2hhbmdlZCc7CgogIC8vLyBQcm9maWxlIHByaXZhY3kgc2V0dGluZ3MgdXBkYXRlZAogIFN0cmluZyBnZXQgcHJpdmFjeVVwZGF0ZWQgPT4gJ3Byb2ZpbGVfcHJpdmFjeV91cGRhdGVkJzsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/models/index.dart", + "data": + "ZXhwb3J0ICdhbmFseXRpY3NfZXZlbnQuZGFydCc7CmV4cG9ydCAnYW5hbHl0aWNzX2V2ZW50cy5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/observer/analytics_route_observer.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfYW5hbHl0aWNzL2ZpcmViYXNlX2FuYWx5dGljcy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CgovLy8gQW5hbHl0aWNzIHJvdXRlIG9ic2VydmVyIHRoYXQgdXNlcyBGaXJlYmFzZSBBbmFseXRpY3MgT2JzZXJ2ZXIgaW50ZXJuYWxseQovLy8gd2hpbGUgcHJvdmlkaW5nIGFkZGl0aW9uYWwgbG9nZ2luZyBhbmQgZXJyb3IgaGFuZGxpbmcgY2FwYWJpbGl0aWVzCmNsYXNzIEFuYWx5dGljc1JvdXRlT2JzZXJ2ZXIgZXh0ZW5kcyBOYXZpZ2F0b3JPYnNlcnZlciB7CiAgQW5hbHl0aWNzUm91dGVPYnNlcnZlcigpCiAgICA6IF9sb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpLAogICAgICBfZmlyZWJhc2VPYnNlcnZlciA9IEZpcmViYXNlQW5hbHl0aWNzT2JzZXJ2ZXIoCiAgICAgICAgYW5hbHl0aWNzOiBGaXJlYmFzZUFuYWx5dGljcy5pbnN0YW5jZSwKICAgICAgKTsKCiAgZmluYWwgTG9nZ2VyIF9sb2dnZXI7CiAgZmluYWwgRmlyZWJhc2VBbmFseXRpY3NPYnNlcnZlciBfZmlyZWJhc2VPYnNlcnZlcjsKCiAgQG92ZXJyaWRlCiAgdm9pZCBkaWRQdXNoKFJvdXRlIHJvdXRlLCBSb3V0ZT8gcHJldmlvdXNSb3V0ZSkgewogICAgc3VwZXIuZGlkUHVzaChyb3V0ZSwgcHJldmlvdXNSb3V0ZSk7CgogICAgdHJ5IHsKICAgICAgLy8gRGVsZWdhdGUgdG8gRmlyZWJhc2UgQW5hbHl0aWNzIE9ic2VydmVyIGZvciBuYXRpdmUgc2NyZWVuIHRyYWNraW5nCiAgICAgIF9maXJlYmFzZU9ic2VydmVyLmRpZFB1c2gocm91dGUsIHByZXZpb3VzUm91dGUpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byB0cmFjayBzY3JlZW4gcHVzaCBldmVudCcsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBkaWRQb3AoUm91dGUgcm91dGUsIFJvdXRlPyBwcmV2aW91c1JvdXRlKSB7CiAgICBzdXBlci5kaWRQb3Aocm91dGUsIHByZXZpb3VzUm91dGUpOwoKICAgIHRyeSB7CiAgICAgIC8vIERlbGVnYXRlIHRvIEZpcmViYXNlIEFuYWx5dGljcyBPYnNlcnZlcgogICAgICBfZmlyZWJhc2VPYnNlcnZlci5kaWRQb3Aocm91dGUsIHByZXZpb3VzUm91dGUpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byB0cmFjayBzY3JlZW4gcG9wIGV2ZW50JywgZSwgcyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIGRpZFJlcGxhY2Uoe1JvdXRlPGR5bmFtaWM+PyBuZXdSb3V0ZSwgUm91dGU8ZHluYW1pYz4/IG9sZFJvdXRlfSkgewogICAgc3VwZXIuZGlkUmVwbGFjZShuZXdSb3V0ZTogbmV3Um91dGUsIG9sZFJvdXRlOiBvbGRSb3V0ZSk7CgogICAgdHJ5IHsKICAgICAgLy8gRGVsZWdhdGUgdG8gRmlyZWJhc2UgQW5hbHl0aWNzIE9ic2VydmVyCiAgICAgIF9maXJlYmFzZU9ic2VydmVyLmRpZFJlcGxhY2UobmV3Um91dGU6IG5ld1JvdXRlLCBvbGRSb3V0ZTogb2xkUm91dGUpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byB0cmFjayBzY3JlZW4gcmVwbGFjZSBldmVudCcsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBkaWRSZW1vdmUoUm91dGU8ZHluYW1pYz4gcm91dGUsIFJvdXRlPGR5bmFtaWM+PyBwcmV2aW91c1JvdXRlKSB7CiAgICBzdXBlci5kaWRSZW1vdmUocm91dGUsIHByZXZpb3VzUm91dGUpOwoKICAgIHRyeSB7CiAgICAgIC8vIERlbGVnYXRlIHRvIEZpcmViYXNlIEFuYWx5dGljcyBPYnNlcnZlcgogICAgICBfZmlyZWJhc2VPYnNlcnZlci5kaWRSZW1vdmUocm91dGUsIHByZXZpb3VzUm91dGUpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byB0cmFjayBzY3JlZW4gcmVtb3ZlIGV2ZW50JywgZSwgcyk7CiAgICB9CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/observer/index.dart", + "data": "ZXhwb3J0ICdhbmFseXRpY3Nfcm91dGVfb2JzZXJ2ZXIuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9zcmMvY2xpZW50L2FuYWx5dGljc19jbGllbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL21vZGVscy9hbmFseXRpY3NfZXZlbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL21vZGVscy9hbmFseXRpY3NfZXZlbnRzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CgpjbGFzcyBBbmFseXRpY3NIZWxwZXIgewogIHN0YXRpYyBBbmFseXRpY3NDbGllbnQ/IF9jbGllbnQ7CiAgc3RhdGljIExvZ2dlcj8gX2xvZ2dlcjsKCiAgc3RhdGljIEFuYWx5dGljc0NsaWVudD8gZ2V0IF9hbmFseXRpY3NDbGllbnQgewogICAgX2NsaWVudCA/Pz0gX2dldEFuYWx5dGljc0NsaWVudCgpOwogICAgcmV0dXJuIF9jbGllbnQ7CiAgfQoKICBzdGF0aWMgTG9nZ2VyIGdldCBfbG9nIHsKICAgIF9sb2dnZXIgPz89IGRpLmdldDxMb2dnZXI+KCk7CiAgICByZXR1cm4gX2xvZ2dlciE7CiAgfQoKICBzdGF0aWMgQW5hbHl0aWNzQ2xpZW50PyBfZ2V0QW5hbHl0aWNzQ2xpZW50KCkgewogICAgdHJ5IHsKICAgICAgcmV0dXJuIGRpLmdldDxBbmFseXRpY3NDbGllbnQ+KCk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2cudygnQW5hbHl0aWNzIGNsaWVudCBub3QgYXZhaWxhYmxlOiAkZScpOwogICAgICByZXR1cm4gbnVsbDsKICAgIH0KICB9CgogIC8vLyBMb2cgYSBjdXN0b20gZXZlbnQgd2l0aCBvcHRpb25hbCBwYXJhbWV0ZXJzCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dFdmVudCgKICAgIFN0cmluZyBuYW1lLCB7CiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KSBhc3luYyB7CiAgICBmaW5hbCBjbGllbnQgPSBfYW5hbHl0aWNzQ2xpZW50OwogICAgaWYgKGNsaWVudCA9PSBudWxsKSByZXR1cm47CgogICAgdHJ5IHsKICAgICAgYXdhaXQgY2xpZW50LmxvZ0V2ZW50KG5hbWU6IG5hbWUsIHBhcmFtZXRlcnM6IHBhcmFtZXRlcnMpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nLmUoJ0ZhaWxlZCB0byBsb2cgYW5hbHl0aWNzIGV2ZW50OiAkbmFtZScsIGUsIHMpOwogICAgfQogIH0KCiAgLy8vIExvZyB1c2VyIHNpZ24gaW4KICBzdGF0aWMgRnV0dXJlPHZvaWQ+IGxvZ1NpZ25Jbih7CiAgICBTdHJpbmc/IG1ldGhvZCwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzLAogIH0pIGFzeW5jIHsKICAgIGZpbmFsIGNsaWVudCA9IF9hbmFseXRpY3NDbGllbnQ7CiAgICBpZiAoY2xpZW50ID09IG51bGwpIHJldHVybjsKCiAgICB0cnkgewogICAgICBhd2FpdCBjbGllbnQubG9nTG9naW4obG9naW5NZXRob2Q6IG1ldGhvZCwgcGFyYW1ldGVyczogcGFyYW1ldGVycyk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2cuZSgnRmFpbGVkIHRvIGxvZyBzaWduIGluIGV2ZW50JywgZSwgcyk7CiAgICB9CiAgfQoKICAvLy8gTG9nIHVzZXIgc2lnbiB1cAogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nU2lnblVwKHsKICAgIFN0cmluZz8gbWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5sb2dTaWduVXAoc2lnblVwTWV0aG9kOiBtZXRob2QsIHBhcmFtZXRlcnM6IHBhcmFtZXRlcnMpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nLmUoJ0ZhaWxlZCB0byBsb2cgc2lnbiB1cCBldmVudCcsIGUsIHMpOwogICAgfQogIH0KCiAgLy8vIFNldCB1c2VyIElEIGZvciBhbmFseXRpY3MKICBzdGF0aWMgRnV0dXJlPHZvaWQ+IHNldFVzZXJJZChTdHJpbmc/IHVzZXJJZCkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5zZXRVc2VySWQodXNlcklkKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZy5lKCdGYWlsZWQgdG8gc2V0IGFuYWx5dGljcyB1c2VyIElEJywgZSwgcyk7CiAgICB9CiAgfQoKICAvLy8gU2V0IHVzZXIgcHJvcGVydHkKICBzdGF0aWMgRnV0dXJlPHZvaWQ+IHNldFVzZXJQcm9wZXJ0eSh7CiAgICByZXF1aXJlZCBTdHJpbmcgbmFtZSwKICAgIHJlcXVpcmVkIFN0cmluZz8gdmFsdWUsCiAgfSkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5zZXRVc2VyUHJvcGVydHkobmFtZTogbmFtZSwgdmFsdWU6IHZhbHVlKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZy5lKCdGYWlsZWQgdG8gc2V0IGFuYWx5dGljcyB1c2VyIHByb3BlcnR5OiAkbmFtZScsIGUsIHMpOwogICAgfQogIH0KCiAgLy8vIExvZyBhcHAgb3BlbiBldmVudAogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQXBwT3Blbih7TWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnN9KSBhc3luYyB7CiAgICBmaW5hbCBjbGllbnQgPSBfYW5hbHl0aWNzQ2xpZW50OwogICAgaWYgKGNsaWVudCA9PSBudWxsKSByZXR1cm47CgogICAgdHJ5IHsKICAgICAgYXdhaXQgY2xpZW50LmxvZ0FwcE9wZW4ocGFyYW1ldGVyczogcGFyYW1ldGVycyk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2cuZSgnRmFpbGVkIHRvIGxvZyBhcHAgb3BlbiBldmVudCcsIGUsIHMpOwogICAgfQogIH0KCiAgLy8vIExvZyBjdXN0b20gYW5hbHl0aWNzIGV2ZW50CiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dDdXN0b21FdmVudChBbmFseXRpY3NFdmVudCBldmVudCkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5sb2dDdXN0b21FdmVudChldmVudCk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2cuZSgnRmFpbGVkIHRvIGxvZyBjdXN0b20gYW5hbHl0aWNzIGV2ZW50OiAke2V2ZW50Lm5hbWV9JywgZSwgcyk7CiAgICB9CiAgfQoKICAvLy8gQ29tbW9uIGJ1c2luZXNzIGV2ZW50cwogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nRmVhdHVyZVVzZWQoCiAgICBTdHJpbmcgZmVhdHVyZU5hbWUsIHsKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzLAogIH0pIGFzeW5jIHsKICAgIGF3YWl0IGxvZ0V2ZW50KAogICAgICBBbmFseXRpY3NFdmVudHMuZmVhdHVyZS51c2VkLAogICAgICBwYXJhbWV0ZXJzOiB7CiAgICAgICAgJ2ZlYXR1cmVfbmFtZSc6IGZlYXR1cmVOYW1lLAogICAgICAgICd0aW1lc3RhbXAnOiBEYXRlVGltZS5ub3coKS50b0lzbzg2MDFTdHJpbmcoKSwKICAgICAgICAuLi4/cGFyYW1ldGVycywKICAgICAgfSwKICAgICk7CiAgfQoKICBzdGF0aWMgRnV0dXJlPHZvaWQ+IGxvZ0J1dHRvblByZXNzZWQoCiAgICBTdHJpbmcgYnV0dG9uTmFtZSwgewogICAgU3RyaW5nPyBzY3JlZW5OYW1lLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQoCiAgICAgIEFuYWx5dGljc0V2ZW50cy5mZWF0dXJlLmJ1dHRvblByZXNzZWQsCiAgICAgIHBhcmFtZXRlcnM6IHsKICAgICAgICAnYnV0dG9uX25hbWUnOiBidXR0b25OYW1lLAogICAgICAgIGlmIChzY3JlZW5OYW1lICE9IG51bGwpICdzY3JlZW5fbmFtZSc6IHNjcmVlbk5hbWUsCiAgICAgICAgJ3RpbWVzdGFtcCc6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogICAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgICB9LAogICAgKTsKICB9CgogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQXBwRXJyb3IoCiAgICBTdHJpbmcgZXJyb3JUeXBlLCB7CiAgICBTdHJpbmc/IGVycm9yTWVzc2FnZSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzLAogIH0pIGFzeW5jIHsKICAgIGF3YWl0IGxvZ0V2ZW50KAogICAgICBBbmFseXRpY3NFdmVudHMuZXJyb3IuYXBwRXJyb3IsCiAgICAgIHBhcmFtZXRlcnM6IHsKICAgICAgICAnZXJyb3JfdHlwZSc6IGVycm9yVHlwZSwKICAgICAgICBpZiAoZXJyb3JNZXNzYWdlICE9IG51bGwpICdlcnJvcl9tZXNzYWdlJzogZXJyb3JNZXNzYWdlLAogICAgICAgICd0aW1lc3RhbXAnOiBEYXRlVGltZS5ub3coKS50b0lzbzg2MDFTdHJpbmcoKSwKICAgICAgICAuLi4/cGFyYW1ldGVycywKICAgICAgfSwKICAgICk7CiAgfQoKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/index.dart", + "data": "ZXhwb3J0ICdhbmFseXRpY3NfaGVscGVyLmRhcnQnOwo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml", + "data": + "bmFtZTogYW5hbHl0aWNzCmRlc2NyaXB0aW9uOiAiRXZlbnQgdHJhY2tpbmcgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19IGJlaGluZCBhIHN3YXBwYWJsZSBjbGllbnQgaW50ZXJmYWNlLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZmlyZWJhc2VfYW5hbHl0aWNzOiBeMTEuMy41CiAgY29yZToKICAgIHBhdGg6IC4uL2NvcmUKICBkaToKICAgIHBhdGg6IC4uL2RpCgpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF41LjAuMAoKZmx1dHRlcjoK", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/{{analytics_dir}}/README.md", + "data": + "IyBBbmFseXRpY3MgUGFja2FnZQoKQSBzdHJ1Y3R1cmVkIGFuYWx5dGljcyBwYWNrYWdlIGZvciB7e25hbWUudGl0bGVDYXNlKCl9fSB3aXRoIEZpcmViYXNlIEFuYWx5dGljcyBpbnRlZ3JhdGlvbiBhbmQgcGx1Z2dhYmxlIHByb3ZpZGVyIGFyY2hpdGVjdHVyZS4KCiMjIEZlYXR1cmVzCgotIOKchSAqKkludGVyZmFjZS1iYXNlZCBkZXNpZ24qKiAtIEVhc3kgdG8gc3dpdGNoIGFuYWx5dGljcyBwcm92aWRlcnMKLSDinIUgKipBdXRvbWF0aWMgc2NyZWVuIHRyYWNraW5nKiogLSBSb3V0ZSBvYnNlcnZlciB0cmFja3MgbmF2aWdhdGlvbiBhdXRvbWF0aWNhbGx5Ci0g4pyFICoqU3RydWN0dXJlZCBldmVudCBuYW1lcyoqIC0gVHlwZS1zYWZlLCBvcmdhbml6ZWQgZXZlbnQgY29uc3RhbnRzCi0g4pyFICoqRGVwZW5kZW5jeSBpbmplY3Rpb24qKiAtIEludGVncmF0ZWQgd2l0aCBESSBjb250YWluZXIKLSDinIUgKipFcnJvciBoYW5kbGluZyoqIC0gR3JhY2VmdWwgZmFpbHVyZXMgZG9uJ3QgY3Jhc2ggdGhlIGFwcAotIOKchSAqKkRlYnVnIGxvZ2dpbmcqKiAtIENvbmZpZ3VyYWJsZSBsb2dnaW5nIGZvciBkZXZlbG9wbWVudAoKIyMgUXVpY2sgU3RhcnQKCiMjIyAxLiBJbml0aWFsaXplIEFuYWx5dGljcwoKYGBgZGFydAppbXBvcnQgJ3BhY2thZ2U6YW5hbHl0aWNzL2FuYWx5dGljcy5kYXJ0JzsKCmZpbmFsIGFuYWx5dGljc0NvbmZpZyA9IERlZmF1bHRBbmFseXRpY3NDb25maWcoCiAgZW5hYmxlQW5hbHl0aWNzOiB0cnVlLAogIGVuYWJsZURlYnVnTG9nZ2luZzogdHJ1ZSwgLy8gU2V0IHRvIGZhbHNlIGluIHByb2R1Y3Rpb24KKTsKYXdhaXQgaW5pdChjb25maWc6IGFuYWx5dGljc0NvbmZpZyk7CmBgYAoKIyMjIDIuIEF1dG9tYXRpYyBTY3JlZW4gVHJhY2tpbmcKCkFkZCB0aGUgYEFuYWx5dGljc1JvdXRlT2JzZXJ2ZXJgIHRvIHlvdXIgcm91dGVyOgoKYGBgZGFydApHb1JvdXRlcigKICBvYnNlcnZlcnM6IFsKICAgIEFuYWx5dGljc1JvdXRlT2JzZXJ2ZXIoKSwgLy8gVXNlcyBGaXJlYmFzZUFuYWx5dGljc09ic2VydmVyIGludGVybmFsbHkKICBdLAogIC8vIC4uLiByb3V0ZXMKKTsKYGBgCgpUaGUgYEFuYWx5dGljc1JvdXRlT2JzZXJ2ZXJgIGludGVybmFsbHkgdXNlcyBGaXJlYmFzZSdzIG5hdGl2ZSBgRmlyZWJhc2VBbmFseXRpY3NPYnNlcnZlcmAgZm9yIHByb3BlciBzY3JlZW4gdHJhY2tpbmcgd2hpbGUgYWRkaW5nIGFkZGl0aW9uYWwgbG9nZ2luZyBhbmQgZXJyb3IgaGFuZGxpbmcuCgojIyMgMy4gTWFudWFsIEV2ZW50IFRyYWNraW5nCgojIyMjIFVzaW5nIFN0cnVjdHVyZWQgRXZlbnQgTmFtZXMgKFJlY29tbWVuZGVkKQoKYGBgZGFydAovLyBVc2VyIGV2ZW50cwphd2FpdCBBbmFseXRpY3NIZWxwZXIubG9nRXZlbnQoQW5hbHl0aWNzRXZlbnRzLnVzZXIuYXV0aGVudGljYXRlZFJlZGlyZWN0KTsKCi8vIEF1dGhlbnRpY2F0aW9uIGV2ZW50cwphd2FpdCBBbmFseXRpY3NIZWxwZXIubG9nRXZlbnQoCiAgQW5hbHl0aWNzRXZlbnRzLmF1dGguc2lnbkluLAogIHBhcmFtZXRlcnM6IHsnbWV0aG9kJzogJ2dvb2dsZSd9LAopOwoKLy8gRmVhdHVyZSB1c2FnZQphd2FpdCBBbmFseXRpY3NIZWxwZXIubG9nRXZlbnQoCiAgQW5hbHl0aWNzRXZlbnRzLmZlYXR1cmUudXNlZCwKICBwYXJhbWV0ZXJzOiB7J2ZlYXR1cmVfbmFtZSc6ICdleHBvcnQnfSwKKTsKCi8vIEEgZ3JvdXAgb2YgeW91ciBvd24sIGFkZGVkIHRvIEFuYWx5dGljc0V2ZW50cwphd2FpdCBBbmFseXRpY3NIZWxwZXIubG9nRXZlbnQoCiAgQW5hbHl0aWNzRXZlbnRzLmZlYXR1cmUudHV0b3JpYWxDb21wbGV0ZWQsCiAgcGFyYW1ldGVyczogewogICAgJ3R1dG9yaWFsX2lkJzogJ2dldHRpbmdfc3RhcnRlZCcsCiAgICAnZHVyYXRpb25fc2Vjb25kcyc6IDEyMCwKICB9LAopOwpgYGAKCiMjIyMgVXNpbmcgSGVscGVyIE1ldGhvZHMKCmBgYGRhcnQKLy8gRmVhdHVyZSB0cmFja2luZwphd2FpdCBBbmFseXRpY3NIZWxwZXIubG9nRmVhdHVyZVVzZWQoJ2V4cG9ydCcpOwoKLy8gRXJyb3IgdHJhY2tpbmcKYXdhaXQgQW5hbHl0aWNzSGVscGVyLmxvZ0FwcEVycm9yKCduZXR3b3JrX3RpbWVvdXQnKTsKCi8vIEJ1dHRvbiBpbnRlcmFjdGlvbnMKYXdhaXQgQW5hbHl0aWNzSGVscGVyLmxvZ0J1dHRvblByZXNzZWQoJ2dldF9zdGFydGVkJyk7CgovLyBBdXRoZW50aWNhdGlvbiBldmVudHMKYXdhaXQgQW5hbHl0aWNzSGVscGVyLmxvZ1NpZ25JbihtZXRob2Q6ICdnb29nbGUnKTsKYXdhaXQgQW5hbHl0aWNzSGVscGVyLmxvZ1NpZ25VcChtZXRob2Q6ICdlbWFpbCcpOwoKLy8gVXNlciBtYW5hZ2VtZW50CmF3YWl0IEFuYWx5dGljc0hlbHBlci5zZXRVc2VySWQoJ3VzZXIxMjMnKTsKYXdhaXQgQW5hbHl0aWNzSGVscGVyLnNldFVzZXJQcm9wZXJ0eShuYW1lOiAncGxhbicsIHZhbHVlOiAncHJlbWl1bScpOwoKLy8gQXBwIGxpZmVjeWNsZQphd2FpdCBBbmFseXRpY3NIZWxwZXIubG9nQXBwT3BlbigpOwpgYGAKCiMjIyMgRGlyZWN0IENsaWVudCBVc2FnZQoKYGBgZGFydApmaW5hbCBhbmFseXRpY3NDbGllbnQgPSBkaS5nZXQ8QW5hbHl0aWNzQ2xpZW50PigpOwoKYXdhaXQgYW5hbHl0aWNzQ2xpZW50LmxvZ0V2ZW50KAogIG5hbWU6ICdjdXN0b21fZXZlbnQnLAogIHBhcmFtZXRlcnM6IHsna2V5JzogJ3ZhbHVlJ30sCik7Cgphd2FpdCBhbmFseXRpY3NDbGllbnQuc2V0VXNlcklkKCd1c2VyMTIzJyk7CmF3YWl0IGFuYWx5dGljc0NsaWVudC5zZXRVc2VyUHJvcGVydHkobmFtZTogJ3BsYW4nLCB2YWx1ZTogJ3ByZW1pdW0nKTsKYGBgCgojIyBTdHJ1Y3R1cmVkIEV2ZW50IE5hbWVzCgpUaGUgcGFja2FnZSBwcm92aWRlcyBvcmdhbml6ZWQsIHR5cGUtc2FmZSBldmVudCBuYW1lcyB0aHJvdWdoIGBBbmFseXRpY3NFdmVudHNgOgoKIyMjIEF2YWlsYWJsZSBFdmVudCBDYXRlZ29yaWVzCgpgYGBkYXJ0Ci8vIFVzZXItcmVsYXRlZCBldmVudHMKQW5hbHl0aWNzRXZlbnRzLnVzZXIuYXV0aGVudGljYXRlZFJlZGlyZWN0CkFuYWx5dGljc0V2ZW50cy51c2VyLnByb2ZpbGVVcGRhdGVkCkFuYWx5dGljc0V2ZW50cy51c2VyLm9uYm9hcmRpbmdDb21wbGV0ZWQKCi8vIEF1dGhlbnRpY2F0aW9uIGV2ZW50cwpBbmFseXRpY3NFdmVudHMuYXV0aC5zaWduSW4KQW5hbHl0aWNzRXZlbnRzLmF1dGguc2lnblVwCkFuYWx5dGljc0V2ZW50cy5hdXRoLnNpZ25PdXQKCi8vIE5hdmlnYXRpb24gZXZlbnRzCkFuYWx5dGljc0V2ZW50cy5uYXZpZ2F0aW9uLnNjcmVlblZpZXcKQW5hbHl0aWNzRXZlbnRzLm5hdmlnYXRpb24udGFiU3dpdGNoZWQKCi8vIEZlYXR1cmUgdXNhZ2UKQW5hbHl0aWNzRXZlbnRzLmZlYXR1cmUudXNlZApBbmFseXRpY3NFdmVudHMuZmVhdHVyZS50dXRvcmlhbENvbXBsZXRlZAoKLy8gQXBwIGxpZmVjeWNsZQpBbmFseXRpY3NFdmVudHMuYXBwLm9wZW4KQW5hbHl0aWNzRXZlbnRzLmFwcC5iYWNrZ3JvdW5kCkFuYWx5dGljc0V2ZW50cy5hcHAuZm9yZWdyb3VuZAoKLy8gRXJyb3IgZXZlbnRzCkFuYWx5dGljc0V2ZW50cy5lcnJvci5hcHBFcnJvcgpBbmFseXRpY3NFdmVudHMuZXJyb3IubmV0d29ya0Vycm9yCmBgYAoKIyMgQ29uZmlndXJhdGlvbgoKIyMjIEFuYWx5dGljc0NvbmZpZyBPcHRpb25zCgpgYGBkYXJ0CmZpbmFsIGNvbmZpZyA9IERlZmF1bHRBbmFseXRpY3NDb25maWcoCiAgZW5hYmxlQW5hbHl0aWNzOiB0cnVlLCAgICAgICAgLy8gRW5hYmxlL2Rpc2FibGUgYW5hbHl0aWNzIGNvbGxlY3Rpb24KICBlbmFibGVEZWJ1Z0xvZ2dpbmc6IGZhbHNlLCAgICAvLyBEZWJ1ZyBsb2dnaW5nIChkZXZlbG9wbWVudCBvbmx5KQogIHVzZXJJZDogJ3VzZXIxMjMnLCAgICAgICAgICAgLy8gT3B0aW9uYWw6IFNldCBpbml0aWFsIHVzZXIgSUQKICBkZWZhdWx0VXNlclByb3BlcnRpZXM6IHsgICAgICAvLyBPcHRpb25hbDogRGVmYXVsdCB1c2VyIHByb3BlcnRpZXMKICAgICd1c2VyX3R5cGUnOiAncHJlbWl1bScsCiAgICAnYXBwX3ZlcnNpb24nOiAnMS4wLjAnLAogIH0sCik7CmBgYAoKIyMjIEVudmlyb25tZW50LWJhc2VkIENvbmZpZ3VyYXRpb24KCmBgYGRhcnQKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvZm91bmRhdGlvbi5kYXJ0JzsKCmZpbmFsIGNvbmZpZyA9IERlZmF1bHRBbmFseXRpY3NDb25maWcoCiAgZW5hYmxlQW5hbHl0aWNzOiAha0RlYnVnTW9kZSwgICAgICAgICAgLy8gRGlzYWJsZSBpbiBkZWJ1ZyBidWlsZHMKICBlbmFibGVEZWJ1Z0xvZ2dpbmc6IGtEZWJ1Z01vZGUsICAgICAgICAvLyBEZWJ1ZyBsb2dnaW5nIGluIGRldmVsb3BtZW50Cik7CmBgYAoKIyMgQXJjaGl0ZWN0dXJlCgojIyMgQ29tcG9uZW50cwoKLSAqKkFuYWx5dGljc0NsaWVudCoqIC0gQWJzdHJhY3QgaW50ZXJmYWNlIGZvciBhbmFseXRpY3Mgb3BlcmF0aW9ucwotICoqRmlyZWJhc2VBbmFseXRpY3NDbGllbnQqKiAtIEZpcmViYXNlIEFuYWx5dGljcyBpbXBsZW1lbnRhdGlvbgotICoqQW5hbHl0aWNzUm91dGVPYnNlcnZlcioqIC0gQXV0b21hdGljIHNjcmVlbiB0cmFja2luZwotICoqQW5hbHl0aWNzSGVscGVyKiogLSBDb252ZW5pZW5jZSBtZXRob2RzIGZvciBjb21tb24gZXZlbnRzCi0gKipBbmFseXRpY3NFdmVudHMqKiAtIFN0cnVjdHVyZWQgZXZlbnQgbmFtZSBjb25zdGFudHMKCiMjIyBQbHVnZ2FibGUgRGVzaWduCgpTd2l0Y2ggYW5hbHl0aWNzIHByb3ZpZGVycyBieSBpbXBsZW1lbnRpbmcgYEFuYWx5dGljc0NsaWVudGA6CgpgYGBkYXJ0CmNsYXNzIE1peHBhbmVsQW5hbHl0aWNzQ2xpZW50IGltcGxlbWVudHMgQW5hbHl0aWNzQ2xpZW50IHsKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gbG9nRXZlbnQoe3JlcXVpcmVkIFN0cmluZyBuYW1lLCBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVyc30pIHsKICAgIC8vIE1peHBhbmVsIGltcGxlbWVudGF0aW9uCiAgfQogIC8vIC4uLiBvdGhlciBtZXRob2RzCn0KCi8vIFJlZ2lzdGVyIHdpdGggREkKZGkucmVnaXN0ZXI8QW5hbHl0aWNzQ2xpZW50PihNaXhwYW5lbEFuYWx5dGljc0NsaWVudChjb25maWcpKTsKYGBgCgojIyBCZXN0IFByYWN0aWNlcwoKMS4gKipVc2UgU3RydWN0dXJlZCBFdmVudHMqKjogQWx3YXlzIHByZWZlciBgQW5hbHl0aWNzRXZlbnRzLmNhdGVnb3J5LmV2ZW50TmFtZWAgb3ZlciBzdHJpbmcgbGl0ZXJhbHMKMi4gKipJbmNsdWRlIENvbnRleHQqKjogQWRkIHJlbGV2YW50IHBhcmFtZXRlcnMgbGlrZSBgc2NyZWVuX25hbWVgLCBgdXNlcl9pZGAsIGV0Yy4KMy4gKipDb25zaXN0ZW50IE5hbWluZyoqOiBVc2Ugc25ha2VfY2FzZSBmb3IgZXZlbnQgbmFtZXMgYW5kIHBhcmFtZXRlcnMKNC4gKipFcnJvciBIYW5kbGluZyoqOiBBbmFseXRpY3MgZmFpbHVyZXMgc2hvdWxkIG5ldmVyIGNyYXNoIHRoZSBhcHAKNS4gKipQcml2YWN5Kio6IFJlc3BlY3QgdXNlciBwcml2YWN5IHNldHRpbmdzIGFuZCBkYXRhIHJlZ3VsYXRpb25zCgojIyBFeGFtcGxlcwoKIyMjIFRyYWNrIFVzZXIgSm91cm5leQoKYGBgZGFydAovLyBBcHAgbGF1bmNoCmF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dBcHBPcGVuKCk7CgovLyBVc2VyIHNpZ25zIGluCmF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dTaWduSW4obWV0aG9kOiAnZ29vZ2xlJyk7CgovLyBVc2VyIGNvbXBsZXRlcyBvbmJvYXJkaW5nCmF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dFdmVudCgKICBBbmFseXRpY3NFdmVudHMudXNlci5vbmJvYXJkaW5nQ29tcGxldGVkLAogIHBhcmFtZXRlcnM6IHsnc3RlcHNfY29tcGxldGVkJzogNX0sCik7CgovLyBVc2VyIHVzZXMgYSBmZWF0dXJlCmF3YWl0IEFuYWx5dGljc0hlbHBlci5sb2dGZWF0dXJlVXNlZCgnZXhwb3J0Jyk7CmBgYAoKIyMjIEVycm9yIFRyYWNraW5nCgpgYGBkYXJ0CnRyeSB7CiAgLy8gU29tZSBvcGVyYXRpb24KfSBjYXRjaCAoZXJyb3IpIHsKICBhd2FpdCBBbmFseXRpY3NIZWxwZXIubG9nQXBwRXJyb3IoCiAgICAnYXBpX2NhbGxfZmFpbGVkJywKICAgIGVycm9yTWVzc2FnZTogZXJyb3IudG9TdHJpbmcoKSwKICAgIHBhcmFtZXRlcnM6IHsKICAgICAgJ2VuZHBvaW50JzogJy9hcGkvaXRlbXMnLAogICAgICAndXNlcl9pZCc6IHVzZXJJZCwKICAgIH0sCiAgKTsKICByZXRocm93Owp9CmBgYAoKVGhpcyBzdHJ1Y3R1cmVkIGFwcHJvYWNoIGVuc3VyZXMgY29uc2lzdGVudCwgZGlzY292ZXJhYmxlLCBhbmQgbWFpbnRhaW5hYmxlIGFuYWx5dGljcyB0aHJvdWdob3V0IHRoZSB7e25hbWUudGl0bGVDYXNlKCl9fSBhcHAu", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{analytics_dir}}/test/analytics_test.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9hbmFseXRpY3MuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3Rlc3QvZmx1dHRlcl90ZXN0LmRhcnQnOwoKdm9pZCBtYWluKCkgewogIGdyb3VwKCdBbmFseXRpY3MgUGFja2FnZScsICgpIHsKICAgIHRlc3QoJ3Nob3VsZCBjcmVhdGUgQW5hbHl0aWNzRXZlbnQgY29ycmVjdGx5JywgKCkgewogICAgICBjb25zdCBldmVudCA9IEFuYWx5dGljc0V2ZW50KAogICAgICAgIG5hbWU6ICd0ZXN0X2V2ZW50JywKICAgICAgICBwYXJhbWV0ZXJzOiB7J2tleSc6ICd2YWx1ZSd9LAogICAgICApOwoKICAgICAgZXhwZWN0KGV2ZW50Lm5hbWUsICd0ZXN0X2V2ZW50Jyk7CiAgICAgIGV4cGVjdChldmVudC5wYXJhbWV0ZXJzLCB7J2tleSc6ICd2YWx1ZSd9KTsKICAgIH0pOwoKICAgIHRlc3QoJ3Nob3VsZCBjcmVhdGUgRGVmYXVsdEFuYWx5dGljc0NvbmZpZyBjb3JyZWN0bHknLCAoKSB7CiAgICAgIGNvbnN0IGNvbmZpZyA9IERlZmF1bHRBbmFseXRpY3NDb25maWcoCiAgICAgICAgZW5hYmxlQW5hbHl0aWNzOiB0cnVlLAogICAgICAgIGVuYWJsZURlYnVnTG9nZ2luZzogZmFsc2UsCiAgICAgICk7CgogICAgICBleHBlY3QoY29uZmlnLmVuYWJsZUFuYWx5dGljcywgaXNUcnVlKTsKICAgICAgZXhwZWN0KGNvbmZpZy5lbmFibGVEZWJ1Z0xvZ2dpbmcsIGlzRmFsc2UpOwogICAgfSk7CiAgfSk7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/crashlytics.dart", + "data": + "bGlicmFyeTsKCmltcG9ydCAnZGFydDphc3luYyc7CgppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y3Jhc2hseXRpY3Mvc3JjL2NsaWVudC9pbmRleC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmNyYXNobHl0aWNzL3NyYy9jb25maWcvaW5kZXguZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKCmV4cG9ydCAnc3JjL2NsaWVudC9pbmRleC5kYXJ0JzsKZXhwb3J0ICdzcmMvY29uZmlnL2luZGV4LmRhcnQnOwpleHBvcnQgJ3NyYy9tb2RlbHMvaW5kZXguZGFydCc7CgovLy8gSW5pdGlhbGl6ZSB0aGUgY3Jhc2hseXRpY3MgbW9kdWxlIHdpdGggZGVwZW5kZW5jeSBpbmplY3Rpb24KRnV0dXJlPHZvaWQ+IGluaXQoe0NyYXNobHl0aWNzQ29uZmlnPyBjb25maWd9KSBhc3luYyB7CiAgYXdhaXQgcmVnaXN0ZXJDcmFzaGx5dGljc1dpdGhESShjb25maWcgPz8gY29uc3QgRGVmYXVsdENyYXNobHl0aWNzQ29uZmlnKCkpOwogIGZpbmFsIGxvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CiAgbG9nZ2VyLmkoJ/CflKUgQ3Jhc2hseXRpY3MgbW9kdWxlIGluaXRpYWxpemVkJyk7Cn0KCi8vLyBSZWdpc3RlciBjcmFzaGx5dGljcyBjb21wb25lbnRzIHdpdGggREkgY29udGFpbmVyCkZ1dHVyZTx2b2lkPiByZWdpc3RlckNyYXNobHl0aWNzV2l0aERJKENyYXNobHl0aWNzQ29uZmlnIGNvbmZpZykgYXN5bmMgewogIGZpbmFsIGxvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CgogIHRyeSB7CiAgICAvLyBSZWdpc3RlciBjb25maWcKICAgIGRpLnJlZ2lzdGVyPENyYXNobHl0aWNzQ29uZmlnPihjb25maWcpOwoKICAgIC8vIENyZWF0ZSBhbmQgcmVnaXN0ZXIgY3Jhc2hseXRpY3MgY2xpZW50CiAgICBmaW5hbCBjbGllbnQgPSBGaXJlYmFzZUNyYXNobHl0aWNzQ2xpZW50KGNvbmZpZzogY29uZmlnLCBsb2dnZXI6IGxvZ2dlcik7CiAgICBkaS5yZWdpc3RlcjxDcmFzaGx5dGljc0NsaWVudD4oCiAgICAgIGNsaWVudCwKICAgICAgZGlzcG9zZTogKGNsaWVudCkgPT4gY2xpZW50LmRpc3Bvc2UoKSwKICAgICk7CgogICAgLy8gSW5pdGlhbGl6ZSB0aGUgY2xpZW50CiAgICBhd2FpdCBjbGllbnQuaW5pdGlhbGl6ZSgpOwoKICAgIC8vIENoZWNrIGlmIHRoZXJlIGFyZSBhbnkgdW5zZW50IHJlcG9ydHMKICAgIHVuYXdhaXRlZCgKICAgICAgY2xpZW50LmNoZWNrRm9yVW5zZW50UmVwb3J0cygpLnRoZW48dm9pZD4oKGhhc1JlcG9ydHMpIHsKICAgICAgICBpZiAoaGFzUmVwb3J0cykgewogICAgICAgICAgbG9nZ2VyLncoJ+KaoO+4jyBUaGVyZSBhcmUgdW5zZW50IGNyYXNoIHJlcG9ydHMuJyk7CiAgICAgICAgICB1bmF3YWl0ZWQoY2xpZW50LnNlbmRVbnNlbnRSZXBvcnRzKCkpOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICBsb2dnZXIuaSgn4pyFIE5vIHVuc2VudCBjcmFzaCByZXBvcnRzIGZvdW5kLicpOwogICAgICAgIH0KICAgICAgfSksCiAgICApOwoKICAgIGxvZ2dlci5pKCfwn5SlIENyYXNobHl0aWNzIHJlZ2lzdGVyZWQgd2l0aCBESSBjb250YWluZXInKTsKICB9IGNhdGNoIChlLCBzdGFja1RyYWNlKSB7CiAgICBsb2dnZXIuZSgn4p2MIEZhaWxlZCB0byByZWdpc3RlciBjcmFzaGx5dGljcyB3aXRoIERJJywgZSwgc3RhY2tUcmFjZSk7CiAgICByZXRocm93OwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/crashlytics_client.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNyYXNobHl0aWNzL3NyYy9tb2RlbHMvdXNlcl9tZXRhZGF0YS5kYXJ0JzsKCmFic3RyYWN0IGNsYXNzIENyYXNobHl0aWNzQ2xpZW50IHsKICAvLy8gSW5pdGlhbGl6ZSB0aGUgY3Jhc2hseXRpY3MgY2xpZW50CiAgRnV0dXJlPHZvaWQ+IGluaXRpYWxpemUoKTsKCiAgLy8vIFJlY29yZCBhIGZhdGFsIGVycm9yL2V4Y2VwdGlvbgogIEZ1dHVyZTx2b2lkPiByZWNvcmRFcnJvcigKICAgIGR5bmFtaWMgZXhjZXB0aW9uLAogICAgU3RhY2tUcmFjZT8gc3RhY2tUcmFjZSwgewogICAgYm9vbCBmYXRhbCA9IGZhbHNlLAogICAgSXRlcmFibGU8T2JqZWN0PiBpbmZvcm1hdGlvbiA9IGNvbnN0IFtdLAogIH0pOwoKICAvLy8gUmVjb3JkIGEgbm9uLWZhdGFsIGVycm9yIHdpdGggY3VzdG9tIGNvbnRleHQKICBGdXR1cmU8dm9pZD4gcmVjb3JkRmx1dHRlckZhdGFsRXJyb3IoCiAgICBkeW5hbWljIGV4Y2VwdGlvbiwKICAgIFN0YWNrVHJhY2Ugc3RhY2tUcmFjZSwgewogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IGNvbnRleHQsCiAgfSk7CgogIC8vLyBMb2cgYSBjdXN0b20gbWVzc2FnZSBmb3IgZGVidWdnaW5nCiAgRnV0dXJlPHZvaWQ+IGxvZyhTdHJpbmcgbWVzc2FnZSk7CgogIC8vLyBTZXQgdXNlciBpZGVudGlmaWVyIGZvciBjcmFzaCByZXBvcnRzCiAgRnV0dXJlPHZvaWQ+IHNldFVzZXJJZGVudGlmaWVyKFN0cmluZyBpZGVudGlmaWVyKTsKCiAgLy8vIFNldCB1c2VyIG1ldGFkYXRhCiAgRnV0dXJlPHZvaWQ+IHNldFVzZXJNZXRhZGF0YShVc2VyTWV0YWRhdGEgbWV0YWRhdGEpOwoKICAvLy8gU2V0IGN1c3RvbSBrZXktdmFsdWUgcGFpcnMgZm9yIGRlYnVnZ2luZwogIEZ1dHVyZTx2b2lkPiBzZXRDdXN0b21LZXkoU3RyaW5nIGtleSwgZHluYW1pYyB2YWx1ZSk7CgogIC8vLyBTZXQgY3VzdG9tIGtleXMgZnJvbSBhIG1hcAogIEZ1dHVyZTx2b2lkPiBzZXRDdXN0b21LZXlzKE1hcDxTdHJpbmcsIGR5bmFtaWM+IGtleXMpOwoKICAvLy8gQ2hlY2sgaWYgY3Jhc2hseXRpY3MgY29sbGVjdGlvbiBpcyBlbmFibGVkCiAgYm9vbCBnZXQgaXNDcmFzaGx5dGljc0NvbGxlY3Rpb25FbmFibGVkOwoKICAvLy8gRW5hYmxlIG9yIGRpc2FibGUgY3Jhc2hseXRpY3MgY29sbGVjdGlvbgogIEZ1dHVyZTx2b2lkPiBzZXRDcmFzaGx5dGljc0NvbGxlY3Rpb25FbmFibGVkKGJvb2wgZW5hYmxlZCk7CgogIC8vLyBTZW5kIGFueSB1bnNlbnQgY3Jhc2ggcmVwb3J0cwogIEZ1dHVyZTx2b2lkPiBzZW5kVW5zZW50UmVwb3J0cygpOwoKICAvLy8gRGVsZXRlIGFueSB1bnNlbnQgY3Jhc2ggcmVwb3J0cwogIEZ1dHVyZTx2b2lkPiBkZWxldGVVbnNlbnRSZXBvcnRzKCk7CgogIC8vLyBDaGVjayBpZiB0aGVyZSBhcmUgYW55IHVuc2VudCBjcmFzaCByZXBvcnRzCiAgRnV0dXJlPGJvb2w+IGNoZWNrRm9yVW5zZW50UmVwb3J0cygpOwoKICAvLy8gRGlzcG9zZSBvZiByZXNvdXJjZXMKICB2b2lkIGRpc3Bvc2UoKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/firebase_crashlytics_client.dart", + "data": + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKaW1wb3J0ICdkYXJ0OmNvbGxlY3Rpb24nOwoKaW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmNyYXNobHl0aWNzL3NyYy9jbGllbnQvY3Jhc2hseXRpY3NfY2xpZW50LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y3Jhc2hseXRpY3Mvc3JjL2NvbmZpZy9jcmFzaGx5dGljc19jb25maWcuZGFydCc7CmltcG9ydCAncGFja2FnZTpjcmFzaGx5dGljcy9zcmMvbW9kZWxzL3VzZXJfbWV0YWRhdGEuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX2NyYXNobHl0aWNzL2ZpcmViYXNlX2NyYXNobHl0aWNzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9mb3VuZGF0aW9uLmRhcnQnOwoKY2xhc3MgRmlyZWJhc2VDcmFzaGx5dGljc0NsaWVudCBpbXBsZW1lbnRzIENyYXNobHl0aWNzQ2xpZW50IHsKICBGaXJlYmFzZUNyYXNobHl0aWNzQ2xpZW50KHtyZXF1aXJlZCB0aGlzLmNvbmZpZywgTG9nZ2VyPyBsb2dnZXJ9KQogICAgOiBfbG9nZ2VyID0gbG9nZ2VyID8/IGRpLmdldDxMb2dnZXI+KCk7CgogIGZpbmFsIENyYXNobHl0aWNzQ29uZmlnIGNvbmZpZzsKICBmaW5hbCBMb2dnZXIgX2xvZ2dlcjsKICBsYXRlIGZpbmFsIEZpcmViYXNlQ3Jhc2hseXRpY3MgX2NyYXNobHl0aWNzOwogIGZpbmFsIFF1ZXVlPFN0cmluZz4gX2xvZ0J1ZmZlciA9IFF1ZXVlPFN0cmluZz4oKTsKICBib29sIF9pc0luaXRpYWxpemVkID0gZmFsc2U7CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBpbml0aWFsaXplKCkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgX2NyYXNobHl0aWNzID0gRmlyZWJhc2VDcmFzaGx5dGljcy5pbnN0YW5jZTsKCiAgICAgIC8vIFNldCBjcmFzaGx5dGljcyBjb2xsZWN0aW9uIGVuYWJsZWQgYmFzZWQgb24gY29uZmlnCiAgICAgIGF3YWl0IF9jcmFzaGx5dGljcy5zZXRDcmFzaGx5dGljc0NvbGxlY3Rpb25FbmFibGVkKAogICAgICAgIGNvbmZpZy5lbmFibGVJbkRlYnVnTW9kZSB8fCBrUmVsZWFzZU1vZGUsCiAgICAgICk7CgogICAgICAvLyBTZXQgdXAgYXV0b21hdGljIEZsdXR0ZXIgZXJyb3IgaGFuZGxpbmcKICAgICAgaWYgKGNvbmZpZy5lbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbikgewogICAgICAgIEZsdXR0ZXJFcnJvci5vbkVycm9yID0gKGVycm9yRGV0YWlscykgewogICAgICAgICAgX2NyYXNobHl0aWNzLnJlY29yZEZsdXR0ZXJGYXRhbEVycm9yKGVycm9yRGV0YWlscyk7CiAgICAgICAgfTsKCiAgICAgICAgLy8gQ2F0Y2ggZXJyb3JzIGZyb20gdGhlIEZsdXR0ZXIgZnJhbWV3b3JrIHRoYXQgYXJlIG5vdCBoYW5kbGVkIGJ5IEZsdXR0ZXJFcnJvcgogICAgICAgIFBsYXRmb3JtRGlzcGF0Y2hlci5pbnN0YW5jZS5vbkVycm9yID0gKGVycm9yLCBzdGFjaykgewogICAgICAgICAgX2NyYXNobHl0aWNzLnJlY29yZEVycm9yKGVycm9yLCBzdGFjaywgZmF0YWw6IHRydWUpOwogICAgICAgICAgcmV0dXJuIHRydWU7CiAgICAgICAgfTsKICAgICAgfQoKICAgICAgLy8gU2V0IGRlZmF1bHQgY3VzdG9tIGtleXMKICAgICAgaWYgKGNvbmZpZy5jdXN0b21LZXlzLmlzTm90RW1wdHkpIHsKICAgICAgICBhd2FpdCBzZXRDdXN0b21LZXlzKGNvbmZpZy5jdXN0b21LZXlzKTsKICAgICAgfQoKICAgICAgX2lzSW5pdGlhbGl6ZWQgPSB0cnVlOwogICAgICBfbG9nZ2VyLmkoJ/CflKUgRmlyZWJhc2UgQ3Jhc2hseXRpY3MgaW5pdGlhbGl6ZWQgc3VjY2Vzc2Z1bGx5Jyk7CiAgICB9IGNhdGNoIChlLCBzdGFja1RyYWNlKSB7CiAgICAgIF9sb2dnZXIuZSgn4p2MIEZhaWxlZCB0byBpbml0aWFsaXplIEZpcmViYXNlIENyYXNobHl0aWNzOiAkZScsIHN0YWNrVHJhY2UpOwogICAgICByZXRocm93OwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJlY29yZEVycm9yKAogICAgZHluYW1pYyBleGNlcHRpb24sCiAgICBTdGFja1RyYWNlPyBzdGFja1RyYWNlLCB7CiAgICBib29sIGZhdGFsID0gZmFsc2UsCiAgICBJdGVyYWJsZTxPYmplY3Q+IGluZm9ybWF0aW9uID0gY29uc3QgW10sCiAgfSkgYXN5bmMgewogICAgaWYgKCFfaXNJbml0aWFsaXplZCkgewogICAgICBfbG9nZ2VyLncoJ+KaoO+4jyBDcmFzaGx5dGljcyBub3QgaW5pdGlhbGl6ZWQsIHNraXBwaW5nIGVycm9yIHJlY29yZGluZycpOwogICAgICByZXR1cm47CiAgICB9CgogICAgdHJ5IHsKICAgICAgYXdhaXQgX2NyYXNobHl0aWNzLnJlY29yZEVycm9yKAogICAgICAgIGV4Y2VwdGlvbiwKICAgICAgICBzdGFja1RyYWNlLAogICAgICAgIGZhdGFsOiBmYXRhbCwKICAgICAgICBpbmZvcm1hdGlvbjogaW5mb3JtYXRpb24sCiAgICAgICk7CgogICAgICBfbG9nZ2VyLmQoCiAgICAgICAgJ/Cfk50gUmVjb3JkZWQgJHtmYXRhbCA/ICdmYXRhbCcgOiAnbm9uLWZhdGFsJ30gZXJyb3I6ICRleGNlcHRpb24nLAogICAgICApOwogICAgfSBjYXRjaCAoZSkgewogICAgICBfbG9nZ2VyLmUoJ+KdjCBGYWlsZWQgdG8gcmVjb3JkIGVycm9yOiAkZScpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJlY29yZEZsdXR0ZXJGYXRhbEVycm9yKAogICAgZHluYW1pYyBleGNlcHRpb24sCiAgICBTdGFja1RyYWNlIHN0YWNrVHJhY2UsIHsKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBjb250ZXh0LAogIH0pIGFzeW5jIHsKICAgIGlmICghX2lzSW5pdGlhbGl6ZWQpIHsKICAgICAgX2xvZ2dlci53KAogICAgICAgICfimqDvuI8gQ3Jhc2hseXRpY3Mgbm90IGluaXRpYWxpemVkLCBza2lwcGluZyBGbHV0dGVyIGZhdGFsIGVycm9yIHJlY29yZGluZycsCiAgICAgICk7CiAgICAgIHJldHVybjsKICAgIH0KCiAgICB0cnkgewogICAgICAvLyBTZXQgY29udGV4dCBhcyBjdXN0b20ga2V5cyBpZiBwcm92aWRlZAogICAgICBpZiAoY29udGV4dCAhPSBudWxsICYmIGNvbnRleHQuaXNOb3RFbXB0eSkgewogICAgICAgIGF3YWl0IHNldEN1c3RvbUtleXMoY29udGV4dCk7CiAgICAgIH0KCiAgICAgIGF3YWl0IF9jcmFzaGx5dGljcy5yZWNvcmRFcnJvcihleGNlcHRpb24sIHN0YWNrVHJhY2UsIGZhdGFsOiB0cnVlKTsKCiAgICAgIF9sb2dnZXIuZCgn8J+TnSBSZWNvcmRlZCBGbHV0dGVyIGZhdGFsIGVycm9yOiAkZXhjZXB0aW9uJyk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2dnZXIuZSgn4p2MIEZhaWxlZCB0byByZWNvcmQgRmx1dHRlciBmYXRhbCBlcnJvcjogJGUnKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2coU3RyaW5nIG1lc3NhZ2UpIGFzeW5jIHsKICAgIGlmICghX2lzSW5pdGlhbGl6ZWQgfHwgIWNvbmZpZy5lbmFibGVDdXN0b21Mb2dzKSB7CiAgICAgIHJldHVybjsKICAgIH0KCiAgICB0cnkgewogICAgICBhd2FpdCBfY3Jhc2hseXRpY3MubG9nKG1lc3NhZ2UpOwoKICAgICAgLy8gQWRkIHRvIGxvY2FsIGJ1ZmZlciBmb3IgZGVidWdnaW5nCiAgICAgIF9sb2dCdWZmZXIuYWRkKCdbJHtEYXRlVGltZS5ub3coKS50b0lzbzg2MDFTdHJpbmcoKX1dICRtZXNzYWdlJyk7CgogICAgICAvLyBNYWludGFpbiBidWZmZXIgc2l6ZQogICAgICB3aGlsZSAoX2xvZ0J1ZmZlci5sZW5ndGggPiBjb25maWcubG9nQnVmZmVyU2l6ZSkgewogICAgICAgIF9sb2dCdWZmZXIucmVtb3ZlRmlyc3QoKTsKICAgICAgfQoKICAgICAgX2xvZ2dlci5kKCfwn5OKIExvZ2dlZCBtZXNzYWdlOiAkbWVzc2FnZScpOwogICAgfSBjYXRjaCAoZSkgewogICAgICBfbG9nZ2VyLmUoJ+KdjCBGYWlsZWQgdG8gbG9nIG1lc3NhZ2U6ICRlJyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gc2V0VXNlcklkZW50aWZpZXIoU3RyaW5nIGlkZW50aWZpZXIpIGFzeW5jIHsKICAgIGlmICghX2lzSW5pdGlhbGl6ZWQgfHwgIWNvbmZpZy5lbmFibGVVc2VyTWV0YWRhdGEpIHsKICAgICAgcmV0dXJuOwogICAgfQoKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9jcmFzaGx5dGljcy5zZXRVc2VySWRlbnRpZmllcihpZGVudGlmaWVyKTsKICAgICAgX2xvZ2dlci5kKCfwn5GkIFNldCB1c2VyIGlkZW50aWZpZXI6ICRpZGVudGlmaWVyJyk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2dnZXIuZSgn4p2MIEZhaWxlZCB0byBzZXQgdXNlciBpZGVudGlmaWVyOiAkZScpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHNldFVzZXJNZXRhZGF0YShVc2VyTWV0YWRhdGEgbWV0YWRhdGEpIGFzeW5jIHsKICAgIGlmICghX2lzSW5pdGlhbGl6ZWQgfHwgIWNvbmZpZy5lbmFibGVVc2VyTWV0YWRhdGEpIHsKICAgICAgcmV0dXJuOwogICAgfQoKICAgIHRyeSB7CiAgICAgIGlmIChtZXRhZGF0YS51c2VySWQgIT0gbnVsbCkgewogICAgICAgIGF3YWl0IHNldFVzZXJJZGVudGlmaWVyKG1ldGFkYXRhLnVzZXJJZCEpOwogICAgICB9CgogICAgICAvLyBTZXQgY3VzdG9tIGF0dHJpYnV0ZXMKICAgICAgZmluYWwgYWxsQXR0cmlidXRlcyA9IDxTdHJpbmcsIGR5bmFtaWM+ewogICAgICAgIGlmIChtZXRhZGF0YS5lbWFpbCAhPSBudWxsKSAnZW1haWwnOiBtZXRhZGF0YS5lbWFpbCEsCiAgICAgICAgaWYgKG1ldGFkYXRhLm5hbWUgIT0gbnVsbCkgJ25hbWUnOiBtZXRhZGF0YS5uYW1lISwKICAgICAgICAuLi5tZXRhZGF0YS5jdXN0b21BdHRyaWJ1dGVzLAogICAgICB9OwoKICAgICAgYXdhaXQgc2V0Q3VzdG9tS2V5cyhhbGxBdHRyaWJ1dGVzKTsKICAgICAgX2xvZ2dlci5kKCfwn5GkIFNldCB1c2VyIG1ldGFkYXRhOiAkbWV0YWRhdGEnKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgX2xvZ2dlci5lKCfinYwgRmFpbGVkIHRvIHNldCB1c2VyIG1ldGFkYXRhOiAkZScpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHNldEN1c3RvbUtleShTdHJpbmcga2V5LCBkeW5hbWljIHZhbHVlKSBhc3luYyB7CiAgICBpZiAoIV9pc0luaXRpYWxpemVkKSB7CiAgICAgIHJldHVybjsKICAgIH0KCiAgICB0cnkgewogICAgICBhd2FpdCBfY3Jhc2hseXRpY3Muc2V0Q3VzdG9tS2V5KGtleSwgdmFsdWUpOwogICAgICBfbG9nZ2VyLmQoJ/CflJEgU2V0IGN1c3RvbSBrZXk6ICRrZXkgPSAkdmFsdWUnKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgX2xvZ2dlci5lKCfinYwgRmFpbGVkIHRvIHNldCBjdXN0b20ga2V5OiAkZScpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHNldEN1c3RvbUtleXMoTWFwPFN0cmluZywgZHluYW1pYz4ga2V5cykgYXN5bmMgewogICAgaWYgKCFfaXNJbml0aWFsaXplZCB8fCBrZXlzLmlzRW1wdHkpIHsKICAgICAgcmV0dXJuOwogICAgfQoKICAgIHRyeSB7CiAgICAgIGZvciAoZmluYWwgZW50cnkgaW4ga2V5cy5lbnRyaWVzKSB7CiAgICAgICAgYXdhaXQgX2NyYXNobHl0aWNzLnNldEN1c3RvbUtleShlbnRyeS5rZXksIGVudHJ5LnZhbHVlKTsKICAgICAgfQogICAgICBfbG9nZ2VyLmQoJ/CflJEgU2V0ICR7a2V5cy5sZW5ndGh9IGN1c3RvbSBrZXlzJyk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2dnZXIuZSgn4p2MIEZhaWxlZCB0byBzZXQgY3VzdG9tIGtleXM6ICRlJyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBib29sIGdldCBpc0NyYXNobHl0aWNzQ29sbGVjdGlvbkVuYWJsZWQgewogICAgaWYgKCFfaXNJbml0aWFsaXplZCkgcmV0dXJuIGZhbHNlOwogICAgcmV0dXJuIF9jcmFzaGx5dGljcy5pc0NyYXNobHl0aWNzQ29sbGVjdGlvbkVuYWJsZWQ7CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gc2V0Q3Jhc2hseXRpY3NDb2xsZWN0aW9uRW5hYmxlZChib29sIGVuYWJsZWQpIGFzeW5jIHsKICAgIGlmICghX2lzSW5pdGlhbGl6ZWQpIHsKICAgICAgcmV0dXJuOwogICAgfQoKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9jcmFzaGx5dGljcy5zZXRDcmFzaGx5dGljc0NvbGxlY3Rpb25FbmFibGVkKGVuYWJsZWQpOwogICAgICBfbG9nZ2VyLmQoCiAgICAgICAgJ+Kame+4jyBDcmFzaGx5dGljcyBjb2xsZWN0aW9uICR7ZW5hYmxlZCA/ICdlbmFibGVkJyA6ICdkaXNhYmxlZCd9JywKICAgICAgKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgX2xvZ2dlci5lKCfinYwgRmFpbGVkIHRvIHNldCBjcmFzaGx5dGljcyBjb2xsZWN0aW9uIGVuYWJsZWQ6ICRlJyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gc2VuZFVuc2VudFJlcG9ydHMoKSBhc3luYyB7CiAgICBpZiAoIV9pc0luaXRpYWxpemVkKSB7CiAgICAgIHJldHVybjsKICAgIH0KCiAgICB0cnkgewogICAgICBhd2FpdCBfY3Jhc2hseXRpY3Muc2VuZFVuc2VudFJlcG9ydHMoKTsKICAgICAgX2xvZ2dlci5kKCfwn5OkIFNlbnQgdW5zZW50IGNyYXNoIHJlcG9ydHMnKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgX2xvZ2dlci5lKCfinYwgRmFpbGVkIHRvIHNlbmQgdW5zZW50IHJlcG9ydHM6ICRlJyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gZGVsZXRlVW5zZW50UmVwb3J0cygpIGFzeW5jIHsKICAgIGlmICghX2lzSW5pdGlhbGl6ZWQpIHsKICAgICAgcmV0dXJuOwogICAgfQoKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9jcmFzaGx5dGljcy5kZWxldGVVbnNlbnRSZXBvcnRzKCk7CiAgICAgIF9sb2dnZXIuZCgn8J+Xke+4jyBEZWxldGVkIHVuc2VudCBjcmFzaCByZXBvcnRzJyk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2dnZXIuZSgn4p2MIEZhaWxlZCB0byBkZWxldGUgdW5zZW50IHJlcG9ydHM6ICRlJyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8Ym9vbD4gY2hlY2tGb3JVbnNlbnRSZXBvcnRzKCkgYXN5bmMgewogICAgaWYgKCFfaXNJbml0aWFsaXplZCkgewogICAgICByZXR1cm4gZmFsc2U7CiAgICB9CgogICAgdHJ5IHsKICAgICAgZmluYWwgaGFzVW5zZW50ID0gYXdhaXQgX2NyYXNobHl0aWNzLmNoZWNrRm9yVW5zZW50UmVwb3J0cygpOwogICAgICBfbG9nZ2VyLmQoJ/Cfk4sgSGFzIHVuc2VudCByZXBvcnRzOiAkaGFzVW5zZW50Jyk7CiAgICAgIHJldHVybiBoYXNVbnNlbnQ7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2dnZXIuZSgn4p2MIEZhaWxlZCB0byBjaGVjayBmb3IgdW5zZW50IHJlcG9ydHM6ICRlJyk7CiAgICAgIHJldHVybiBmYWxzZTsKICAgIH0KICB9CgogIC8vLyBHZXQgYnVmZmVyZWQgbG9nIG1lc3NhZ2VzIGZvciBkZWJ1Z2dpbmcKICBMaXN0PFN0cmluZz4gZ2V0IGJ1ZmZlcmVkTG9ncyA9PiBfbG9nQnVmZmVyLnRvTGlzdCgpOwoKICAvLy8gQ2xlYXIgdGhlIGxvZyBidWZmZXIKICB2b2lkIGNsZWFyTG9nQnVmZmVyKCkgewogICAgX2xvZ0J1ZmZlci5jbGVhcigpOwogICAgX2xvZ2dlci5kKCfwn6e5IENsZWFyZWQgbG9nIGJ1ZmZlcicpOwogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBkaXNwb3NlKCkgewogICAgX2xvZ0J1ZmZlci5jbGVhcigpOwogICAgX2lzSW5pdGlhbGl6ZWQgPSBmYWxzZTsKICAgIF9sb2dnZXIuZCgn8J+nuSBGaXJlYmFzZSBDcmFzaGx5dGljcyBjbGllbnQgZGlzcG9zZWQnKTsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/client/index.dart", + "data": + "ZXhwb3J0ICdjcmFzaGx5dGljc19jbGllbnQuZGFydCc7CmV4cG9ydCAnZmlyZWJhc2VfY3Jhc2hseXRpY3NfY2xpZW50LmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/config/crashlytics_config.dart", + "data": + "Y2xhc3MgQ3Jhc2hseXRpY3NDb25maWcgewogIGNvbnN0IENyYXNobHl0aWNzQ29uZmlnKHsKICAgIHRoaXMuZW5hYmxlSW5EZWJ1Z01vZGUgPSBmYWxzZSwKICAgIHRoaXMuZW5hYmxlQXV0b21hdGljRGF0YUNvbGxlY3Rpb24gPSB0cnVlLAogICAgdGhpcy5lbmFibGVDdXN0b21Mb2dzID0gdHJ1ZSwKICAgIHRoaXMubG9nQnVmZmVyU2l6ZSA9IDEwMCwKICAgIHRoaXMuZW5hYmxlVXNlck1ldGFkYXRhID0gdHJ1ZSwKICAgIHRoaXMuY3VzdG9tS2V5cyA9IGNvbnN0IHt9LAogIH0pOwoKICAvLy8gRW5hYmxlIGNyYXNobHl0aWNzIGluIGRlYnVnIG1vZGUgKHR5cGljYWxseSBkaXNhYmxlZCBmb3IgZGV2ZWxvcG1lbnQpCiAgZmluYWwgYm9vbCBlbmFibGVJbkRlYnVnTW9kZTsKCiAgLy8vIEVuYWJsZSBhdXRvbWF0aWMgZGF0YSBjb2xsZWN0aW9uCiAgZmluYWwgYm9vbCBlbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbjsKCiAgLy8vIEVuYWJsZSBjdXN0b20gbG9nZ2luZwogIGZpbmFsIGJvb2wgZW5hYmxlQ3VzdG9tTG9nczsKCiAgLy8vIE1heGltdW0gbnVtYmVyIG9mIGxvZyBtZXNzYWdlcyB0byBidWZmZXIKICBmaW5hbCBpbnQgbG9nQnVmZmVyU2l6ZTsKCiAgLy8vIEVuYWJsZSB1c2VyIG1ldGFkYXRhIGNvbGxlY3Rpb24KICBmaW5hbCBib29sIGVuYWJsZVVzZXJNZXRhZGF0YTsKCiAgLy8vIERlZmF1bHQgY3VzdG9tIGtleXMgdG8gc2V0IG9uIGluaXRpYWxpemF0aW9uCiAgZmluYWwgTWFwPFN0cmluZywgZHluYW1pYz4gY3VzdG9tS2V5czsKCiAgQ3Jhc2hseXRpY3NDb25maWcgY29weVdpdGgoewogICAgYm9vbD8gZW5hYmxlSW5EZWJ1Z01vZGUsCiAgICBib29sPyBlbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbiwKICAgIGJvb2w/IGVuYWJsZUN1c3RvbUxvZ3MsCiAgICBpbnQ/IGxvZ0J1ZmZlclNpemUsCiAgICBib29sPyBlbmFibGVVc2VyTWV0YWRhdGEsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gY3VzdG9tS2V5cywKICB9KSB7CiAgICByZXR1cm4gQ3Jhc2hseXRpY3NDb25maWcoCiAgICAgIGVuYWJsZUluRGVidWdNb2RlOiBlbmFibGVJbkRlYnVnTW9kZSA/PyB0aGlzLmVuYWJsZUluRGVidWdNb2RlLAogICAgICBlbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbjoKICAgICAgICAgIGVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uID8/IHRoaXMuZW5hYmxlQXV0b21hdGljRGF0YUNvbGxlY3Rpb24sCiAgICAgIGVuYWJsZUN1c3RvbUxvZ3M6IGVuYWJsZUN1c3RvbUxvZ3MgPz8gdGhpcy5lbmFibGVDdXN0b21Mb2dzLAogICAgICBsb2dCdWZmZXJTaXplOiBsb2dCdWZmZXJTaXplID8/IHRoaXMubG9nQnVmZmVyU2l6ZSwKICAgICAgZW5hYmxlVXNlck1ldGFkYXRhOiBlbmFibGVVc2VyTWV0YWRhdGEgPz8gdGhpcy5lbmFibGVVc2VyTWV0YWRhdGEsCiAgICAgIGN1c3RvbUtleXM6IGN1c3RvbUtleXMgPz8gdGhpcy5jdXN0b21LZXlzLAogICAgKTsKICB9CgogIE1hcDxTdHJpbmcsIGR5bmFtaWM+IHRvTWFwKCkgewogICAgcmV0dXJuIHsKICAgICAgJ2VuYWJsZUluRGVidWdNb2RlJzogZW5hYmxlSW5EZWJ1Z01vZGUsCiAgICAgICdlbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbic6IGVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uLAogICAgICAnZW5hYmxlQ3VzdG9tTG9ncyc6IGVuYWJsZUN1c3RvbUxvZ3MsCiAgICAgICdsb2dCdWZmZXJTaXplJzogbG9nQnVmZmVyU2l6ZSwKICAgICAgJ2VuYWJsZVVzZXJNZXRhZGF0YSc6IGVuYWJsZVVzZXJNZXRhZGF0YSwKICAgICAgJ2N1c3RvbUtleXMnOiBjdXN0b21LZXlzLAogICAgfTsKICB9CgogIEBvdmVycmlkZQogIFN0cmluZyB0b1N0cmluZygpIHsKICAgIHJldHVybiAnQ3Jhc2hseXRpY3NDb25maWd7ZW5hYmxlSW5EZWJ1Z01vZGU6ICRlbmFibGVJbkRlYnVnTW9kZSwgZW5hYmxlQXV0b21hdGljRGF0YUNvbGxlY3Rpb246ICRlbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbiwgZW5hYmxlQ3VzdG9tTG9nczogJGVuYWJsZUN1c3RvbUxvZ3MsIGxvZ0J1ZmZlclNpemU6ICRsb2dCdWZmZXJTaXplLCBlbmFibGVVc2VyTWV0YWRhdGE6ICRlbmFibGVVc2VyTWV0YWRhdGEsIGN1c3RvbUtleXM6ICRjdXN0b21LZXlzfSc7CiAgfQoKICBAb3ZlcnJpZGUKICBib29sIG9wZXJhdG9yID09KE9iamVjdCBvdGhlcikgPT4KICAgICAgaWRlbnRpY2FsKHRoaXMsIG90aGVyKSB8fAogICAgICBvdGhlciBpcyBDcmFzaGx5dGljc0NvbmZpZyAmJgogICAgICAgICAgcnVudGltZVR5cGUgPT0gb3RoZXIucnVudGltZVR5cGUgJiYKICAgICAgICAgIGVuYWJsZUluRGVidWdNb2RlID09IG90aGVyLmVuYWJsZUluRGVidWdNb2RlICYmCiAgICAgICAgICBlbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbiA9PQogICAgICAgICAgICAgIG90aGVyLmVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uICYmCiAgICAgICAgICBlbmFibGVDdXN0b21Mb2dzID09IG90aGVyLmVuYWJsZUN1c3RvbUxvZ3MgJiYKICAgICAgICAgIGxvZ0J1ZmZlclNpemUgPT0gb3RoZXIubG9nQnVmZmVyU2l6ZSAmJgogICAgICAgICAgZW5hYmxlVXNlck1ldGFkYXRhID09IG90aGVyLmVuYWJsZVVzZXJNZXRhZGF0YSAmJgogICAgICAgICAgX21hcEVxdWFscyhjdXN0b21LZXlzLCBvdGhlci5jdXN0b21LZXlzKTsKCiAgQG92ZXJyaWRlCiAgaW50IGdldCBoYXNoQ29kZSA9PgogICAgICBlbmFibGVJbkRlYnVnTW9kZS5oYXNoQ29kZSBeCiAgICAgIGVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uLmhhc2hDb2RlIF4KICAgICAgZW5hYmxlQ3VzdG9tTG9ncy5oYXNoQ29kZSBeCiAgICAgIGxvZ0J1ZmZlclNpemUuaGFzaENvZGUgXgogICAgICBlbmFibGVVc2VyTWV0YWRhdGEuaGFzaENvZGUgXgogICAgICBjdXN0b21LZXlzLmhhc2hDb2RlOwoKICBib29sIF9tYXBFcXVhbHMoTWFwPFN0cmluZywgZHluYW1pYz4gYSwgTWFwPFN0cmluZywgZHluYW1pYz4gYikgewogICAgaWYgKGEubGVuZ3RoICE9IGIubGVuZ3RoKSByZXR1cm4gZmFsc2U7CiAgICBmb3IgKGZpbmFsIGtleSBpbiBhLmtleXMpIHsKICAgICAgaWYgKCFiLmNvbnRhaW5zS2V5KGtleSkgfHwgYVtrZXldICE9IGJba2V5XSkgcmV0dXJuIGZhbHNlOwogICAgfQogICAgcmV0dXJuIHRydWU7CiAgfQp9CgpjbGFzcyBEZWZhdWx0Q3Jhc2hseXRpY3NDb25maWcgZXh0ZW5kcyBDcmFzaGx5dGljc0NvbmZpZyB7CiAgY29uc3QgRGVmYXVsdENyYXNobHl0aWNzQ29uZmlnKHsKICAgIHN1cGVyLmVuYWJsZUluRGVidWdNb2RlID0gZmFsc2UsCiAgICBzdXBlci5lbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbiA9IHRydWUsCiAgICBzdXBlci5lbmFibGVDdXN0b21Mb2dzID0gdHJ1ZSwKICAgIHN1cGVyLmxvZ0J1ZmZlclNpemUgPSAxMDAsCiAgICBzdXBlci5lbmFibGVVc2VyTWV0YWRhdGEgPSB0cnVlLAogICAgc3VwZXIuY3VzdG9tS2V5cyA9IGNvbnN0IHt9LAogIH0pOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/config/index.dart", + "data": "ZXhwb3J0ICdjcmFzaGx5dGljc19jb25maWcuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/crash_report.dart", + "data": + "Y2xhc3MgQ3Jhc2hSZXBvcnQgewogIGNvbnN0IENyYXNoUmVwb3J0KHsKICAgIHJlcXVpcmVkIHRoaXMuZXhjZXB0aW9uLAogICAgdGhpcy5zdGFja1RyYWNlLAogICAgdGhpcy5mYXRhbCA9IGZhbHNlLAogICAgdGhpcy50aW1lc3RhbXAsCiAgICB0aGlzLmN1c3RvbUtleXMgPSBjb25zdCB7fSwKICAgIHRoaXMubG9ncyA9IGNvbnN0IFtdLAogICAgdGhpcy51c2VyTWV0YWRhdGEsCiAgfSk7CgogIGZpbmFsIGR5bmFtaWMgZXhjZXB0aW9uOwogIGZpbmFsIFN0YWNrVHJhY2U/IHN0YWNrVHJhY2U7CiAgZmluYWwgYm9vbCBmYXRhbDsKICBmaW5hbCBEYXRlVGltZT8gdGltZXN0YW1wOwogIGZpbmFsIE1hcDxTdHJpbmcsIGR5bmFtaWM+IGN1c3RvbUtleXM7CiAgZmluYWwgTGlzdDxTdHJpbmc+IGxvZ3M7CiAgZmluYWwgTWFwPFN0cmluZywgZHluYW1pYz4/IHVzZXJNZXRhZGF0YTsKCiAgQ3Jhc2hSZXBvcnQgY29weVdpdGgoewogICAgZHluYW1pYyBleGNlcHRpb24sCiAgICBTdGFja1RyYWNlPyBzdGFja1RyYWNlLAogICAgYm9vbD8gZmF0YWwsCiAgICBEYXRlVGltZT8gdGltZXN0YW1wLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IGN1c3RvbUtleXMsCiAgICBMaXN0PFN0cmluZz4/IGxvZ3MsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gdXNlck1ldGFkYXRhLAogIH0pIHsKICAgIHJldHVybiBDcmFzaFJlcG9ydCgKICAgICAgZXhjZXB0aW9uOiBleGNlcHRpb24gPz8gdGhpcy5leGNlcHRpb24sCiAgICAgIHN0YWNrVHJhY2U6IHN0YWNrVHJhY2UgPz8gdGhpcy5zdGFja1RyYWNlLAogICAgICBmYXRhbDogZmF0YWwgPz8gdGhpcy5mYXRhbCwKICAgICAgdGltZXN0YW1wOiB0aW1lc3RhbXAgPz8gdGhpcy50aW1lc3RhbXAsCiAgICAgIGN1c3RvbUtleXM6IGN1c3RvbUtleXMgPz8gdGhpcy5jdXN0b21LZXlzLAogICAgICBsb2dzOiBsb2dzID8/IHRoaXMubG9ncywKICAgICAgdXNlck1ldGFkYXRhOiB1c2VyTWV0YWRhdGEgPz8gdGhpcy51c2VyTWV0YWRhdGEsCiAgICApOwogIH0KCiAgTWFwPFN0cmluZywgZHluYW1pYz4gdG9NYXAoKSB7CiAgICByZXR1cm4gewogICAgICAnZXhjZXB0aW9uJzogZXhjZXB0aW9uLnRvU3RyaW5nKCksCiAgICAgICdzdGFja1RyYWNlJzogc3RhY2tUcmFjZT8udG9TdHJpbmcoKSwKICAgICAgJ2ZhdGFsJzogZmF0YWwsCiAgICAgICd0aW1lc3RhbXAnOiB0aW1lc3RhbXA/LnRvSXNvODYwMVN0cmluZygpLAogICAgICAnY3VzdG9tS2V5cyc6IGN1c3RvbUtleXMsCiAgICAgICdsb2dzJzogbG9ncywKICAgICAgJ3VzZXJNZXRhZGF0YSc6IHVzZXJNZXRhZGF0YSwKICAgIH07CiAgfQoKICBAb3ZlcnJpZGUKICBTdHJpbmcgdG9TdHJpbmcoKSB7CiAgICByZXR1cm4gJ0NyYXNoUmVwb3J0e2V4Y2VwdGlvbjogJGV4Y2VwdGlvbiwgZmF0YWw6ICRmYXRhbCwgdGltZXN0YW1wOiAkdGltZXN0YW1wfSc7CiAgfQoKICBAb3ZlcnJpZGUKICBib29sIG9wZXJhdG9yID09KE9iamVjdCBvdGhlcikgPT4KICAgICAgaWRlbnRpY2FsKHRoaXMsIG90aGVyKSB8fAogICAgICBvdGhlciBpcyBDcmFzaFJlcG9ydCAmJgogICAgICAgICAgcnVudGltZVR5cGUgPT0gb3RoZXIucnVudGltZVR5cGUgJiYKICAgICAgICAgIGV4Y2VwdGlvbi50b1N0cmluZygpID09IG90aGVyLmV4Y2VwdGlvbi50b1N0cmluZygpICYmCiAgICAgICAgICBzdGFja1RyYWNlLnRvU3RyaW5nKCkgPT0gb3RoZXIuc3RhY2tUcmFjZS50b1N0cmluZygpICYmCiAgICAgICAgICBmYXRhbCA9PSBvdGhlci5mYXRhbCAmJgogICAgICAgICAgdGltZXN0YW1wID09IG90aGVyLnRpbWVzdGFtcDsKCiAgQG92ZXJyaWRlCiAgaW50IGdldCBoYXNoQ29kZSA9PgogICAgICBleGNlcHRpb24uaGFzaENvZGUgXgogICAgICBzdGFja1RyYWNlLmhhc2hDb2RlIF4KICAgICAgZmF0YWwuaGFzaENvZGUgXgogICAgICB0aW1lc3RhbXAuaGFzaENvZGU7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/index.dart", + "data": + "ZXhwb3J0ICdjcmFzaF9yZXBvcnQuZGFydCc7CmV4cG9ydCAndXNlcl9tZXRhZGF0YS5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/lib/src/models/user_metadata.dart", + "data": + "Y2xhc3MgVXNlck1ldGFkYXRhIHsKICBjb25zdCBVc2VyTWV0YWRhdGEoewogICAgdGhpcy51c2VySWQsCiAgICB0aGlzLmVtYWlsLAogICAgdGhpcy5uYW1lLAogICAgdGhpcy5jdXN0b21BdHRyaWJ1dGVzID0gY29uc3Qge30sCiAgfSk7CgogIGZpbmFsIFN0cmluZz8gdXNlcklkOwogIGZpbmFsIFN0cmluZz8gZW1haWw7CiAgZmluYWwgU3RyaW5nPyBuYW1lOwogIGZpbmFsIE1hcDxTdHJpbmcsIGR5bmFtaWM+IGN1c3RvbUF0dHJpYnV0ZXM7CgogIFVzZXJNZXRhZGF0YSBjb3B5V2l0aCh7CiAgICBTdHJpbmc/IHVzZXJJZCwKICAgIFN0cmluZz8gZW1haWwsCiAgICBTdHJpbmc/IG5hbWUsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gY3VzdG9tQXR0cmlidXRlcywKICB9KSB7CiAgICByZXR1cm4gVXNlck1ldGFkYXRhKAogICAgICB1c2VySWQ6IHVzZXJJZCA/PyB0aGlzLnVzZXJJZCwKICAgICAgZW1haWw6IGVtYWlsID8/IHRoaXMuZW1haWwsCiAgICAgIG5hbWU6IG5hbWUgPz8gdGhpcy5uYW1lLAogICAgICBjdXN0b21BdHRyaWJ1dGVzOiBjdXN0b21BdHRyaWJ1dGVzID8/IHRoaXMuY3VzdG9tQXR0cmlidXRlcywKICAgICk7CiAgfQoKICBNYXA8U3RyaW5nLCBkeW5hbWljPiB0b01hcCgpIHsKICAgIHJldHVybiB7CiAgICAgICd1c2VySWQnOiB1c2VySWQsCiAgICAgICdlbWFpbCc6IGVtYWlsLAogICAgICAnbmFtZSc6IG5hbWUsCiAgICAgICdjdXN0b21BdHRyaWJ1dGVzJzogY3VzdG9tQXR0cmlidXRlcywKICAgIH07CiAgfQoKICBAb3ZlcnJpZGUKICBTdHJpbmcgdG9TdHJpbmcoKSB7CiAgICByZXR1cm4gJ1VzZXJNZXRhZGF0YXt1c2VySWQ6ICR1c2VySWQsIGVtYWlsOiAkZW1haWwsIG5hbWU6ICRuYW1lLCBjdXN0b21BdHRyaWJ1dGVzOiAkY3VzdG9tQXR0cmlidXRlc30nOwogIH0KCiAgQG92ZXJyaWRlCiAgYm9vbCBvcGVyYXRvciA9PShPYmplY3Qgb3RoZXIpID0+CiAgICAgIGlkZW50aWNhbCh0aGlzLCBvdGhlcikgfHwKICAgICAgb3RoZXIgaXMgVXNlck1ldGFkYXRhICYmCiAgICAgICAgICBydW50aW1lVHlwZSA9PSBvdGhlci5ydW50aW1lVHlwZSAmJgogICAgICAgICAgdXNlcklkID09IG90aGVyLnVzZXJJZCAmJgogICAgICAgICAgZW1haWwgPT0gb3RoZXIuZW1haWwgJiYKICAgICAgICAgIG5hbWUgPT0gb3RoZXIubmFtZSAmJgogICAgICAgICAgX21hcEVxdWFscyhjdXN0b21BdHRyaWJ1dGVzLCBvdGhlci5jdXN0b21BdHRyaWJ1dGVzKTsKCiAgQG92ZXJyaWRlCiAgaW50IGdldCBoYXNoQ29kZSA9PgogICAgICB1c2VySWQuaGFzaENvZGUgXgogICAgICBlbWFpbC5oYXNoQ29kZSBeCiAgICAgIG5hbWUuaGFzaENvZGUgXgogICAgICBjdXN0b21BdHRyaWJ1dGVzLmhhc2hDb2RlOwoKICBib29sIF9tYXBFcXVhbHMoTWFwPFN0cmluZywgZHluYW1pYz4gYSwgTWFwPFN0cmluZywgZHluYW1pYz4gYikgewogICAgaWYgKGEubGVuZ3RoICE9IGIubGVuZ3RoKSByZXR1cm4gZmFsc2U7CiAgICBmb3IgKGZpbmFsIGtleSBpbiBhLmtleXMpIHsKICAgICAgaWYgKCFiLmNvbnRhaW5zS2V5KGtleSkgfHwgYVtrZXldICE9IGJba2V5XSkgcmV0dXJuIGZhbHNlOwogICAgfQogICAgcmV0dXJuIHRydWU7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml", + "data": + "bmFtZTogY3Jhc2hseXRpY3MKZGVzY3JpcHRpb246ICJDcmFzaCBhbmQgbm9uLWZhdGFsIGVycm9yIHJlcG9ydGluZyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBmaXJlYmFzZV9jb3JlOiBeMy4xNC4wCiAgZmlyZWJhc2VfY3Jhc2hseXRpY3M6IF40LjEuNAogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmZsdXR0ZXI6Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/README.md", + "data": + "IyBDcmFzaGx5dGljcwoKRmlyZWJhc2UgQ3Jhc2hseXRpY3MgaW50ZWdyYXRpb24gcGFja2FnZSBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0gLSBwcm92aWRpbmcgY29tcHJlaGVuc2l2ZSBjcmFzaCByZXBvcnRpbmcgYW5kIGVycm9yIHRyYWNraW5nIGNhcGFiaWxpdGllcy4KClshW25vbnN0b3BfY2xpXShodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlL3N0YXJ0ZWQlMjB3aXRoLW5vbnN0b3BfY2xpLTE2NkM0RS5zdmc/c3R5bGU9ZmxhdC1zcXVhcmUpXShodHRwczovL3B1Yi5kZXYvcGFja2FnZXMvbm9uc3RvcF9jbGkpClshW21lbG9zXShodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlL21haW50YWluZWQlMjB3aXRoLW1lbG9zLWY3MDBmZi5zdmc/c3R5bGU9ZmxhdC1zcXVhcmUpXShodHRwczovL2dpdGh1Yi5jb20vaW52ZXJ0YXNlL21lbG9zKQoKIyMgRmVhdHVyZXMg4pyoCgotIPCflKUgKipGaXJlYmFzZSBDcmFzaGx5dGljcyBJbnRlZ3JhdGlvbioqIC0gQ29tcGxldGUgRmlyZWJhc2UgQ3Jhc2hseXRpY3MgaW1wbGVtZW50YXRpb24KLSDwn5qAICoqQXV0b21hdGljIEVycm9yIEhhbmRsaW5nKiogLSBDYXRjaGVzIEZsdXR0ZXIgZnJhbWV3b3JrIGVycm9ycyBhdXRvbWF0aWNhbGx5Ci0g8J+RpCAqKlVzZXIgQ29udGV4dCoqIC0gVHJhY2sgY3Jhc2hlcyB3aXRoIHVzZXIgaW5mb3JtYXRpb24gYW5kIG1ldGFkYXRhCi0g8J+UkSAqKkN1c3RvbSBBdHRyaWJ1dGVzKiogLSBBZGQgY3VzdG9tIGtleS12YWx1ZSBwYWlycyBmb3IgZGVidWdnaW5nCi0g8J+TiiAqKkN1c3RvbSBMb2dnaW5nKiogLSBMb2cgY3VzdG9tIG1lc3NhZ2VzIGZvciBjcmFzaCBhbmFseXNpcwotIOKame+4jyAqKkNvbmZpZ3VyYWJsZSoqIC0gRmxleGlibGUgY29uZmlndXJhdGlvbiBmb3IgZGlmZmVyZW50IGVudmlyb25tZW50cwotIPCfp6kgKipESSBJbnRlZ3JhdGlvbioqIC0gU2VhbWxlc3MgaW50ZWdyYXRpb24gd2l0aCBkZXBlbmRlbmN5IGluamVjdGlvbgotIPCfm6HvuI8gKipUeXBlIFNhZmUqKiAtIEZ1bGwgVHlwZVNjcmlwdC1zdHlsZSB0eXBlIHNhZmV0eSB3aXRoIERhcnQKCiMjIEluc3RhbGxhdGlvbiDwn5K7CgpBZGQgdG8geW91ciBgcHVic3BlYy55YW1sYDoKCmBgYHlhbWwKZGVwZW5kZW5jaWVzOgogIGNyYXNobHl0aWNzOgogICAgcGF0aDogLi4vcGFja2FnZXMvY3Jhc2hseXRpY3MKYGBgCgojIyBRdWljayBTdGFydCDwn5qACgojIyMgMS4gSW5pdGlhbGl6ZSBpbiB5b3VyIGFwcAoKYGBgZGFydAppbXBvcnQgJ3BhY2thZ2U6Y3Jhc2hseXRpY3MvY3Jhc2hseXRpY3MuZGFydCc7Cgp2b2lkIG1haW4oKSBhc3luYyB7CiAgV2lkZ2V0c0ZsdXR0ZXJCaW5kaW5nLmVuc3VyZUluaXRpYWxpemVkKCk7CiAgCiAgLy8gSW5pdGlhbGl6ZSBjcmFzaGx5dGljcyB3aXRoIGRlZmF1bHQgY29uZmlnCiAgYXdhaXQgY3Jhc2hseXRpY3MuaW5pdCgpOwogIAogIHJ1bkFwcChNeUFwcCgpKTsKfQpgYGAKCiMjIyAyLiBVc2UgdGhlIHNlcnZpY2UKCmBgYGRhcnQKaW1wb3J0ICdwYWNrYWdlOmNyYXNobHl0aWNzL2NyYXNobHl0aWNzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CgpjbGFzcyBNeVdpZGdldCBleHRlbmRzIFN0YXRlZnVsV2lkZ2V0IHsKICBAb3ZlcnJpZGUKICBfTXlXaWRnZXRTdGF0ZSBjcmVhdGVTdGF0ZSgpID0+IF9NeVdpZGdldFN0YXRlKCk7Cn0KCmNsYXNzIF9NeVdpZGdldFN0YXRlIGV4dGVuZHMgU3RhdGU8TXlXaWRnZXQ+IHsKICBsYXRlIGZpbmFsIENyYXNobHl0aWNzU2VydmljZSBfY3Jhc2hseXRpY3M7CgogIEBvdmVycmlkZQogIHZvaWQgaW5pdFN0YXRlKCkgewogICAgc3VwZXIuaW5pdFN0YXRlKCk7CiAgICBfY3Jhc2hseXRpY3MgPSBkaS5nZXQ8Q3Jhc2hseXRpY3NTZXJ2aWNlPigpOwogIH0KCiAgdm9pZCBfcmVwb3J0RXJyb3IoKSBhc3luYyB7CiAgICB0cnkgewogICAgICAvLyBTb21lIHJpc2t5IG9wZXJhdGlvbgogICAgICB0aHJvdyBFeGNlcHRpb24oJ1NvbWV0aGluZyB3ZW50IHdyb25nIScpOwogICAgfSBjYXRjaCAoZXJyb3IsIHN0YWNrVHJhY2UpIHsKICAgICAgLy8gUmVwb3J0IHRoZSBlcnJvcgogICAgICBhd2FpdCBfY3Jhc2hseXRpY3MucmVwb3J0RXJyb3IoCiAgICAgICAgZXJyb3IsIAogICAgICAgIHN0YWNrVHJhY2U6IHN0YWNrVHJhY2UsCiAgICAgICAgY29udGV4dDogeydmZWF0dXJlJzogJ3VzZXJfYWN0aW9uJywgJ3NjcmVlbic6ICdob21lJ30sCiAgICAgICk7CiAgICB9CiAgfQp9CmBgYAoKIyMgQ29uZmlndXJhdGlvbiDimpnvuI8KCiMjIyBCYXNpYyBDb25maWd1cmF0aW9uCgpgYGBkYXJ0CmltcG9ydCAncGFja2FnZTpjcmFzaGx5dGljcy9jcmFzaGx5dGljcy5kYXJ0JzsKCmF3YWl0IGNyYXNobHl0aWNzLmluaXQoCiAgY29uZmlnOiBDcmFzaGx5dGljc0NvbmZpZygKICAgIGVuYWJsZUluRGVidWdNb2RlOiBmYWxzZSwKICAgIGVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uOiB0cnVlLAogICAgZW5hYmxlQ3VzdG9tTG9nczogdHJ1ZSwKICAgIGxvZ0J1ZmZlclNpemU6IDEwMCwKICAgIGVuYWJsZVVzZXJNZXRhZGF0YTogdHJ1ZSwKICAgIGN1c3RvbUtleXM6IHsKICAgICAgJ2FwcF92ZXJzaW9uJzogJzEuMC4wJywKICAgICAgJ2Vudmlyb25tZW50JzogJ3Byb2R1Y3Rpb24nLAogICAgfSwKICApLAopOwpgYGAKCiMjIyBBZHZhbmNlZCBDb25maWd1cmF0aW9uCgpgYGBkYXJ0Ci8vIEN1c3RvbSBjb25maWd1cmF0aW9uIGZvciBkaWZmZXJlbnQgZW52aXJvbm1lbnRzCmZpbmFsIGNvbmZpZyA9IGtEZWJ1Z01vZGUgCiAgPyBDcmFzaGx5dGljc0NvbmZpZygKICAgICAgZW5hYmxlSW5EZWJ1Z01vZGU6IHRydWUsCiAgICAgIGVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uOiBmYWxzZSwKICAgICAgY3VzdG9tS2V5czogeydlbnZpcm9ubWVudCc6ICdkZWJ1Zyd9LAogICAgKQogIDogQ3Jhc2hseXRpY3NDb25maWcoCiAgICAgIGVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uOiB0cnVlLAogICAgICBsb2dCdWZmZXJTaXplOiAyMDAsCiAgICAgIGN1c3RvbUtleXM6IHsnZW52aXJvbm1lbnQnOiAncHJvZHVjdGlvbid9LAogICAgKTsKCmF3YWl0IGNyYXNobHl0aWNzLmluaXQoY29uZmlnOiBjb25maWcpOwpgYGAKCiMjIFVzYWdlIEV4YW1wbGVzIPCfk50KCiMjIyBFcnJvciBSZXBvcnRpbmcKCmBgYGRhcnQKZmluYWwgY3Jhc2hseXRpY3MgPSBkaS5nZXQ8Q3Jhc2hseXRpY3NTZXJ2aWNlPigpOwoKLy8gUmVwb3J0IGEgbm9uLWZhdGFsIGVycm9yCmF3YWl0IGNyYXNobHl0aWNzLnJlcG9ydEVycm9yKAogIEV4Y2VwdGlvbignTmV0d29yayB0aW1lb3V0JyksCiAgc3RhY2tUcmFjZTogU3RhY2tUcmFjZS5jdXJyZW50LAogIGNvbnRleHQ6IHsnZW5kcG9pbnQnOiAnL2FwaS91c2VycycsICd0aW1lb3V0JzogJzMwcyd9LAopOwoKLy8gUmVwb3J0IGEgZmF0YWwgZXJyb3IKYXdhaXQgY3Jhc2hseXRpY3MucmVwb3J0RXJyb3IoCiAgZXJyb3IsCiAgc3RhY2tUcmFjZTogc3RhY2tUcmFjZSwKICBmYXRhbDogdHJ1ZSwKICB1c2VySWQ6ICd1c2VyMTIzJywKKTsKYGBgCgojIyMgVXNlciBDb250ZXh0CgpgYGBkYXJ0Ci8vIFNldCB1c2VyIGluZm9ybWF0aW9uCmF3YWl0IGNyYXNobHl0aWNzLnNldFVzZXIoCiAgdXNlcklkOiAndXNlcjEyMycsCiAgZW1haWw6ICd1c2VyQGV4YW1wbGUuY29tJywKICBuYW1lOiAnSm9obiBEb2UnLAogIGN1c3RvbUF0dHJpYnV0ZXM6IHsKICAgICdzdWJzY3JpcHRpb24nOiAncHJlbWl1bScsCiAgICAnbGFzdF9sb2dpbic6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogIH0sCik7CgovLyBDbGVhciB1c2VyIGluZm9ybWF0aW9uIChlLmcuLCBvbiBsb2dvdXQpCmF3YWl0IGNyYXNobHl0aWNzLmNsZWFyVXNlcigpOwpgYGAKCiMjIyBDdXN0b20gTG9nZ2luZwoKYGBgZGFydAovLyBMb2cgY3VzdG9tIG1lc3NhZ2VzCmF3YWl0IGNyYXNobHl0aWNzLmxvZ01lc3NhZ2UoJ1VzZXIgY29tcGxldGVkIG9uYm9hcmRpbmcnKTsKYXdhaXQgY3Jhc2hseXRpY3MubG9nTWVzc2FnZSgnQVBJIGNhbGwgc3RhcnRlZDogL3VzZXJzL3Byb2ZpbGUnKTsKCi8vIFNldCBjdXN0b20gYXR0cmlidXRlcwphd2FpdCBjcmFzaGx5dGljcy5zZXRDdXN0b21BdHRyaWJ1dGVzKHsKICAnZmVhdHVyZV9mbGFnX2EnOiB0cnVlLAogICdhcGlfdmVyc2lvbic6ICd2MicsCiAgJ2RldmljZV90eXBlJzogJ21vYmlsZScsCn0pOwoKLy8gU2V0IGluZGl2aWR1YWwgY3VzdG9tIGF0dHJpYnV0ZQphd2FpdCBjcmFzaGx5dGljcy5zZXRDdXN0b21BdHRyaWJ1dGUoJ2N1cnJlbnRfc2NyZWVuJywgJ3Byb2ZpbGUnKTsKYGBgCgojIyMgTWFudWFsIENyYXNoIFJlcG9ydGluZwoKYGBgZGFydAovLyBVc2UgdGhlIGNsaWVudCBkaXJlY3RseSBmb3IgYWR2YW5jZWQgc2NlbmFyaW9zCmZpbmFsIGNsaWVudCA9IGRpLmdldDxDcmFzaGx5dGljc0NsaWVudD4oKTsKCmF3YWl0IGNsaWVudC5yZWNvcmRFcnJvcigKICBDdXN0b21FeGNlcHRpb24oJ0RhdGFiYXNlIGNvbm5lY3Rpb24gZmFpbGVkJyksCiAgU3RhY2tUcmFjZS5jdXJyZW50LAogIGZhdGFsOiBmYWxzZSwKICBpbmZvcm1hdGlvbjogWydEYXRhYmFzZSBob3N0OiBkYi5leGFtcGxlLmNvbScsICdDb25uZWN0aW9uIHRpbWVvdXQ6IDMwcyddLAopOwpgYGAKCiMjIEFQSSBSZWZlcmVuY2Ug8J+TmgoKIyMjIENyYXNobHl0aWNzU2VydmljZQoKTWFpbiBzZXJ2aWNlIGNsYXNzIGZvciBjcmFzaCByZXBvcnRpbmc6Cgp8IE1ldGhvZCB8IERlc2NyaXB0aW9uIHwKfC0tLS0tLS0tfC0tLS0tLS0tLS0tLS18CnwgYHJlcG9ydEVycm9yKClgIHwgUmVwb3J0IGFuIGVycm9yIG9yIGV4Y2VwdGlvbiB8CnwgYHJlcG9ydEZsdXR0ZXJFcnJvcigpYCB8IFJlcG9ydCBGbHV0dGVyIGZyYW1ld29yayBlcnJvcnMgfAp8IGBsb2dNZXNzYWdlKClgIHwgTG9nIGN1c3RvbSBtZXNzYWdlcyB8CnwgYHNldFVzZXIoKWAgfCBTZXQgdXNlciBpbmZvcm1hdGlvbiB8CnwgYGNsZWFyVXNlcigpYCB8IENsZWFyIHVzZXIgaW5mb3JtYXRpb24gfAp8IGBzZXRDdXN0b21BdHRyaWJ1dGVzKClgIHwgU2V0IG11bHRpcGxlIGN1c3RvbSBhdHRyaWJ1dGVzIHwKfCBgc2V0Q3VzdG9tQXR0cmlidXRlKClgIHwgU2V0IHNpbmdsZSBjdXN0b20gYXR0cmlidXRlIHwKfCBgc2V0Q3Jhc2hSZXBvcnRpbmdFbmFibGVkKClgIHwgRW5hYmxlL2Rpc2FibGUgY3Jhc2ggcmVwb3J0aW5nIHwKfCBgc2VuZFBlbmRpbmdSZXBvcnRzKClgIHwgU2VuZCB1bnNlbnQgY3Jhc2ggcmVwb3J0cyB8CnwgYGRlbGV0ZVBlbmRpbmdSZXBvcnRzKClgIHwgRGVsZXRlIHVuc2VudCBjcmFzaCByZXBvcnRzIHwKCiMjIyBDcmFzaGx5dGljc0NvbmZpZwoKQ29uZmlndXJhdGlvbiBvcHRpb25zOgoKfCBQcm9wZXJ0eSB8IERlZmF1bHQgfCBEZXNjcmlwdGlvbiB8CnwtLS0tLS0tLS0tfC0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfAp8IGBlbmFibGVJbkRlYnVnTW9kZWAgfCBgZmFsc2VgIHwgRW5hYmxlIGNyYXNobHl0aWNzIGluIGRlYnVnIG1vZGUgfAp8IGBlbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbmAgfCBgdHJ1ZWAgfCBFbmFibGUgYXV0b21hdGljIGVycm9yIGNvbGxlY3Rpb24gfAp8IGBlbmFibGVDdXN0b21Mb2dzYCB8IGB0cnVlYCB8IEVuYWJsZSBjdXN0b20gbG9nZ2luZyB8CnwgYGxvZ0J1ZmZlclNpemVgIHwgYDEwMGAgfCBNYXhpbXVtIGxvZyBidWZmZXIgc2l6ZSB8CnwgYGVuYWJsZVVzZXJNZXRhZGF0YWAgfCBgdHJ1ZWAgfCBFbmFibGUgdXNlciBtZXRhZGF0YSBjb2xsZWN0aW9uIHwKfCBgY3VzdG9tS2V5c2AgfCBge31gIHwgRGVmYXVsdCBjdXN0b20ga2V5cyB8CgojIyBJbnRlZ3JhdGlvbiB3aXRoIERJIPCflKcKClRoZSBwYWNrYWdlIGludGVncmF0ZXMgc2VhbWxlc3NseSB3aXRoIHRoZSBESSBjb250YWluZXI6CgpgYGBkYXJ0Ci8vIEdldCBzZXJ2aWNlcyBmcm9tIERJCmZpbmFsIGNyYXNobHl0aWNzID0gZGkuZ2V0PENyYXNobHl0aWNzU2VydmljZT4oKTsKZmluYWwgY2xpZW50ID0gZGkuZ2V0PENyYXNobHl0aWNzQ2xpZW50PigpOwpmaW5hbCBjb25maWcgPSBkaS5nZXQ8Q3Jhc2hseXRpY3NDb25maWc+KCk7CgovLyBTZXJ2aWNlcyBhcmUgYXV0b21hdGljYWxseSBkaXNwb3NlZCB3aGVuIERJIGNvbnRhaW5lciBpcyBkaXNwb3NlZApgYGAKCiMjIFRlc3Rpbmcg8J+nqgoKYGBgZGFydAovLyBVcGRhdGUgdGVzdCBmaWxlCmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3Rlc3QvZmx1dHRlcl90ZXN0LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y3Jhc2hseXRpY3MvY3Jhc2hseXRpY3MuZGFydCc7Cgp2b2lkIG1haW4oKSB7CiAgZ3JvdXAoJ0NyYXNobHl0aWNzJywgKCkgewogICAgdGVzdCgnY2FuIGJlIGluaXRpYWxpemVkJywgKCkgYXN5bmMgewogICAgICAvLyBUZXN0IGluaXRpYWxpemF0aW9uCiAgICAgIGV4cGVjdCgoKSA9PiBjcmFzaGx5dGljcy5pbml0KCksIGlzTm90KHRocm93c0V4Y2VwdGlvbikpOwogICAgfSk7CiAgfSk7Cn0KYGBgCgojIyBCZXN0IFByYWN0aWNlcyDwn5KhCgoxLiAqKkluaXRpYWxpemUgRWFybHkqKjogSW5pdGlhbGl6ZSBjcmFzaGx5dGljcyBhcyBlYXJseSBhcyBwb3NzaWJsZSBpbiB5b3VyIGFwcAoyLiAqKkVudmlyb25tZW50IENvbmZpZ3VyYXRpb24qKjogVXNlIGRpZmZlcmVudCBjb25maWdzIGZvciBkZWJ1Zy9yZWxlYXNlIGJ1aWxkcwozLiAqKlVzZXIgUHJpdmFjeSoqOiBCZSBtaW5kZnVsIG9mIHVzZXIgcHJpdmFjeSB3aGVuIHNldHRpbmcgdXNlciBtZXRhZGF0YQo0LiAqKkNvbnRleHQgaXMgS2V5Kio6IEFsd2F5cyBwcm92aWRlIGNvbnRleHQgd2hlbiByZXBvcnRpbmcgZXJyb3JzCjUuICoqVGVzdCBpbiBEZWJ1ZyoqOiBFbmFibGUgZGVidWcgbW9kZSBkdXJpbmcgZGV2ZWxvcG1lbnQgdG8gdGVzdCBpbnRlZ3JhdGlvbgo2LiAqKk1vbml0b3IgVXNhZ2UqKjogUmVndWxhcmx5IGNoZWNrIEZpcmViYXNlIGNvbnNvbGUgZm9yIGNyYXNoIHJlcG9ydHMKCiMjIFJlcXVpcmVtZW50cyDwn5OLCgotIEZsdXR0ZXIgU0RLID49My4xOS4wCi0gRGFydCBTREsgXjMuMy4wCi0gRmlyZWJhc2UgcHJvamVjdCB3aXRoIENyYXNobHl0aWNzIGVuYWJsZWQKLSBgY29yZWAgYW5kIGBkaWAgcGFja2FnZXMgZnJvbSB7e25hbWUudGl0bGVDYXNlKCl9fSBtb25vcmVwbwoKW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXTogaHR0cHM6Ly9kb2NzLmZsdXR0ZXIuZGV2L2dldC1zdGFydGVkL2luc3RhbGw=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/test/crashlytics_test.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNyYXNobHl0aWNzL2NyYXNobHl0aWNzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlcl90ZXN0L2ZsdXR0ZXJfdGVzdC5kYXJ0JzsKCnZvaWQgbWFpbigpIHsKICBncm91cCgnQ3Jhc2hseXRpY3MnLCAoKSB7CiAgICBncm91cCgnQ3Jhc2hseXRpY3NDb25maWcnLCAoKSB7CiAgICAgIHRlc3QoJ2NyZWF0ZXMgZGVmYXVsdCBjb25maWcgY29ycmVjdGx5JywgKCkgewogICAgICAgIGNvbnN0IGNvbmZpZyA9IERlZmF1bHRDcmFzaGx5dGljc0NvbmZpZygpOwoKICAgICAgICBleHBlY3QoY29uZmlnLmVuYWJsZUluRGVidWdNb2RlLCBmYWxzZSk7CiAgICAgICAgZXhwZWN0KGNvbmZpZy5lbmFibGVBdXRvbWF0aWNEYXRhQ29sbGVjdGlvbiwgdHJ1ZSk7CiAgICAgICAgZXhwZWN0KGNvbmZpZy5lbmFibGVDdXN0b21Mb2dzLCB0cnVlKTsKICAgICAgICBleHBlY3QoY29uZmlnLmxvZ0J1ZmZlclNpemUsIDEwMCk7CiAgICAgICAgZXhwZWN0KGNvbmZpZy5lbmFibGVVc2VyTWV0YWRhdGEsIHRydWUpOwogICAgICAgIGV4cGVjdChjb25maWcuY3VzdG9tS2V5cywgaXNFbXB0eSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnY3JlYXRlcyBjdXN0b20gY29uZmlnIGNvcnJlY3RseScsICgpIHsKICAgICAgICBjb25zdCBjb25maWcgPSBDcmFzaGx5dGljc0NvbmZpZygKICAgICAgICAgIGVuYWJsZUluRGVidWdNb2RlOiB0cnVlLAogICAgICAgICAgZW5hYmxlQXV0b21hdGljRGF0YUNvbGxlY3Rpb246IGZhbHNlLAogICAgICAgICAgbG9nQnVmZmVyU2l6ZTogNTAsCiAgICAgICAgICBjdXN0b21LZXlzOiB7J3Rlc3QnOiAndmFsdWUnfSwKICAgICAgICApOwoKICAgICAgICBleHBlY3QoY29uZmlnLmVuYWJsZUluRGVidWdNb2RlLCB0cnVlKTsKICAgICAgICBleHBlY3QoY29uZmlnLmVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uLCBmYWxzZSk7CiAgICAgICAgZXhwZWN0KGNvbmZpZy5sb2dCdWZmZXJTaXplLCA1MCk7CiAgICAgICAgZXhwZWN0KGNvbmZpZy5jdXN0b21LZXlzLCB7J3Rlc3QnOiAndmFsdWUnfSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnY29weVdpdGggd29ya3MgY29ycmVjdGx5JywgKCkgewogICAgICAgIGNvbnN0IGNvbmZpZyA9IENyYXNobHl0aWNzQ29uZmlnKCk7CiAgICAgICAgZmluYWwgdXBkYXRlZCA9IGNvbmZpZy5jb3B5V2l0aChlbmFibGVJbkRlYnVnTW9kZTogdHJ1ZSk7CgogICAgICAgIGV4cGVjdCh1cGRhdGVkLmVuYWJsZUluRGVidWdNb2RlLCB0cnVlKTsKICAgICAgICBleHBlY3QoCiAgICAgICAgICB1cGRhdGVkLmVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uLAogICAgICAgICAgY29uZmlnLmVuYWJsZUF1dG9tYXRpY0RhdGFDb2xsZWN0aW9uLAogICAgICAgICk7CiAgICAgIH0pOwogICAgfSk7CgogICAgZ3JvdXAoJ1VzZXJNZXRhZGF0YScsICgpIHsKICAgICAgdGVzdCgnY3JlYXRlcyB1c2VyIG1ldGFkYXRhIGNvcnJlY3RseScsICgpIHsKICAgICAgICBjb25zdCBtZXRhZGF0YSA9IFVzZXJNZXRhZGF0YSgKICAgICAgICAgIHVzZXJJZDogJ3VzZXIxMjMnLAogICAgICAgICAgZW1haWw6ICd0ZXN0QGV4YW1wbGUuY29tJywKICAgICAgICAgIG5hbWU6ICdUZXN0IFVzZXInLAogICAgICAgICAgY3VzdG9tQXR0cmlidXRlczogeydyb2xlJzogJ2FkbWluJ30sCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLnVzZXJJZCwgJ3VzZXIxMjMnKTsKICAgICAgICBleHBlY3QobWV0YWRhdGEuZW1haWwsICd0ZXN0QGV4YW1wbGUuY29tJyk7CiAgICAgICAgZXhwZWN0KG1ldGFkYXRhLm5hbWUsICdUZXN0IFVzZXInKTsKICAgICAgICBleHBlY3QobWV0YWRhdGEuY3VzdG9tQXR0cmlidXRlcywgeydyb2xlJzogJ2FkbWluJ30pOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ3RvTWFwIHdvcmtzIGNvcnJlY3RseScsICgpIHsKICAgICAgICBjb25zdCBtZXRhZGF0YSA9IFVzZXJNZXRhZGF0YSgKICAgICAgICAgIHVzZXJJZDogJ3VzZXIxMjMnLAogICAgICAgICAgZW1haWw6ICd0ZXN0QGV4YW1wbGUuY29tJywKICAgICAgICApOwoKICAgICAgICBmaW5hbCBtYXAgPSBtZXRhZGF0YS50b01hcCgpOwogICAgICAgIGV4cGVjdChtYXBbJ3VzZXJJZCddLCAndXNlcjEyMycpOwogICAgICAgIGV4cGVjdChtYXBbJ2VtYWlsJ10sICd0ZXN0QGV4YW1wbGUuY29tJyk7CiAgICAgICAgZXhwZWN0KG1hcFsnbmFtZSddLCBudWxsKTsKICAgICAgICBleHBlY3QobWFwWydjdXN0b21BdHRyaWJ1dGVzJ10sIGlzRW1wdHkpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ2NvcHlXaXRoIHdvcmtzIGNvcnJlY3RseScsICgpIHsKICAgICAgICBjb25zdCBtZXRhZGF0YSA9IFVzZXJNZXRhZGF0YSh1c2VySWQ6ICd1c2VyMTIzJyk7CiAgICAgICAgZmluYWwgdXBkYXRlZCA9IG1ldGFkYXRhLmNvcHlXaXRoKGVtYWlsOiAnbmV3QGV4YW1wbGUuY29tJyk7CgogICAgICAgIGV4cGVjdCh1cGRhdGVkLnVzZXJJZCwgJ3VzZXIxMjMnKTsKICAgICAgICBleHBlY3QodXBkYXRlZC5lbWFpbCwgJ25ld0BleGFtcGxlLmNvbScpOwogICAgICAgIGV4cGVjdCh1cGRhdGVkLm5hbWUsIG51bGwpOwogICAgICB9KTsKCiAgICAgIHRlc3QoJ2VxdWFsaXR5IHdvcmtzIGNvcnJlY3RseScsICgpIHsKICAgICAgICBjb25zdCBtZXRhZGF0YTEgPSBVc2VyTWV0YWRhdGEodXNlcklkOiAndXNlcjEyMycpOwogICAgICAgIGNvbnN0IG1ldGFkYXRhMiA9IFVzZXJNZXRhZGF0YSh1c2VySWQ6ICd1c2VyMTIzJyk7CiAgICAgICAgY29uc3QgbWV0YWRhdGEzID0gVXNlck1ldGFkYXRhKHVzZXJJZDogJ3VzZXI0NTYnKTsKCiAgICAgICAgZXhwZWN0KG1ldGFkYXRhMSwgZXF1YWxzKG1ldGFkYXRhMikpOwogICAgICAgIGV4cGVjdChtZXRhZGF0YTEsIGlzTm90KGVxdWFscyhtZXRhZGF0YTMpKSk7CiAgICAgIH0pOwogICAgfSk7CgogICAgZ3JvdXAoJ0NyYXNoUmVwb3J0JywgKCkgewogICAgICB0ZXN0KCdjcmVhdGVzIGNyYXNoIHJlcG9ydCBjb3JyZWN0bHknLCAoKSB7CiAgICAgICAgZmluYWwgdGltZXN0YW1wID0gRGF0ZVRpbWUubm93KCk7CiAgICAgICAgZmluYWwgcmVwb3J0ID0gQ3Jhc2hSZXBvcnQoCiAgICAgICAgICBleGNlcHRpb246IEV4Y2VwdGlvbignVGVzdCBlcnJvcicpLAogICAgICAgICAgc3RhY2tUcmFjZTogU3RhY2tUcmFjZS5jdXJyZW50LAogICAgICAgICAgZmF0YWw6IHRydWUsCiAgICAgICAgICB0aW1lc3RhbXA6IHRpbWVzdGFtcCwKICAgICAgICAgIGN1c3RvbUtleXM6IHsna2V5JzogJ3ZhbHVlJ30sCiAgICAgICAgKTsKCiAgICAgICAgZXhwZWN0KHJlcG9ydC5leGNlcHRpb24udG9TdHJpbmcoKSwgY29udGFpbnMoJ1Rlc3QgZXJyb3InKSk7CiAgICAgICAgZXhwZWN0KHJlcG9ydC5mYXRhbCwgdHJ1ZSk7CiAgICAgICAgZXhwZWN0KHJlcG9ydC50aW1lc3RhbXAsIHRpbWVzdGFtcCk7CiAgICAgICAgZXhwZWN0KHJlcG9ydC5jdXN0b21LZXlzLCB7J2tleSc6ICd2YWx1ZSd9KTsKICAgICAgfSk7CgogICAgICB0ZXN0KCd0b01hcCB3b3JrcyBjb3JyZWN0bHknLCAoKSB7CiAgICAgICAgZmluYWwgcmVwb3J0ID0gQ3Jhc2hSZXBvcnQoCiAgICAgICAgICBleGNlcHRpb246IEV4Y2VwdGlvbignVGVzdCBlcnJvcicpLAogICAgICAgICAgZmF0YWw6IGZhbHNlLAogICAgICAgICAgY3VzdG9tS2V5czogeyd0ZXN0JzogJ2RhdGEnfSwKICAgICAgICApOwoKICAgICAgICBmaW5hbCBtYXAgPSByZXBvcnQudG9NYXAoKTsKICAgICAgICBleHBlY3QobWFwWydmYXRhbCddLCBmYWxzZSk7CiAgICAgICAgZXhwZWN0KG1hcFsnY3VzdG9tS2V5cyddLCB7J3Rlc3QnOiAnZGF0YSd9KTsKICAgICAgICBleHBlY3QobWFwWydleGNlcHRpb24nXSwgY29udGFpbnMoJ1Rlc3QgZXJyb3InKSk7CiAgICAgIH0pOwoKICAgICAgdGVzdCgnY29weVdpdGggd29ya3MgY29ycmVjdGx5JywgKCkgewogICAgICAgIGZpbmFsIHJlcG9ydCA9IENyYXNoUmVwb3J0KAogICAgICAgICAgZXhjZXB0aW9uOiBFeGNlcHRpb24oJ1Rlc3QgZXJyb3InKSwKICAgICAgICAgIGZhdGFsOiBmYWxzZSwKICAgICAgICApOwoKICAgICAgICBmaW5hbCB1cGRhdGVkID0gcmVwb3J0LmNvcHlXaXRoKGZhdGFsOiB0cnVlKTsKICAgICAgICBleHBlY3QodXBkYXRlZC5mYXRhbCwgdHJ1ZSk7CiAgICAgICAgZXhwZWN0KHVwZGF0ZWQuZXhjZXB0aW9uLnRvU3RyaW5nKCksIHJlcG9ydC5leGNlcHRpb24udG9TdHJpbmcoKSk7CiAgICAgIH0pOwogICAgfSk7CiAgfSk7Cn0K", "type": "text" }, { - "path": "{{ name.snakeCase() }}/features/README.md", - "data": "IyBQdXQgYWxsIHlvdXIgZmVhdHVyZXMgaW4gdGhpcyBmb2xkZXI=", + "path": + "{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/developer.dart", + "data": + "ZXhwb3J0ICdzcmMvcm91dGVzL2luZGV4LmRhcnQnOwpleHBvcnQgJ3NyYy9zY3JlZW5zL2luZGV4LmRhcnQnOwpleHBvcnQgJ3NyYy93aWRnZXRzL2luZGV4LmRhcnQnOwo=", "type": "text" }, { - "path": "{{ name.snakeCase() }}/melos.yaml", + "path": + "{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/routes/developer_routes.dart", "data": - "bmFtZToge3sgbmFtZS5zbmFrZUNhc2UoKSB9fV93b3Jrc3BhY2UKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBNT05PUkVQTyBDT05GSUdVUkFUSU9OCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBEZWZpbmUgdGhlIHBhY2thZ2VzIHRoYXQgbWFrZSB1cCB5b3VyIG1vbm9yZXBvCiMgQ3VzdG9taXplIHRoZXNlIHBhdHRlcm5zIHRvIG1hdGNoIHlvdXIgcHJvamVjdCBzdHJ1Y3R1cmUKcGFja2FnZXM6CiAgLSBhcHBzLyoKICAtIGZlYXR1cmVzLyoKICAtIHBhY2thZ2VzLyoKICAtIHBsdWdpbnMvKgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIFNDUklQVFMKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwpzY3JpcHRzOgogIGNsZWFuOmZsdXR0ZXI6CiAgICBkZXNjcmlwdGlvbjogIlJ1biBmbHV0dGVyIGNsZWFuIGluIGFsbCBGbHV0dGVyIHBhY2thZ2VzIgogICAgcnVuOiBmbHV0dGVyIGNsZWFuCiAgICBleGVjOgogICAgICBjb25jdXJyZW5jeTogNAogICAgcGFja2FnZUZpbHRlcnM6CiAgICAgIGZsdXR0ZXI6IHRydWUKCiAgbGludDoKICAgIGRlc2NyaXB0aW9uOiAiUnVuIGZvcm1hdCBhbmQgYW5hbHl6ZSBhbGwgcGFja2FnZXMiCiAgICBydW46IHwKICAgICAgZGFydCBmb3JtYXQgLS1zZXQtZXhpdC1pZi1jaGFuZ2VkIC4gJiYKICAgICAgZGFydCBhbmFseXplIC0tZmF0YWwtaW5mb3MgLgogICAgZXhlYzoKICAgICAgY29uY3VycmVuY3k6IDUKICAgICAgZmFpbEZhc3Q6IHRydWUKCiAgZml4OgogICAgZGVzY3JpcHRpb246ICJBcHBseSBEYXJ0IGF1dG9tYXRlZCBmaXhlcyB0byBhbGwgcGFja2FnZXMiCiAgICBydW46IGRhcnQgZml4IC0tYXBwbHkKICAgIGV4ZWM6CiAgICAgIGNvbmN1cnJlbmN5OiA1CiAgICBwYWNrYWdlRmlsdGVyczoKICAgICAgZGlyRXhpc3RzOiBsaWIKCiAgZml4OmRyeToKICAgIGRlc2NyaXB0aW9uOiAiU2hvdyBEYXJ0IGF1dG9tYXRlZCBmaXhlcyB3aXRob3V0IGFwcGx5aW5nIHRoZW0iCiAgICBydW46IGRhcnQgZml4IC0tZHJ5LXJ1bgogICAgZXhlYzoKICAgICAgY29uY3VycmVuY3k6IDUKICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICBkaXJFeGlzdHM6IGxpYgoKICBnZW5lcmF0ZToKICAgIGRlc2NyaXB0aW9uOiAiR2VuZXJhdGUgY29kZSBmb3IgRGFydCBwYWNrYWdlcyB1c2luZyBidWlsZF9ydW5uZXIiCiAgICBydW46IGRhcnQgcnVuIGJ1aWxkX3J1bm5lciBidWlsZCAtLWRlbGV0ZS1jb25mbGljdGluZy1vdXRwdXRzCiAgICBleGVjOgogICAgICBjb25jdXJyZW5jeTogMQogICAgcGFja2FnZUZpbHRlcnM6CiAgICAgIGRlcGVuZHNPbjogYnVpbGRfcnVubmVyCgogIHRlc3Q6CiAgICBkZXNjcmlwdGlvbjogIlJ1biBhbGwgdGVzdHMgKERhcnQgYW5kIEZsdXR0ZXIpIgogICAgcnVuOiBtZWxvcyBydW4gdGVzdDpzZWxlY3QgLS1uby1zZWxlY3QKCiAgdGVzdDpzZWxlY3Q6CiAgICBkZXNjcmlwdGlvbjogIlJ1biB0ZXN0cyBpbiBGbHV0dGVyIHBhY2thZ2VzIgogICAgcnVuOiBmbHV0dGVyIHRlc3QgLS1jb3ZlcmFnZQogICAgZXhlYzoKICAgICAgY29uY3VycmVuY3k6IDUKICAgICAgZmFpbEZhc3Q6IHRydWUKICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICBkaXJFeGlzdHM6IHRlc3QKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBJREUgQ09ORklHVVJBVElPTgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgT3B0aW9uYWw6IENvbmZpZ3VyZSBJREUgaW50ZWdyYXRpb24KaWRlOgogIGludGVsbGlqOgogICAgZW5hYmxlZDogdHJ1ZQogICAgbW9kdWxlTmFtZVByZWZpeDogJyc=", + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JyBhcyBjb3JlOwppbXBvcnQgJ3BhY2thZ2U6ZGV2ZWxvcGVyL3NyYy9zY3JlZW5zL2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmdvX3JvdXRlci9nb19yb3V0ZXIuZGFydCc7CgpjbGFzcyBEZXZlbG9wZXJSb3V0ZXMgewogIHN0YXRpYyBjb25zdCBTdHJpbmcgZGV2ZWxvcGVyID0gJy9kZXZlbG9wZXInOwp9CgpjbGFzcyBEZXZlbG9wZXJSb3V0ZXIgZXh0ZW5kcyBjb3JlLkNvcmVSb3V0ZXIgewogIEBvdmVycmlkZQogIExpc3Q8Um91dGVCYXNlPiBnZXQgcm91dGVzID0+IFsKICAgIEdvUm91dGUoCiAgICAgIHBhdGg6IERldmVsb3BlclJvdXRlcy5kZXZlbG9wZXIsCiAgICAgIG5hbWU6ICdkZXZlbG9wZXItc2NyZWVuJywKICAgICAgYnVpbGRlcjogKEJ1aWxkQ29udGV4dCBjb250ZXh0LCBHb1JvdXRlclN0YXRlIHN0YXRlKSB7CiAgICAgICAgcmV0dXJuIGNvbnN0IERldmVsb3BlclNjcmVlbigpOwogICAgICB9LAogICAgKSwKICBdOwp9Cg==", "type": "text" }, { - "path": "{{ name.snakeCase() }}/packages/README.md", + "path": + "{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/routes/index.dart", + "data": "ZXhwb3J0ICdkZXZlbG9wZXJfcm91dGVzLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/screens/developer_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnRhbGtlcl9mbHV0dGVyL3RhbGtlcl9mbHV0dGVyLmRhcnQnOwoKY2xhc3MgRGV2ZWxvcGVyU2NyZWVuIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBjb25zdCBEZXZlbG9wZXJTY3JlZW4oe3N1cGVyLmtleX0pOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBUYWxrZXJTY3JlZW4odGFsa2VyOiBkaS5nZXQ8TG9nZ2VyPigpLmxvZ2dlciBhcyBUYWxrZXIpOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/screens/index.dart", + "data": "ZXhwb3J0ICdkZXZlbG9wZXJfc2NyZWVuLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/widgets/index.dart", + "data": "ZXhwb3J0ICdvcGVuX2Rldl90b29sc193cmFwcGVyLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{developer_dir}}/lib/src/widgets/open_dev_tools_wrapper.dart", + "data": + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpkZXZlbG9wZXIvc3JjL3JvdXRlcy9pbmRleC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZlYXR1cmVfZmxhZ3MvZmVhdHVyZV9mbGFncy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL3NlcnZpY2VzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Z29fcm91dGVyL2dvX3JvdXRlci5kYXJ0JzsKCmNsYXNzIE9wZW5EZXZUb29sc1dyYXBwZXIgZXh0ZW5kcyBTdGF0ZWZ1bFdpZGdldCB7CiAgY29uc3QgT3BlbkRldlRvb2xzV3JhcHBlcih7CiAgICBzdXBlci5rZXksCiAgICByZXF1aXJlZCB0aGlzLmNoaWxkLAogICAgdGhpcy50YXBDb3VudCA9IDUsCiAgICB0aGlzLnRhcFdpbmRvdyA9IGNvbnN0IER1cmF0aW9uKHNlY29uZHM6IDIpLAogICAgdGhpcy5lbmFibGVIYXB0aWNzID0gdHJ1ZSwKICAgIHRoaXMuZW5hYmxlVmlzdWFsRmVlZGJhY2sgPSBmYWxzZSwKICB9KTsKCiAgZmluYWwgV2lkZ2V0IGNoaWxkOwogIGZpbmFsIGludCB0YXBDb3VudDsKICBmaW5hbCBEdXJhdGlvbiB0YXBXaW5kb3c7CiAgZmluYWwgYm9vbCBlbmFibGVIYXB0aWNzOwogIGZpbmFsIGJvb2wgZW5hYmxlVmlzdWFsRmVlZGJhY2s7CgogIEBvdmVycmlkZQogIFN0YXRlPE9wZW5EZXZUb29sc1dyYXBwZXI+IGNyZWF0ZVN0YXRlKCkgPT4gX09wZW5EZXZUb29sc1dyYXBwZXJTdGF0ZSgpOwp9CgpjbGFzcyBfT3BlbkRldlRvb2xzV3JhcHBlclN0YXRlIGV4dGVuZHMgU3RhdGU8T3BlbkRldlRvb2xzV3JhcHBlcj4KICAgIHdpdGggU2luZ2xlVGlja2VyUHJvdmlkZXJTdGF0ZU1peGluIHsKICBpbnQgX2N1cnJlbnRUYXBDb3VudCA9IDA7CiAgVGltZXI/IF9yZXNldFRpbWVyOwogIGxhdGUgQW5pbWF0aW9uQ29udHJvbGxlciBfYW5pbWF0aW9uQ29udHJvbGxlcjsKICBsYXRlIEFuaW1hdGlvbjxkb3VibGU+IF9zY2FsZUFuaW1hdGlvbjsKICBib29sIF9pc0FuaW1hdGluZyA9IGZhbHNlOwoKICBAb3ZlcnJpZGUKICB2b2lkIGluaXRTdGF0ZSgpIHsKICAgIHN1cGVyLmluaXRTdGF0ZSgpOwogICAgX2FuaW1hdGlvbkNvbnRyb2xsZXIgPSBBbmltYXRpb25Db250cm9sbGVyKAogICAgICBkdXJhdGlvbjogY29uc3QgRHVyYXRpb24obWlsbGlzZWNvbmRzOiAyMDApLAogICAgICB2c3luYzogdGhpcywKICAgICk7CiAgICBfc2NhbGVBbmltYXRpb24gPSBUd2Vlbjxkb3VibGU+KGJlZ2luOiAxLjAsIGVuZDogMC45NSkuYW5pbWF0ZSgKICAgICAgQ3VydmVkQW5pbWF0aW9uKHBhcmVudDogX2FuaW1hdGlvbkNvbnRyb2xsZXIsIGN1cnZlOiBDdXJ2ZXMuZWFzZUluT3V0KSwKICAgICk7CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIGRpc3Bvc2UoKSB7CiAgICBfcmVzZXRUaW1lcj8uY2FuY2VsKCk7CiAgICBfYW5pbWF0aW9uQ29udHJvbGxlci5kaXNwb3NlKCk7CiAgICBzdXBlci5kaXNwb3NlKCk7CiAgfQoKICB2b2lkIF9oYW5kbGVUYXAoKSB7CiAgICBzZXRTdGF0ZSgoKSB7CiAgICAgIF9jdXJyZW50VGFwQ291bnQrKzsKICAgIH0pOwoKICAgIC8vIFByb3ZpZGUgaGFwdGljIGZlZWRiYWNrIGZvciBwcm9ncmVzcwogICAgaWYgKHdpZGdldC5lbmFibGVIYXB0aWNzKSB7CiAgICAgIEhhcHRpY0ZlZWRiYWNrLnNlbGVjdGlvbkNsaWNrKCk7CiAgICB9CgogICAgLy8gVmlzdWFsIGZlZWRiYWNrIGFuaW1hdGlvbgogICAgaWYgKHdpZGdldC5lbmFibGVWaXN1YWxGZWVkYmFjayAmJiAhX2lzQW5pbWF0aW5nKSB7CiAgICAgIF9pc0FuaW1hdGluZyA9IHRydWU7CiAgICAgIF9hbmltYXRpb25Db250cm9sbGVyLmZvcndhcmQoKS50aGVuKChfKSB7CiAgICAgICAgX2FuaW1hdGlvbkNvbnRyb2xsZXIucmV2ZXJzZSgpLnRoZW4oKF8pIHsKICAgICAgICAgIGlmIChtb3VudGVkKSB7CiAgICAgICAgICAgIHNldFN0YXRlKCgpIHsKICAgICAgICAgICAgICBfaXNBbmltYXRpbmcgPSBmYWxzZTsKICAgICAgICAgICAgfSk7CiAgICAgICAgICB9CiAgICAgICAgfSk7CiAgICAgIH0pOwogICAgfQoKICAgIC8vIENoZWNrIGlmIHdlJ3ZlIHJlYWNoZWQgdGhlIHJlcXVpcmVkIHRhcCBjb3VudAogICAgaWYgKF9jdXJyZW50VGFwQ291bnQgPj0gd2lkZ2V0LnRhcENvdW50KSB7CiAgICAgIF9uYXZpZ2F0ZVRvRGV2ZWxvcGVyU2NyZWVuKCk7CiAgICAgIHJldHVybjsKICAgIH0KCiAgICAvLyBSZXNldCB0aGUgdGltZXIKICAgIF9yZXNldFRpbWVyPy5jYW5jZWwoKTsKICAgIF9yZXNldFRpbWVyID0gVGltZXIod2lkZ2V0LnRhcFdpbmRvdywgX3Jlc2V0VGFwQ291bnQpOwogIH0KCiAgdm9pZCBfbmF2aWdhdGVUb0RldmVsb3BlclNjcmVlbigpIHsKICAgIC8vIFByb3ZpZGUgc3VjY2VzcyBoYXB0aWMgZmVlZGJhY2sKICAgIGlmICh3aWRnZXQuZW5hYmxlSGFwdGljcykgewogICAgICBIYXB0aWNGZWVkYmFjay5tZWRpdW1JbXBhY3QoKTsKICAgIH0KCiAgICAvLyBSZXNldCBzdGF0ZQogICAgX3Jlc2V0VGFwQ291bnQoKTsKCiAgICAvLyBOYXZpZ2F0ZSB0byBkZXZlbG9wZXIgc2NyZWVuCiAgICBpZiAobW91bnRlZCkgewogICAgICBjb250ZXh0LnB1c2goRGV2ZWxvcGVyUm91dGVzLmRldmVsb3Blcik7CiAgICB9CiAgfQoKICB2b2lkIF9yZXNldFRhcENvdW50KCkgewogICAgX3Jlc2V0VGltZXI/LmNhbmNlbCgpOwogICAgaWYgKG1vdW50ZWQpIHsKICAgICAgc2V0U3RhdGUoKCkgewogICAgICAgIF9jdXJyZW50VGFwQ291bnQgPSAwOwogICAgICB9KTsKICAgIH0KICB9CgogIFdpZGdldCBfYnVpbGRDaGlsZCgpIHsKICAgIGlmICh3aWRnZXQuZW5hYmxlVmlzdWFsRmVlZGJhY2spIHsKICAgICAgcmV0dXJuIEFuaW1hdGVkQnVpbGRlcigKICAgICAgICBhbmltYXRpb246IF9zY2FsZUFuaW1hdGlvbiwKICAgICAgICBidWlsZGVyOiAoY29udGV4dCwgY2hpbGQpIHsKICAgICAgICAgIHJldHVybiBUcmFuc2Zvcm0uc2NhbGUoCiAgICAgICAgICAgIHNjYWxlOiBfc2NhbGVBbmltYXRpb24udmFsdWUsCiAgICAgICAgICAgIGNoaWxkOiB3aWRnZXQuY2hpbGQsCiAgICAgICAgICApOwogICAgICAgIH0sCiAgICAgICk7CiAgICB9CiAgICByZXR1cm4gd2lkZ2V0LmNoaWxkOwogIH0KCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKEJ1aWxkQ29udGV4dCBjb250ZXh0KSB7CiAgICBmaW5hbCB0aGVtZSA9IFRoZW1lLm9mKGNvbnRleHQpOwoKICAgIHJldHVybiBGZWF0dXJlRmxhZ1dyYXBwZXIoCiAgICAgIGZsYWdLZXk6ICdkZXZlbG9wZXJfc2NyZWVuX2VuYWJsZWQnLAogICAgICBidWlsZGVyOiAoY29udGV4dCwgaXNFbmFibGVkKSB7CiAgICAgICAgaWYgKCFpc0VuYWJsZWQpIHsKICAgICAgICAgIHJldHVybiB3aWRnZXQuY2hpbGQ7IC8vIEp1c3QgcmV0dXJuIHRoZSBjaGlsZCBpZiBmZWF0dXJlIGlzIGRpc2FibGVkCiAgICAgICAgfQoKICAgICAgICByZXR1cm4gR2VzdHVyZURldGVjdG9yKAogICAgICAgICAgb25UYXA6IF9oYW5kbGVUYXAsCiAgICAgICAgICBiZWhhdmlvcjogSGl0VGVzdEJlaGF2aW9yLnRyYW5zbHVjZW50LAogICAgICAgICAgY2hpbGQ6IFN0YWNrKAogICAgICAgICAgICBjaGlsZHJlbjogWwogICAgICAgICAgICAgIF9idWlsZENoaWxkKCksCiAgICAgICAgICAgICAgLy8gT3B0aW9uYWwgZGVidWcgaW5kaWNhdG9yIChvbmx5IHZpc2libGUgaW4gZGVidWcgYW5kIHdpdGggdmlzdWFsIGZlZWRiYWNrIGVuYWJsZWQpCiAgICAgICAgICAgICAgaWYgKHdpZGdldC5lbmFibGVWaXN1YWxGZWVkYmFjayAmJiBfY3VycmVudFRhcENvdW50ID4gMCkgLi4uWwogICAgICAgICAgICAgICAgUG9zaXRpb25lZC5maWxsKAogICAgICAgICAgICAgICAgICBjaGlsZDogSWdub3JlUG9pbnRlcigKICAgICAgICAgICAgICAgICAgICBjaGlsZDogQ29udGFpbmVyKAogICAgICAgICAgICAgICAgICAgICAgZGVjb3JhdGlvbjogQm94RGVjb3JhdGlvbigKICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyOiBCb3JkZXIuYWxsKAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yOiB0aGVtZS5jb2xvclNjaGVtZS5lcnJvci53aXRoVmFsdWVzKGFscGhhOiAwLjMpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoOiAyLAogICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgIGNoaWxkOiBDZW50ZXIoCiAgICAgICAgICAgICAgICAgICAgICAgIGNoaWxkOiBDb250YWluZXIoCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkZGluZzogY29uc3QgRWRnZUluc2V0cy5zeW1tZXRyaWMoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3Jpem9udGFsOiA4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVydGljYWw6IDQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBkZWNvcmF0aW9uOiBCb3hEZWNvcmF0aW9uKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I6IENvbG9ycy5yZWQud2l0aFZhbHVlcyhhbHBoYTogMC44KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlclJhZGl1czogQm9yZGVyUmFkaXVzLmNpcmN1bGFyKDQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2hpbGQ6IFRleHQoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnJF9jdXJyZW50VGFwQ291bnQvJHt3aWRnZXQudGFwQ291bnR9JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlOiB0aGVtZS50ZXh0VGhlbWUubGFiZWxTbWFsbD8uY29weVdpdGgoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yOiB0aGVtZS5jb2xvclNjaGVtZS5vbkVycm9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250V2VpZ2h0OiBGb250V2VpZ2h0LmJvbGQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgIF0sCiAgICAgICAgICApLAogICAgICAgICk7CiAgICAgIH0sCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml", + "data": + "bmFtZTogZGV2ZWxvcGVyCmRlc2NyaXB0aW9uOiAiSW4tYXBwIGRldmVsb3BlciB0b29scyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX06IGxvZ3MsIGZsYWdzIGFuZCBlbnZpcm9ubWVudCBpbnNwZWN0aW9uLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZ29fcm91dGVyOiBeMTUuMS4zCiAgdGFsa2VyX2ZsdXR0ZXI6IF40LjQuMQogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGVzaWduX3N5c3RlbToKICAgIHBhdGg6IC4uL2Rlc2lnbl9zeXN0ZW0KICBkaToKICAgIHBhdGg6IC4uL2RpCiAgZmVhdHVyZV9mbGFnczoKICAgIHBhdGg6IC4uL2ZlYXR1cmVfZmxhZ3MKCmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCgpmbHV0dGVyOgo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{developer_dir}}/test/developer_test.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRldmVsb3Blci9kZXZlbG9wZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlcl90ZXN0L2ZsdXR0ZXJfdGVzdC5kYXJ0JzsKCnZvaWQgbWFpbigpIHsKICBncm91cCgnRGV2ZWxvcGVyIFBhY2thZ2UnLCAoKSB7CiAgICB0ZXN0KCdwYWNrYWdlIGV4cG9ydHMgYXJlIGF2YWlsYWJsZScsICgpIHsKICAgICAgZXhwZWN0KERldmVsb3BlclJvdXRlcy5kZXZlbG9wZXIsICcvZGV2ZWxvcGVyJyk7CiAgICB9KTsKCiAgICB0ZXN0V2lkZ2V0cygnRGVidWdHZXN0dXJlRGV0ZWN0b3IgZXhwb3J0cyBhcmUgYXZhaWxhYmxlJywgKHRlc3RlcikgYXN5bmMgewogICAgICAvLyBUZXN0IHRoYXQgdGhlIHdpZGdldCBjYW4gYmUgaW5zdGFudGlhdGVkCiAgICAgIGNvbnN0IHdpZGdldCA9IE9wZW5EZXZUb29sc1dyYXBwZXIoY2hpbGQ6IFRleHQoJ1Rlc3QnKSk7CgogICAgICBleHBlY3Qod2lkZ2V0LCBpc05vdE51bGwpOwogICAgICBleHBlY3Qod2lkZ2V0LnRhcENvdW50LCA1KTsgLy8gRGVmYXVsdCB2YWx1ZQogICAgICBleHBlY3Qod2lkZ2V0LnRhcFdpbmRvdywgY29uc3QgRHVyYXRpb24oc2Vjb25kczogMikpOyAvLyBEZWZhdWx0IHZhbHVlCiAgICAgIGV4cGVjdCh3aWRnZXQuZW5hYmxlSGFwdGljcywgdHJ1ZSk7IC8vIERlZmF1bHQgdmFsdWUKICAgICAgZXhwZWN0KHdpZGdldC5lbmFibGVWaXN1YWxGZWVkYmFjaywgZmFsc2UpOyAvLyBEZWZhdWx0IHZhbHVlCiAgICB9KTsKCiAgICB0ZXN0V2lkZ2V0cygnRGVidWdHZXN0dXJlRGV0ZWN0b3IgY3VzdG9tIHZhbHVlcyB3b3JrJywgKHRlc3RlcikgYXN5bmMgewogICAgICAvLyBUZXN0IGN1c3RvbSBjb25maWd1cmF0aW9uCiAgICAgIGNvbnN0IHdpZGdldCA9IE9wZW5EZXZUb29sc1dyYXBwZXIoCiAgICAgICAgdGFwQ291bnQ6IDcsCiAgICAgICAgdGFwV2luZG93OiBEdXJhdGlvbihzZWNvbmRzOiAyKSwKICAgICAgICBlbmFibGVIYXB0aWNzOiBmYWxzZSwKICAgICAgICBlbmFibGVWaXN1YWxGZWVkYmFjazogdHJ1ZSwKICAgICAgICBjaGlsZDogVGV4dCgnVGVzdCcpLAogICAgICApOwoKICAgICAgZXhwZWN0KHdpZGdldC50YXBDb3VudCwgNyk7CiAgICAgIGV4cGVjdCh3aWRnZXQudGFwV2luZG93LCBjb25zdCBEdXJhdGlvbihzZWNvbmRzOiAyKSk7CiAgICAgIGV4cGVjdCh3aWRnZXQuZW5hYmxlSGFwdGljcywgZmFsc2UpOwogICAgICBleHBlY3Qod2lkZ2V0LmVuYWJsZVZpc3VhbEZlZWRiYWNrLCB0cnVlKTsKICAgIH0pOwogIH0pOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/feature_flags.dart", + "data": + "bGlicmFyeTsKCmltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci9sb2dnZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZlYXR1cmVfZmxhZ3Mvc3JjL2ZlYXR1cmVfZmxhZ19zZXJ2aWNlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmVhdHVyZV9mbGFncy9zcmMvcmVnaXN0ZXIuZGFydCc7CgpleHBvcnQgJ3NyYy9pbmRleC5kYXJ0JzsKCi8vLyBJbml0aWFsaXplIHRoZSBmZWF0dXJlIGZsYWdzIG1vZHVsZSBhbmQgcmVnaXN0ZXIgd2l0aCBESQpGdXR1cmU8dm9pZD4gaW5pdCh7CiAgRmVhdHVyZUZsYWdzQ29uZmlnIGNvbmZpZyA9IGNvbnN0IEZlYXR1cmVGbGFnc0NvbmZpZygKICAgIGZldGNoVGltZW91dDogRHVyYXRpb24oc2Vjb25kczogMTApLAogICAgbWluaW11bUZldGNoSW50ZXJ2YWw6IER1cmF0aW9uKG1pbnV0ZXM6IDMwKSwKICAgIGRlZmF1bHRQYXJhbWV0ZXJzOiB7fSwKICApLAp9KSBhc3luYyB7CiAgZmluYWwgbG9nZ2VyID0gZGkuZ2V0PExvZ2dlcj4oKTsKICB0cnkgewogICAgbG9nZ2VyLmkoJ2luaXQgZmVhdHVyZSBmbGFncyBtb2R1bGUgd2l0aCBGaXJlYmFzZSBSZW1vdGUgQ29uZmlnJyk7CiAgICBhd2FpdCByZWdpc3RlckZlYXR1cmVGbGFnc1dpdGhESShjb25maWc6IGNvbmZpZyk7CiAgICBmaW5hbCBzZXJ2aWNlID0gZGkuZ2V0PEZlYXR1cmVGbGFnPigpOwogICAgYXdhaXQgc2VydmljZS5pbml0KCk7CiAgICBsb2dnZXIuaSgnRmVhdHVyZSBmbGFncyBtb2R1bGUgaW5pdGlhbGl6ZWQgd2l0aCBGaXJlYmFzZSBSZW1vdGUgQ29uZmlnJyk7CiAgfSBjYXRjaCAoZSwgc3RhY2tUcmFjZSkgewogICAgbG9nZ2VyLmUoJ0ZhaWxlZCB0byBpbml0aWFsaXplIGZlYXR1cmUgZmxhZ3MgbW9kdWxlJywgZSwgc3RhY2tUcmFjZSk7CiAgICByZXRocm93OwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_provider.dart", + "data": + "Ly8vIEFic3RyYWN0IGludGVyZmFjZSBmb3IgZmVhdHVyZSBmbGFnIHByb3ZpZGVycwphYnN0cmFjdCBjbGFzcyBGZWF0dXJlRmxhZ1Byb3ZpZGVyIHsKICAvLy8gSW5pdGlhbGl6ZSB0aGUgZmVhdHVyZSBmbGFnIHByb3ZpZGVyCiAgRnV0dXJlPHZvaWQ+IGluaXQoKTsKCiAgLy8vIEdldCBhIGJvb2xlYW4gZmVhdHVyZSBmbGFnIHZhbHVlCiAgRnV0dXJlPGJvb2w+IGdldEJvb2woU3RyaW5nIGtleSwge2Jvb2wgZGVmYXVsdFZhbHVlID0gZmFsc2V9KTsKCiAgLy8vIEdldCBhIHN0cmluZyBmZWF0dXJlIGZsYWcgdmFsdWUKICBGdXR1cmU8U3RyaW5nPiBnZXRTdHJpbmcoU3RyaW5nIGtleSwge1N0cmluZyBkZWZhdWx0VmFsdWUgPSAnJ30pOwoKICAvLy8gR2V0IGFuIGludGVnZXIgZmVhdHVyZSBmbGFnIHZhbHVlCiAgRnV0dXJlPGludD4gZ2V0SW50KFN0cmluZyBrZXksIHtpbnQgZGVmYXVsdFZhbHVlID0gMH0pOwoKICAvLy8gR2V0IGEgZG91YmxlIGZlYXR1cmUgZmxhZyB2YWx1ZQogIEZ1dHVyZTxkb3VibGU+IGdldERvdWJsZShTdHJpbmcga2V5LCB7ZG91YmxlIGRlZmF1bHRWYWx1ZSA9IDAuMH0pOwoKICAvLy8gQ2hlY2sgaWYgYSBmZWF0dXJlIGZsYWcgZXhpc3RzCiAgRnV0dXJlPGJvb2w+IGhhc0ZsYWcoU3RyaW5nIGtleSk7CgogIC8vLyBEaXNwb3NlIHJlc291cmNlcwogIHZvaWQgZGlzcG9zZSgpOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_service.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL2xvZ2dlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZlYXR1cmVfZmxhZ3Mvc3JjL2ZlYXR1cmVfZmxhZ19wcm92aWRlci5kYXJ0JzsKCi8vLyBDb25maWd1cmF0aW9uIGNsYXNzIGZvciBmZWF0dXJlIGZsYWdzIGluaXRpYWxpemF0aW9uCmNsYXNzIEZlYXR1cmVGbGFnc0NvbmZpZyB7CiAgY29uc3QgRmVhdHVyZUZsYWdzQ29uZmlnKHsKICAgIHRoaXMuZmV0Y2hUaW1lb3V0LAogICAgdGhpcy5taW5pbXVtRmV0Y2hJbnRlcnZhbCwKICAgIHJlcXVpcmVkIHRoaXMuZGVmYXVsdFBhcmFtZXRlcnMsCiAgfSk7CgogIGZpbmFsIER1cmF0aW9uPyBmZXRjaFRpbWVvdXQ7CiAgZmluYWwgRHVyYXRpb24/IG1pbmltdW1GZXRjaEludGVydmFsOwogIGZpbmFsIE1hcDxTdHJpbmcsIGR5bmFtaWM+IGRlZmF1bHRQYXJhbWV0ZXJzOwp9CgovLy8gU2VydmljZSBmb3IgbWFuYWdpbmcgZmVhdHVyZSBmbGFncyBhY3Jvc3MgdGhlIGFwcGxpY2F0aW9uCmNsYXNzIEZlYXR1cmVGbGFnIHsKICBmaW5hbCBGZWF0dXJlRmxhZ1Byb3ZpZGVyIF9wcm92aWRlcjsKICBmaW5hbCBMb2dnZXIgX2xvZ2dlcjsKICBib29sIF9pbml0aWFsaXplZCA9IGZhbHNlOwoKICBGZWF0dXJlRmxhZyh7cmVxdWlyZWQgRmVhdHVyZUZsYWdQcm92aWRlciBwcm92aWRlciwgcmVxdWlyZWQgTG9nZ2VyIGxvZ2dlcn0pCiAgICA6IF9wcm92aWRlciA9IHByb3ZpZGVyLAogICAgICBfbG9nZ2VyID0gbG9nZ2VyOwoKICAvLy8gSW5pdGlhbGl6ZSB0aGUgZmVhdHVyZSBmbGFnIHNlcnZpY2UKICBGdXR1cmU8dm9pZD4gaW5pdCgpIGFzeW5jIHsKICAgIGlmIChfaW5pdGlhbGl6ZWQpIHsKICAgICAgX2xvZ2dlci53KCdGZWF0dXJlRmxhZyBhbHJlYWR5IGluaXRpYWxpemVkJyk7CiAgICAgIHJldHVybjsKICAgIH0KCiAgICB0cnkgewogICAgICBhd2FpdCBfcHJvdmlkZXIuaW5pdCgpOwogICAgICBfaW5pdGlhbGl6ZWQgPSB0cnVlOwogICAgICBfbG9nZ2VyLmkoJ0ZlYXR1cmVGbGFnIGluaXRpYWxpemVkIHN1Y2Nlc3NmdWxseScpOwogICAgfSBjYXRjaCAoZSwgc3RhY2tUcmFjZSkgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byBpbml0aWFsaXplIEZlYXR1cmVGbGFnJywgZSwgc3RhY2tUcmFjZSk7CiAgICAgIHJldGhyb3c7CiAgICB9CiAgfQoKICAvLy8gQ2hlY2sgaWYgc2VydmljZSBpcyBpbml0aWFsaXplZAogIGJvb2wgZ2V0IGlzSW5pdGlhbGl6ZWQgPT4gX2luaXRpYWxpemVkOwoKICAvLy8gR2V0IGEgYm9vbGVhbiBmZWF0dXJlIGZsYWcKICBGdXR1cmU8Ym9vbD4gaXNFbmFibGVkKFN0cmluZyBrZXksIHtib29sIGRlZmF1bHRWYWx1ZSA9IGZhbHNlfSkgYXN5bmMgewogICAgX2Vuc3VyZUluaXRpYWxpemVkKCk7CiAgICBmaW5hbCB2YWx1ZSA9IGF3YWl0IF9wcm92aWRlci5nZXRCb29sKGtleSwgZGVmYXVsdFZhbHVlOiBkZWZhdWx0VmFsdWUpOwogICAgX2xvZ2dlci5pKCdpc0VuYWJsZWQoJGtleSk6IFByb3ZpZGVyIHJldHVybmVkID0gJHZhbHVlJyk7CiAgICByZXR1cm4gdmFsdWU7CiAgfQoKICAvLy8gR2V0IGEgc3RyaW5nIGNvbmZpZ3VyYXRpb24gdmFsdWUKICBGdXR1cmU8U3RyaW5nPiBnZXRDb25maWcoU3RyaW5nIGtleSwge1N0cmluZyBkZWZhdWx0VmFsdWUgPSAnJ30pIGFzeW5jIHsKICAgIF9lbnN1cmVJbml0aWFsaXplZCgpOwoKICAgIGZpbmFsIHZhbHVlID0gYXdhaXQgX3Byb3ZpZGVyLmdldFN0cmluZyhrZXksIGRlZmF1bHRWYWx1ZTogZGVmYXVsdFZhbHVlKTsKICAgIHJldHVybiB2YWx1ZTsKICB9CgogIC8vLyBHZXQgYW4gaW50ZWdlciBjb25maWd1cmF0aW9uIHZhbHVlCiAgRnV0dXJlPGludD4gZ2V0SW50Q29uZmlnKFN0cmluZyBrZXksIHtpbnQgZGVmYXVsdFZhbHVlID0gMH0pIGFzeW5jIHsKICAgIF9lbnN1cmVJbml0aWFsaXplZCgpOwoKICAgIGZpbmFsIHZhbHVlID0gYXdhaXQgX3Byb3ZpZGVyLmdldEludChrZXksIGRlZmF1bHRWYWx1ZTogZGVmYXVsdFZhbHVlKTsKICAgIHJldHVybiB2YWx1ZTsKICB9CgogIC8vLyBHZXQgYSBkb3VibGUgY29uZmlndXJhdGlvbiB2YWx1ZQogIEZ1dHVyZTxkb3VibGU+IGdldERvdWJsZUNvbmZpZygKICAgIFN0cmluZyBrZXksIHsKICAgIGRvdWJsZSBkZWZhdWx0VmFsdWUgPSAwLjAsCiAgfSkgYXN5bmMgewogICAgX2Vuc3VyZUluaXRpYWxpemVkKCk7CgogICAgZmluYWwgdmFsdWUgPSBhd2FpdCBfcHJvdmlkZXIuZ2V0RG91YmxlKGtleSwgZGVmYXVsdFZhbHVlOiBkZWZhdWx0VmFsdWUpOwogICAgcmV0dXJuIHZhbHVlOwogIH0KCiAgLy8vIERpc3Bvc2UgdGhlIHNlcnZpY2UKICB2b2lkIGRpc3Bvc2UoKSB7CiAgICBfcHJvdmlkZXIuZGlzcG9zZSgpOwogICAgX2luaXRpYWxpemVkID0gZmFsc2U7CiAgICBfbG9nZ2VyLmkoJ0ZlYXR1cmVGbGFnIGRpc3Bvc2VkJyk7CiAgfQoKICAvLy8gRW5zdXJlIHRoZSBzZXJ2aWNlIGlzIGluaXRpYWxpemVkCiAgdm9pZCBfZW5zdXJlSW5pdGlhbGl6ZWQoKSB7CiAgICBpZiAoIV9pbml0aWFsaXplZCkgewogICAgICB0aHJvdyBTdGF0ZUVycm9yKCdGZWF0dXJlRmxhZyBub3QgaW5pdGlhbGl6ZWQuIENhbGwgaW5pdGlhbGl6ZSgpIGZpcnN0LicpOwogICAgfQogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/feature_flag_wrapper.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmVhdHVyZV9mbGFncy9zcmMvZmVhdHVyZV9mbGFnX3NlcnZpY2UuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL21hdGVyaWFsLmRhcnQnOwoKLy8vIEEgd3JhcHBlciB3aWRnZXQgdGhhdCBjb25kaXRpb25hbGx5IHJlbmRlcnMgY29udGVudCBiYXNlZCBvbiBmZWF0dXJlIGZsYWdzCmNsYXNzIEZlYXR1cmVGbGFnV3JhcHBlciBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgY29uc3QgRmVhdHVyZUZsYWdXcmFwcGVyKHsKICAgIHN1cGVyLmtleSwKICAgIHJlcXVpcmVkIHRoaXMuZmxhZ0tleSwKICAgIHJlcXVpcmVkIHRoaXMuYnVpbGRlciwKICAgIHRoaXMuZGVmYXVsdFZhbHVlID0gZmFsc2UsCiAgICB0aGlzLmxvYWRpbmcsCiAgfSk7CgogIC8vLyBUaGUgZmVhdHVyZSBmbGFnIGtleSB0byBjaGVjawogIGZpbmFsIFN0cmluZyBmbGFnS2V5OwoKICAvLy8gQnVpbGRlciBmdW5jdGlvbiB0aGF0IHJlY2VpdmVzIHRoZSBjb250ZXh0IGFuZCBmZWF0dXJlIGZsYWcgdmFsdWUKICBmaW5hbCBXaWRnZXQgRnVuY3Rpb24oQnVpbGRDb250ZXh0IGNvbnRleHQsIGJvb2wgdmFsdWUpIGJ1aWxkZXI7CgogIC8vLyBUaGUgZGVmYXVsdCB2YWx1ZSB0byB1c2UgaWYgdGhlIGZlYXR1cmUgZmxhZyBzZXJ2aWNlIGlzIHVuYXZhaWxhYmxlCiAgZmluYWwgYm9vbCBkZWZhdWx0VmFsdWU7CgogIC8vLyBXaWRnZXQgdG8gc2hvdyB3aGlsZSBsb2FkaW5nIHRoZSBmZWF0dXJlIGZsYWcgdmFsdWUKICBmaW5hbCBXaWRnZXQ/IGxvYWRpbmc7CgogIEBvdmVycmlkZQogIFdpZGdldCBidWlsZChCdWlsZENvbnRleHQgY29udGV4dCkgewogICAgcmV0dXJuIEZ1dHVyZUJ1aWxkZXI8Ym9vbD4oCiAgICAgIGZ1dHVyZTogX2NoZWNrRmVhdHVyZUZsYWcoKSwKICAgICAgYnVpbGRlcjogKGNvbnRleHQsIHNuYXBzaG90KSB7CiAgICAgICAgLy8gU2hvdyBsb2FkaW5nIHdpZGdldCB3aGlsZSB3YWl0aW5nIGZvciBmZWF0dXJlIGZsYWcKICAgICAgICBpZiAoc25hcHNob3QuY29ubmVjdGlvblN0YXRlID09IENvbm5lY3Rpb25TdGF0ZS53YWl0aW5nKSB7CiAgICAgICAgICByZXR1cm4gbG9hZGluZyA/PyBjb25zdCBTaXplZEJveC5zaHJpbmsoKTsKICAgICAgICB9CgogICAgICAgIC8vIEdldCB0aGUgZmVhdHVyZSBmbGFnIHZhbHVlLCBkZWZhdWx0aW5nIHRvIGZhbHNlIGlmIHRoZXJlJ3MgYW4gZXJyb3IKICAgICAgICBmaW5hbCBpc0VuYWJsZWQgPSBzbmFwc2hvdC5kYXRhID8/IGRlZmF1bHRWYWx1ZTsKCiAgICAgICAgLy8gUmV0dXJuIHRoZSB3aWRnZXQgYnVpbHQgYnkgdGhlIGJ1aWxkZXIgZnVuY3Rpb24KICAgICAgICByZXR1cm4gYnVpbGRlcihjb250ZXh0LCBpc0VuYWJsZWQpOwogICAgICB9LAogICAgKTsKICB9CgogIEZ1dHVyZTxib29sPiBfY2hlY2tGZWF0dXJlRmxhZygpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIGZlYXR1cmVGbGFnID0gZGkuZ2V0PEZlYXR1cmVGbGFnPigpOwogICAgICByZXR1cm4gYXdhaXQgZmVhdHVyZUZsYWcuaXNFbmFibGVkKGZsYWdLZXksIGRlZmF1bHRWYWx1ZTogZGVmYXVsdFZhbHVlKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgLy8gSWYgdGhlcmUncyBhbiBlcnJvciBhY2Nlc3NpbmcgdGhlIGZlYXR1cmUgZmxhZyBzZXJ2aWNlLAogICAgICAvLyByZXR1cm4gdGhlIGRlZmF1bHQgdmFsdWUKICAgICAgcmV0dXJuIGRlZmF1bHRWYWx1ZTsKICAgIH0KICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/firebase_remote_config_provider.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL2xvZ2dlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmVhdHVyZV9mbGFncy9mZWF0dXJlX2ZsYWdzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfcmVtb3RlX2NvbmZpZy9maXJlYmFzZV9yZW1vdGVfY29uZmlnLmRhcnQnOwoKLy8vIEZpcmViYXNlIFJlbW90ZSBDb25maWcgaW1wbGVtZW50YXRpb24gb2YgRmVhdHVyZUZsYWdQcm92aWRlcgpjbGFzcyBGaXJlYmFzZVJlbW90ZUNvbmZpZ1Byb3ZpZGVyIGltcGxlbWVudHMgRmVhdHVyZUZsYWdQcm92aWRlciB7CiAgbGF0ZSBmaW5hbCBGaXJlYmFzZVJlbW90ZUNvbmZpZyBfcmVtb3RlQ29uZmlnOwogIGZpbmFsIEZlYXR1cmVGbGFnc0NvbmZpZyBfY29uZmlnOwogIGxhdGUgZmluYWwgTG9nZ2VyIGxvZ2dlcjsKCiAgRmlyZWJhc2VSZW1vdGVDb25maWdQcm92aWRlcih7RmVhdHVyZUZsYWdzQ29uZmlnPyBjb25maWd9KQogICAgOiBfY29uZmlnID0KICAgICAgICAgIGNvbmZpZyA/PwogICAgICAgICAgY29uc3QgRmVhdHVyZUZsYWdzQ29uZmlnKAogICAgICAgICAgICBkZWZhdWx0UGFyYW1ldGVyczoge30sCiAgICAgICAgICAgIGZldGNoVGltZW91dDogRHVyYXRpb24oc2Vjb25kczogMTApLAogICAgICAgICAgICBtaW5pbXVtRmV0Y2hJbnRlcnZhbDogRHVyYXRpb24oaG91cnM6IDEpLAogICAgICAgICAgKSB7CiAgICBpZiAoZGkuaGFzPExvZ2dlcj4oKSkgewogICAgICBsb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGluaXQoKSBhc3luYyB7CiAgICB0cnkgewogICAgICBfcmVtb3RlQ29uZmlnID0gRmlyZWJhc2VSZW1vdGVDb25maWcuaW5zdGFuY2U7CgogICAgICAvLyBDb25maWd1cmUgc2V0dGluZ3MKICAgICAgYXdhaXQgX3JlbW90ZUNvbmZpZy5zZXRDb25maWdTZXR0aW5ncygKICAgICAgICBSZW1vdGVDb25maWdTZXR0aW5ncygKICAgICAgICAgIGZldGNoVGltZW91dDogX2NvbmZpZy5mZXRjaFRpbWVvdXQgPz8gY29uc3QgRHVyYXRpb24oc2Vjb25kczogMTApLAogICAgICAgICAgbWluaW11bUZldGNoSW50ZXJ2YWw6CiAgICAgICAgICAgICAgX2NvbmZpZy5taW5pbXVtRmV0Y2hJbnRlcnZhbCA/PyBjb25zdCBEdXJhdGlvbihob3VyczogMSksCiAgICAgICAgKSwKICAgICAgKTsKCiAgICAgIC8vIFNldCBkZWZhdWx0IHZhbHVlcwogICAgICBhd2FpdCBfc2V0RGVmYXVsdHMoKTsKCiAgICAgIC8vIEZldGNoIGFuZCBhY3RpdmF0ZQogICAgICBmaW5hbCBhY3RpdmF0ZWQgPSBhd2FpdCBfcmVtb3RlQ29uZmlnLmZldGNoQW5kQWN0aXZhdGUoKTsKCiAgICAgIGxvZ2dlci5pKCdGaXJlYmFzZSBSZW1vdGUgQ29uZmlnIGluaXRpYWxpemVkIHN1Y2Nlc3NmdWxseScpOwogICAgICBsb2dnZXIuaSgnUmVtb3RlIENvbmZpZyBhY3RpdmF0ZWQ6ICRhY3RpdmF0ZWQnKTsKICAgICAgbG9nZ2VyLmkoJ0xhc3QgZmV0Y2ggc3RhdHVzOiAke19yZW1vdGVDb25maWcubGFzdEZldGNoU3RhdHVzfScpOwogICAgICBsb2dnZXIuaSgnTGFzdCBmZXRjaCB0aW1lOiAke19yZW1vdGVDb25maWcubGFzdEZldGNoVGltZX0nKTsKCiAgICAgIC8vIExvZyBhbGwgY3VycmVudCB2YWx1ZXMgZm9yIGRlYnVnZ2luZwogICAgICBmaW5hbCBhbGxWYWx1ZXMgPSBfcmVtb3RlQ29uZmlnLmdldEFsbCgpOwogICAgICBmb3IgKGZpbmFsIGVudHJ5IGluIGFsbFZhbHVlcy5lbnRyaWVzKSB7CiAgICAgICAgbG9nZ2VyLmkoJ1JlbW90ZSBDb25maWcgLSAke2VudHJ5LmtleX06ICR7ZW50cnkudmFsdWUuYXNTdHJpbmcoKX0nKTsKICAgICAgfQogICAgfSBjYXRjaCAoZSwgc3RhY2tUcmFjZSkgewogICAgICBsb2dnZXIuZSgnRmFpbGVkIHRvIGluaXRpYWxpemUgRmlyZWJhc2UgUmVtb3RlIENvbmZpZycsIGUsIHN0YWNrVHJhY2UpOwogICAgICByZXRocm93OwogICAgfQogIH0KCiAgRnV0dXJlPHZvaWQ+IF9zZXREZWZhdWx0cygpIGFzeW5jIHsKICAgIGF3YWl0IF9yZW1vdGVDb25maWcuc2V0RGVmYXVsdHMoX2NvbmZpZy5kZWZhdWx0UGFyYW1ldGVycyk7CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8Ym9vbD4gZ2V0Qm9vbChTdHJpbmcga2V5LCB7Ym9vbCBkZWZhdWx0VmFsdWUgPSBmYWxzZX0pIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIHZhbHVlID0gX3JlbW90ZUNvbmZpZy5nZXRCb29sKGtleSk7CiAgICAgIGZpbmFsIGNvbmZpZ1ZhbHVlID0gX3JlbW90ZUNvbmZpZy5nZXRWYWx1ZShrZXkpOwogICAgICBsb2dnZXIuaSgKICAgICAgICAnR2V0dGluZyBib29sIGZsYWcgIiRrZXkiOiB2YWx1ZT0kdmFsdWUsJwogICAgICAgICcgc291cmNlPSR7Y29uZmlnVmFsdWUuc291cmNlfScsCiAgICAgICk7CiAgICAgIHJldHVybiB2YWx1ZTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgbG9nZ2VyLmUoCiAgICAgICAgJ0ZhaWxlZCB0byBnZXQgYm9vbCBmbGFnOiAka2V5LCB1c2luZyBkZWZhdWx0OiAkZGVmYXVsdFZhbHVlJywKICAgICAgICBlLAogICAgICApOwogICAgICByZXR1cm4gZGVmYXVsdFZhbHVlOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPFN0cmluZz4gZ2V0U3RyaW5nKFN0cmluZyBrZXksIHtTdHJpbmcgZGVmYXVsdFZhbHVlID0gJyd9KSBhc3luYyB7CiAgICB0cnkgewogICAgICBmaW5hbCB2YWx1ZSA9IF9yZW1vdGVDb25maWcuZ2V0U3RyaW5nKGtleSk7CiAgICAgIHJldHVybiB2YWx1ZS5pc0VtcHR5ID8gZGVmYXVsdFZhbHVlIDogdmFsdWU7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIGxvZ2dlci53KCdGYWlsZWQgdG8gZ2V0IHN0cmluZyBmbGFnOiAka2V5LCB1c2luZyBkZWZhdWx0OiAkZGVmYXVsdFZhbHVlJyk7CiAgICAgIHJldHVybiBkZWZhdWx0VmFsdWU7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8aW50PiBnZXRJbnQoU3RyaW5nIGtleSwge2ludCBkZWZhdWx0VmFsdWUgPSAwfSkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgcmV0dXJuIF9yZW1vdGVDb25maWcuZ2V0SW50KGtleSk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIGxvZ2dlci53KCdGYWlsZWQgdG8gZ2V0IGludCBmbGFnOiAka2V5LCB1c2luZyBkZWZhdWx0OiAkZGVmYXVsdFZhbHVlJyk7CiAgICAgIHJldHVybiBkZWZhdWx0VmFsdWU7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8ZG91YmxlPiBnZXREb3VibGUoU3RyaW5nIGtleSwge2RvdWJsZSBkZWZhdWx0VmFsdWUgPSAwLjB9KSBhc3luYyB7CiAgICB0cnkgewogICAgICByZXR1cm4gX3JlbW90ZUNvbmZpZy5nZXREb3VibGUoa2V5KTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgbG9nZ2VyLncoJ0ZhaWxlZCB0byBnZXQgZG91YmxlIGZsYWc6ICRrZXksIHVzaW5nIGRlZmF1bHQ6ICRkZWZhdWx0VmFsdWUnKTsKICAgICAgcmV0dXJuIGRlZmF1bHRWYWx1ZTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTxib29sPiBoYXNGbGFnKFN0cmluZyBrZXkpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIHZhbHVlID0gX3JlbW90ZUNvbmZpZy5nZXRWYWx1ZShrZXkpOwogICAgICByZXR1cm4gdmFsdWUuc291cmNlICE9IFZhbHVlU291cmNlLnZhbHVlU3RhdGljOwogICAgfSBjYXRjaCAoZSkgewogICAgICBsb2dnZXIudygnRmFpbGVkIHRvIGNoZWNrIGlmIGZsYWcgZXhpc3RzOiAka2V5Jyk7CiAgICAgIHJldHVybiBmYWxzZTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIHZvaWQgZGlzcG9zZSgpIHsKICAgIC8vIEZpcmViYXNlIFJlbW90ZSBDb25maWcgZG9lc24ndCByZXF1aXJlIGV4cGxpY2l0IGRpc3Bvc2FsCiAgICBsb2dnZXIuaSgnRmlyZWJhc2UgUmVtb3RlIENvbmZpZyBwcm92aWRlciBkaXNwb3NlZCcpOwogIH0KCiAgLy8vIEdldCB0aGUgbGFzdCBmZXRjaCB0aW1lCiAgRGF0ZVRpbWUgZ2V0IGxhc3RGZXRjaFRpbWUgPT4gX3JlbW90ZUNvbmZpZy5sYXN0RmV0Y2hUaW1lOwoKICAvLy8gR2V0IHRoZSBsYXN0IGZldGNoIHN0YXR1cwogIFJlbW90ZUNvbmZpZ0ZldGNoU3RhdHVzIGdldCBsYXN0RmV0Y2hTdGF0dXMgPT4gX3JlbW90ZUNvbmZpZy5sYXN0RmV0Y2hTdGF0dXM7CgogIC8vLyBBZGQgYSBsaXN0ZW5lciBmb3IgY29uZmlnIHVwZGF0ZXMKICBTdHJlYW08UmVtb3RlQ29uZmlnVXBkYXRlPiBnZXQgb25Db25maWdVcGRhdGVkID0+CiAgICAgIF9yZW1vdGVDb25maWcub25Db25maWdVcGRhdGVkOwoKICAvLy8gR2V0IHRoZSBjdXJyZW50IGNvbmZpZ3VyYXRpb24KICBGZWF0dXJlRmxhZ3NDb25maWcgZ2V0IGNvbmZpZyA9PiBfY29uZmlnOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/index.dart", + "data": + "ZXhwb3J0ICdmZWF0dXJlX2ZsYWdfcHJvdmlkZXIuZGFydCc7CmV4cG9ydCAnZmVhdHVyZV9mbGFnX3NlcnZpY2UuZGFydCc7CmV4cG9ydCAnZmVhdHVyZV9mbGFnX3dyYXBwZXIuZGFydCc7CmV4cG9ydCAnZmlyZWJhc2VfcmVtb3RlX2NvbmZpZ19wcm92aWRlci5kYXJ0JzsKZXhwb3J0ICdyZWdpc3Rlci5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/lib/src/register.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL2xvZ2dlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmVhdHVyZV9mbGFncy9zcmMvZmVhdHVyZV9mbGFnX3Byb3ZpZGVyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmVhdHVyZV9mbGFncy9zcmMvZmVhdHVyZV9mbGFnX3NlcnZpY2UuZGFydCc7CmltcG9ydCAncGFja2FnZTpmZWF0dXJlX2ZsYWdzL3NyYy9maXJlYmFzZV9yZW1vdGVfY29uZmlnX3Byb3ZpZGVyLmRhcnQnOwoKLy8vIFJlZ2lzdGVyIGZlYXR1cmUgZmxhZ3Mgc2VydmljZXMgd2l0aCBkZXBlbmRlbmN5IGluamVjdGlvbgovLy8KRnV0dXJlPHZvaWQ+IHJlZ2lzdGVyRmVhdHVyZUZsYWdzV2l0aERJKHtGZWF0dXJlRmxhZ3NDb25maWc/IGNvbmZpZ30pIGFzeW5jIHsKICBmaW5hbCBsb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpOwoKICAvLyBSZWdpc3RlciB0aGUgcHJvdmlkZXIKICBmaW5hbCBwcm92aWRlciA9IEZpcmViYXNlUmVtb3RlQ29uZmlnUHJvdmlkZXIoY29uZmlnOiBjb25maWcpOwogIGRpLnJlZ2lzdGVyPEZlYXR1cmVGbGFnUHJvdmlkZXI+KHByb3ZpZGVyLCBkaXNwb3NlOiAocCkgPT4gcC5kaXNwb3NlKCkpOwoKICAvLyBSZWdpc3RlciB0aGUgc2VydmljZQogIGZpbmFsIHNlcnZpY2UgPSBGZWF0dXJlRmxhZyhwcm92aWRlcjogcHJvdmlkZXIsIGxvZ2dlcjogbG9nZ2VyKTsKICBkaS5yZWdpc3RlcjxGZWF0dXJlRmxhZz4oc2VydmljZSwgZGlzcG9zZTogKHMpID0+IHMuZGlzcG9zZSgpKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml", + "data": + "bmFtZTogZmVhdHVyZV9mbGFncwpkZXNjcmlwdGlvbjogIlJlbW90ZS1jb25maWcgYmFja2VkIGZlYXR1cmUgZmxhZ3MgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZmlyZWJhc2VfY29yZTogXjMuMTQuMAogIGZpcmViYXNlX3JlbW90ZV9jb25maWc6IF41LjEuNgogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmZsdXR0ZXI6Cg==", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/README.md", + "data": + "IyBGZWF0dXJlIEZsYWdzCgpGZWF0dXJlIGZsYWdzIGFuZCByZW1vdGUgY29uZmlndXJhdGlvbiBtYW5hZ2VtZW50IGZvciB7e25hbWUudGl0bGVDYXNlKCl9fQoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgojIyBJbnN0YWxsYXRpb24g8J+SuwoKKirinZcgSW4gb3JkZXIgdG8gc3RhcnQgdXNpbmcgRmVhdHVyZSBGbGFncyB5b3UgbXVzdCBoYXZlIHRoZSBbRmx1dHRlciBTREtdW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXSBpbnN0YWxsZWQgb24geW91ciBtYWNoaW5lLioqCgpJbnN0YWxsIHZpYSBgZGFydCBwdWIgYWRkYDoKCmBgYHNoCmRhcnQgcHViIGFkZCBmZWF0dXJlX2ZsYWdzCmBgYAoKW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXTogaHR0cHM6Ly9kb2NzLmZsdXR0ZXIuZGV2L2dldC1zdGFydGVkL2luc3RhbGw=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/network.dart", + "data": + "bGlicmFyeTsKCmltcG9ydCAncGFja2FnZTpjb3JlL2NvcmUuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvc3JjL2F1dGgvaW5kZXguZGFydCc7CmltcG9ydCAncGFja2FnZTpuZXR3b3JrL3NyYy9jbGllbnQvaW5kZXguZGFydCc7CmltcG9ydCAncGFja2FnZTpuZXR3b3JrL3NyYy9jb25maWcvaW5kZXguZGFydCc7CgpleHBvcnQgJ3NyYy9hcGkvaW5kZXguZGFydCc7CmV4cG9ydCAnc3JjL2F1dGgvaW5kZXguZGFydCc7CmV4cG9ydCAnc3JjL2NsaWVudC9pbmRleC5kYXJ0JzsKZXhwb3J0ICdzcmMvY29uZmlnL2luZGV4LmRhcnQnOwpleHBvcnQgJ3NyYy9leGNlcHRpb25zL2luZGV4LmRhcnQnOwpleHBvcnQgJ3NyYy9pbnRlcmNlcHRvcnMvaW5kZXguZGFydCc7CmV4cG9ydCAnc3JjL21vZGVscy9pbmRleC5kYXJ0JzsKZXhwb3J0ICdzcmMvdXRpbHMvaW5kZXguZGFydCc7CgpGdXR1cmU8dm9pZD4gaW5pdCh7CiAgcmVxdWlyZWQgTmV0d29ya0NvbmZpZyBjb25maWcsCiAgYm9vbCB1c2VBdXRoZW50aWNhdGlvbiA9IHRydWUsCn0pIGFzeW5jIHsKICBhd2FpdCByZWdpc3Rlck5ldHdvcmtXaXRoREkoY29uZmlnLCB1c2VBdXRoZW50aWNhdGlvbjogdXNlQXV0aGVudGljYXRpb24pOwogIGZpbmFsIGxvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CiAgbG9nZ2VyLmkoJ05ldHdvcmsgbW9kdWxlIGluaXRpYWxpemVkIHdpdGggYmFzZSBVUkw6ICR7Y29uZmlnLmJhc2VVcmx9Jyk7Cn0KCkZ1dHVyZTx2b2lkPiByZWdpc3Rlck5ldHdvcmtXaXRoREkoCiAgTmV0d29ya0NvbmZpZyBjb25maWcsIHsKICBib29sIHVzZUF1dGhlbnRpY2F0aW9uID0gdHJ1ZSwKfSkgYXN5bmMgewogIGZpbmFsIGxvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CgogIC8vIFRyeSB0byBnZXQgQXV0aFRva2VuUHJvdmlkZXIgZnJvbSBESSBpZiBhdXRoZW50aWNhdGlvbiBpcyBlbmFibGVkCiAgQXV0aFRva2VuUHJvdmlkZXI/IGF1dGhUb2tlblByb3ZpZGVyOwogIGlmICh1c2VBdXRoZW50aWNhdGlvbiAmJiBjb25maWcuYXV0aFRva2VuUHJvdmlkZXIgPT0gbnVsbCkgewogICAgdHJ5IHsKICAgICAgYXV0aFRva2VuUHJvdmlkZXIgPSBkaS5nZXQ8QXV0aFRva2VuUHJvdmlkZXI+KCk7CiAgICAgIGxvZ2dlci5kKCfwn5SQIEZvdW5kIEF1dGhUb2tlblByb3ZpZGVyIGluIERJLCB1c2luZyBmb3IgYXV0aGVudGljYXRpb24nKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgbG9nZ2VyLmQoCiAgICAgICAgJ/CflJMgTm8gQXV0aFRva2VuUHJvdmlkZXIgZm91bmQgaW4gREksIGNyZWF0aW5nIHVuYXV0aGVudGljYXRlZCBjbGllbnQnLAogICAgICApOwogICAgfQogIH0gZWxzZSBpZiAoY29uZmlnLmF1dGhUb2tlblByb3ZpZGVyICE9IG51bGwpIHsKICAgIGF1dGhUb2tlblByb3ZpZGVyID0gY29uZmlnLmF1dGhUb2tlblByb3ZpZGVyOwogICAgbG9nZ2VyLmQoJ/CflJAgVXNpbmcgcHJvdmlkZWQgQXV0aFRva2VuUHJvdmlkZXInKTsKICB9CgogIC8vIENyZWF0ZSBuZXR3b3JrIGNvbmZpZyB3aXRoIGF1dGggcHJvdmlkZXIgaWYgYXZhaWxhYmxlCiAgbGF0ZSBmaW5hbCBOZXR3b3JrQ29uZmlnIGZpbmFsQ29uZmlnOwogIGlmIChhdXRoVG9rZW5Qcm92aWRlciAhPSBudWxsICYmIGNvbmZpZy5hdXRoVG9rZW5Qcm92aWRlciA9PSBudWxsKSB7CiAgICAvLyBDcmVhdGUgYSBuZXcgY29uZmlnIHdpdGggdGhlIGF1dGggcHJvdmlkZXIKICAgIGZpbmFsQ29uZmlnID0gRGVmYXVsdE5ldHdvcmtDb25maWcoCiAgICAgIGJhc2VVcmw6IGNvbmZpZy5iYXNlVXJsLAogICAgICBjb25uZWN0VGltZW91dDogY29uZmlnLmNvbm5lY3RUaW1lb3V0LAogICAgICByZWNlaXZlVGltZW91dDogY29uZmlnLnJlY2VpdmVUaW1lb3V0LAogICAgICBzZW5kVGltZW91dDogY29uZmlnLnNlbmRUaW1lb3V0LAogICAgICBkZWZhdWx0SGVhZGVyczogY29uZmlnLmRlZmF1bHRIZWFkZXJzLAogICAgICBlbmFibGVMb2dnaW5nOiBjb25maWcuZW5hYmxlTG9nZ2luZywKICAgICAgYXV0aFRva2VuUHJvdmlkZXI6IGF1dGhUb2tlblByb3ZpZGVyLAogICAgKTsKICB9IGVsc2UgewogICAgZmluYWxDb25maWcgPSBjb25maWc7CiAgfQoKICBmaW5hbCBuZXR3b3JrQ2xpZW50ID0gRGlvTmV0d29ya0NsaWVudChmaW5hbENvbmZpZyk7CiAgZGkucmVnaXN0ZXI8TmV0d29ya0NsaWVudD4oCiAgICBuZXR3b3JrQ2xpZW50LAogICAgZGlzcG9zZTogKGNsaWVudCkgPT4gY2xpZW50LmRpc3Bvc2UoKSwKICApOwogIGRpLnJlZ2lzdGVyPE5ldHdvcmtDb25maWc+KGZpbmFsQ29uZmlnKTsKCiAgaWYgKGZpbmFsQ29uZmlnLmF1dGhUb2tlblByb3ZpZGVyICE9IG51bGwpIHsKICAgIGxvZ2dlci5pKCfwn5SQIE5ldHdvcmsgY2xpZW50IHJlZ2lzdGVyZWQgd2l0aCBhdXRoZW50aWNhdGlvbiBzdXBwb3J0Jyk7CiAgfSBlbHNlIHsKICAgIGxvZ2dlci5pKCfwn5STIE5ldHdvcmsgY2xpZW50IHJlZ2lzdGVyZWQgd2l0aG91dCBhdXRoZW50aWNhdGlvbicpOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/api/index.dart", + "data": + "Ly9HRU5FUkFURUQgQkFSUkVMIEZJTEUKZXhwb3J0ICduZXR3b3JrX2FwaS5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/api/network_api.dart", + "data": + "YWJzdHJhY3QgY2xhc3MgQXBpVXJsQ29uZmlnIHsKICBmaW5hbCBTdHJpbmcgcGF0aFByZWZpeDsKCiAgY29uc3QgQXBpVXJsQ29uZmlnKHtyZXF1aXJlZCB0aGlzLnBhdGhQcmVmaXh9KTsKfQoKYWJzdHJhY3QgY2xhc3MgTmV0d29ya0FwaTxUIGV4dGVuZHMgQXBpVXJsQ29uZmlnPiB7CiAgZmluYWwgVCB1cmxDb25maWc7CgogIGNvbnN0IE5ldHdvcmtBcGkoe3JlcXVpcmVkIHRoaXMudXJsQ29uZmlnfSk7CgogIE1hcDxTdHJpbmcsIFN0cmluZz4gZ2V0IGhlYWRlcnM7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/auth/auth_token_provider.dart", + "data": + "YWJzdHJhY3QgY2xhc3MgQXV0aFRva2VuUHJvdmlkZXIgewogIC8vLyBHZXRzIGEgdmFsaWQgYXV0aGVudGljYXRpb24gdG9rZW4KICAvLy8gUmV0dXJucyBudWxsIGlmIHVzZXIgaXMgbm90IGF1dGhlbnRpY2F0ZWQKICBGdXR1cmU8U3RyaW5nPz4gZ2V0VmFsaWRUb2tlbigpOwoKICAvLy8gR2V0cyB0aGUgY3VycmVudCB0b2tlbiB3aXRob3V0IHZhbGlkYXRpb24KICAvLy8gUmV0dXJucyBudWxsIGlmIG5vIHRva2VuIGlzIGF2YWlsYWJsZQogIFN0cmluZz8gZ2V0Q3VycmVudFRva2VuKCk7CgogIC8vLyBTdHJlYW0gb2YgYXV0aGVudGljYXRpb24gdG9rZW4gY2hhbmdlcwogIC8vLyBFbWl0cyBuZXcgdG9rZW5zIHdoZW4gYXV0aGVudGljYXRpb24gc3RhdGUgY2hhbmdlcwogIFN0cmVhbTxTdHJpbmc/PiBnZXQgdG9rZW5TdHJlYW07CgogIC8vLyBGb3JjZXMgYSB0b2tlbiByZWZyZXNoCiAgLy8vIFJldHVybnMgdGhlIG5ldyB0b2tlbiBvciBudWxsIGlmIHJlZnJlc2ggZmFpbHMKICBGdXR1cmU8U3RyaW5nPz4gcmVmcmVzaFRva2VuKCk7CgogIC8vLyBDaGVja3MgaWYgdGhlIHVzZXIgaXMgY3VycmVudGx5IGF1dGhlbnRpY2F0ZWQKICBib29sIGdldCBpc0F1dGhlbnRpY2F0ZWQ7CgogIC8vLyBDaGVja3MgaWYgdGhlIGN1cnJlbnQgdG9rZW4gaXMgZXhwaXJlZAogIC8vLyBSZXR1cm5zIHRydWUgaWYgdG9rZW4gaXMgbnVsbCBvciBleHBpcmVkCiAgYm9vbCBnZXQgaXNUb2tlbkV4cGlyZWQ7CgogIC8vLyBTaWduIG91dCB0aGUgY3VycmVudCB1c2VyCiAgRnV0dXJlPHZvaWQ+IHNpZ25PdXQoKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/auth/index.dart", + "data": "ZXhwb3J0ICdhdXRoX3Rva2VuX3Byb3ZpZGVyLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/dio_network_client.dart", + "data": + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjb3JlL2NvcmUuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpby9kaW8uZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyL2ZvdW5kYXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpsb2NhbGl6YXRpb24vbG9jYWxpemF0aW9uLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bmV0d29yay9zcmMvY2xpZW50L25ldHdvcmtfY2xpZW50LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bmV0d29yay9zcmMvY29uZmlnL25ldHdvcmtfY29uZmlnLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bmV0d29yay9zcmMvZXhjZXB0aW9ucy9uZXR3b3JrX2V4Y2VwdGlvbnMuZGFydCc7CmltcG9ydCAncGFja2FnZTpuZXR3b3JrL3NyYy9pbnRlcmNlcHRvcnMvYXV0aF9pbnRlcmNlcHRvci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvc3JjL21vZGVscy9uZXR3b3JrX3Jlc3BvbnNlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6dGFsa2VyX2Rpb19sb2dnZXIvdGFsa2VyX2Rpb19sb2dnZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTp0YWxrZXJfZmx1dHRlci90YWxrZXJfZmx1dHRlci5kYXJ0JzsKCmNsYXNzIERpb05ldHdvcmtDbGllbnQgaW1wbGVtZW50cyBOZXR3b3JrQ2xpZW50IHsKICBEaW9OZXR3b3JrQ2xpZW50KHRoaXMuX2NvbmZpZykgOiBfZGlvID0gRGlvKCkgewogICAgX3NldHVwRGlvKCk7CiAgfQoKICBmaW5hbCBOZXR3b3JrQ29uZmlnIF9jb25maWc7CiAgZmluYWwgRGlvIF9kaW87CiAgZmluYWwgTG9nZ2VyIF9sb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpOwoKICB2b2lkIF9zZXR1cERpbygpIHsKICAgIF9kaW8ub3B0aW9ucyA9IEJhc2VPcHRpb25zKAogICAgICBiYXNlVXJsOiBfY29uZmlnLmJhc2VVcmwsCiAgICAgIGNvbm5lY3RUaW1lb3V0OiBfY29uZmlnLmNvbm5lY3RUaW1lb3V0LAogICAgICByZWNlaXZlVGltZW91dDogX2NvbmZpZy5yZWNlaXZlVGltZW91dCwKICAgICAgLy9zZW5kVGltZW91dCBjYW5ub3QgYmUgdXNlZCB3aXRob3V0IGEgcmVxdWVzdCBib2R5IHRvIHNlbmQgb24gV2ViCiAgICAgIHNlbmRUaW1lb3V0OiBrSXNXZWIgPyBudWxsIDogX2NvbmZpZy5zZW5kVGltZW91dCwKICAgICAgaGVhZGVyczogX2NvbmZpZy5kZWZhdWx0SGVhZGVycywKICAgICk7CgogICAgLy8gQWRkIGF1dGhlbnRpY2F0aW9uIGludGVyY2VwdG9yIGlmIHRva2VuIHByb3ZpZGVyIGlzIGF2YWlsYWJsZQogICAgaWYgKF9jb25maWcuYXV0aFRva2VuUHJvdmlkZXIgIT0gbnVsbCkgewogICAgICBfZGlvLmludGVyY2VwdG9ycy5hZGQoQXV0aEludGVyY2VwdG9yKF9jb25maWcuYXV0aFRva2VuUHJvdmlkZXIhKSk7CiAgICAgIF9sb2dnZXIuZCgn8J+UkCBBdXRoIGludGVyY2VwdG9yIGFkZGVkIHRvIG5ldHdvcmsgY2xpZW50Jyk7CiAgICB9CgogICAgaWYgKF9jb25maWcuZW5hYmxlTG9nZ2luZykgewogICAgICBfZGlvLmludGVyY2VwdG9ycy5hZGQoCiAgICAgICAgVGFsa2VyRGlvTG9nZ2VyKAogICAgICAgICAgdGFsa2VyOiBkaS5nZXQ8TG9nZ2VyPigpLmxvZ2dlciBhcyBUYWxrZXIsCiAgICAgICAgICBzZXR0aW5nczogVGFsa2VyRGlvTG9nZ2VyU2V0dGluZ3MoCiAgICAgICAgICAgIHByaW50UmVzcG9uc2VEYXRhOiB0cnVlLAogICAgICAgICAgICBwcmludFJlc3BvbnNlVGltZTogdHJ1ZSwKICAgICAgICAgICksCiAgICAgICAgKSwKICAgICAgKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTxOZXR3b3JrUmVzcG9uc2U8VD4+IGdldDxUPigKICAgIFN0cmluZyBwYXRoLCB7CiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcXVlcnlQYXJhbWV0ZXJzLAogICAgTWFwPFN0cmluZywgU3RyaW5nPj8gaGVhZGVycywKICAgIHJlcXVpcmVkIFQgRnVuY3Rpb24oT2JqZWN0PyBqc29uKSBmcm9tSnNvblQsCiAgfSkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgZmluYWwgcmVzcG9uc2UgPSBhd2FpdCBfZGlvLmdldCgKICAgICAgICBwYXRoLAogICAgICAgIHF1ZXJ5UGFyYW1ldGVyczogcXVlcnlQYXJhbWV0ZXJzLAogICAgICAgIG9wdGlvbnM6IE9wdGlvbnMoaGVhZGVyczogaGVhZGVycyksCiAgICAgICk7CiAgICAgIHJldHVybiBfaGFuZGxlUmVzcG9uc2U8VD4ocmVzcG9uc2UsIGZyb21Kc29uVDogZnJvbUpzb25UKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgcmV0dXJuIF9oYW5kbGVFcnJvcjxUPihlKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTxOZXR3b3JrUmVzcG9uc2U8VD4+IHBvc3Q8VD4oCiAgICBTdHJpbmcgcGF0aCwgewogICAgZHluYW1pYyBkYXRhLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHF1ZXJ5UGFyYW1ldGVycywKICAgIE1hcDxTdHJpbmcsIFN0cmluZz4/IGhlYWRlcnMsCiAgICByZXF1aXJlZCBUIEZ1bmN0aW9uKE9iamVjdD8ganNvbikgZnJvbUpzb25ULAogIH0pIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIHJlc3BvbnNlID0gYXdhaXQgX2Rpby5wb3N0KAogICAgICAgIHBhdGgsCiAgICAgICAgZGF0YTogZGF0YSwKICAgICAgICBxdWVyeVBhcmFtZXRlcnM6IHF1ZXJ5UGFyYW1ldGVycywKICAgICAgICBvcHRpb25zOiBPcHRpb25zKGhlYWRlcnM6IGhlYWRlcnMpLAogICAgICApOwogICAgICByZXR1cm4gX2hhbmRsZVJlc3BvbnNlPFQ+KHJlc3BvbnNlLCBmcm9tSnNvblQ6IGZyb21Kc29uVCk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIHJldHVybiBfaGFuZGxlRXJyb3I8VD4oZSk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8TmV0d29ya1Jlc3BvbnNlPFQ+PiBwdXQ8VD4oCiAgICBTdHJpbmcgcGF0aCwgewogICAgZHluYW1pYyBkYXRhLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHF1ZXJ5UGFyYW1ldGVycywKICAgIE1hcDxTdHJpbmcsIFN0cmluZz4/IGhlYWRlcnMsCiAgICByZXF1aXJlZCBUIEZ1bmN0aW9uKE9iamVjdD8ganNvbikgZnJvbUpzb25ULAogIH0pIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIHJlc3BvbnNlID0gYXdhaXQgX2Rpby5wdXQoCiAgICAgICAgcGF0aCwKICAgICAgICBkYXRhOiBkYXRhLAogICAgICAgIHF1ZXJ5UGFyYW1ldGVyczogcXVlcnlQYXJhbWV0ZXJzLAogICAgICAgIG9wdGlvbnM6IE9wdGlvbnMoaGVhZGVyczogaGVhZGVycyksCiAgICAgICk7CiAgICAgIHJldHVybiBfaGFuZGxlUmVzcG9uc2U8VD4ocmVzcG9uc2UsIGZyb21Kc29uVDogZnJvbUpzb25UKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgcmV0dXJuIF9oYW5kbGVFcnJvcjxUPihlKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTxOZXR3b3JrUmVzcG9uc2U8VD4+IHBhdGNoPFQ+KAogICAgU3RyaW5nIHBhdGgsIHsKICAgIGR5bmFtaWMgZGF0YSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBxdWVyeVBhcmFtZXRlcnMsCiAgICBNYXA8U3RyaW5nLCBTdHJpbmc+PyBoZWFkZXJzLAogICAgcmVxdWlyZWQgVCBGdW5jdGlvbihPYmplY3Q/IGpzb24pIGZyb21Kc29uVCwKICB9KSBhc3luYyB7CiAgICB0cnkgewogICAgICBmaW5hbCByZXNwb25zZSA9IGF3YWl0IF9kaW8ucGF0Y2goCiAgICAgICAgcGF0aCwKICAgICAgICBkYXRhOiBkYXRhLAogICAgICAgIHF1ZXJ5UGFyYW1ldGVyczogcXVlcnlQYXJhbWV0ZXJzLAogICAgICAgIG9wdGlvbnM6IE9wdGlvbnMoaGVhZGVyczogaGVhZGVycyksCiAgICAgICk7CiAgICAgIHJldHVybiBfaGFuZGxlUmVzcG9uc2U8VD4ocmVzcG9uc2UsIGZyb21Kc29uVDogZnJvbUpzb25UKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgcmV0dXJuIF9oYW5kbGVFcnJvcjxUPihlKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTxOZXR3b3JrUmVzcG9uc2U8VD4+IGRlbGV0ZTxUPigKICAgIFN0cmluZyBwYXRoLCB7CiAgICBkeW5hbWljIGRhdGEsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcXVlcnlQYXJhbWV0ZXJzLAogICAgTWFwPFN0cmluZywgU3RyaW5nPj8gaGVhZGVycywKICAgIHJlcXVpcmVkIFQgRnVuY3Rpb24oT2JqZWN0PyBqc29uKSBmcm9tSnNvblQsCiAgfSkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgZmluYWwgcmVzcG9uc2UgPSBhd2FpdCBfZGlvLmRlbGV0ZSgKICAgICAgICBwYXRoLAogICAgICAgIGRhdGE6IGRhdGEsCiAgICAgICAgcXVlcnlQYXJhbWV0ZXJzOiBxdWVyeVBhcmFtZXRlcnMsCiAgICAgICAgb3B0aW9uczogT3B0aW9ucyhoZWFkZXJzOiBoZWFkZXJzKSwKICAgICAgKTsKICAgICAgcmV0dXJuIF9oYW5kbGVSZXNwb25zZTxUPihyZXNwb25zZSwgZnJvbUpzb25UOiBmcm9tSnNvblQpOwogICAgfSBjYXRjaCAoZSkgewogICAgICByZXR1cm4gX2hhbmRsZUVycm9yPFQ+KGUpOwogICAgfQogIH0KCiAgTmV0d29ya1Jlc3BvbnNlPFQ+IF9oYW5kbGVSZXNwb25zZTxUPigKICAgIFJlc3BvbnNlIHJlc3BvbnNlLCB7CiAgICByZXF1aXJlZCBUIEZ1bmN0aW9uKE9iamVjdD8ganNvbikgZnJvbUpzb25ULAogIH0pIHsKICAgIHRyeSB7CiAgICAgIGlmIChyZXNwb25zZS5kYXRhIGlzIE1hcDxTdHJpbmcsIGR5bmFtaWM+KSB7CiAgICAgICAgZmluYWwgcmVzcG9uc2VEYXRhID0gcmVzcG9uc2UuZGF0YSBhcyBNYXA8U3RyaW5nLCBkeW5hbWljPjsKCiAgICAgICAgaWYgKHJlc3BvbnNlRGF0YS5jb250YWluc0tleSgnc3VjY2VzcycpKSB7CiAgICAgICAgICBpZiAocmVzcG9uc2VEYXRhWydzdWNjZXNzJ10gPT0gdHJ1ZSkgewogICAgICAgICAgICByZXR1cm4gU3VjY2Vzc1Jlc3BvbnNlLmZyb21Kc29uKHsKICAgICAgICAgICAgICAnc3RhdHVzQ29kZSc6IHJlc3BvbnNlLnN0YXR1c0NvZGUgPz8gMjAwLAogICAgICAgICAgICAgICdoZWFkZXJzJzogcmVzcG9uc2UuaGVhZGVycy5tYXAsCiAgICAgICAgICAgICAgLi4ucmVzcG9uc2VEYXRhLAogICAgICAgICAgICB9LCBmcm9tSnNvblQpOwogICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgRXJyb3JSZXNwb25zZS5mcm9tSnNvbih7CiAgICAgICAgICAgICAgJ3N0YXR1c0NvZGUnOiByZXNwb25zZS5zdGF0dXNDb2RlID8/IDUwMCwKICAgICAgICAgICAgICAnaGVhZGVycyc6IHJlc3BvbnNlLmhlYWRlcnMubWFwLAogICAgICAgICAgICAgIC4uLnJlc3BvbnNlRGF0YSwKICAgICAgICAgICAgfSk7CiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CgogICAgICAvLyBJZGVhbGx5LCBzaG91bGQgbm90IHJlYWNoIGhlcmUgaWYgdGhlIEFQSSBmb2xsb3dzIHRoZQogICAgICAvLyBzdGFuZGFyZCByZXNwb25zZSBmb3JtYXQKICAgICAgX2xvZ2dlci53KCdJbnZhbGlkIHJlc3BvbnNlIGZvcm1hdDogJHtyZXNwb25zZS5kYXRhfScpOwoKICAgICAgLy8gRmFsbGJhY2sgdG8gYSBnZW5lcmljIHN1Y2Nlc3MgcmVzcG9uc2UKICAgICAgcmV0dXJuIFN1Y2Nlc3NSZXNwb25zZS5mcm9tSnNvbih7CiAgICAgICAgJ3N0YXR1c0NvZGUnOiByZXNwb25zZS5zdGF0dXNDb2RlID8/IDIwMCwKICAgICAgICAnaGVhZGVycyc6IHJlc3BvbnNlLmhlYWRlcnMubWFwLAogICAgICAgICdkYXRhJzogcmVzcG9uc2UuZGF0YSwKICAgICAgfSwgKHZhbHVlKSA9PiB2YWx1ZSBhcyBUKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCdFcnJvciBwYXJzaW5nIHJlc3BvbnNlJywgZSk7CiAgICAgIHJldHVybiBFcnJvclJlc3BvbnNlKAogICAgICAgIHN0YXR1c0NvZGU6IHJlc3BvbnNlLnN0YXR1c0NvZGUgPz8gNTAwLAogICAgICAgIHN1Y2Nlc3M6IGZhbHNlLAogICAgICAgIG1lc3NhZ2U6ICdOZXR3b3JrIENsaWVudCBmYWlsZWQgdG8gcGFyc2UgcmVzcG9uc2UhJywKICAgICAgICBlcnJvcjogRXJyb3JEZXRhaWxzKAogICAgICAgICAgY29kZTogJ1BBUlNFX0VSUk9SJywKICAgICAgICAgIG1lc3NhZ2U6IGUudG9TdHJpbmcoKSwKICAgICAgICAgIGRldGFpbHM6IHsnc3RhY2tUcmFjZSc6IHMudG9TdHJpbmcoKX0sCiAgICAgICAgKSwKICAgICAgICB0aW1lc3RhbXA6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogICAgICApOwogICAgfQogIH0KCiAgTmV0d29ya1Jlc3BvbnNlPFQ+IF9oYW5kbGVFcnJvcjxUPihkeW5hbWljIGVycm9yKSB7CiAgICBOZXR3b3JrRXhjZXB0aW9uIG5ldHdvcmtFeGNlcHRpb247CiAgICBFcnJvclJlc3BvbnNlPyBhcGlFcnJvcjsKCiAgICBpZiAoZXJyb3IgaXMgRGlvRXhjZXB0aW9uKSB7CiAgICAgIGFwaUVycm9yID0gX3BhcnNlQXBpRXJyb3IoZXJyb3IucmVzcG9uc2UpOwogICAgICBuZXR3b3JrRXhjZXB0aW9uID0gX21hcERpb0V4Y2VwdGlvbihlcnJvciwgYXBpRXJyb3IpOwogICAgfSBlbHNlIHsKICAgICAgbmV0d29ya0V4Y2VwdGlvbiA9IFVua25vd25OZXR3b3JrRXhjZXB0aW9uKG1lc3NhZ2U6IGVycm9yLnRvU3RyaW5nKCkpOwogICAgfQoKICAgIF9sb2dnZXIuZSgnTmV0d29yayBlcnJvciBvY2N1cnJlZCcsIG5ldHdvcmtFeGNlcHRpb24pOwoKICAgIHRocm93IG5ldHdvcmtFeGNlcHRpb247CiAgfQoKICBFcnJvclJlc3BvbnNlPyBfcGFyc2VBcGlFcnJvcihSZXNwb25zZT8gcmVzcG9uc2UpIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIHJlc3BvbnNlRGF0YSA9IHJlc3BvbnNlPy5kYXRhOwogICAgICBpZiAocmVzcG9uc2VEYXRhIGlzIE1hcDxTdHJpbmcsIGR5bmFtaWM+ICYmCiAgICAgICAgICByZXNwb25zZURhdGEuY29udGFpbnNLZXkoJ3N1Y2Nlc3MnKSAmJgogICAgICAgICAgcmVzcG9uc2VEYXRhWydzdWNjZXNzJ10gPT0gZmFsc2UpIHsKICAgICAgICByZXR1cm4gRXJyb3JSZXNwb25zZS5mcm9tSnNvbih7CiAgICAgICAgICAnc3RhdHVzQ29kZSc6IHJlc3BvbnNlPy5zdGF0dXNDb2RlID8/IDUwMCwKICAgICAgICAgICdoZWFkZXJzJzogcmVzcG9uc2U/LmhlYWRlcnMubWFwLAogICAgICAgICAgLi4ucmVzcG9uc2VEYXRhLAogICAgICAgIH0pOwogICAgICB9CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2dnZXIudygnRmFpbGVkIHRvIHBhcnNlIEFQSSBlcnJvcjogJGUnKTsKICAgIH0KICAgIHJldHVybiBudWxsOwogIH0KCiAgTmV0d29ya0V4Y2VwdGlvbiBfbWFwRGlvRXhjZXB0aW9uKAogICAgRGlvRXhjZXB0aW9uIGRpb0V4Y2VwdGlvbiwKICAgIEVycm9yUmVzcG9uc2U/IGFwaUVycm9yLAogICkgewogICAgZmluYWwgbWVzc2FnZSA9IGFwaUVycm9yPy5lcnJvci5tZXNzYWdlOwoKICAgIHN3aXRjaCAoZGlvRXhjZXB0aW9uLnR5cGUpIHsKICAgICAgY2FzZSBEaW9FeGNlcHRpb25UeXBlLmNvbm5lY3Rpb25UaW1lb3V0OgogICAgICAgIHJldHVybiBDb25uZWN0aW9uVGltZW91dEV4Y2VwdGlvbihtZXNzYWdlOiBtZXNzYWdlKTsKICAgICAgY2FzZSBEaW9FeGNlcHRpb25UeXBlLnJlY2VpdmVUaW1lb3V0OgogICAgICAgIHJldHVybiBSZWNlaXZlVGltZW91dEV4Y2VwdGlvbihtZXNzYWdlOiBtZXNzYWdlKTsKICAgICAgY2FzZSBEaW9FeGNlcHRpb25UeXBlLnNlbmRUaW1lb3V0OgogICAgICAgIHJldHVybiBTZW5kVGltZW91dEV4Y2VwdGlvbihtZXNzYWdlOiBtZXNzYWdlKTsKICAgICAgY2FzZSBEaW9FeGNlcHRpb25UeXBlLmNvbm5lY3Rpb25FcnJvcjoKICAgICAgICBpZiAoZGlvRXhjZXB0aW9uLmVycm9yIGlzIFNvY2tldEV4Y2VwdGlvbikgewogICAgICAgICAgcmV0dXJuIE5vSW50ZXJuZXRDb25uZWN0aW9uRXhjZXB0aW9uKG1lc3NhZ2U6IG1lc3NhZ2UpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gVW5rbm93bk5ldHdvcmtFeGNlcHRpb24obWVzc2FnZTogbWVzc2FnZSk7CiAgICAgIGNhc2UgRGlvRXhjZXB0aW9uVHlwZS5iYWRSZXNwb25zZToKICAgICAgICBmaW5hbCBzdGF0dXNDb2RlID0gZGlvRXhjZXB0aW9uLnJlc3BvbnNlPy5zdGF0dXNDb2RlOwogICAgICAgIHN3aXRjaCAoc3RhdHVzQ29kZSkgewogICAgICAgICAgY2FzZSA0MDA6CiAgICAgICAgICAgIHJldHVybiBCYWRSZXF1ZXN0RXhjZXB0aW9uKG1lc3NhZ2U6IG1lc3NhZ2UsIGVycm9yOiBhcGlFcnJvcik7CiAgICAgICAgICBjYXNlIDQwMToKICAgICAgICAgICAgcmV0dXJuIFVuYXV0aG9yaXplZEV4Y2VwdGlvbihtZXNzYWdlOiBtZXNzYWdlLCBlcnJvcjogYXBpRXJyb3IpOwogICAgICAgICAgY2FzZSA0MDM6CiAgICAgICAgICAgIHJldHVybiBGb3JiaWRkZW5FeGNlcHRpb24obWVzc2FnZTogbWVzc2FnZSwgZXJyb3I6IGFwaUVycm9yKTsKICAgICAgICAgIGNhc2UgNDA0OgogICAgICAgICAgICByZXR1cm4gTm90Rm91bmRFeGNlcHRpb24obWVzc2FnZTogbWVzc2FnZSwgZXJyb3I6IGFwaUVycm9yKTsKICAgICAgICAgIGNhc2UgNDA5OgogICAgICAgICAgICByZXR1cm4gQ29uZmxpY3RFeGNlcHRpb24obWVzc2FnZTogbWVzc2FnZSwgZXJyb3I6IGFwaUVycm9yKTsKICAgICAgICAgIGNhc2UgNDIyOgogICAgICAgICAgICByZXR1cm4gVW5wcm9jZXNzYWJsZUVudGl0eUV4Y2VwdGlvbigKICAgICAgICAgICAgICBtZXNzYWdlOiBtZXNzYWdlLAogICAgICAgICAgICAgIGVycm9yOiBhcGlFcnJvciwKICAgICAgICAgICAgKTsKICAgICAgICAgIGNhc2UgNTAwOgogICAgICAgICAgY2FzZSA1MDI6CiAgICAgICAgICBjYXNlIDUwMzoKICAgICAgICAgIGNhc2UgNTA0OgogICAgICAgICAgICByZXR1cm4gSW50ZXJuYWxTZXJ2ZXJFeGNlcHRpb24obWVzc2FnZTogbWVzc2FnZSwgZXJyb3I6IGFwaUVycm9yKTsKICAgICAgICAgIGRlZmF1bHQ6CiAgICAgICAgICAgIHJldHVybiBVbmtub3duTmV0d29ya0V4Y2VwdGlvbigKICAgICAgICAgICAgICBtZXNzYWdlOiBtZXNzYWdlLAogICAgICAgICAgICAgIHN0YXR1c0NvZGU6IHN0YXR1c0NvZGUsCiAgICAgICAgICAgICAgZXJyb3I6IGFwaUVycm9yLAogICAgICAgICAgICApOwogICAgICAgIH0KICAgICAgZGVmYXVsdDoKICAgICAgICByZXR1cm4gVW5rbm93bk5ldHdvcmtFeGNlcHRpb24obWVzc2FnZTogbWVzc2FnZSwgZXJyb3I6IGFwaUVycm9yKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIFQgaGFuZGxlUmVzcG9uc2U8VD4oZmluYWwgTmV0d29ya1Jlc3BvbnNlPFQ+IHJlc3BvbnNlKSB7CiAgICBzd2l0Y2ggKHJlc3BvbnNlKSB7CiAgICAgIGNhc2UgU3VjY2Vzc1Jlc3BvbnNlPFQ+KCk6CiAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmRhdGEhOwogICAgICBjYXNlIEVycm9yUmVzcG9uc2U8VD4oKToKICAgICAgICB0aHJvdyBVbmtub3duTmV0d29ya0V4Y2VwdGlvbigKICAgICAgICAgIG1lc3NhZ2U6IHJlc3BvbnNlLm1lc3NhZ2UgPz8gc3RyaW5ncy5lcnJvcnMubmV0d29ya19lcnJvciwKICAgICAgICAgIHN0YXR1c0NvZGU6IHJlc3BvbnNlLnN0YXR1c0NvZGUsCiAgICAgICAgICBlcnJvcjogcmVzcG9uc2UsCiAgICAgICAgKTsKICAgICAgZGVmYXVsdDoKICAgICAgICB0aHJvdyBVbmtub3duTmV0d29ya0V4Y2VwdGlvbigKICAgICAgICAgIG1lc3NhZ2U6IHN0cmluZ3MuZXJyb3JzLnVua25vd25fZXJyb3IsCiAgICAgICAgICBzdGF0dXNDb2RlOiByZXNwb25zZS5zdGF0dXNDb2RlLAogICAgICAgICk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBTdWNjZXNzUmVzcG9uc2U8VD4gaGFuZGxlU3VjY2Vzc1Jlc3BvbnNlPFQ+KAogICAgZmluYWwgTmV0d29ya1Jlc3BvbnNlPFQ+IHJlc3BvbnNlLAogICkgewogICAgc3dpdGNoIChyZXNwb25zZSkgewogICAgICBjYXNlIFN1Y2Nlc3NSZXNwb25zZTxUPigpOgogICAgICAgIHJldHVybiByZXNwb25zZTsKICAgICAgY2FzZSBFcnJvclJlc3BvbnNlPFQ+KCk6CiAgICAgICAgdGhyb3cgVW5rbm93bk5ldHdvcmtFeGNlcHRpb24oCiAgICAgICAgICBtZXNzYWdlOiByZXNwb25zZS5tZXNzYWdlID8/IHN0cmluZ3MuZXJyb3JzLm5ldHdvcmtfZXJyb3IsCiAgICAgICAgICBzdGF0dXNDb2RlOiByZXNwb25zZS5zdGF0dXNDb2RlLAogICAgICAgICAgZXJyb3I6IHJlc3BvbnNlLAogICAgICAgICk7CiAgICAgIGRlZmF1bHQ6CiAgICAgICAgdGhyb3cgVW5rbm93bk5ldHdvcmtFeGNlcHRpb24oCiAgICAgICAgICBtZXNzYWdlOiBzdHJpbmdzLmVycm9ycy51bmtub3duX2Vycm9yLAogICAgICAgICAgc3RhdHVzQ29kZTogcmVzcG9uc2Uuc3RhdHVzQ29kZSwKICAgICAgICApOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBkaXNwb3NlKCkgewogICAgX2Rpby5jbG9zZSgpOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/index.dart", + "data": + "ZXhwb3J0ICdkaW9fbmV0d29ya19jbGllbnQuZGFydCc7CmV4cG9ydCAnbmV0d29ya19jbGllbnQuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/client/network_client.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvc3JjL21vZGVscy9uZXR3b3JrX3Jlc3BvbnNlLmRhcnQnOwoKYWJzdHJhY3QgY2xhc3MgTmV0d29ya0NsaWVudCB7CiAgRnV0dXJlPE5ldHdvcmtSZXNwb25zZTxUPj4gZ2V0PFQ+KAogICAgU3RyaW5nIHBhdGgsIHsKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBxdWVyeVBhcmFtZXRlcnMsCiAgICBNYXA8U3RyaW5nLCBTdHJpbmc+PyBoZWFkZXJzLAogICAgcmVxdWlyZWQgVCBGdW5jdGlvbihPYmplY3Q/IGpzb24pIGZyb21Kc29uVCwKICB9KTsKCiAgRnV0dXJlPE5ldHdvcmtSZXNwb25zZTxUPj4gcG9zdDxUPigKICAgIFN0cmluZyBwYXRoLCB7CiAgICBkeW5hbWljIGRhdGEsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcXVlcnlQYXJhbWV0ZXJzLAogICAgTWFwPFN0cmluZywgU3RyaW5nPj8gaGVhZGVycywKICAgIHJlcXVpcmVkIFQgRnVuY3Rpb24oT2JqZWN0PyBqc29uKSBmcm9tSnNvblQsCiAgfSk7CgogIEZ1dHVyZTxOZXR3b3JrUmVzcG9uc2U8VD4+IHB1dDxUPigKICAgIFN0cmluZyBwYXRoLCB7CiAgICBkeW5hbWljIGRhdGEsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcXVlcnlQYXJhbWV0ZXJzLAogICAgTWFwPFN0cmluZywgU3RyaW5nPj8gaGVhZGVycywKICAgIHJlcXVpcmVkIFQgRnVuY3Rpb24oT2JqZWN0PyBqc29uKSBmcm9tSnNvblQsCiAgfSk7CgogIEZ1dHVyZTxOZXR3b3JrUmVzcG9uc2U8VD4+IHBhdGNoPFQ+KAogICAgU3RyaW5nIHBhdGgsIHsKICAgIGR5bmFtaWMgZGF0YSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBxdWVyeVBhcmFtZXRlcnMsCiAgICBNYXA8U3RyaW5nLCBTdHJpbmc+PyBoZWFkZXJzLAogICAgcmVxdWlyZWQgVCBGdW5jdGlvbihPYmplY3Q/IGpzb24pIGZyb21Kc29uVCwKICB9KTsKCiAgRnV0dXJlPE5ldHdvcmtSZXNwb25zZTxUPj4gZGVsZXRlPFQ+KAogICAgU3RyaW5nIHBhdGgsIHsKICAgIGR5bmFtaWMgZGF0YSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBxdWVyeVBhcmFtZXRlcnMsCiAgICBNYXA8U3RyaW5nLCBTdHJpbmc+PyBoZWFkZXJzLAogICAgcmVxdWlyZWQgVCBGdW5jdGlvbihPYmplY3Q/IGpzb24pIGZyb21Kc29uVCwKICB9KTsKCiAgVCBoYW5kbGVSZXNwb25zZTxUPihmaW5hbCBOZXR3b3JrUmVzcG9uc2U8VD4gcmVzcG9uc2UpOwoKICBTdWNjZXNzUmVzcG9uc2U8VD4gaGFuZGxlU3VjY2Vzc1Jlc3BvbnNlPFQ+KAogICAgZmluYWwgTmV0d29ya1Jlc3BvbnNlPFQ+IHJlc3BvbnNlLAogICk7CgogIHZvaWQgZGlzcG9zZSgpOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/config/index.dart", + "data": "ZXhwb3J0ICduZXR3b3JrX2NvbmZpZy5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/config/network_config.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvc3JjL2F1dGgvYXV0aF90b2tlbl9wcm92aWRlci5kYXJ0JzsKCmFic3RyYWN0IGNsYXNzIE5ldHdvcmtDb25maWcgewogIFN0cmluZyBnZXQgYmFzZVVybDsKICBEdXJhdGlvbiBnZXQgY29ubmVjdFRpbWVvdXQ7CiAgRHVyYXRpb24gZ2V0IHJlY2VpdmVUaW1lb3V0OwogIER1cmF0aW9uIGdldCBzZW5kVGltZW91dDsKICBNYXA8U3RyaW5nLCBTdHJpbmc+IGdldCBkZWZhdWx0SGVhZGVyczsKICBib29sIGdldCBlbmFibGVMb2dnaW5nOwogIEF1dGhUb2tlblByb3ZpZGVyPyBnZXQgYXV0aFRva2VuUHJvdmlkZXI7Cn0KCmNsYXNzIERlZmF1bHROZXR3b3JrQ29uZmlnIGltcGxlbWVudHMgTmV0d29ya0NvbmZpZyB7CiAgY29uc3QgRGVmYXVsdE5ldHdvcmtDb25maWcoewogICAgcmVxdWlyZWQgdGhpcy5iYXNlVXJsLAogICAgdGhpcy5jb25uZWN0VGltZW91dCA9IGNvbnN0IER1cmF0aW9uKHNlY29uZHM6IDMwKSwKICAgIHRoaXMucmVjZWl2ZVRpbWVvdXQgPSBjb25zdCBEdXJhdGlvbihzZWNvbmRzOiAzMCksCiAgICB0aGlzLnNlbmRUaW1lb3V0ID0gY29uc3QgRHVyYXRpb24oc2Vjb25kczogMzApLAogICAgdGhpcy5kZWZhdWx0SGVhZGVycyA9IGNvbnN0IHt9LAogICAgdGhpcy5lbmFibGVMb2dnaW5nID0gdHJ1ZSwKICAgIHRoaXMuYXV0aFRva2VuUHJvdmlkZXIsCiAgfSk7CgogIEBvdmVycmlkZQogIGZpbmFsIFN0cmluZyBiYXNlVXJsOwoKICBAb3ZlcnJpZGUKICBmaW5hbCBEdXJhdGlvbiBjb25uZWN0VGltZW91dDsKCiAgQG92ZXJyaWRlCiAgZmluYWwgRHVyYXRpb24gcmVjZWl2ZVRpbWVvdXQ7CgogIEBvdmVycmlkZQogIGZpbmFsIER1cmF0aW9uIHNlbmRUaW1lb3V0OwoKICBAb3ZlcnJpZGUKICBmaW5hbCBNYXA8U3RyaW5nLCBTdHJpbmc+IGRlZmF1bHRIZWFkZXJzOwoKICBAb3ZlcnJpZGUKICBmaW5hbCBib29sIGVuYWJsZUxvZ2dpbmc7CgogIEBvdmVycmlkZQogIGZpbmFsIEF1dGhUb2tlblByb3ZpZGVyPyBhdXRoVG9rZW5Qcm92aWRlcjsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/exceptions/index.dart", + "data": "ZXhwb3J0ICduZXR3b3JrX2V4Y2VwdGlvbnMuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/exceptions/network_exceptions.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpuZXR3b3JrL25ldHdvcmsuZGFydCc7CgphYnN0cmFjdCBjbGFzcyBOZXR3b3JrRXhjZXB0aW9uIGltcGxlbWVudHMgRXhjZXB0aW9uIHsKICBjb25zdCBOZXR3b3JrRXhjZXB0aW9uKHt0aGlzLm1lc3NhZ2UsIHRoaXMuc3RhdHVzQ29kZSwgdGhpcy5lcnJvcn0pOwoKICBmaW5hbCBTdHJpbmc/IG1lc3NhZ2U7CiAgZmluYWwgaW50PyBzdGF0dXNDb2RlOwogIGZpbmFsIEVycm9yUmVzcG9uc2U/IGVycm9yOwoKICBTdHJpbmcgZ2V0IGRpc3BsYXlNZXNzYWdlID0+IG1lc3NhZ2UgPz8gc3RyaW5ncy5lcnJvcnMudW5rbm93bl9uZXR3b3JrOwoKICBAb3ZlcnJpZGUKICBTdHJpbmcgdG9TdHJpbmcoKSA9PiAnJHJ1bnRpbWVUeXBlOiAkZGlzcGxheU1lc3NhZ2UnOwp9CgpjbGFzcyBCYWRSZXF1ZXN0RXhjZXB0aW9uIGV4dGVuZHMgTmV0d29ya0V4Y2VwdGlvbiB7CiAgY29uc3QgQmFkUmVxdWVzdEV4Y2VwdGlvbih7c3VwZXIubWVzc2FnZSwgc3VwZXIuZXJyb3J9KQogICAgOiBzdXBlcihzdGF0dXNDb2RlOiA0MDApOwoKICBAb3ZlcnJpZGUKICBTdHJpbmcgZ2V0IGRpc3BsYXlNZXNzYWdlID0+IG1lc3NhZ2UgPz8gc3RyaW5ncy5lcnJvcnMuYmFkX3JlcXVlc3Q7Cn0KCmNsYXNzIFVuYXV0aG9yaXplZEV4Y2VwdGlvbiBleHRlbmRzIE5ldHdvcmtFeGNlcHRpb24gewogIGNvbnN0IFVuYXV0aG9yaXplZEV4Y2VwdGlvbih7c3VwZXIubWVzc2FnZSwgc3VwZXIuZXJyb3J9KQogICAgOiBzdXBlcihzdGF0dXNDb2RlOiA0MDEpOwoKICBAb3ZlcnJpZGUKICBTdHJpbmcgZ2V0IGRpc3BsYXlNZXNzYWdlID0+IG1lc3NhZ2UgPz8gc3RyaW5ncy5lcnJvcnMudW5hdXRob3JpemVkOwp9CgpjbGFzcyBGb3JiaWRkZW5FeGNlcHRpb24gZXh0ZW5kcyBOZXR3b3JrRXhjZXB0aW9uIHsKICBjb25zdCBGb3JiaWRkZW5FeGNlcHRpb24oe3N1cGVyLm1lc3NhZ2UsIHN1cGVyLmVycm9yfSkKICAgIDogc3VwZXIoc3RhdHVzQ29kZTogNDAzKTsKCiAgQG92ZXJyaWRlCiAgU3RyaW5nIGdldCBkaXNwbGF5TWVzc2FnZSA9PiBtZXNzYWdlID8/IHN0cmluZ3MuZXJyb3JzLmZvcmJpZGRlbjsKfQoKY2xhc3MgTm90Rm91bmRFeGNlcHRpb24gZXh0ZW5kcyBOZXR3b3JrRXhjZXB0aW9uIHsKICBjb25zdCBOb3RGb3VuZEV4Y2VwdGlvbih7c3VwZXIubWVzc2FnZSwgc3VwZXIuZXJyb3J9KQogICAgOiBzdXBlcihzdGF0dXNDb2RlOiA0MDQpOwoKICBAb3ZlcnJpZGUKICBTdHJpbmcgZ2V0IGRpc3BsYXlNZXNzYWdlID0+IG1lc3NhZ2UgPz8gc3RyaW5ncy5lcnJvcnMubm90X2ZvdW5kOwp9CgpjbGFzcyBDb25mbGljdEV4Y2VwdGlvbiBleHRlbmRzIE5ldHdvcmtFeGNlcHRpb24gewogIGNvbnN0IENvbmZsaWN0RXhjZXB0aW9uKHtzdXBlci5tZXNzYWdlLCBzdXBlci5lcnJvcn0pCiAgICA6IHN1cGVyKHN0YXR1c0NvZGU6IDQwOSk7CgogIEBvdmVycmlkZQogIFN0cmluZyBnZXQgZGlzcGxheU1lc3NhZ2UgPT4gbWVzc2FnZSA/PyBzdHJpbmdzLmVycm9ycy5jb25mbGljdDsKfQoKY2xhc3MgVW5wcm9jZXNzYWJsZUVudGl0eUV4Y2VwdGlvbiBleHRlbmRzIE5ldHdvcmtFeGNlcHRpb24gewogIGNvbnN0IFVucHJvY2Vzc2FibGVFbnRpdHlFeGNlcHRpb24oe3N1cGVyLm1lc3NhZ2UsIHN1cGVyLmVycm9yfSkKICAgIDogc3VwZXIoc3RhdHVzQ29kZTogNDIyKTsKCiAgQG92ZXJyaWRlCiAgU3RyaW5nIGdldCBkaXNwbGF5TWVzc2FnZSA9PiBtZXNzYWdlID8/IHN0cmluZ3MuZXJyb3JzLnVucHJvY2Vzc2FibGVfZW50aXR5Owp9CgpjbGFzcyBJbnRlcm5hbFNlcnZlckV4Y2VwdGlvbiBleHRlbmRzIE5ldHdvcmtFeGNlcHRpb24gewogIGNvbnN0IEludGVybmFsU2VydmVyRXhjZXB0aW9uKHtzdXBlci5tZXNzYWdlLCBzdXBlci5lcnJvcn0pCiAgICA6IHN1cGVyKHN0YXR1c0NvZGU6IDUwMCk7CgogIEBvdmVycmlkZQogIFN0cmluZyBnZXQgZGlzcGxheU1lc3NhZ2UgPT4gbWVzc2FnZSA/PyBzdHJpbmdzLmVycm9ycy5pbnRlcm5hbF9zZXJ2ZXJfZXJyb3I7Cn0KCmNsYXNzIENvbm5lY3Rpb25UaW1lb3V0RXhjZXB0aW9uIGV4dGVuZHMgTmV0d29ya0V4Y2VwdGlvbiB7CiAgY29uc3QgQ29ubmVjdGlvblRpbWVvdXRFeGNlcHRpb24oe3N1cGVyLm1lc3NhZ2V9KSA6IHN1cGVyKHN0YXR1c0NvZGU6IDU5OSk7CgogIEBvdmVycmlkZQogIFN0cmluZyBnZXQgZGlzcGxheU1lc3NhZ2UgPT4gbWVzc2FnZSA/PyBzdHJpbmdzLmVycm9ycy5jb25uZWN0aW9uX3RpbWVvdXQ7Cn0KCmNsYXNzIFJlY2VpdmVUaW1lb3V0RXhjZXB0aW9uIGV4dGVuZHMgTmV0d29ya0V4Y2VwdGlvbiB7CiAgY29uc3QgUmVjZWl2ZVRpbWVvdXRFeGNlcHRpb24oe3N1cGVyLm1lc3NhZ2V9KSA6IHN1cGVyKHN0YXR1c0NvZGU6IG51bGwpOwoKICBAb3ZlcnJpZGUKICBTdHJpbmcgZ2V0IGRpc3BsYXlNZXNzYWdlID0+IG1lc3NhZ2UgPz8gc3RyaW5ncy5lcnJvcnMucmVjZWl2ZV90aW1lb3V0Owp9CgpjbGFzcyBTZW5kVGltZW91dEV4Y2VwdGlvbiBleHRlbmRzIE5ldHdvcmtFeGNlcHRpb24gewogIGNvbnN0IFNlbmRUaW1lb3V0RXhjZXB0aW9uKHtzdXBlci5tZXNzYWdlfSkgOiBzdXBlcihzdGF0dXNDb2RlOiBudWxsKTsKCiAgQG92ZXJyaWRlCiAgU3RyaW5nIGdldCBkaXNwbGF5TWVzc2FnZSA9PiBtZXNzYWdlID8/IHN0cmluZ3MuZXJyb3JzLnNlbmRfdGltZW91dDsKfQoKY2xhc3MgTm9JbnRlcm5ldENvbm5lY3Rpb25FeGNlcHRpb24gZXh0ZW5kcyBOZXR3b3JrRXhjZXB0aW9uIHsKICBjb25zdCBOb0ludGVybmV0Q29ubmVjdGlvbkV4Y2VwdGlvbih7c3VwZXIubWVzc2FnZX0pCiAgICA6IHN1cGVyKHN0YXR1c0NvZGU6IG51bGwpOwoKICBAb3ZlcnJpZGUKICBTdHJpbmcgZ2V0IGRpc3BsYXlNZXNzYWdlID0+IG1lc3NhZ2UgPz8gc3RyaW5ncy5lcnJvcnMubm9faW50ZXJuZXQ7Cn0KCmNsYXNzIFVua25vd25OZXR3b3JrRXhjZXB0aW9uIGV4dGVuZHMgTmV0d29ya0V4Y2VwdGlvbiB7CiAgY29uc3QgVW5rbm93bk5ldHdvcmtFeGNlcHRpb24oe3N1cGVyLm1lc3NhZ2UsIHN1cGVyLnN0YXR1c0NvZGUsIHN1cGVyLmVycm9yfSk7CgogIEBvdmVycmlkZQogIFN0cmluZyBnZXQgZGlzcGxheU1lc3NhZ2UgPT4gbWVzc2FnZSA/PyBzdHJpbmdzLmVycm9ycy51bmtub3duX25ldHdvcms7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/auth_interceptor.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGlvL2Rpby5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvc3JjL2F1dGgvYXV0aF90b2tlbl9wcm92aWRlci5kYXJ0JzsKCmNsYXNzIEF1dGhJbnRlcmNlcHRvciBleHRlbmRzIEludGVyY2VwdG9yIHsKICBBdXRoSW50ZXJjZXB0b3IodGhpcy5fYXV0aFRva2VuUHJvdmlkZXIpIDogX2xvZ2dlciA9IGRpLmdldDxMb2dnZXI+KCk7CgogIGZpbmFsIEF1dGhUb2tlblByb3ZpZGVyIF9hdXRoVG9rZW5Qcm92aWRlcjsKICBmaW5hbCBMb2dnZXIgX2xvZ2dlcjsKCiAgQG92ZXJyaWRlCiAgdm9pZCBvblJlcXVlc3QoCiAgICBSZXF1ZXN0T3B0aW9ucyBvcHRpb25zLAogICAgUmVxdWVzdEludGVyY2VwdG9ySGFuZGxlciBoYW5kbGVyLAogICkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgLy8gU2tpcCBhdXRoZW50aWNhdGlvbiBmb3IgcmVxdWVzdHMgdGhhdCBhbHJlYWR5IGhhdmUgYXV0aG9yaXphdGlvbiBoZWFkZXIKICAgICAgaWYgKG9wdGlvbnMuaGVhZGVycy5jb250YWluc0tleSgnQXV0aG9yaXphdGlvbicpKSB7CiAgICAgICAgaGFuZGxlci5uZXh0KG9wdGlvbnMpOwogICAgICAgIHJldHVybjsKICAgICAgfQoKICAgICAgZmluYWwgdG9rZW4gPSBhd2FpdCBfYXV0aFRva2VuUHJvdmlkZXIuZ2V0VmFsaWRUb2tlbigpOwogICAgICBpZiAodG9rZW4gIT0gbnVsbCkgewogICAgICAgIG9wdGlvbnMuaGVhZGVyc1snQXV0aG9yaXphdGlvbiddID0gJ0JlYXJlciAkdG9rZW4nOwogICAgICAgIF9sb2dnZXIuZCgn8J+UkCBBZGRlZCBCZWFyZXIgdG9rZW4gdG8gcmVxdWVzdDogJHtvcHRpb25zLnVyaX0nKTsKICAgICAgfSBlbHNlIHsKICAgICAgICBfbG9nZ2VyLmQoJ/CflJMgTm8gYXV0aCB0b2tlbiBhdmFpbGFibGUgZm9yIHJlcXVlc3Q6ICR7b3B0aW9ucy51cml9Jyk7CiAgICAgIH0KCiAgICAgIGhhbmRsZXIubmV4dChvcHRpb25zKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgX2xvZ2dlci5lKCfwn5qoIEVycm9yIGFkZGluZyBhdXRoIHRva2VuIHRvIHJlcXVlc3QnLCBlKTsKICAgICAgaGFuZGxlci5uZXh0KG9wdGlvbnMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBvbkVycm9yKERpb0V4Y2VwdGlvbiBlcnIsIEVycm9ySW50ZXJjZXB0b3JIYW5kbGVyIGhhbmRsZXIpIGFzeW5jIHsKICAgIC8vIEhhbmRsZSA0MDEgVW5hdXRob3JpemVkIC0gdG9rZW4gbWlnaHQgYmUgZXhwaXJlZAogICAgaWYgKGVyci5yZXNwb25zZT8uc3RhdHVzQ29kZSA9PSA0MDEpIHsKICAgICAgX2xvZ2dlci53KCfwn5SQIFJlY2VpdmVkIDQwMSBVbmF1dGhvcml6ZWQsIGF0dGVtcHRpbmcgdG9rZW4gcmVmcmVzaCcpOwoKICAgICAgdHJ5IHsKICAgICAgICBmaW5hbCBuZXdUb2tlbiA9IGF3YWl0IF9hdXRoVG9rZW5Qcm92aWRlci5yZWZyZXNoVG9rZW4oKTsKICAgICAgICBpZiAobmV3VG9rZW4gIT0gbnVsbCkgewogICAgICAgICAgX2xvZ2dlci5pKCfwn5SEIFRva2VuIHJlZnJlc2hlZCBzdWNjZXNzZnVsbHksIHJldHJ5aW5nIHJlcXVlc3QnKTsKCiAgICAgICAgICAvLyBDbG9uZSB0aGUgcmVxdWVzdCB3aXRoIG5ldyB0b2tlbgogICAgICAgICAgZmluYWwgcmVxdWVzdE9wdGlvbnMgPSBlcnIucmVxdWVzdE9wdGlvbnM7CiAgICAgICAgICByZXF1ZXN0T3B0aW9ucy5oZWFkZXJzWydBdXRob3JpemF0aW9uJ10gPSAnQmVhcmVyICRuZXdUb2tlbic7CgogICAgICAgICAgLy8gUmV0cnkgdGhlIHJlcXVlc3QKICAgICAgICAgIGZpbmFsIHJlc3BvbnNlID0gYXdhaXQgRGlvKCkuZmV0Y2gocmVxdWVzdE9wdGlvbnMpOwogICAgICAgICAgaGFuZGxlci5yZXNvbHZlKHJlc3BvbnNlKTsKICAgICAgICAgIHJldHVybjsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgX2xvZ2dlci53KCfinYwgVG9rZW4gcmVmcmVzaCBmYWlsZWQsIHVzZXIgbmVlZHMgdG8gcmUtYXV0aGVudGljYXRlJyk7CiAgICAgICAgfQogICAgICB9IGNhdGNoIChyZWZyZXNoRXJyb3IpIHsKICAgICAgICBfbG9nZ2VyLmUoJ/CfmqggRXJyb3IgZHVyaW5nIHRva2VuIHJlZnJlc2gnLCByZWZyZXNoRXJyb3IpOwogICAgICB9CiAgICB9CgogICAgLy8gSGFuZGxlIDQwMyBGb3JiaWRkZW4gLSB1c2VyIGRvZXNuJ3QgaGF2ZSBwZXJtaXNzaW9uCiAgICBpZiAoZXJyLnJlc3BvbnNlPy5zdGF0dXNDb2RlID09IDQwMykgewogICAgICBfbG9nZ2VyLncoJ/CfmqsgUmVjZWl2ZWQgNDAzIEZvcmJpZGRlbiwgaW5zdWZmaWNpZW50IHBlcm1pc3Npb25zJyk7CiAgICB9CgogICAgaGFuZGxlci5uZXh0KGVycik7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/index.dart", + "data": + "ZXhwb3J0ICdhdXRoX2ludGVyY2VwdG9yLmRhcnQnOwpleHBvcnQgJ2xvZ2dpbmdfaW50ZXJjZXB0b3IuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/interceptors/logging_interceptor.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGlvL2Rpby5kYXJ0JzsKCmNsYXNzIExvZ2dpbmdJbnRlcmNlcHRvciBleHRlbmRzIEludGVyY2VwdG9yIHsKICBMb2dnaW5nSW50ZXJjZXB0b3IoKSA6IF9sb2dnZXIgPSBkaS5nZXQ8TG9nZ2VyPigpOwoKICBmaW5hbCBMb2dnZXIgX2xvZ2dlcjsKCiAgQG92ZXJyaWRlCiAgdm9pZCBvblJlcXVlc3QoUmVxdWVzdE9wdGlvbnMgb3B0aW9ucywgUmVxdWVzdEludGVyY2VwdG9ySGFuZGxlciBoYW5kbGVyKSB7CiAgICBfbG9nZ2VyLmQoJ/CfmoAgUmVxdWVzdDogJHtvcHRpb25zLm1ldGhvZH0gJHtvcHRpb25zLnVyaX0nKTsKICAgIF9sb2dnZXIuZCgnSGVhZGVyczogJHtvcHRpb25zLmhlYWRlcnN9Jyk7CiAgICBpZiAob3B0aW9ucy5kYXRhICE9IG51bGwpIHsKICAgICAgX2xvZ2dlci5kKCdEYXRhOiAke29wdGlvbnMuZGF0YX0nKTsKICAgIH0KICAgIGlmIChvcHRpb25zLnF1ZXJ5UGFyYW1ldGVycy5pc05vdEVtcHR5KSB7CiAgICAgIF9sb2dnZXIuZCgnUXVlcnkgUGFyYW1ldGVyczogJHtvcHRpb25zLnF1ZXJ5UGFyYW1ldGVyc30nKTsKICAgIH0KICAgIHN1cGVyLm9uUmVxdWVzdChvcHRpb25zLCBoYW5kbGVyKTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgb25SZXNwb25zZShSZXNwb25zZSByZXNwb25zZSwgUmVzcG9uc2VJbnRlcmNlcHRvckhhbmRsZXIgaGFuZGxlcikgewogICAgX2xvZ2dlci5kKAogICAgICAn4pyFIFJlc3BvbnNlOiAke3Jlc3BvbnNlLnN0YXR1c0NvZGV9ICR7cmVzcG9uc2UucmVxdWVzdE9wdGlvbnMudXJpfScsCiAgICApOwogICAgX2xvZ2dlci5kKCdSZXNwb25zZSBEYXRhOiAke3Jlc3BvbnNlLmRhdGF9Jyk7CiAgICBzdXBlci5vblJlc3BvbnNlKHJlc3BvbnNlLCBoYW5kbGVyKTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgb25FcnJvcihEaW9FeGNlcHRpb24gZXJyLCBFcnJvckludGVyY2VwdG9ySGFuZGxlciBoYW5kbGVyKSB7CiAgICBfbG9nZ2VyLmUoCiAgICAgICfinYwgRXJyb3I6ICR7ZXJyLnJlc3BvbnNlPy5zdGF0dXNDb2RlfSAke2Vyci5yZXF1ZXN0T3B0aW9ucy51cml9JywKICAgICAgZXJyLAogICAgICBlcnIuc3RhY2tUcmFjZSwKICAgICk7CiAgICBpZiAoZXJyLnJlc3BvbnNlPy5kYXRhICE9IG51bGwpIHsKICAgICAgX2xvZ2dlci5lKCdFcnJvciBEYXRhOiAke2Vyci5yZXNwb25zZT8uZGF0YX0nKTsKICAgIH0KICAgIHN1cGVyLm9uRXJyb3IoZXJyLCBoYW5kbGVyKTsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/index.dart", + "data": "ZXhwb3J0ICduZXR3b3JrX3Jlc3BvbnNlLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/network_response.dart", "data": - "IyBQdXQgYWxsIHlvdXIgc2hhcmVkIHBhY2thZ2VzIGluIHRoaXMgZm9sZGVyCg==", + "aW1wb3J0ICdwYWNrYWdlOmpzb25fYW5ub3RhdGlvbi9qc29uX2Fubm90YXRpb24uZGFydCc7CgpwYXJ0ICduZXR3b3JrX3Jlc3BvbnNlLmcuZGFydCc7CgphYnN0cmFjdCBjbGFzcyBOZXR3b3JrUmVzcG9uc2U8VD4gewogIGNvbnN0IE5ldHdvcmtSZXNwb25zZSh7CiAgICByZXF1aXJlZCB0aGlzLnN0YXR1c0NvZGUsCiAgICByZXF1aXJlZCB0aGlzLnN1Y2Nlc3MsCiAgICB0aGlzLm1lc3NhZ2UsCiAgICByZXF1aXJlZCB0aGlzLnRpbWVzdGFtcCwKICAgIHRoaXMuaGVhZGVycywKICB9KTsKCiAgZmluYWwgaW50IHN0YXR1c0NvZGU7CiAgZmluYWwgYm9vbCBzdWNjZXNzOwogIGZpbmFsIFN0cmluZz8gbWVzc2FnZTsKICBmaW5hbCBTdHJpbmcgdGltZXN0YW1wOwogIGZpbmFsIE1hcDxTdHJpbmcsIExpc3Q8U3RyaW5nPj4/IGhlYWRlcnM7CgogIGJvb2wgZ2V0IGlzU3VjY2VzcyA9PiBzdWNjZXNzICYmIHN0YXR1c0NvZGUgPj0gMjAwICYmIHN0YXR1c0NvZGUgPCAzMDA7Cn0KCkBKc29uU2VyaWFsaXphYmxlKGdlbmVyaWNBcmd1bWVudEZhY3RvcmllczogdHJ1ZSkKY2xhc3MgU3VjY2Vzc1Jlc3BvbnNlPFQ+IGV4dGVuZHMgTmV0d29ya1Jlc3BvbnNlPFQ+IHsKICBmaW5hbCBUIGRhdGE7CiAgZmluYWwgUGFnaW5hdGlvbj8gcGFnaW5hdGlvbjsKCiAgY29uc3QgU3VjY2Vzc1Jlc3BvbnNlKHsKICAgIHJlcXVpcmVkIHN1cGVyLnN0YXR1c0NvZGUsCiAgICByZXF1aXJlZCBzdXBlci5zdWNjZXNzLAogICAgcmVxdWlyZWQgdGhpcy5kYXRhLAogICAgc3VwZXIubWVzc2FnZSwKICAgIHJlcXVpcmVkIHN1cGVyLnRpbWVzdGFtcCwKICAgIHRoaXMucGFnaW5hdGlvbiwKICAgIHN1cGVyLmhlYWRlcnMsCiAgfSk7CgogIGZhY3RvcnkgU3VjY2Vzc1Jlc3BvbnNlLmZyb21Kc29uKAogICAgTWFwPFN0cmluZywgZHluYW1pYz4ganNvbiwKICAgIFQgRnVuY3Rpb24oT2JqZWN0PyBqc29uKSBmcm9tSnNvblQsCiAgKSA9PiBfJFN1Y2Nlc3NSZXNwb25zZUZyb21Kc29uKGpzb24sIGZyb21Kc29uVCk7CgogIE1hcDxTdHJpbmcsIGR5bmFtaWM+IHRvSnNvbihPYmplY3QgRnVuY3Rpb24oVCB2YWx1ZSkgdG9Kc29uVCkgPT4KICAgICAgXyRTdWNjZXNzUmVzcG9uc2VUb0pzb24odGhpcywgdG9Kc29uVCk7Cn0KCkBKc29uU2VyaWFsaXphYmxlKCkKY2xhc3MgRXJyb3JSZXNwb25zZTxUPiBleHRlbmRzIE5ldHdvcmtSZXNwb25zZTxUPiB7CiAgZmluYWwgRXJyb3JEZXRhaWxzIGVycm9yOwoKICBjb25zdCBFcnJvclJlc3BvbnNlKHsKICAgIHJlcXVpcmVkIHN1cGVyLnN0YXR1c0NvZGUsCiAgICByZXF1aXJlZCBzdXBlci5zdWNjZXNzLAogICAgcmVxdWlyZWQgdGhpcy5lcnJvciwKICAgIHJlcXVpcmVkIHN1cGVyLnRpbWVzdGFtcCwKICAgIHN1cGVyLm1lc3NhZ2UsCiAgICBzdXBlci5oZWFkZXJzLAogIH0pOwoKICBmYWN0b3J5IEVycm9yUmVzcG9uc2UuZnJvbUpzb24oTWFwPFN0cmluZywgZHluYW1pYz4ganNvbikgPT4KICAgICAgXyRFcnJvclJlc3BvbnNlRnJvbUpzb24oanNvbik7CgogIE1hcDxTdHJpbmcsIGR5bmFtaWM+IHRvSnNvbigpID0+IF8kRXJyb3JSZXNwb25zZVRvSnNvbih0aGlzKTsKfQoKQEpzb25TZXJpYWxpemFibGUoKQpjbGFzcyBFcnJvckRldGFpbHMgewogIGNvbnN0IEVycm9yRGV0YWlscyh7cmVxdWlyZWQgdGhpcy5jb2RlLCByZXF1aXJlZCB0aGlzLm1lc3NhZ2UsIHRoaXMuZGV0YWlsc30pOwoKICBmaW5hbCBTdHJpbmcgY29kZTsKICBmaW5hbCBTdHJpbmcgbWVzc2FnZTsKICBmaW5hbCBNYXA8U3RyaW5nLCBkeW5hbWljPj8gZGV0YWlsczsKCiAgZmFjdG9yeSBFcnJvckRldGFpbHMuZnJvbUpzb24oTWFwPFN0cmluZywgZHluYW1pYz4ganNvbikgPT4KICAgICAgXyRFcnJvckRldGFpbHNGcm9tSnNvbihqc29uKTsKCiAgTWFwPFN0cmluZywgZHluYW1pYz4gdG9Kc29uKCkgPT4gXyRFcnJvckRldGFpbHNUb0pzb24odGhpcyk7Cn0KCkBKc29uU2VyaWFsaXphYmxlKCkKY2xhc3MgUGFnaW5hdGlvbiB7CiAgY29uc3QgUGFnaW5hdGlvbih7CiAgICByZXF1aXJlZCB0aGlzLnBhZ2UsCiAgICByZXF1aXJlZCB0aGlzLmxpbWl0LAogICAgcmVxdWlyZWQgdGhpcy50b3RhbCwKICAgIHJlcXVpcmVkIHRoaXMudG90YWxQYWdlcywKICAgIHJlcXVpcmVkIHRoaXMuaGFzTmV4dFBhZ2UsCiAgICByZXF1aXJlZCB0aGlzLmhhc1ByZXZpb3VzUGFnZSwKICB9KTsKCiAgZmluYWwgaW50IHBhZ2U7CiAgZmluYWwgaW50IGxpbWl0OwogIGZpbmFsIGludCB0b3RhbDsKICBmaW5hbCBpbnQgdG90YWxQYWdlczsKICBmaW5hbCBib29sIGhhc05leHRQYWdlOwogIGZpbmFsIGJvb2wgaGFzUHJldmlvdXNQYWdlOwoKICBmYWN0b3J5IFBhZ2luYXRpb24uZnJvbUpzb24oTWFwPFN0cmluZywgZHluYW1pYz4ganNvbikgPT4KICAgICAgXyRQYWdpbmF0aW9uRnJvbUpzb24oanNvbik7CgogIE1hcDxTdHJpbmcsIGR5bmFtaWM+IHRvSnNvbigpID0+IF8kUGFnaW5hdGlvblRvSnNvbih0aGlzKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/models/network_response.g.dart", + "data": + "Ly8gR0VORVJBVEVEIENPREUgLSBETyBOT1QgTU9ESUZZIEJZIEhBTkQKCnBhcnQgb2YgJ25ldHdvcmtfcmVzcG9uc2UuZGFydCc7CgovLyAqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgovLyBKc29uU2VyaWFsaXphYmxlR2VuZXJhdG9yCi8vICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqCgpTdWNjZXNzUmVzcG9uc2U8VD4gXyRTdWNjZXNzUmVzcG9uc2VGcm9tSnNvbjxUPigKICBNYXA8U3RyaW5nLCBkeW5hbWljPiBqc29uLAogIFQgRnVuY3Rpb24oT2JqZWN0PyBqc29uKSBmcm9tSnNvblQsCikgPT4gU3VjY2Vzc1Jlc3BvbnNlPFQ+KAogIHN0YXR1c0NvZGU6IChqc29uWydzdGF0dXNDb2RlJ10gYXMgbnVtKS50b0ludCgpLAogIHN1Y2Nlc3M6IGpzb25bJ3N1Y2Nlc3MnXSBhcyBib29sLAogIGRhdGE6IGZyb21Kc29uVChqc29uWydkYXRhJ10pLAogIG1lc3NhZ2U6IGpzb25bJ21lc3NhZ2UnXSBhcyBTdHJpbmc/LAogIHRpbWVzdGFtcDoganNvblsndGltZXN0YW1wJ10gYXMgU3RyaW5nLAogIHBhZ2luYXRpb246IGpzb25bJ3BhZ2luYXRpb24nXSA9PSBudWxsCiAgICAgID8gbnVsbAogICAgICA6IFBhZ2luYXRpb24uZnJvbUpzb24oanNvblsncGFnaW5hdGlvbiddIGFzIE1hcDxTdHJpbmcsIGR5bmFtaWM+KSwKICBoZWFkZXJzOiAoanNvblsnaGVhZGVycyddIGFzIE1hcDxTdHJpbmcsIGR5bmFtaWM+Pyk/Lm1hcCgKICAgIChrLCBlKSA9PgogICAgICAgIE1hcEVudHJ5KGssIChlIGFzIExpc3Q8ZHluYW1pYz4pLm1hcCgoZSkgPT4gZSBhcyBTdHJpbmcpLnRvTGlzdCgpKSwKICApLAopOwoKTWFwPFN0cmluZywgZHluYW1pYz4gXyRTdWNjZXNzUmVzcG9uc2VUb0pzb248VD4oCiAgU3VjY2Vzc1Jlc3BvbnNlPFQ+IGluc3RhbmNlLAogIE9iamVjdD8gRnVuY3Rpb24oVCB2YWx1ZSkgdG9Kc29uVCwKKSA9PiA8U3RyaW5nLCBkeW5hbWljPnsKICAnc3RhdHVzQ29kZSc6IGluc3RhbmNlLnN0YXR1c0NvZGUsCiAgJ3N1Y2Nlc3MnOiBpbnN0YW5jZS5zdWNjZXNzLAogICdtZXNzYWdlJzogaW5zdGFuY2UubWVzc2FnZSwKICAndGltZXN0YW1wJzogaW5zdGFuY2UudGltZXN0YW1wLAogICdoZWFkZXJzJzogaW5zdGFuY2UuaGVhZGVycywKICAnZGF0YSc6IHRvSnNvblQoaW5zdGFuY2UuZGF0YSksCiAgJ3BhZ2luYXRpb24nOiBpbnN0YW5jZS5wYWdpbmF0aW9uLAp9OwoKRXJyb3JSZXNwb25zZTxUPiBfJEVycm9yUmVzcG9uc2VGcm9tSnNvbjxUPihNYXA8U3RyaW5nLCBkeW5hbWljPiBqc29uKSA9PgogICAgRXJyb3JSZXNwb25zZTxUPigKICAgICAgc3RhdHVzQ29kZTogKGpzb25bJ3N0YXR1c0NvZGUnXSBhcyBudW0pLnRvSW50KCksCiAgICAgIHN1Y2Nlc3M6IGpzb25bJ3N1Y2Nlc3MnXSBhcyBib29sLAogICAgICBlcnJvcjogRXJyb3JEZXRhaWxzLmZyb21Kc29uKGpzb25bJ2Vycm9yJ10gYXMgTWFwPFN0cmluZywgZHluYW1pYz4pLAogICAgICB0aW1lc3RhbXA6IGpzb25bJ3RpbWVzdGFtcCddIGFzIFN0cmluZywKICAgICAgbWVzc2FnZToganNvblsnbWVzc2FnZSddIGFzIFN0cmluZz8sCiAgICAgIGhlYWRlcnM6IChqc29uWydoZWFkZXJzJ10gYXMgTWFwPFN0cmluZywgZHluYW1pYz4/KT8ubWFwKAogICAgICAgIChrLCBlKSA9PgogICAgICAgICAgICBNYXBFbnRyeShrLCAoZSBhcyBMaXN0PGR5bmFtaWM+KS5tYXAoKGUpID0+IGUgYXMgU3RyaW5nKS50b0xpc3QoKSksCiAgICAgICksCiAgICApOwoKTWFwPFN0cmluZywgZHluYW1pYz4gXyRFcnJvclJlc3BvbnNlVG9Kc29uPFQ+KEVycm9yUmVzcG9uc2U8VD4gaW5zdGFuY2UpID0+CiAgICA8U3RyaW5nLCBkeW5hbWljPnsKICAgICAgJ3N0YXR1c0NvZGUnOiBpbnN0YW5jZS5zdGF0dXNDb2RlLAogICAgICAnc3VjY2Vzcyc6IGluc3RhbmNlLnN1Y2Nlc3MsCiAgICAgICdtZXNzYWdlJzogaW5zdGFuY2UubWVzc2FnZSwKICAgICAgJ3RpbWVzdGFtcCc6IGluc3RhbmNlLnRpbWVzdGFtcCwKICAgICAgJ2hlYWRlcnMnOiBpbnN0YW5jZS5oZWFkZXJzLAogICAgICAnZXJyb3InOiBpbnN0YW5jZS5lcnJvciwKICAgIH07CgpFcnJvckRldGFpbHMgXyRFcnJvckRldGFpbHNGcm9tSnNvbihNYXA8U3RyaW5nLCBkeW5hbWljPiBqc29uKSA9PiBFcnJvckRldGFpbHMoCiAgY29kZToganNvblsnY29kZSddIGFzIFN0cmluZywKICBtZXNzYWdlOiBqc29uWydtZXNzYWdlJ10gYXMgU3RyaW5nLAogIGRldGFpbHM6IGpzb25bJ2RldGFpbHMnXSBhcyBNYXA8U3RyaW5nLCBkeW5hbWljPj8sCik7CgpNYXA8U3RyaW5nLCBkeW5hbWljPiBfJEVycm9yRGV0YWlsc1RvSnNvbihFcnJvckRldGFpbHMgaW5zdGFuY2UpID0+CiAgICA8U3RyaW5nLCBkeW5hbWljPnsKICAgICAgJ2NvZGUnOiBpbnN0YW5jZS5jb2RlLAogICAgICAnbWVzc2FnZSc6IGluc3RhbmNlLm1lc3NhZ2UsCiAgICAgICdkZXRhaWxzJzogaW5zdGFuY2UuZGV0YWlscywKICAgIH07CgpQYWdpbmF0aW9uIF8kUGFnaW5hdGlvbkZyb21Kc29uKE1hcDxTdHJpbmcsIGR5bmFtaWM+IGpzb24pID0+IFBhZ2luYXRpb24oCiAgcGFnZTogKGpzb25bJ3BhZ2UnXSBhcyBudW0pLnRvSW50KCksCiAgbGltaXQ6IChqc29uWydsaW1pdCddIGFzIG51bSkudG9JbnQoKSwKICB0b3RhbDogKGpzb25bJ3RvdGFsJ10gYXMgbnVtKS50b0ludCgpLAogIHRvdGFsUGFnZXM6IChqc29uWyd0b3RhbFBhZ2VzJ10gYXMgbnVtKS50b0ludCgpLAogIGhhc05leHRQYWdlOiBqc29uWydoYXNOZXh0UGFnZSddIGFzIGJvb2wsCiAgaGFzUHJldmlvdXNQYWdlOiBqc29uWydoYXNQcmV2aW91c1BhZ2UnXSBhcyBib29sLAopOwoKTWFwPFN0cmluZywgZHluYW1pYz4gXyRQYWdpbmF0aW9uVG9Kc29uKFBhZ2luYXRpb24gaW5zdGFuY2UpID0+CiAgICA8U3RyaW5nLCBkeW5hbWljPnsKICAgICAgJ3BhZ2UnOiBpbnN0YW5jZS5wYWdlLAogICAgICAnbGltaXQnOiBpbnN0YW5jZS5saW1pdCwKICAgICAgJ3RvdGFsJzogaW5zdGFuY2UudG90YWwsCiAgICAgICd0b3RhbFBhZ2VzJzogaW5zdGFuY2UudG90YWxQYWdlcywKICAgICAgJ2hhc05leHRQYWdlJzogaW5zdGFuY2UuaGFzTmV4dFBhZ2UsCiAgICAgICdoYXNQcmV2aW91c1BhZ2UnOiBpbnN0YW5jZS5oYXNQcmV2aW91c1BhZ2UsCiAgICB9Owo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/extensions/error_message_extension.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvZm91bmRhdGlvbi5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmxvY2FsaXphdGlvbi9sb2NhbGl6YXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpuZXR3b3JrL25ldHdvcmsuZGFydCc7CgpleHRlbnNpb24gRXJyb3JNZXNzYWdlRXh0ZW5zaW9uIG9uIE9iamVjdD8gewogIFN0cmluZyBtZXNzYWdlKCkgewogICAgcmV0dXJuIHN3aXRjaCAodGhpcykgewogICAgICBOZXR3b3JrRXhjZXB0aW9uIGVycm9yID0+IGVycm9yLmRpc3BsYXlNZXNzYWdlLAogICAgICBDb3JlRXhjZXB0aW9uIGVycm9yID0+IGVycm9yLm1lc3NhZ2UsCiAgICAgIEZvcm1hdEV4Y2VwdGlvbiBfID0+IHN0cmluZ3MuZXJyb3JzLmZvcm1hdF9leGNlcHRpb25fbWVzc2FnZSgKICAgICAgICBFcnJvckNvZGVzLmZvcm1hdEV4Y2VwdGlvbiwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIEVycm9yIGVycm9yID0+IGVycm9yLm1lc3NhZ2UoKSwKICAgICAgXyA9PiBzdHJpbmdzLmVycm9ycy5kZWZhdWx0X2Vycm9yX21lc3NhZ2UsCiAgICB9OwogIH0KCiAgU3RyaW5nIGdldCBkZWJ1Z01lc3NhZ2UgewogICAgaWYgKGtEZWJ1Z01vZGUpIHsKICAgICAgcmV0dXJuICdcbiR7dG9TdHJpbmcoKX1cbnR5cGU6ICRydW50aW1lVHlwZSc7CiAgICB9CiAgICByZXR1cm4gJyc7CiAgfQp9CgovLyBTaW1wbGUgY3JlYXRpdmUgZXJyb3IgbWVzc2FnZXMgd2l0aCBjb2RlcwpleHRlbnNpb24gRXJyb3JNZXNzYWdlT25FcnJvciBvbiBFcnJvciB7CiAgU3RyaW5nIG1lc3NhZ2UoKSB7CiAgICByZXR1cm4gc3dpdGNoICh0aGlzKSB7CiAgICAgIFR5cGVFcnJvciBfID0+IHN0cmluZ3MuZXJyb3JzLnR5cGVfZXJyb3JfbWVzc2FnZSgKICAgICAgICBFcnJvckNvZGVzLnR5cGVFcnJvciwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIEluZGV4RXJyb3IgXyA9PiBzdHJpbmdzLmVycm9ycy5pbmRleF9lcnJvcl9tZXNzYWdlKAogICAgICAgIEVycm9yQ29kZXMuaW5kZXhFcnJvciwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIFJhbmdlRXJyb3IgXyA9PiBzdHJpbmdzLmVycm9ycy5yYW5nZV9lcnJvcl9tZXNzYWdlKAogICAgICAgIEVycm9yQ29kZXMucmFuZ2VFcnJvciwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIEFyZ3VtZW50RXJyb3IgXyA9PiBzdHJpbmdzLmVycm9ycy5hcmd1bWVudF9lcnJvcl9tZXNzYWdlKAogICAgICAgIEVycm9yQ29kZXMuYXJndW1lbnRFcnJvciwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIFN0YXRlRXJyb3IgXyA9PiBzdHJpbmdzLmVycm9ycy5zdGF0ZV9lcnJvcl9tZXNzYWdlKAogICAgICAgIEVycm9yQ29kZXMuc3RhdGVFcnJvciwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIFVuaW1wbGVtZW50ZWRFcnJvciBfID0+IHN0cmluZ3MuZXJyb3JzLnVuaW1wbGVtZW50ZWRfZXJyb3JfbWVzc2FnZSgKICAgICAgICBFcnJvckNvZGVzLnVuaW1wbGVtZW50ZWRFcnJvciwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIFVuc3VwcG9ydGVkRXJyb3IgXyA9PiBzdHJpbmdzLmVycm9ycy51bnN1cHBvcnRlZF9lcnJvcl9tZXNzYWdlKAogICAgICAgIEVycm9yQ29kZXMudW5zdXBwb3J0ZWRFcnJvciwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIENvbmN1cnJlbnRNb2RpZmljYXRpb25FcnJvciBfID0+CiAgICAgICAgc3RyaW5ncy5lcnJvcnMuY29uY3VycmVudF9tb2RpZmljYXRpb25fZXJyb3JfbWVzc2FnZSgKICAgICAgICAgIEVycm9yQ29kZXMuY29uY3VycmVudE1vZGlmaWNhdGlvbkVycm9yLAogICAgICAgICAgZGVidWdNZXNzYWdlLAogICAgICAgICksCiAgICAgIE91dE9mTWVtb3J5RXJyb3IgXyA9PiBzdHJpbmdzLmVycm9ycy5vdXRfb2ZfbWVtb3J5X2Vycm9yX21lc3NhZ2UoCiAgICAgICAgRXJyb3JDb2Rlcy5vdXRPZk1lbW9yeUVycm9yLAogICAgICAgIGRlYnVnTWVzc2FnZSwKICAgICAgKSwKICAgICAgU3RhY2tPdmVyZmxvd0Vycm9yIF8gPT4gc3RyaW5ncy5lcnJvcnMuc3RhY2tfb3ZlcmZsb3dfZXJyb3JfbWVzc2FnZSgKICAgICAgICBFcnJvckNvZGVzLnN0YWNrT3ZlcmZsb3dFcnJvciwKICAgICAgICBkZWJ1Z01lc3NhZ2UsCiAgICAgICksCiAgICAgIF8gPT4gc3RyaW5ncy5lcnJvcnMudW5rbm93bl9lcnJvcl9tZXNzYWdlKAogICAgICAgIEVycm9yQ29kZXMudW5rbm93bkVycm9yLAogICAgICAgIGRlYnVnTWVzc2FnZSwKICAgICAgKSwKICAgIH07CiAgfQp9CgpzZWFsZWQgY2xhc3MgRXJyb3JDb2RlcyB7CiAgc3RhdGljIGNvbnN0IFN0cmluZyB0eXBlRXJyb3IgPSAiRTAwMSI7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBhcmd1bWVudEVycm9yID0gIkUwMDIiOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgcmFuZ2VFcnJvciA9ICJFMDAzIjsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGluZGV4RXJyb3IgPSAiRTAwNCI7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBzdGF0ZUVycm9yID0gIkUwMDUiOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgdW5zdXBwb3J0ZWRFcnJvciA9ICJFMDA2IjsKICBzdGF0aWMgY29uc3QgU3RyaW5nIHVuaW1wbGVtZW50ZWRFcnJvciA9ICJFMDA3IjsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGNvbmN1cnJlbnRNb2RpZmljYXRpb25FcnJvciA9ICJFMDA4IjsKICBzdGF0aWMgY29uc3QgU3RyaW5nIG91dE9mTWVtb3J5RXJyb3IgPSAiRTAwOSI7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBzdGFja092ZXJmbG93RXJyb3IgPSAiRTAxMCI7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBmb3JtYXRFeGNlcHRpb24gPSAiRTAxMSI7CiAgc3RhdGljIGNvbnN0IFN0cmluZyB1bmtub3duRXJyb3IgPSAiRTk5OSI7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBuZXR3b3JrRXhjZXB0aW9uID0gIkUxMDAiOwogIHN0YXRpYyBjb25zdCBTdHJpbmcgY29yZUV4Y2VwdGlvbiA9ICJFMTAxIjsKICBzdGF0aWMgY29uc3QgU3RyaW5nIGRlZmF1bHRFcnJvciA9ICJFMDAwIjsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/extensions/index.dart", + "data": "ZXhwb3J0ICdlcnJvcl9tZXNzYWdlX2V4dGVuc2lvbi5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/lib/src/utils/index.dart", + "data": "ZXhwb3J0ICdleHRlbnNpb25zL2luZGV4LmRhcnQnOwo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml", + "data": + "bmFtZTogbmV0d29yawpkZXNjcmlwdGlvbjogIkhUVFAgbGF5ZXIgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19OiBEaW8gY2xpZW50LCBpbnRlcmNlcHRvcnMsIHR5cGVkIHJlc3BvbnNlcyBhbmQgZXJyb3IgbWFwcGluZy4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjI5LjAiCgpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcgogIGRpbzogXjUuNy4wCiAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICB0YWxrZXJfZGlvX2xvZ2dlcjogXjQuOS4xCiAgdGFsa2VyX2ZsdXR0ZXI6IF40LjQuMQogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgbG9jYWxpemF0aW9uOgogICAgcGF0aDogLi4vbG9jYWxpemF0aW9uCiAgZGk6CiAgICBwYXRoOiAuLi9kaQoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKICBidWlsZF9ydW5uZXI6IF4yLjQuMTUKICBqc29uX3NlcmlhbGl6YWJsZTogXjYuOS41CgpmbHV0dGVyOgo=", + "type": "text" + }, + { + "path": "{{ name.snakeCase() }}/packages/{{network_dir}}/README.md", + "data": + "IyBOZXR3b3JrCgpBIE1lbG9zLW1hbmFnZWQgcHJvamVjdCBmb3IgbW9uby1yZXBvLCBjcmVhdGVkIHVzaW5nIE5vblN0b3AgQ0xJLgoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgojIyBJbnN0YWxsYXRpb24g8J+SuwoKKirinZcgSW4gb3JkZXIgdG8gc3RhcnQgdXNpbmcgTmV0d29yayB5b3UgbXVzdCBoYXZlIHRoZSBbRmx1dHRlciBTREtdW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXSBpbnN0YWxsZWQgb24geW91ciBtYWNoaW5lLioqCgpJbnN0YWxsIHZpYSBgZGFydCBwdWIgYWRkYDoKCmBgYHNoCmRhcnQgcHViIGFkZCBuZXR3b3JrCmBgYAoKW2ZsdXR0ZXJfaW5zdGFsbF9saW5rXTogaHR0cHM6Ly9kb2NzLmZsdXR0ZXIuZGV2L2dldC1zdGFydGVkL2luc3RhbGw=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{network_dir}}/test/network_test.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXJfdGVzdC9mbHV0dGVyX3Rlc3QuZGFydCc7CmltcG9ydCAncGFja2FnZTpuZXR3b3JrL25ldHdvcmsuZGFydCc7Cgp2b2lkIG1haW4oKSB7CiAgdGVzdCgnRGVmYXVsdE5ldHdvcmtDb25maWcgY3JlYXRlcyBpbnN0YW5jZSB3aXRoIHByb3ZpZGVkIHZhbHVlcycsICgpIHsKICAgIGNvbnN0IGNvbmZpZyA9IERlZmF1bHROZXR3b3JrQ29uZmlnKAogICAgICBiYXNlVXJsOiAnaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20nLAogICAgICBlbmFibGVMb2dnaW5nOiB0cnVlLAogICAgKTsKCiAgICBleHBlY3QoY29uZmlnLmJhc2VVcmwsICdodHRwczovL2FwaS5leGFtcGxlLmNvbScpOwogICAgZXhwZWN0KGNvbmZpZy5lbmFibGVMb2dnaW5nLCB0cnVlKTsKICAgIGV4cGVjdChjb25maWcuY29ubmVjdFRpbWVvdXQsIGNvbnN0IER1cmF0aW9uKHNlY29uZHM6IDMwKSk7CiAgfSk7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/notifications.dart", + "data": + "bGlicmFyeTsKCmltcG9ydCAnZGFydDphc3luYyc7CgppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CmltcG9ydCAncGFja2FnZTpub3RpZmljYXRpb25zL3NyYy9jb25maWcvaW5kZXguZGFydCc7CmltcG9ydCAncGFja2FnZTpub3RpZmljYXRpb25zL3NyYy9kaS9pbmRleC5kYXJ0JzsKCmV4cG9ydCAnc3JjL2NsaWVudC9pbmRleC5kYXJ0JzsKZXhwb3J0ICdzcmMvY29uZmlnL2luZGV4LmRhcnQnOwpleHBvcnQgJ3NyYy9kZXZpY2VfaW5mby9pbmRleC5kYXJ0JzsKZXhwb3J0ICdzcmMvZXhjZXB0aW9ucy9pbmRleC5kYXJ0JzsKZXhwb3J0ICdzcmMvbW9kZWxzL2luZGV4LmRhcnQnOwpleHBvcnQgJ3NyYy9zZXJ2aWNlcy9pbmRleC5kYXJ0JzsKCi8vLyBJbml0aWFsaXplIHRoZSBub3RpZmljYXRpb25zIG1vZHVsZSB3aXRoIGRlcGVuZGVuY3kgaW5qZWN0aW9uCkZ1dHVyZTx2b2lkPiBpbml0KHsKICBOb3RpZmljYXRpb25Db25maWcgY29uZmlnID0gY29uc3QgRGVmYXVsdE5vdGlmaWNhdGlvbkNvbmZpZygpLAp9KSBhc3luYyB7CiAgZmluYWwgbG9nZ2VyID0gZGkuZ2V0PExvZ2dlcj4oKTsKICB0cnkgewogICAgYXdhaXQgcmVnaXN0ZXJOb3RpZmljYXRpb25XaXRoREkoY29uZmlnKTsKICAgIGxvZ2dlci5pKCfwn5SUIE5vdGlmaWNhdGlvbnMgbW9kdWxlIGluaXRpYWxpemVkJyk7CiAgfSBjYXRjaCAoZSwgcykgewogICAgbG9nZ2VyLmUoJ/CflJQgRmFpbGVkIHRvIGluaXRpYWxpemUgbm90aWZpY2F0aW9ucyBtb2R1bGUnLCBlLCBzKTsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/firebase_notification_client.dart", + "data": + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTphcHBfYmFkZ2VfcGx1cy9hcHBfYmFkZ2VfcGx1cy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL2xvZ2dlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRlc2lnbl9zeXN0ZW0vdG9hc3QvdG9hc3RzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CmltcG9ydCAncGFja2FnZTpmaXJlYmFzZV9tZXNzYWdpbmcvZmlyZWJhc2VfbWVzc2FnaW5nLmRhcnQnCiAgICBoaWRlIE5vdGlmaWNhdGlvblNldHRpbmdzOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmdvX3JvdXRlci9nb19yb3V0ZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpuZXR3b3JrL25ldHdvcmsuZGFydCc7CmltcG9ydCAncGFja2FnZTpub3RpZmljYXRpb25zL25vdGlmaWNhdGlvbnMuZGFydCc7Cgpjb25zdCBTdHJpbmcgX3RhZyA9ICdGaXJlYmFzZU5vdGlmaWNhdGlvbkNsaWVudCc7CgovLy8gRmlyZWJhc2UgaW1wbGVtZW50YXRpb24gb2YgdGhlIG5vdGlmaWNhdGlvbiBjbGllbnQKY2xhc3MgRmlyZWJhc2VOb3RpZmljYXRpb25DbGllbnQgaW1wbGVtZW50cyBOb3RpZmljYXRpb25DbGllbnQgewogIEZpcmViYXNlTm90aWZpY2F0aW9uQ2xpZW50KHsKICAgIHJlcXVpcmVkIHRoaXMuY29uZmlnLAogICAgTG9nZ2VyPyBsb2dnZXIsCiAgICBOb3RpZmljYXRpb25Ub2tlbk1hbmFnZXI/IHRva2VuTWFuYWdlciwKICAgIE5vdGlmaWNhdGlvblBlcm1pc3Npb25NYW5hZ2VyPyBwZXJtaXNzaW9uTWFuYWdlciwKICB9KSA6IF9sb2dnZXIgPSBsb2dnZXIgPz8gZGkuZ2V0PExvZ2dlcj4oKSwKICAgICAgIF90b2tlbk1hbmFnZXIgPSB0b2tlbk1hbmFnZXIgPz8gZGkuZ2V0PE5vdGlmaWNhdGlvblRva2VuTWFuYWdlcj4oKSwKICAgICAgIF9wZXJtaXNzaW9uTWFuYWdlciA9CiAgICAgICAgICAgcGVybWlzc2lvbk1hbmFnZXIgPz8gZGkuZ2V0PE5vdGlmaWNhdGlvblBlcm1pc3Npb25NYW5hZ2VyPigpOwoKICBmaW5hbCBOb3RpZmljYXRpb25Db25maWcgY29uZmlnOwogIGZpbmFsIExvZ2dlciBfbG9nZ2VyOwogIGZpbmFsIE5vdGlmaWNhdGlvblRva2VuTWFuYWdlciBfdG9rZW5NYW5hZ2VyOwogIGZpbmFsIE5vdGlmaWNhdGlvblBlcm1pc3Npb25NYW5hZ2VyIF9wZXJtaXNzaW9uTWFuYWdlcjsKICBib29sIF9pc0dyYW50ZWQgPSBmYWxzZTsKCiAgRmlyZWJhc2VNZXNzYWdpbmc/IF9maXJlYmFzZU1lc3NhZ2luZzsKCiAgU3RyaW5nPyBfZmNtVG9rZW47CiAgU3RyaW5nPyBfZGV2aWNlSWQ7CgogIC8vIFN0b3JlIHN1YnNjcmlwdGlvbnMgZm9yIGNsZWFudXAKICBTdHJlYW1TdWJzY3JpcHRpb248UmVtb3RlTWVzc2FnZT4/IF9mb3JlZ3JvdW5kTWVzc2FnZVN1YnNjcmlwdGlvbjsKICBTdHJlYW1TdWJzY3JpcHRpb248UmVtb3RlTWVzc2FnZT4/IF9tZXNzYWdlT3BlbmVkQXBwU3Vic2NyaXB0aW9uOwogIFN0cmVhbVN1YnNjcmlwdGlvbjxTdHJpbmc+PyBfdG9rZW5SZWZyZXNoU3Vic2NyaXB0aW9uOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gaW5pdCgpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIF9sb2dnZXIuaSgnJF90YWc6IEluaXRpYWxpemluZyBGaXJlYmFzZSBub3RpZmljYXRpb24gY2xpZW50Jyk7CgogICAgICAvLyBJbml0aWFsaXplIEZpcmViYXNlIE1lc3NhZ2luZwogICAgICBfZmlyZWJhc2VNZXNzYWdpbmcgPSBGaXJlYmFzZU1lc3NhZ2luZy5pbnN0YW5jZTsKCiAgICAgIGZpbmFsIGlzR3JhbnRlZCA9IGF3YWl0IHJlcXVlc3RQZXJtaXNzaW9ucygpOwogICAgICBpZiAoIWlzR3JhbnRlZCkgewogICAgICAgIF9sb2dnZXIudygnJF90YWc6IE5vdGlmaWNhdGlvbiBwZXJtaXNzaW9ucyBub3QgZ3JhbnRlZCcpOwogICAgICAgIHJldHVybjsKICAgICAgfQogICAgICBfaXNHcmFudGVkID0gaXNHcmFudGVkOwoKICAgICAgLy8gU2V0dXAgRkNNIGhhbmRsZXJzIGlmIGJhY2tncm91bmQgbm90aWZpY2F0aW9ucyBhcmUgZW5hYmxlZAogICAgICBhd2FpdCBfc2V0dXBGQ01IYW5kbGVycygpOwoKICAgICAgLy8gR2V0IEZDTSB0b2tlbiBhbmQgcmVnaXN0ZXIgaWYgYmFja2VuZCByZWdpc3RyYXRpb24gaXMgZW5hYmxlZAogICAgICBhd2FpdCBnZXRGQ01Ub2tlbigpOwoKICAgICAgLy8gQ2xlYXIgYmFkZ2UgY291bnQgYXMgdGhlIGxhc3Qgc3RlcCBvZiBpbml0aWFsaXphdGlvbgogICAgICBhd2FpdCBjbGVhckJhZGdlKCk7CgogICAgICBfbG9nZ2VyLmkoJyRfdGFnOiBGaXJlYmFzZSBub3RpZmljYXRpb24gY2xpZW50IGluaXRpYWxpemVkIHN1Y2Nlc3NmdWxseScpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJyRfdGFnOiBFcnJvciBpbml0aWFsaXppbmcgRmlyZWJhc2Ugbm90aWZpY2F0aW9uIGNsaWVudCcsIGUsIHMpOwogICAgICB0aHJvdyBOb3RpZmljYXRpb25FeGNlcHRpb24obWVzc2FnZTogZS5tZXNzYWdlKCkpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPGJvb2w+IHJlcXVlc3RQZXJtaXNzaW9ucyh7Ym9vbCBwcm92aXNpb25hbCA9IGZhbHNlfSkgYXN5bmMgewogICAgcmV0dXJuIGF3YWl0IF9wZXJtaXNzaW9uTWFuYWdlci5yZXF1ZXN0UGVybWlzc2lvbnMoKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBjbGVhckJhZGdlKCkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgX2xvZ2dlci5pKCfwn5SUIENsZWFyaW5nIGJhZGdlIGNvdW50Jyk7CiAgICAgIGlmICghX2lzR3JhbnRlZCkgewogICAgICAgIF9sb2dnZXIudygKICAgICAgICAgICfwn5SUIE5vdGlmaWNhdGlvbiBwZXJtaXNzaW9ucyAnCiAgICAgICAgICAnbm90IGdyYW50ZWQsIGNhbm5vdCBjbGVhciBiYWRnZScsCiAgICAgICAgKTsKICAgICAgICByZXR1cm47CiAgICAgIH0KICAgICAgaWYgKCFhd2FpdCBBcHBCYWRnZVBsdXMuaXNTdXBwb3J0ZWQoKSkgewogICAgICAgIF9sb2dnZXIudygn8J+UlCBBcHBCYWRnZVBsdXMgaXMgbm90IHN1cHBvcnRlZCBvbiB0aGlzIHBsYXRmb3JtJyk7CiAgICAgICAgcmV0dXJuOwogICAgICB9CiAgICAgIGF3YWl0IEFwcEJhZGdlUGx1cy51cGRhdGVCYWRnZSgwKTsKICAgICAgX2xvZ2dlci5pKCfwn5SUIEJhZGdlIGNvdW50IGNsZWFyZWQnKTsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgX2xvZ2dlci53KCfwn5SUIEZhaWxlZCB0byBjbGVhciBiYWRnZSBjb3VudDogJGUnKTsKICAgIH0KICB9CgogIEZ1dHVyZTx2b2lkPiBfc2V0dXBGQ01IYW5kbGVycygpIGFzeW5jIHsKICAgIC8vIEhhbmRsZSBiYWNrZ3JvdW5kIG1lc3NhZ2VzICh3aGVuIGFwcCBpcyB0ZXJtaW5hdGVkKQogICAgRmlyZWJhc2VNZXNzYWdpbmcub25CYWNrZ3JvdW5kTWVzc2FnZShfaGFuZGxlQmFja2dyb3VuZE5vdGlmaWNhdGlvbik7CgogICAgLy8gSGFuZGxlIGZvcmVncm91bmQgbWVzc2FnZXMKICAgIF9mb3JlZ3JvdW5kTWVzc2FnZVN1YnNjcmlwdGlvbiA9IEZpcmViYXNlTWVzc2FnaW5nLm9uTWVzc2FnZS5saXN0ZW4oCiAgICAgIChSZW1vdGVNZXNzYWdlIG1lc3NhZ2UpIHsKICAgICAgICBfbG9nZ2VyLmkoJyRfdGFnOiBSZWNlaXZlZCBmb3JlZ3JvdW5kIG1lc3NhZ2U6ICR7bWVzc2FnZS5tZXNzYWdlSWR9Jyk7CiAgICAgICAgaGFuZGxlRm9yZWdyb3VuZE5vdGlmaWNhdGlvbihtZXNzYWdlKTsKICAgICAgfSwKICAgICAgb25FcnJvcjogKGVycm9yKSB7CiAgICAgICAgX2xvZ2dlci5lKCckX3RhZzogRXJyb3IgaW4gZm9yZWdyb3VuZCBtZXNzYWdlIGhhbmRsZXInLCBlcnJvcik7CiAgICAgIH0sCiAgICApOwoKICAgIC8vIEhhbmRsZSBub3RpZmljYXRpb24gb3BlbmVkIChmcm9tIGJhY2tncm91bmQvdGVybWluYXRlZCkKICAgIF9tZXNzYWdlT3BlbmVkQXBwU3Vic2NyaXB0aW9uID0gRmlyZWJhc2VNZXNzYWdpbmcub25NZXNzYWdlT3BlbmVkQXBwLmxpc3RlbigKICAgICAgKFJlbW90ZU1lc3NhZ2UgbWVzc2FnZSkgewogICAgICAgIF9sb2dnZXIuaSgnJF90YWc6IE5vdGlmaWNhdGlvbiBvcGVuZWQgYXBwOiAke21lc3NhZ2UubWVzc2FnZUlkfScpOwogICAgICAgIGhhbmRsZU5vdGlmaWNhdGlvbk9wZW5lZChtZXNzYWdlLCBzb3VyY2U6ICdiYWNrZ3JvdW5kJyk7CiAgICAgIH0sCiAgICAgIG9uRXJyb3I6IChlcnJvcikgewogICAgICAgIF9sb2dnZXIuZSgnJF90YWc6IEVycm9yIGluIG5vdGlmaWNhdGlvbiBvcGVuZWQgaGFuZGxlcicsIGVycm9yKTsKICAgICAgfSwKICAgICk7CgogICAgLy8gSGFuZGxlIGluaXRpYWwgbWVzc2FnZSAoaWYgYXBwIHdhcyBvcGVuZWQgZnJvbSB0ZXJtaW5hdGVkIHN0YXRlKQogICAgaWYgKF9maXJlYmFzZU1lc3NhZ2luZyAhPSBudWxsKSB7CiAgICAgIGZpbmFsIGluaXRpYWxNZXNzYWdlID0gYXdhaXQgX2ZpcmViYXNlTWVzc2FnaW5nIS5nZXRJbml0aWFsTWVzc2FnZSgpOwogICAgICBpZiAoaW5pdGlhbE1lc3NhZ2UgIT0gbnVsbCkgewogICAgICAgIF9sb2dnZXIuaSgKICAgICAgICAgICckX3RhZzogQXBwIG9wZW5lZCBmcm9tIHRlcm1pbmF0ZWQgc3RhdGU6ICR7aW5pdGlhbE1lc3NhZ2UubWVzc2FnZUlkfScsCiAgICAgICAgKTsKICAgICAgICBoYW5kbGVOb3RpZmljYXRpb25PcGVuZWQoaW5pdGlhbE1lc3NhZ2UsIHNvdXJjZTogJ3Rlcm1pbmF0ZWQnKTsKICAgICAgfQoKICAgICAgLy8gSGFuZGxlIHRva2VuIHJlZnJlc2gKICAgICAgX3Rva2VuUmVmcmVzaFN1YnNjcmlwdGlvbiA9IF9maXJlYmFzZU1lc3NhZ2luZyEub25Ub2tlblJlZnJlc2gubGlzdGVuKAogICAgICAgIChTdHJpbmcgbmV3VG9rZW4pIHsKICAgICAgICAgIF9mY21Ub2tlbiA9IG5ld1Rva2VuOwogICAgICAgICAgX3Rva2VuTWFuYWdlci5oYW5kbGVUb2tlblJlZnJlc2gobmV3VG9rZW4pOwogICAgICAgIH0sCiAgICAgICAgb25FcnJvcjogKGVycm9yKSB7CiAgICAgICAgICBfbG9nZ2VyLmUoJyRfdGFnOiBFcnJvciBpbiB0b2tlbiByZWZyZXNoIGhhbmRsZXInLCBlcnJvcik7CiAgICAgICAgfSwKICAgICAgKTsKICAgIH0KCiAgICBfbG9nZ2VyLmkoJyRfdGFnOiBGQ00gaGFuZGxlcnMgc2V0dXAgY29tcGxldGUnKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTxTdHJpbmc/PiBnZXRGQ01Ub2tlbigpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIF9mY21Ub2tlbiA9IGF3YWl0IF90b2tlbk1hbmFnZXIuZ2V0RkNNVG9rZW4oKTsKCiAgICAgIGlmIChfZmNtVG9rZW4gIT0gbnVsbCkgewogICAgICAgIF9kZXZpY2VJZCA9IGF3YWl0IF90b2tlbk1hbmFnZXIucmVnaXN0ZXJUb2tlbihfZmNtVG9rZW4hKTsKICAgICAgICBfbG9nZ2VyLmkoJyRfdGFnOiBUb2tlbiByZWdpc3RlcmVkIHdpdGggZGV2aWNlIElEOiAkX2RldmljZUlkJyk7CiAgICAgIH0KCiAgICAgIHJldHVybiBfZmNtVG9rZW47CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnJF90YWc6IEVycm9yIGdldHRpbmcgRkNNIHRva2VuJywgZSwgcyk7CiAgICAgIHJldHVybiBudWxsOwogICAgfQogIH0KCiAgLy8vIEhhbmRsZSBiYWNrZ3JvdW5kIG5vdGlmaWNhdGlvbiAod2hlbiBhcHAgaXMgbm90IHJ1bm5pbmcpCiAgQHByYWdtYSgndm06ZW50cnktcG9pbnQnKQogIHN0YXRpYyBGdXR1cmU8dm9pZD4gX2hhbmRsZUJhY2tncm91bmROb3RpZmljYXRpb24oCiAgICBSZW1vdGVNZXNzYWdlIG1lc3NhZ2UsCiAgKSBhc3luYyB7CiAgICB0cnkgewogICAgICBkZWJ1Z1ByaW50KAogICAgICAgICckX3RhZzogSGFuZGxpbmcgYmFja2dyb3VuZCBub3RpZmljYXRpb246ICR7bWVzc2FnZS5tZXNzYWdlSWR9JywKICAgICAgKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgZGVidWdQcmludCgKICAgICAgICAnJF90YWc6IEVycm9yIGhhbmRsaW5nIGJhY2tncm91bmQgbm90aWZpY2F0aW9uOicKICAgICAgICAnICR7bWVzc2FnZS5tZXNzYWdlSWR9IC0gJGUgJHMnLAogICAgICApOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGhhbmRsZUZvcmVncm91bmROb3RpZmljYXRpb24oUmVtb3RlTWVzc2FnZSBtZXNzYWdlKSBhc3luYyB7CiAgICB0cnkgewogICAgICBfbG9nZ2VyLmkoCiAgICAgICAgJyRfdGFnOiBIYW5kbGluZyBmb3JlZ3JvdW5kIG5vdGlmaWNhdGlvbjogJHttZXNzYWdlLm1lc3NhZ2VJZH0nLAogICAgICApOwoKICAgICAgZmluYWwgbm90aWZpY2F0aW9uID0gbWVzc2FnZS5ub3RpZmljYXRpb247CiAgICAgIGlmIChub3RpZmljYXRpb24gIT0gbnVsbCAmJgogICAgICAgICAgbm90aWZpY2F0aW9uLnRpdGxlICE9IG51bGwgJiYKICAgICAgICAgIG5vdGlmaWNhdGlvbi5ib2R5ICE9IG51bGwpIHsKICAgICAgICBUb2FzdC5ub3RpZmljYXRpb24oCiAgICAgICAgICB0aXRsZTogbm90aWZpY2F0aW9uLnRpdGxlISwKICAgICAgICAgIGJvZHk6IG5vdGlmaWNhdGlvbi5ib2R5ISwKICAgICAgICApOwogICAgICB9CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnJF90YWc6IEVycm9yIGhhbmRsaW5nIGZvcmVncm91bmQgbm90aWZpY2F0aW9uJywgZSwgcyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gaGFuZGxlTm90aWZpY2F0aW9uT3BlbmVkKAogICAgUmVtb3RlTWVzc2FnZSBtZXNzYWdlLCB7CiAgICBTdHJpbmc/IHNvdXJjZSwKICB9KSBhc3luYyB7CiAgICB0cnkgewogICAgICBfbG9nZ2VyLmkoJyRfdGFnOiBIYW5kbGluZyBub3RpZmljYXRpb24gb3BlbmVkOiAke21lc3NhZ2UubWVzc2FnZUlkfScpOwoKICAgICAgZmluYWwgbm90aWZpY2F0aW9uID0gbWVzc2FnZS5ub3RpZmljYXRpb247CgogICAgICBfbG9nZ2VyLmkoCiAgICAgICAgJyRfdGFnOiBOb3RpZmljYXRpb24gb3BlbmVkIGZyb20gJHNvdXJjZTogJHtub3RpZmljYXRpb24/LnRpdGxlfSAtICR7bm90aWZpY2F0aW9uPy5ib2R5fScsCiAgICAgICk7CgogICAgICAvLyBEZWVwIGxpbmsgY29udHJhY3Q6IHNlbmQgYSBgcm91dGVgIGluIHRoZSBtZXNzYWdlIGRhdGEgYW5kIHRoZSBhcHAKICAgICAgLy8gbmF2aWdhdGVzIHRoZXJlLCBlLmcuIHsicm91dGUiOiAiL2hvbWUvb3JkZXJzLzQyIn0uIEJyYW5jaCBvbiB5b3VyIG93bgogICAgICAvLyBwYXlsb2FkIGZpZWxkcyBoZXJlIGlmIHlvdSBuZWVkIHNvbWV0aGluZyByaWNoZXIgdGhhbiBhIHBhdGguCiAgICAgIGZpbmFsIHJvdXRlID0gbWVzc2FnZS5kYXRhWydyb3V0ZSddOwogICAgICBpZiAocm91dGUgaXMhIFN0cmluZyB8fCByb3V0ZS5pc0VtcHR5KSByZXR1cm47CgogICAgICBkaS5nZXQ8R29Sb3V0ZXI+KCkucHVzaChyb3V0ZSk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnJF90YWc6IEVycm9yIGhhbmRsaW5nIG5vdGlmaWNhdGlvbiBvcGVuZWQnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIFN0cmluZz8gZ2V0IGZjbVRva2VuID0+IF9mY21Ub2tlbjsKCiAgQG92ZXJyaWRlCiAgU3RyaW5nPyBnZXQgZGV2aWNlSWQgPT4gX2RldmljZUlkOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gZGlzcG9zZSgpIGFzeW5jIHsKICAgIF9sb2dnZXIuaSgnJF90YWc6IERpc3Bvc2luZyBGaXJlYmFzZSBub3RpZmljYXRpb24gY2xpZW50Jyk7CgogICAgLy8gQ2FuY2VsIGFsbCBzdWJzY3JpcHRpb25zIHRvIHByZXZlbnQgbWVtb3J5IGxlYWtzCiAgICBfZm9yZWdyb3VuZE1lc3NhZ2VTdWJzY3JpcHRpb24/LmNhbmNlbCgpOwogICAgX21lc3NhZ2VPcGVuZWRBcHBTdWJzY3JpcHRpb24/LmNhbmNlbCgpOwogICAgX3Rva2VuUmVmcmVzaFN1YnNjcmlwdGlvbj8uY2FuY2VsKCk7CgogICAgX2ZvcmVncm91bmRNZXNzYWdlU3Vic2NyaXB0aW9uID0gbnVsbDsKICAgIF9tZXNzYWdlT3BlbmVkQXBwU3Vic2NyaXB0aW9uID0gbnVsbDsKICAgIF90b2tlblJlZnJlc2hTdWJzY3JpcHRpb24gPSBudWxsOwoKICAgIF9maXJlYmFzZU1lc3NhZ2luZyA9IG51bGw7CiAgfQp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/index.dart", + "data": + "ZXhwb3J0ICdmaXJlYmFzZV9ub3RpZmljYXRpb25fY2xpZW50LmRhcnQnOwpleHBvcnQgJ25vdGlmaWNhdGlvbl9jbGllbnQuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/client/notification_client.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX21lc3NhZ2luZy9maXJlYmFzZV9tZXNzYWdpbmcuZGFydCcKICAgIGhpZGUgTm90aWZpY2F0aW9uU2V0dGluZ3M7CgovLy8gQWJzdHJhY3Qgbm90aWZpY2F0aW9uIGNsaWVudCBpbnRlcmZhY2UKLy8vIFNpbXBsaWZpZWQgaW50ZXJmYWNlIGZvbGxvd2luZyBuZXR3b3JrIHBhY2thZ2UgcGF0dGVybnMKYWJzdHJhY3QgY2xhc3MgTm90aWZpY2F0aW9uQ2xpZW50IHsKICBGdXR1cmU8dm9pZD4gaW5pdCgpOwoKICBGdXR1cmU8Ym9vbD4gcmVxdWVzdFBlcm1pc3Npb25zKHtib29sIHByb3Zpc2lvbmFsID0gZmFsc2V9KTsKCiAgRnV0dXJlPFN0cmluZz8+IGdldEZDTVRva2VuKCk7CgogIEZ1dHVyZTx2b2lkPiBoYW5kbGVGb3JlZ3JvdW5kTm90aWZpY2F0aW9uKFJlbW90ZU1lc3NhZ2UgbWVzc2FnZSk7CgogIEZ1dHVyZTx2b2lkPiBoYW5kbGVOb3RpZmljYXRpb25PcGVuZWQoCiAgICBSZW1vdGVNZXNzYWdlIG1lc3NhZ2UsIHsKICAgIFN0cmluZz8gc291cmNlLAogIH0pOwoKICBGdXR1cmU8dm9pZD4gY2xlYXJCYWRnZSgpOwoKICBTdHJpbmc/IGdldCBmY21Ub2tlbjsKCiAgU3RyaW5nPyBnZXQgZGV2aWNlSWQ7CgogIEZ1dHVyZTx2b2lkPiBkaXNwb3NlKCk7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/config/index.dart", + "data": "ZXhwb3J0ICdub3RpZmljYXRpb25fY29uZmlnLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/config/notification_config.dart", + "data": + "Y2xhc3MgTm90aWZpY2F0aW9uQ29uZmlnIHsKICBjb25zdCBOb3RpZmljYXRpb25Db25maWcoKTsKfQoKY2xhc3MgRGVmYXVsdE5vdGlmaWNhdGlvbkNvbmZpZyBleHRlbmRzIE5vdGlmaWNhdGlvbkNvbmZpZyB7CiAgY29uc3QgRGVmYXVsdE5vdGlmaWNhdGlvbkNvbmZpZygpIDogc3VwZXIoKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/device_info.dart", + "data": + "YWJzdHJhY3QgY2xhc3MgRGV2aWNlSW5mbyB7CiAgRnV0dXJlPFN0cmluZz4gZ2VuZXJhdGVEZXZpY2VJZCgpOwoKICBGdXR1cmU8U3RyaW5nPiBnZXREZXZpY2VOYW1lKCk7Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/device_info_impl.dart", + "data": + "aW1wb3J0ICdkYXJ0OmNvbnZlcnQnOwppbXBvcnQgJ2RhcnQ6aW8nOwoKaW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL2xvZ2dlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmNyeXB0by9jcnlwdG8uZGFydCc7CmltcG9ydCAncGFja2FnZTpkZXZpY2VfaW5mb19wbHVzL2RldmljZV9pbmZvX3BsdXMuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5vdGlmaWNhdGlvbnMvc3JjL2RldmljZV9pbmZvL2RldmljZV9pbmZvLmRhcnQnOwoKLy8vIENvbmNyZXRlIGltcGxlbWVudGF0aW9uIG9mIERldmljZUluZm8gZm9yIEFuZHJvaWQgYW5kIGlPUyBvbmx5Ci8vLyBHZW5lcmF0ZXMgY29uc2lzdGVudCBkZXZpY2UgaWRlbnRpZmllcnMgdXNpbmcgc3RhYmxlIGRldmljZSBjaGFyYWN0ZXJpc3RpY3MKY2xhc3MgRGV2aWNlSW5mb0ltcGwgaW1wbGVtZW50cyBEZXZpY2VJbmZvIHsKICBzdGF0aWMgY29uc3QgU3RyaW5nIF90YWcgPSAnRGV2aWNlSW5mb0ltcGwnOwoKICBmaW5hbCBMb2dnZXIgX2xvZ2dlcjsKICBmaW5hbCBEZXZpY2VJbmZvUGx1Z2luIF9kZXZpY2VJbmZvUGx1Z2luOwoKICBEZXZpY2VJbmZvSW1wbCh7TG9nZ2VyPyBsb2dnZXIsIERldmljZUluZm9QbHVnaW4/IGRldmljZUluZm9QbHVnaW59KQogICAgOiBfbG9nZ2VyID0gbG9nZ2VyID8/IGRpLmdldDxMb2dnZXI+KCksCiAgICAgIF9kZXZpY2VJbmZvUGx1Z2luID0gZGV2aWNlSW5mb1BsdWdpbiA/PyBEZXZpY2VJbmZvUGx1Z2luKCk7CgogIEBvdmVycmlkZQogIEZ1dHVyZTxTdHJpbmc+IGdlbmVyYXRlRGV2aWNlSWQoKSBhc3luYyB7CiAgICB0cnkgewogICAgICBpZiAoUGxhdGZvcm0uaXNBbmRyb2lkKSB7CiAgICAgICAgcmV0dXJuIGF3YWl0IF9nZW5lcmF0ZUFuZHJvaWREZXZpY2VJZCgpOwogICAgICB9IGVsc2UgaWYgKFBsYXRmb3JtLmlzSU9TKSB7CiAgICAgICAgcmV0dXJuIGF3YWl0IF9nZW5lcmF0ZUlPU0RldmljZUlkKCk7CiAgICAgIH0gZWxzZSB7CiAgICAgICAgX2xvZ2dlci53KCckX3RhZzogVW5zdXBwb3J0ZWQgcGxhdGZvcm06ICR7UGxhdGZvcm0ub3BlcmF0aW5nU3lzdGVtfScpOwogICAgICAgIHRocm93IFVuc3VwcG9ydGVkRXJyb3IoJ09ubHkgQW5kcm9pZCBhbmQgaU9TIHBsYXRmb3JtcyBhcmUgc3VwcG9ydGVkJyk7CiAgICAgIH0KICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgX2xvZ2dlci53KCckX3RhZzogRXJyb3IgZ2VuZXJhdGluZyBkZXZpY2UgSUQ6ICRlJyk7CiAgICAgIHJldHVybiBfZ2VuZXJhdGVGYWxsYmFja0lkKCk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8U3RyaW5nPiBnZXREZXZpY2VOYW1lKCkgYXN5bmMgewogICAgdHJ5IHsKICAgICAgaWYgKFBsYXRmb3JtLmlzQW5kcm9pZCkgewogICAgICAgIHJldHVybiBhd2FpdCBfZ2V0QW5kcm9pZERldmljZU5hbWUoKTsKICAgICAgfSBlbHNlIGlmIChQbGF0Zm9ybS5pc0lPUykgewogICAgICAgIHJldHVybiBhd2FpdCBfZ2V0SU9TRGV2aWNlTmFtZSgpOwogICAgICB9IGVsc2UgewogICAgICAgIF9sb2dnZXIudygnJF90YWc6IFVuc3VwcG9ydGVkIHBsYXRmb3JtOiAke1BsYXRmb3JtLm9wZXJhdGluZ1N5c3RlbX0nKTsKICAgICAgICB0aHJvdyBVbnN1cHBvcnRlZEVycm9yKCdPbmx5IEFuZHJvaWQgYW5kIGlPUyBwbGF0Zm9ybXMgYXJlIHN1cHBvcnRlZCcpOwogICAgICB9CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2dnZXIudygnJF90YWc6IEVycm9yIGdldHRpbmcgZGV2aWNlIG5hbWU6ICRlJyk7CiAgICAgIHJldHVybiBfZ2V0RmFsbGJhY2tEZXZpY2VOYW1lKCk7CiAgICB9CiAgfQoKICAvLy8gR2VuZXJhdGUgQW5kcm9pZCBkZXZpY2UgSUQgdXNpbmcgc3RhYmxlIGhhcmR3YXJlIGNoYXJhY3RlcmlzdGljcwogIEZ1dHVyZTxTdHJpbmc+IF9nZW5lcmF0ZUFuZHJvaWREZXZpY2VJZCgpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIGFuZHJvaWRJbmZvID0gYXdhaXQgX2RldmljZUluZm9QbHVnaW4uYW5kcm9pZEluZm87CgogICAgICAvLyBVc2Ugb25seSB0aGUgbW9zdCBzdGFibGUgaGFyZHdhcmUgY2hhcmFjdGVyaXN0aWNzIHRoYXQgZG9uJ3QgY2hhbmdlCiAgICAgIGZpbmFsIHN0YWJsZUNoYXJhY3RlcmlzdGljcyA9IFsKICAgICAgICBhbmRyb2lkSW5mby5icmFuZCwKICAgICAgICBhbmRyb2lkSW5mby5tb2RlbCwKICAgICAgICBhbmRyb2lkSW5mby5kZXZpY2UsCiAgICAgICAgYW5kcm9pZEluZm8uaGFyZHdhcmUsCiAgICAgICAgYW5kcm9pZEluZm8uYm9hcmQsCiAgICAgICAgYW5kcm9pZEluZm8uYm9vdGxvYWRlciwKICAgICAgICBhbmRyb2lkSW5mby5wcm9kdWN0LAogICAgICAgIC8vIGZpbmdlcnByaW50IGNvbnRhaW5zIGJ1aWxkIGluZm8gYnV0IGlzIGdlbmVyYWxseSBzdGFibGUgZm9yIHNhbWUgZGV2aWNlCiAgICAgICAgYW5kcm9pZEluZm8uZmluZ2VycHJpbnQsCiAgICAgICAgLy8gUGh5c2ljYWwgZGV2aWNlIGluZGljYXRvcgogICAgICAgIGFuZHJvaWRJbmZvLmlzUGh5c2ljYWxEZXZpY2UudG9TdHJpbmcoKSwKICAgICAgICAvLyBIYXJkd2FyZSBmZWF0dXJlcyB0aGF0IGFyZSBzdGFibGUKICAgICAgICBhbmRyb2lkSW5mby5zdXBwb3J0ZWRBYmlzLmpvaW4oJywnKSwKICAgICAgICBhbmRyb2lkSW5mby5zdXBwb3J0ZWQzMkJpdEFiaXMuam9pbignLCcpLAogICAgICAgIGFuZHJvaWRJbmZvLnN1cHBvcnRlZDY0Qml0QWJpcy5qb2luKCcsJyksCiAgICAgIF07CgogICAgICAvLyBSZW1vdmUgZW1wdHkgdmFsdWVzIGFuZCBjcmVhdGUgYSBkZXRlcm1pbmlzdGljIHN0cmluZwogICAgICBmaW5hbCBjbGVhbkNoYXJhY3RlcmlzdGljcyA9IHN0YWJsZUNoYXJhY3RlcmlzdGljcwogICAgICAgICAgLndoZXJlKChjaGFyKSA9PiBjaGFyLmlzTm90RW1wdHkgJiYgY2hhciAhPSAnbnVsbCcpCiAgICAgICAgICAudG9MaXN0KCk7CgogICAgICBpZiAoY2xlYW5DaGFyYWN0ZXJpc3RpY3MuaXNFbXB0eSkgewogICAgICAgIHRocm93IEV4Y2VwdGlvbignTm8gc3RhYmxlIGNoYXJhY3RlcmlzdGljcyBmb3VuZCBmb3IgQW5kcm9pZCBkZXZpY2UnKTsKICAgICAgfQoKICAgICAgLy8gU29ydCB0byBlbnN1cmUgY29uc2lzdGVudCBvcmRlcmluZwogICAgICBjbGVhbkNoYXJhY3RlcmlzdGljcy5zb3J0KCk7CgogICAgICBmaW5hbCBkZXZpY2VTdHJpbmcgPSBjbGVhbkNoYXJhY3RlcmlzdGljcy5qb2luKCd8Jyk7CiAgICAgIGZpbmFsIGRldmljZUhhc2ggPSBfZ2VuZXJhdGVEZXRlcm1pbmlzdGljSGFzaChkZXZpY2VTdHJpbmcpOwoKICAgICAgX2xvZ2dlci5kKAogICAgICAgICckX3RhZzogR2VuZXJhdGVkIEFuZHJvaWQgZGV2aWNlIElEIGZyb20gJwogICAgICAgICcke2NsZWFuQ2hhcmFjdGVyaXN0aWNzLmxlbmd0aH0gY2hhcmFjdGVyaXN0aWNzJywKICAgICAgKTsKICAgICAgcmV0dXJuICdhbmRyb2lkXyRkZXZpY2VIYXNoJzsKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgX2xvZ2dlci53KCckX3RhZzogQW5kcm9pZCBkZXZpY2UgSUQgZ2VuZXJhdGlvbiBmYWlsZWQ6ICRlJyk7CiAgICAgIHJldGhyb3c7CiAgICB9CiAgfQoKICAvLy8gR2VuZXJhdGUgaU9TIGRldmljZSBJRCB1c2luZyBpZGVudGlmaWVyRm9yVmVuZG9yIG9yIHN0YWJsZSBjaGFyYWN0ZXJpc3RpY3MKICBGdXR1cmU8U3RyaW5nPiBfZ2VuZXJhdGVJT1NEZXZpY2VJZCgpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGZpbmFsIGlvc0luZm8gPSBhd2FpdCBfZGV2aWNlSW5mb1BsdWdpbi5pb3NJbmZvOwoKICAgICAgLy8gRmlyc3QgcHJlZmVyZW5jZTogdXNlIGlkZW50aWZpZXJGb3JWZW5kb3IgYXMgaXQncyBkZXNpZ25lZCB0byBiZSBzdGFibGUKICAgICAgZmluYWwgaWRlbnRpZmllciA9IGlvc0luZm8uaWRlbnRpZmllckZvclZlbmRvcjsKICAgICAgaWYgKGlkZW50aWZpZXIgIT0gbnVsbCAmJiBpZGVudGlmaWVyLmlzTm90RW1wdHkgJiYgaWRlbnRpZmllciAhPSAnbnVsbCcpIHsKICAgICAgICBfbG9nZ2VyLmQoJyRfdGFnOiBVc2luZyBpT1MgaWRlbnRpZmllckZvclZlbmRvcicpOwogICAgICAgIHJldHVybiAnaW9zXyRpZGVudGlmaWVyJzsKICAgICAgfQoKICAgICAgLy8gRmFsbGJhY2s6IHVzZSBzdGFibGUgZGV2aWNlIGNoYXJhY3RlcmlzdGljcwogICAgICBfbG9nZ2VyLmkoCiAgICAgICAgJyRfdGFnOiBpT1MgaWRlbnRpZmllckZvclZlbmRvciBub3QgYXZhaWxhYmxlLCAnCiAgICAgICAgJ3VzaW5nIGRldmljZSBjaGFyYWN0ZXJpc3RpY3MnLAogICAgICApOwoKICAgICAgZmluYWwgc3RhYmxlQ2hhcmFjdGVyaXN0aWNzID0gWwogICAgICAgIGlvc0luZm8ubW9kZWwsCiAgICAgICAgaW9zSW5mby5sb2NhbGl6ZWRNb2RlbCwKICAgICAgICBpb3NJbmZvLnN5c3RlbU5hbWUsCiAgICAgICAgaW9zSW5mby5pc1BoeXNpY2FsRGV2aWNlLnRvU3RyaW5nKCksCiAgICAgICAgLy8gTWFjaGluZSBpZGVudGlmaWVyIGZyb20gdXRzbmFtZSAoaGFyZHdhcmUgbW9kZWwpCiAgICAgICAgaW9zSW5mby51dHNuYW1lLm1hY2hpbmUsCiAgICAgICAgaW9zSW5mby51dHNuYW1lLnN5c25hbWUsCiAgICAgICAgLy8gVGhlc2UgYXJlIGdlbmVyYWxseSBzdGFibGUgZm9yIHRoZSBzYW1lIGRldmljZSBtb2RlbAogICAgICAgIGlvc0luZm8udXRzbmFtZS5ub2RlbmFtZSwKICAgICAgXTsKCiAgICAgIC8vIFJlbW92ZSBlbXB0eSB2YWx1ZXMgYW5kIGNyZWF0ZSBhIGRldGVybWluaXN0aWMgc3RyaW5nCiAgICAgIGZpbmFsIGNsZWFuQ2hhcmFjdGVyaXN0aWNzID0gc3RhYmxlQ2hhcmFjdGVyaXN0aWNzCiAgICAgICAgICAud2hlcmUoKGNoYXIpID0+IGNoYXIuaXNOb3RFbXB0eSAmJiBjaGFyICE9ICdudWxsJykKICAgICAgICAgIC50b0xpc3QoKTsKCiAgICAgIGlmIChjbGVhbkNoYXJhY3RlcmlzdGljcy5pc0VtcHR5KSB7CiAgICAgICAgdGhyb3cgRXhjZXB0aW9uKCdObyBzdGFibGUgY2hhcmFjdGVyaXN0aWNzIGZvdW5kIGZvciBpT1MgZGV2aWNlJyk7CiAgICAgIH0KCiAgICAgIC8vIFNvcnQgdG8gZW5zdXJlIGNvbnNpc3RlbnQgb3JkZXJpbmcKICAgICAgY2xlYW5DaGFyYWN0ZXJpc3RpY3Muc29ydCgpOwoKICAgICAgZmluYWwgZGV2aWNlU3RyaW5nID0gY2xlYW5DaGFyYWN0ZXJpc3RpY3Muam9pbignfCcpOwogICAgICBmaW5hbCBkZXZpY2VIYXNoID0gX2dlbmVyYXRlRGV0ZXJtaW5pc3RpY0hhc2goZGV2aWNlU3RyaW5nKTsKCiAgICAgIF9sb2dnZXIuZCgKICAgICAgICAnJF90YWc6IEdlbmVyYXRlZCBpT1MgZGV2aWNlIElEIGZyb20gJwogICAgICAgICcke2NsZWFuQ2hhcmFjdGVyaXN0aWNzLmxlbmd0aH0gY2hhcmFjdGVyaXN0aWNzJywKICAgICAgKTsKICAgICAgcmV0dXJuICdpb3NfJGRldmljZUhhc2gnOwogICAgfSBjYXRjaCAoZSkgewogICAgICBfbG9nZ2VyLncoJyRfdGFnOiBpT1MgZGV2aWNlIElEIGdlbmVyYXRpb24gZmFpbGVkOiAkZScpOwogICAgICByZXRocm93OwogICAgfQogIH0KCiAgLy8vIEdldCBBbmRyb2lkIGRldmljZSBuYW1lIGluIGZvcm1hdCAiQnJhbmQgTW9kZWwiCiAgRnV0dXJlPFN0cmluZz4gX2dldEFuZHJvaWREZXZpY2VOYW1lKCkgYXN5bmMgewogICAgZmluYWwgYW5kcm9pZEluZm8gPSBhd2FpdCBfZGV2aWNlSW5mb1BsdWdpbi5hbmRyb2lkSW5mbzsKICAgIGZpbmFsIGJyYW5kID0gYW5kcm9pZEluZm8uYnJhbmQ7CiAgICBmaW5hbCBtb2RlbCA9IGFuZHJvaWRJbmZvLm1vZGVsOwoKICAgIHJldHVybiAnJGJyYW5kICRtb2RlbCc7CiAgfQoKICAvLy8gR2V0IGlPUyBkZXZpY2UgbmFtZSBpbiBmb3JtYXQgIk5hbWUgKE1vZGVsKSIKICBGdXR1cmU8U3RyaW5nPiBfZ2V0SU9TRGV2aWNlTmFtZSgpIGFzeW5jIHsKICAgIGZpbmFsIGlvc0luZm8gPSBhd2FpdCBfZGV2aWNlSW5mb1BsdWdpbi5pb3NJbmZvOwogICAgZmluYWwgbmFtZSA9IGlvc0luZm8ubmFtZTsKICAgIGZpbmFsIG1vZGVsID0gaW9zSW5mby5tb2RlbDsKCiAgICByZXR1cm4gJyRuYW1lICgkbW9kZWwpJzsKICB9CgogIC8vLyBHZW5lcmF0ZSBhIGRldGVybWluaXN0aWMgaGFzaCBmcm9tIGRldmljZSBjaGFyYWN0ZXJpc3RpY3MKICAvLy8gVXNlcyBTSEEtMjU2IHRvIGNyZWF0ZSBhIGNvbnNpc3RlbnQsIHN0YWJsZSBoYXNoCiAgU3RyaW5nIF9nZW5lcmF0ZURldGVybWluaXN0aWNIYXNoKFN0cmluZyBpbnB1dCkgewogICAgLy8gQWRkIGEgc2FsdCB0byBtYWtlIHRoZSBoYXNoIG1vcmUgdW5pcXVlIHdoaWxlIGtlZXBpbmcgaXQgZGV0ZXJtaW5pc3RpYwogICAgY29uc3Qgc2FsdCA9ICdkZXZpY2VfaWRlbnRpZmllcl9zYWx0XzIwMjQnOwogICAgZmluYWwgc2FsdGVkSW5wdXQgPSAnJHNhbHR8JGlucHV0JzsKCiAgICBmaW5hbCBieXRlcyA9IHV0ZjguZW5jb2RlKHNhbHRlZElucHV0KTsKICAgIGZpbmFsIGRpZ2VzdCA9IHNoYTI1Ni5jb252ZXJ0KGJ5dGVzKTsKCiAgICAvLyBSZXR1cm4gZmlyc3QgMTYgY2hhcmFjdGVycyBmb3IgcmVhc29uYWJsZSBsZW5ndGgKICAgIHJldHVybiBkaWdlc3QudG9TdHJpbmcoKS5zdWJzdHJpbmcoMCwgMTYpOwogIH0KCiAgLy8vIEdlbmVyYXRlIGEgZmFsbGJhY2sgZGV2aWNlIElEIHdoZW4gYWxsIG90aGVyIG1ldGhvZHMgZmFpbAogIFN0cmluZyBfZ2VuZXJhdGVGYWxsYmFja0lkKCkgewogICAgZmluYWwgcGxhdGZvcm0gPSBQbGF0Zm9ybS5pc0FuZHJvaWQgPyAnYW5kcm9pZCcgOiAnaW9zJzsKCiAgICAvLyBVc2UgYSBkZXRlcm1pbmlzdGljIGZhbGxiYWNrIGJhc2VkIG9uIHBsYXRmb3JtCiAgICBmaW5hbCBmYWxsYmFja1N0cmluZyA9ICcke3BsYXRmb3JtfV9mYWxsYmFja19kZXZpY2UnOwogICAgZmluYWwgZmFsbGJhY2tIYXNoID0gX2dlbmVyYXRlRGV0ZXJtaW5pc3RpY0hhc2goZmFsbGJhY2tTdHJpbmcpOwoKICAgIF9sb2dnZXIudygnJF90YWc6IFVzaW5nIGRldGVybWluaXN0aWMgZmFsbGJhY2sgSUQnKTsKICAgIHJldHVybiAnJHtwbGF0Zm9ybX1fJGZhbGxiYWNrSGFzaCc7CiAgfQoKICAvLy8gR2V0IGZhbGxiYWNrIGRldmljZSBuYW1lIHdoZW4gZGV0ZWN0aW9uIGZhaWxzCiAgU3RyaW5nIF9nZXRGYWxsYmFja0RldmljZU5hbWUoKSB7CiAgICByZXR1cm4gUGxhdGZvcm0uaXNBbmRyb2lkID8gJ0FuZHJvaWQgRGV2aWNlJyA6ICdpT1MgRGV2aWNlJzsKICB9Cn0K", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/device_info/index.dart", + "data": + "ZXhwb3J0ICdkZXZpY2VfaW5mby5kYXJ0JzsKZXhwb3J0ICdkZXZpY2VfaW5mb19pbXBsLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/di/index.dart", + "data": "ZXhwb3J0ICdub3RpZmljYXRpb25fZGkuZGFydCc7Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/di/notification_di.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bm90aWZpY2F0aW9ucy9zcmMvY2xpZW50L2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bm90aWZpY2F0aW9ucy9zcmMvY29uZmlnL2luZGV4LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bm90aWZpY2F0aW9ucy9zcmMvZGV2aWNlX2luZm8vaW5kZXguZGFydCc7CmltcG9ydCAncGFja2FnZTpub3RpZmljYXRpb25zL3NyYy9zZXJ2aWNlcy9ub3RpZmljYXRpb25fcGVybWlzc2lvbl9tYW5hZ2VyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bm90aWZpY2F0aW9ucy9zcmMvc2VydmljZXMvbm90aWZpY2F0aW9uX3Rva2VuX21hbmFnZXIuZGFydCc7CgovLy8gUmVnaXN0ZXIgbm90aWZpY2F0aW9uIHNlcnZpY2VzIHdpdGggZGVwZW5kZW5jeSBpbmplY3Rpb24KRnV0dXJlPHZvaWQ+IHJlZ2lzdGVyTm90aWZpY2F0aW9uV2l0aERJKE5vdGlmaWNhdGlvbkNvbmZpZyBjb25maWcpIGFzeW5jIHsKICAvLyBSZWdpc3RlciBjb25maWcKICBkaS5yZWdpc3RlcjxOb3RpZmljYXRpb25Db25maWc+KGNvbmZpZyk7CgogIC8vIFJlZ2lzdGVyIGRldmljZSBpbmZvIHNlcnZpY2UKICBkaS5yZWdpc3RlcjxEZXZpY2VJbmZvPihEZXZpY2VJbmZvSW1wbCgpKTsKCiAgLy8gUmVnaXN0ZXIgdG9rZW4gbWFuYWdlcgogIGRpLnJlZ2lzdGVyPE5vdGlmaWNhdGlvblRva2VuTWFuYWdlcj4oRmlyZWJhc2VUb2tlbk1hbmFnZXIoKSk7CgogIC8vIFJlZ2lzdGVyIHBlcm1pc3Npb24gbWFuYWdlcgogIGRpLnJlZ2lzdGVyPE5vdGlmaWNhdGlvblBlcm1pc3Npb25NYW5hZ2VyPihGaXJlYmFzZVBlcm1pc3Npb25NYW5hZ2VyKCkpOwoKICAvLyBSZWdpc3RlciBub3RpZmljYXRpb24gY2xpZW50IHdpdGggcHJvcGVyIGRpc3Bvc2FsCiAgZGkucmVnaXN0ZXI8Tm90aWZpY2F0aW9uQ2xpZW50PigKICAgIEZpcmViYXNlTm90aWZpY2F0aW9uQ2xpZW50KGNvbmZpZzogY29uZmlnKSwKICAgIGRpc3Bvc2U6IChjbGllbnQpID0+IGNsaWVudC5kaXNwb3NlKCksCiAgKTsKfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/exceptions/index.dart", + "data": "ZXhwb3J0ICdub3RpZmljYXRpb25fZXhjZXB0aW9ucy5kYXJ0JzsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/exceptions/notification_exceptions.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKCmNsYXNzIE5vdGlmaWNhdGlvbkV4Y2VwdGlvbiBleHRlbmRzIENvcmVFeGNlcHRpb24gewogIE5vdGlmaWNhdGlvbkV4Y2VwdGlvbih7cmVxdWlyZWQgc3VwZXIubWVzc2FnZX0pOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/device_token_models.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmpzb25fYW5ub3RhdGlvbi9qc29uX2Fubm90YXRpb24uZGFydCc7CgpwYXJ0ICdkZXZpY2VfdG9rZW5fbW9kZWxzLmcuZGFydCc7CgpASnNvblNlcmlhbGl6YWJsZSgpCmNsYXNzIERldmljZVRva2VuUmVxdWVzdCB7CiAgZmluYWwgU3RyaW5nIGZjbVRva2VuOwogIGZpbmFsIFN0cmluZyBkZXZpY2VJZDsKICBmaW5hbCBTdHJpbmcgZGV2aWNlTmFtZTsKICBmaW5hbCBTdHJpbmcgZGV2aWNlVHlwZTsKCiAgY29uc3QgRGV2aWNlVG9rZW5SZXF1ZXN0KHsKICAgIHJlcXVpcmVkIHRoaXMuZmNtVG9rZW4sCiAgICByZXF1aXJlZCB0aGlzLmRldmljZUlkLAogICAgcmVxdWlyZWQgdGhpcy5kZXZpY2VOYW1lLAogICAgcmVxdWlyZWQgdGhpcy5kZXZpY2VUeXBlLAogIH0pOwoKICBmYWN0b3J5IERldmljZVRva2VuUmVxdWVzdC5mcm9tSnNvbihNYXA8U3RyaW5nLCBkeW5hbWljPiBqc29uKSA9PgogICAgICBfJERldmljZVRva2VuUmVxdWVzdEZyb21Kc29uKGpzb24pOwoKICBNYXA8U3RyaW5nLCBkeW5hbWljPiB0b0pzb24oKSA9PiBfJERldmljZVRva2VuUmVxdWVzdFRvSnNvbih0aGlzKTsKfQoKQEpzb25TZXJpYWxpemFibGUoKQpjbGFzcyBEZXZpY2VUb2tlblVwZGF0ZVJlcXVlc3QgewogIGZpbmFsIFN0cmluZyBmY21Ub2tlbjsKCiAgY29uc3QgRGV2aWNlVG9rZW5VcGRhdGVSZXF1ZXN0KHtyZXF1aXJlZCB0aGlzLmZjbVRva2VufSk7CgogIGZhY3RvcnkgRGV2aWNlVG9rZW5VcGRhdGVSZXF1ZXN0LmZyb21Kc29uKE1hcDxTdHJpbmcsIGR5bmFtaWM+IGpzb24pID0+CiAgICAgIF8kRGV2aWNlVG9rZW5VcGRhdGVSZXF1ZXN0RnJvbUpzb24oanNvbik7CgogIE1hcDxTdHJpbmcsIGR5bmFtaWM+IHRvSnNvbigpID0+IF8kRGV2aWNlVG9rZW5VcGRhdGVSZXF1ZXN0VG9Kc29uKHRoaXMpOwp9Cg==", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/device_token_models.g.dart", + "data": + "Ly8gR0VORVJBVEVEIENPREUgLSBETyBOT1QgTU9ESUZZIEJZIEhBTkQKCnBhcnQgb2YgJ2RldmljZV90b2tlbl9tb2RlbHMuZGFydCc7CgovLyAqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgovLyBKc29uU2VyaWFsaXphYmxlR2VuZXJhdG9yCi8vICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqCgpEZXZpY2VUb2tlblJlcXVlc3QgXyREZXZpY2VUb2tlblJlcXVlc3RGcm9tSnNvbihNYXA8U3RyaW5nLCBkeW5hbWljPiBqc29uKSA9PgogICAgRGV2aWNlVG9rZW5SZXF1ZXN0KAogICAgICBmY21Ub2tlbjoganNvblsnZmNtVG9rZW4nXSBhcyBTdHJpbmcsCiAgICAgIGRldmljZUlkOiBqc29uWydkZXZpY2VJZCddIGFzIFN0cmluZywKICAgICAgZGV2aWNlTmFtZToganNvblsnZGV2aWNlTmFtZSddIGFzIFN0cmluZywKICAgICAgZGV2aWNlVHlwZToganNvblsnZGV2aWNlVHlwZSddIGFzIFN0cmluZywKICAgICk7CgpNYXA8U3RyaW5nLCBkeW5hbWljPiBfJERldmljZVRva2VuUmVxdWVzdFRvSnNvbihEZXZpY2VUb2tlblJlcXVlc3QgaW5zdGFuY2UpID0+CiAgICA8U3RyaW5nLCBkeW5hbWljPnsKICAgICAgJ2ZjbVRva2VuJzogaW5zdGFuY2UuZmNtVG9rZW4sCiAgICAgICdkZXZpY2VJZCc6IGluc3RhbmNlLmRldmljZUlkLAogICAgICAnZGV2aWNlTmFtZSc6IGluc3RhbmNlLmRldmljZU5hbWUsCiAgICAgICdkZXZpY2VUeXBlJzogaW5zdGFuY2UuZGV2aWNlVHlwZSwKICAgIH07CgpEZXZpY2VUb2tlblVwZGF0ZVJlcXVlc3QgXyREZXZpY2VUb2tlblVwZGF0ZVJlcXVlc3RGcm9tSnNvbigKICBNYXA8U3RyaW5nLCBkeW5hbWljPiBqc29uLAopID0+IERldmljZVRva2VuVXBkYXRlUmVxdWVzdChmY21Ub2tlbjoganNvblsnZmNtVG9rZW4nXSBhcyBTdHJpbmcpOwoKTWFwPFN0cmluZywgZHluYW1pYz4gXyREZXZpY2VUb2tlblVwZGF0ZVJlcXVlc3RUb0pzb24oCiAgRGV2aWNlVG9rZW5VcGRhdGVSZXF1ZXN0IGluc3RhbmNlLAopID0+IDxTdHJpbmcsIGR5bmFtaWM+eydmY21Ub2tlbic6IGluc3RhbmNlLmZjbVRva2VufTsK", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/models/index.dart", + "data": "ZXhwb3J0ICdkZXZpY2VfdG9rZW5fbW9kZWxzLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/index.dart", + "data": + "ZXhwb3J0ICdub3RpZmljYXRpb25fcGVybWlzc2lvbl9tYW5hZ2VyLmRhcnQnOwpleHBvcnQgJ25vdGlmaWNhdGlvbl90b2tlbl9tYW5hZ2VyLmRhcnQnOwo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/notification_permission_manager.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvbG9nZ2VyL2xvZ2dlci5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfbWVzc2FnaW5nL2ZpcmViYXNlX21lc3NhZ2luZy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5ldHdvcmsvbmV0d29yay5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm5vdGlmaWNhdGlvbnMvc3JjL2V4Y2VwdGlvbnMvbm90aWZpY2F0aW9uX2V4Y2VwdGlvbnMuZGFydCc7CgphYnN0cmFjdCBjbGFzcyBOb3RpZmljYXRpb25QZXJtaXNzaW9uTWFuYWdlciB7CiAgRnV0dXJlPGJvb2w+IHJlcXVlc3RQZXJtaXNzaW9ucygpOwp9CgpjbGFzcyBGaXJlYmFzZVBlcm1pc3Npb25NYW5hZ2VyIGltcGxlbWVudHMgTm90aWZpY2F0aW9uUGVybWlzc2lvbk1hbmFnZXIgewogIHN0YXRpYyBjb25zdCBTdHJpbmcgX3RhZyA9ICdGaXJlYmFzZVBlcm1pc3Npb25NYW5hZ2VyJzsKCiAgZmluYWwgTG9nZ2VyIF9sb2dnZXI7CiAgZmluYWwgRmlyZWJhc2VNZXNzYWdpbmcgX2ZpcmViYXNlTWVzc2FnaW5nOwoKICBGaXJlYmFzZVBlcm1pc3Npb25NYW5hZ2VyKHsKICAgIExvZ2dlcj8gbG9nZ2VyLAogICAgRmlyZWJhc2VNZXNzYWdpbmc/IGZpcmViYXNlTWVzc2FnaW5nLAogIH0pIDogX2xvZ2dlciA9IGxvZ2dlciA/PyBkaS5nZXQ8TG9nZ2VyPigpLAogICAgICAgX2ZpcmViYXNlTWVzc2FnaW5nID0gZmlyZWJhc2VNZXNzYWdpbmcgPz8gRmlyZWJhc2VNZXNzYWdpbmcuaW5zdGFuY2U7CgogIEBvdmVycmlkZQogIEZ1dHVyZTxib29sPiByZXF1ZXN0UGVybWlzc2lvbnMoKSBhc3luYyB7CiAgICB0cnkgewogICAgICBfbG9nZ2VyLmkoJyRfdGFnOiBSZXF1ZXN0aW5nIG5vdGlmaWNhdGlvbiBwZXJtaXNzaW9ucycpOwogICAgICBmaW5hbCBhdXRoU2V0dGluZ3MgPSBhd2FpdCBfZmlyZWJhc2VNZXNzYWdpbmcucmVxdWVzdFBlcm1pc3Npb24oKTsKCiAgICAgIGZpbmFsIGlzR3JhbnRlZCA9CiAgICAgICAgICBhdXRoU2V0dGluZ3MuYXV0aG9yaXphdGlvblN0YXR1cyA9PSBBdXRob3JpemF0aW9uU3RhdHVzLmF1dGhvcml6ZWQgfHwKICAgICAgICAgIGF1dGhTZXR0aW5ncy5hdXRob3JpemF0aW9uU3RhdHVzID09IEF1dGhvcml6YXRpb25TdGF0dXMucHJvdmlzaW9uYWw7CgogICAgICBfbG9nZ2VyLmkoCiAgICAgICAgJyRfdGFnOiBOb3RpZmljYXRpb24gcGVybWlzc2lvbnMgcmVxdWVzdGVkICcKICAgICAgICAnd2l0aCBzdGF0dXM6ICR7YXV0aFNldHRpbmdzLmF1dGhvcml6YXRpb25TdGF0dXN9LCAnCiAgICAgICAgJ2dyYW50ZWQ6ICRpc0dyYW50ZWQnLAogICAgICApOwoKICAgICAgcmV0dXJuIGlzR3JhbnRlZDsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCckX3RhZzogRXJyb3IgcmVxdWVzdGluZyBwZXJtaXNzaW9ucycsIGUsIHMpOwogICAgICB0aHJvdyBOb3RpZmljYXRpb25FeGNlcHRpb24obWVzc2FnZTogZS5tZXNzYWdlKCkpOwogICAgfQogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/lib/src/services/notification_token_manager.dart", + "data": + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci9sb2dnZXIuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX21lc3NhZ2luZy9maXJlYmFzZV9tZXNzYWdpbmcuZGFydCc7CmltcG9ydCAncGFja2FnZTpuZXR3b3JrL25ldHdvcmsuZGFydCc7CmltcG9ydCAncGFja2FnZTpub3RpZmljYXRpb25zL25vdGlmaWNhdGlvbnMuZGFydCc7CgphYnN0cmFjdCBjbGFzcyBOb3RpZmljYXRpb25Ub2tlbk1hbmFnZXIgewogIEZ1dHVyZTxTdHJpbmc/PiBnZXRGQ01Ub2tlbigpOwoKICBGdXR1cmU8U3RyaW5nPiByZWdpc3RlclRva2VuKFN0cmluZyBmY21Ub2tlbik7CgogIEZ1dHVyZTx2b2lkPiB1blJlZ2lzdGVyVG9rZW4oU3RyaW5nIGRldmljZUlkKTsKCiAgRnV0dXJlPHZvaWQ+IGhhbmRsZVRva2VuUmVmcmVzaChTdHJpbmcgbmV3VG9rZW4pOwp9CgpjbGFzcyBGaXJlYmFzZVRva2VuTWFuYWdlciBpbXBsZW1lbnRzIE5vdGlmaWNhdGlvblRva2VuTWFuYWdlciB7CiAgc3RhdGljIGNvbnN0IFN0cmluZyBfdGFnID0gJ0ZpcmViYXNlVG9rZW5NYW5hZ2VyJzsKCiAgZmluYWwgTG9nZ2VyIF9sb2dnZXI7CiAgZmluYWwgTmV0d29ya0NsaWVudCBfbmV0d29ya0NsaWVudDsKICBmaW5hbCBEZXZpY2VJbmZvIF9kZXZpY2VJbmZvOwogIGZpbmFsIEZpcmViYXNlTWVzc2FnaW5nIF9maXJlYmFzZU1lc3NhZ2luZzsKCiAgRmlyZWJhc2VUb2tlbk1hbmFnZXIoewogICAgTG9nZ2VyPyBsb2dnZXIsCiAgICBOZXR3b3JrQ2xpZW50PyBuZXR3b3JrQ2xpZW50LAogICAgRGV2aWNlSW5mbz8gZGV2aWNlSW5mbywKICAgIEZpcmViYXNlTWVzc2FnaW5nPyBmaXJlYmFzZU1lc3NhZ2luZywKICB9KSA6IF9sb2dnZXIgPSBsb2dnZXIgPz8gZGkuZ2V0PExvZ2dlcj4oKSwKICAgICAgIF9uZXR3b3JrQ2xpZW50ID0gbmV0d29ya0NsaWVudCA/PyBkaS5nZXQ8TmV0d29ya0NsaWVudD4oKSwKICAgICAgIF9kZXZpY2VJbmZvID0gZGV2aWNlSW5mbyA/PyBkaS5nZXQ8RGV2aWNlSW5mbz4oKSwKICAgICAgIF9maXJlYmFzZU1lc3NhZ2luZyA9IGZpcmViYXNlTWVzc2FnaW5nID8/IEZpcmViYXNlTWVzc2FnaW5nLmluc3RhbmNlOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8U3RyaW5nPz4gZ2V0RkNNVG9rZW4oKSBhc3luYyB7CiAgICB0cnkgewogICAgICBmaW5hbCB0b2tlbiA9IGF3YWl0IF9maXJlYmFzZU1lc3NhZ2luZy5nZXRUb2tlbigpOwogICAgICBpZiAodG9rZW4gIT0gbnVsbCkgewogICAgICAgIF9sb2dnZXIuaSgnJF90YWc6IEZDTSBUb2tlbiBvYnRhaW5lZCBzdWNjZXNzZnVsbHknKTsKICAgICAgfSBlbHNlIHsKICAgICAgICBfbG9nZ2VyLncoJyRfdGFnOiBGYWlsZWQgdG8gb2J0YWluIEZDTSB0b2tlbicpOwogICAgICB9CiAgICAgIHJldHVybiB0b2tlbjsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCckX3RhZzogRXJyb3IgZ2V0dGluZyBGQ00gdG9rZW4nLCBlLCBzKTsKICAgICAgcmV0aHJvdzsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTxTdHJpbmc+IHJlZ2lzdGVyVG9rZW4oU3RyaW5nIGZjbVRva2VuKSBhc3luYyB7CiAgICB0cnkgewogICAgICBfbG9nZ2VyLmkoJyRfdGFnOiBSZWdpc3RlcmluZyBGQ00gdG9rZW4gd2l0aCBiYWNrZW5kJyk7CgogICAgICBmaW5hbCBkZXZpY2VJZCA9IGF3YWl0IF9kZXZpY2VJbmZvLmdlbmVyYXRlRGV2aWNlSWQoKTsKICAgICAgZmluYWwgZGV2aWNlTmFtZSA9IGF3YWl0IF9kZXZpY2VJbmZvLmdldERldmljZU5hbWUoKTsKICAgICAgZmluYWwgZGV2aWNlVHlwZSA9IFBsYXRmb3JtLmlzSU9TID8gJ2lvcycgOiAnYW5kcm9pZCc7CgogICAgICBmaW5hbCByZXF1ZXN0ID0gRGV2aWNlVG9rZW5SZXF1ZXN0KAogICAgICAgIGZjbVRva2VuOiBmY21Ub2tlbiwKICAgICAgICBkZXZpY2VJZDogZGV2aWNlSWQsCiAgICAgICAgZGV2aWNlTmFtZTogZGV2aWNlTmFtZSwKICAgICAgICBkZXZpY2VUeXBlOiBkZXZpY2VUeXBlLAogICAgICApOwoKICAgICAgYXdhaXQgX25ldHdvcmtDbGllbnQucG9zdDxNYXA8U3RyaW5nLCBkeW5hbWljPj4oCiAgICAgICAgJy9kZXZpY2UtdG9rZW5zL21lJywKICAgICAgICBkYXRhOiByZXF1ZXN0LnRvSnNvbigpLAogICAgICAgIGZyb21Kc29uVDogKGpzb24pID0+IGpzb24gYXMgTWFwPFN0cmluZywgZHluYW1pYz4sCiAgICAgICk7CiAgICAgIF9sb2dnZXIuaSgnJF90YWc6IEZDTSB0b2tlbiByZWdpc3RlcmVkIHN1Y2Nlc3NmdWxseScpOwogICAgICByZXR1cm4gZGV2aWNlSWQ7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnJF90YWc6IEVycm9yIHJlZ2lzdGVyaW5nIEZDTSB0b2tlbicsIGUsIHMpOwogICAgICByZXRocm93OwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHVuUmVnaXN0ZXJUb2tlbihTdHJpbmcgZGV2aWNlSWQpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIF9sb2dnZXIuaSgnJF90YWc6IFJlbW92aW5nIEZDTSB0b2tlbiBmcm9tIGJhY2tlbmQnKTsKCiAgICAgIGF3YWl0IF9uZXR3b3JrQ2xpZW50LmRlbGV0ZTxNYXA8U3RyaW5nLCBkeW5hbWljPj4oCiAgICAgICAgJy9kZXZpY2UtdG9rZW5zL21lLyRkZXZpY2VJZCcsCiAgICAgICAgZnJvbUpzb25UOiAoanNvbikgPT4ganNvbiBhcyBNYXA8U3RyaW5nLCBkeW5hbWljPiwKICAgICAgKTsKICAgICAgX2xvZ2dlci5pKCckX3RhZzogRkNNIHRva2VuIHJlbW92ZWQgc3VjY2Vzc2Z1bGx5Jyk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnJF90YWc6IEVycm9yIHJlbW92aW5nIEZDTSB0b2tlbicsIGUsIHMpOwogICAgICByZXRocm93OwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGhhbmRsZVRva2VuUmVmcmVzaChTdHJpbmcgbmV3VG9rZW4pIGFzeW5jIHsKICAgIF9sb2dnZXIuaSgnJF90YWc6IEZDTSB0b2tlbiByZWZyZXNoZWQnKTsKICAgIGF3YWl0IHJlZ2lzdGVyVG9rZW4obmV3VG9rZW4pOwogIH0KfQo=", + "type": "text" + }, + { + "path": + "{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml", + "data": + "bmFtZTogbm90aWZpY2F0aW9ucwpkZXNjcmlwdGlvbjogIkZpcmViYXNlIENsb3VkIE1lc3NhZ2luZywgcGVybWlzc2lvbnMgYW5kIGRldmljZS10b2tlbiBtYW5hZ2VtZW50IGZvciB7e25hbWUudGl0bGVDYXNlKCl9fS4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjI5LjAiCgpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcgogIGFwcF9iYWRnZV9wbHVzOiBeMS4xLjIKICBjcnlwdG86IF4zLjAuNgogIGRldmljZV9pbmZvX3BsdXM6IF4xMS4xLjEKICBmaXJlYmFzZV9jb3JlOiBeMy4xNC4wCiAgZmlyZWJhc2VfbWVzc2FnaW5nOiBeMTUuMS42CiAgZ29fcm91dGVyOiBeMTUuMS4zCiAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICBwZXJtaXNzaW9uX2hhbmRsZXI6IF4xMS4xLjAKICB0aW1lem9uZTogXjAuOS40CiAgY29yZToKICAgIHBhdGg6IC4uL2NvcmUKICBkZXNpZ25fc3lzdGVtOgogICAgcGF0aDogLi4vZGVzaWduX3N5c3RlbQogIGRpOgogICAgcGF0aDogLi4vZGkKe3sjbmV0d29ya319ICBuZXR3b3JrOgogICAgcGF0aDogLi4vbmV0d29yawp7ey9uZXR3b3JrfX17eyNhbmFseXRpY3N9fSAgYW5hbHl0aWNzOgogICAgcGF0aDogLi4vYW5hbHl0aWNzCnt7L2FuYWx5dGljc319CmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCiAgYnVpbGRfcnVubmVyOiBeMi40LjE1CiAganNvbl9zZXJpYWxpemFibGU6IF42LjkuNQoKZmx1dHRlcjoKICB1c2VzLW1hdGVyaWFsLWRlc2lnbjogdHJ1ZQo=", "type": "text" }, { "path": "{{ name.snakeCase() }}/plugins/README.md", - "data": "IyBQdXQgYWxsIHlvdXIgcGx1Z2lucyBpbiB0aGlzIGZvbGRlcgo=", + "data": + "IyBQbHVnaW5zCgpQbGF0Zm9ybSBjaGFubmVscyBhbmQgbmF0aXZlIGludGVncmF0aW9ucy4KCmBgYHNoCm5vbnN0b3AgY3JlYXRlIHBsdWdpbiBteV9wbHVnaW4gLW8gcGx1Z2lucwpgYGAK", "type": "text" }, { "path": "{{ name.snakeCase() }}/pubspec.yaml", "data": - "bmFtZToge3sgbmFtZS5zbmFrZUNhc2UoKSB9fV93b3Jrc3BhY2UKZGVzY3JpcHRpb246IHt7IGRlc2NyaXB0aW9uIH19CgplbnZpcm9ubWVudDoKICBzZGs6ICc+PTMuMC4wIDw0LjAuMCcKCmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl9saW50czogXjUuMC4wCiAgbWVsb3M6IF42LjIuMAo=", + "bmFtZToge3sgbmFtZS5zbmFrZUNhc2UoKSB9fV93b3Jrc3BhY2UKZGVzY3JpcHRpb246ICJ7e3tkZXNjcmlwdGlvbn19fSIKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6ICc+PTMuOC4wIDw0LjAuMCcKCmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl9saW50czogXjUuMC4wCiAgbWVsb3M6IF42LjIuMAo=", "type": "text" }, { "path": "{{ name.snakeCase() }}/README.md", "data": - "IyB7eyBuYW1lLnRpdGxlQ2FzZSgpIH19Cgp7eyBkZXNjcmlwdGlvbiB9fQoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgoKIyMgVGVjaG5vbG9naWVzIGF0IHBsYXkKCi0gRGFydAotIEZsdXR0ZXIKLSBNZWxvcwoKIyMgR2V0dGluZyBTdGFydGVkCgotIEFsbCBhcHBzIGFyZSBpbiB0aGUgYC9hcHBzYCBkaXJlY3RvcnkuCi0gQWxsIGZlYXR1cmVzIGFyZSBpbiB0aGUgYC9mZWF0dXJlc2AgZGlyZWN0b3J5Ci0gQWxsIHNoYXJlZCBwYWNrYWdlcyBhcmUgaW4gdGhlIGAvcGFja2FnZXNgIGRpcmVjdG9yeQotIEFsbCBwbHVnaW5zIGFyZSBpbiB0aGUgYC9wbHVnaW5zYCBkaXJlY3Rvcnk=", + "IyB7eyBuYW1lLnRpdGxlQ2FzZSgpIH19Cgp7e3tkZXNjcmlwdGlvbn19fQoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgojIyBMYXlvdXQKCmBgYAphcHBzL3t7bmFtZS5zbmFrZUNhc2UoKX19ICAgICAgdGhlIGFwcGxpY2F0aW9uIHNoZWxsOiBib290c3RyYXAsIHJvdXRlciwgc3BsYXNoCmZlYXR1cmVzLyAgICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbCBzbGljZXMgKHVpICsgc3RhdGUgKyBkYXRhKQpwYWNrYWdlcy8gICAgICAgICAgICAgICAgICAgICAgc2hhcmVkIGNhcGFiaWxpdGllcwpwbHVnaW5zLyAgICAgICAgICAgICAgICAgICAgICAgbmF0aXZlIGludGVncmF0aW9ucwpgYGAKCkRlcGVuZGVuY2llcyBwb2ludCBpbndhcmQ6IGFwcHMgZGVwZW5kIG9uIGZlYXR1cmVzLCBmZWF0dXJlcyBkZXBlbmQgb24KcGFja2FnZXMsIHBhY2thZ2VzIGRlcGVuZCBvbiBub3RoaW5nIGFib3ZlIHRoZW0uCgojIyMgV2hhdCBpcyBhbHJlYWR5IHdpcmVkCgp8IE1vZHVsZSB8IFJvbGUgfAp8IC0tLSB8IC0tLSB8CnwgYHBhY2thZ2VzL2NvcmVgIHwgbG9nZ2VyLCBESSBib290c3RyYXAsIHJvdXRlIG9ic2VydmVycywgZXJyb3IgdHlwZXMsIEJMb0Mgb2JzZXJ2ZXIgfAp8IGBwYWNrYWdlcy9kaWAgfCBzZXJ2aWNlIGxvY2F0b3IgKEdldEl0KSBiZWhpbmQgYSBzbWFsbCBpbnRlcmZhY2UgfAp8IGBwYWNrYWdlcy9kZXNpZ25fc3lzdGVtYCB8IE1hdGVyaWFsIDMgdGhlbWUsIHNoYXJlZCBjb21wb25lbnRzLCB0b2FzdHMsIGxvYWRlcnMgfAp8IGBwYWNrYWdlcy9sb2NhbGl6YXRpb25gIHwgdHlwZS1zYWZlIHN0cmluZ3MgZ2VuZXJhdGVkIGZyb20gWUFNTCAoaTY5bikgfAp8IGBwYWNrYWdlcy91dGlsc2AgfCBkZXBlbmRlbmN5LWxpZ2h0IGhlbHBlcnMgfAp7eyNuZXR3b3JrfX18IGBwYWNrYWdlcy9uZXR3b3JrYCB8IERpbyBjbGllbnQsIGF1dGggKyBsb2dnaW5nIGludGVyY2VwdG9ycywgdHlwZWQgZXJyb3JzIHwKe3svbmV0d29ya319e3sjYW5hbHl0aWNzfX18IGBwYWNrYWdlcy9hbmFseXRpY3NgIHwgZXZlbnQgdHJhY2tpbmcgYmVoaW5kIGEgc3dhcHBhYmxlIGNsaWVudCB8Cnt7L2FuYWx5dGljc319e3sjY3Jhc2hseXRpY3N9fXwgYHBhY2thZ2VzL2NyYXNobHl0aWNzYCB8IGNyYXNoIGFuZCBub24tZmF0YWwgcmVwb3J0aW5nIHwKe3svY3Jhc2hseXRpY3N9fXt7I25vdGlmaWNhdGlvbnN9fXwgYHBhY2thZ2VzL25vdGlmaWNhdGlvbnNgIHwgRkNNLCBwZXJtaXNzaW9ucywgZGV2aWNlLXRva2VuIHJlZ2lzdHJhdGlvbiB8Cnt7L25vdGlmaWNhdGlvbnN9fXt7I2ZlYXR1cmVfZmxhZ3N9fXwgYHBhY2thZ2VzL2ZlYXR1cmVfZmxhZ3NgIHwgUmVtb3RlIENvbmZpZyBmbGFncyB3aXRoIGEgd2lkZ2V0IHdyYXBwZXIgfAp7ey9mZWF0dXJlX2ZsYWdzfX17eyNkZXZlbG9wZXJ9fXwgYHBhY2thZ2VzL2RldmVsb3BlcmAgfCBpbi1hcHAgZGV2IHRvb2xzLCByZWFjaGFibGUgYnkgYSBoaWRkZW4gNS10YXAgZ2VzdHVyZSB8Cnt7L2RldmVsb3Blcn19e3sjYXV0aH19fCBgZmVhdHVyZXMvYXV0aGAgfCBGaXJlYmFzZSBBdXRoIChlbWFpbCwgR29vZ2xlLCBBcHBsZSkgKyByb3V0ZSBndWFyZHMgfAp7ey9hdXRofX17eyNkYXNoYm9hcmR9fXwgYGZlYXR1cmVzL2Rhc2hib2FyZGAgfCBib3R0b20tbmF2aWdhdGlvbiBzaGVsbCB3aXRoIHN0YXJ0ZXIgdGFicyB8Cnt7L2Rhc2hib2FyZH19CgojIyBHZXR0aW5nIHN0YXJ0ZWQKCmBgYHNoCmRhcnQgcHViIGdsb2JhbCBhY3RpdmF0ZSBtZWxvcwptZWxvcyBib290c3RyYXAKYGBgCnt7I2ZpcmViYXNlfX0KIyMjIEZpcmViYXNlCgpUaGlzIHByb2plY3QgdXNlcyBGaXJlYmFzZSwgc28gY29uZmlndXJlIGl0IGJlZm9yZSB0aGUgZmlyc3QgcnVuIC0gdGhlCmdlbmVyYXRlZCBgbGliL2ZpcmViYXNlX29wdGlvbnMuZGFydGAgZGVsaWJlcmF0ZWx5IHRocm93cyB1bnRpbCB5b3UgZG86CgpgYGBzaApkYXJ0IHB1YiBnbG9iYWwgYWN0aXZhdGUgZmx1dHRlcmZpcmVfY2xpCmNkIGFwcHMve3tuYW1lLnNuYWtlQ2FzZSgpfX0KZmx1dHRlcmZpcmUgY29uZmlndXJlCmBgYAp7ey9maXJlYmFzZX19CiMjIyBSdW4KCmBgYHNoCmNkIGFwcHMve3tuYW1lLnNuYWtlQ2FzZSgpfX0KZmx1dHRlciBydW4gLS1kYXJ0LWRlZmluZT1CQVNFX1VSTD1odHRwczovL2FwaS5leGFtcGxlLmNvbQpgYGAKCkV2ZXJ5IGVudmlyb25tZW50IHZhbHVlIGlzIGEgYC0tZGFydC1kZWZpbmVgIHJlYWQgaW4KYHBhY2thZ2VzL2NvcmUvbGliL2NvbnN0YW50cy9lbnZpcm9ubWVudC5kYXJ0YC4gQWRkIG5ldyBvbmVzIHRoZXJlIHJhdGhlciB0aGFuCnNjYXR0ZXJpbmcgYFN0cmluZy5mcm9tRW52aXJvbm1lbnRgIGFyb3VuZCB0aGUgY29kZWJhc2UuCnt7I2ZpcmViYXNlfX0KVG8gcnVuIGFnYWluc3QgdGhlIEZpcmViYXNlIGVtdWxhdG9yIHN1aXRlLCBhZGQKYC0tZGFydC1kZWZpbmU9VVNFX0VNVUxBVE9SUz10cnVlYC4Ke3svZmlyZWJhc2V9fQojIyBEYXktdG8tZGF5CgpgYGBzaAptZWxvcyBsaW50ICAgICAgICAgICAgICMgZm9ybWF0ICsgYW5hbHl6ZSBldmVyeXRoaW5nCm1lbG9zIHRlc3QgICAgICAgICAgICAgIyBydW4gZXZlcnkgcGFja2FnZSdzIHRlc3RzCm1lbG9zIHJ1biBnZW5lcmF0ZSAgICAgIyBidWlsZF9ydW5uZXIgd2hlcmUgaXQgaXMgbmVlZGVkCm1lbG9zIHJ1biBnZW5lcmF0ZTppNjluICAjIHJlZ2VuZXJhdGUgc3RyaW5ncyBhZnRlciBlZGl0aW5nIG1lc3NhZ2VzLmk2OW4ueWFtbApgYGAKCiMjIEFkZGluZyB0byB0aGUgbW9ub3JlcG8KCmBgYHNoCm5vbnN0b3AgY3JlYXRlIHBhY2thZ2UgbXlfZmVhdHVyZSAtbyBmZWF0dXJlcwpub25zdG9wIGNyZWF0ZSBwYWNrYWdlIG15X3BhY2thZ2UgLW8gcGFja2FnZXMKbm9uc3RvcCBjcmVhdGUgYXBwIG15X3NlY29uZF9hcHAgLW8gYXBwcwpub25zdG9wIGNyZWF0ZSBwbHVnaW4gbXlfcGx1Z2luIC1vIHBsdWdpbnMKYGBgCgojIyBXaGVyZSB0byBzdGFydAoKMS4gUmVwbGFjZSB0aGUgcGxhY2Vob2xkZXIgdGFicyBpbiBgZmVhdHVyZXMvZGFzaGJvYXJkYC4KMi4gUHV0IHlvdXIgcHJvZHVjdCBzdHJpbmdzIGluIGBwYWNrYWdlcy9sb2NhbGl6YXRpb24vbGliL21lc3NhZ2VzLmk2OW4ueWFtbGAuCjMuIFNldCB0aGUgcGFsZXR0ZSBpbiBgcGFja2FnZXMvZGVzaWduX3N5c3RlbS9saWIvZ2VuZXJhdGVkL3RoZW1lLmRhcnRgLgo0LiBBZGQgZmVhdHVyZSBtb2R1bGVzIGFuZCBzcHJlYWQgdGhlaXIgcm91dGVzIGludG8KICAgYGFwcHMve3tuYW1lLnNuYWtlQ2FzZSgpfX0vbGliL3JvdXRlci9yb3V0ZXIuZGFydGAuCg==", "type": "text" } ], "hooks": [ + { + "path": "commands/checked_process.dart", + "data": + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCi8vLyBSdW5zIFtleGVjdXRhYmxlXSBhbmQgdGhyb3dzIHdoZW4gaXQgZmFpbHMuCi8vLwovLy8gYGNsaV9jb3JlYCdzIGB0cmFja09wZXJhdGlvbmAgcmVwb3J0cyBzdWNjZXNzIGFzIHNvb24gYXMgdGhlIGZ1dHVyZQovLy8gY29tcGxldGVzLCBzbyBhIG5vbi16ZXJvIGV4aXQgY29kZSBzaG93cyBhIGdyZWVuIHRpY2sgYW5kIGdlbmVyYXRpb24KLy8vIGNhcnJpZXMgb24gdG8gcHJvZHVjZSBhIGhhbGYtYnVpbHQgcHJvamVjdC4gRXZlcnkgc3RlcCBoZXJlIGlzIGxvYWQtYmVhcmluZwovLy8gLSBpZiBgZmx1dHRlciBjcmVhdGVgIG9yIGBtZWxvcyBib290c3RyYXBgIGZhaWxzIHRoZXJlIGlzIG5vdGhpbmcgd29ydGgKLy8vIGNvbnRpbnVpbmcgZm9yIC0gc28gdGhlIGV4aXQgY29kZSBpcyBjaGVja2VkIGFuZCB0aGUgY2hpbGQncyBzdGRlcnIgaXMKLy8vIHN1cmZhY2VkLgpGdXR1cmU8dm9pZD4gcnVuQ2hlY2tlZCgKICBIb29rQ29udGV4dCBjb250ZXh0LCB7CiAgcmVxdWlyZWQgU3RyaW5nIHN0YXJ0TWVzc2FnZSwKICByZXF1aXJlZCBTdHJpbmcgZW5kTWVzc2FnZSwKICByZXF1aXJlZCBTdHJpbmcgZXhlY3V0YWJsZSwKICByZXF1aXJlZCBMaXN0PFN0cmluZz4gYXJndW1lbnRzLAogIFN0cmluZz8gd29ya2luZ0RpcmVjdG9yeSwKfSkgYXN5bmMgewogIGZpbmFsIHByb2dyZXNzID0gY29udGV4dC5sb2dnZXIucHJvZ3Jlc3Moc3RhcnRNZXNzYWdlKTsKCiAgZmluYWwgUHJvY2Vzc1Jlc3VsdCByZXN1bHQ7CiAgdHJ5IHsKICAgIHJlc3VsdCA9IGF3YWl0IFByb2Nlc3MucnVuKAogICAgICBleGVjdXRhYmxlLAogICAgICBhcmd1bWVudHMsCiAgICAgIHdvcmtpbmdEaXJlY3Rvcnk6IHdvcmtpbmdEaXJlY3RvcnksCiAgICAgIHJ1bkluU2hlbGw6IHRydWUsCiAgICApOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBwcm9ncmVzcy5mYWlsKCckc3RhcnRNZXNzYWdlIGZhaWxlZDogJGVycm9yJyk7CiAgICByZXRocm93OwogIH0KCiAgaWYgKHJlc3VsdC5leGl0Q29kZSAhPSAwKSB7CiAgICBwcm9ncmVzcy5mYWlsKCckc3RhcnRNZXNzYWdlIGZhaWxlZCAoZXhpdCAke3Jlc3VsdC5leGl0Q29kZX0pJyk7CiAgICBmaW5hbCBkZXRhaWxzID0gJyR7cmVzdWx0LnN0ZGVycn0nLnRyaW0oKS5pc05vdEVtcHR5CiAgICAgICAgPyAnJHtyZXN1bHQuc3RkZXJyfScudHJpbSgpCiAgICAgICAgOiAnJHtyZXN1bHQuc3Rkb3V0fScudHJpbSgpOwogICAgaWYgKGRldGFpbHMuaXNOb3RFbXB0eSkgY29udGV4dC5sb2dnZXIuZXJyKGRldGFpbHMpOwogICAgdGhyb3cgUHJvY2Vzc0V4Y2VwdGlvbigKICAgICAgZXhlY3V0YWJsZSwKICAgICAgYXJndW1lbnRzLAogICAgICAnZXhpdGVkIHdpdGggJHtyZXN1bHQuZXhpdENvZGV9JywKICAgICAgcmVzdWx0LmV4aXRDb2RlLAogICAgKTsKICB9CgogIHByb2dyZXNzLmNvbXBsZXRlKGVuZE1lc3NhZ2UpOwp9Cg==", + "type": "text" + }, { "path": "commands/flutter_create_command.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JyBzaG93IEJhc2VGbHV0dGVyQ29tbWFuZDsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6cGF0aC9wYXRoLmRhcnQnIGFzIHA7CgpmaW5hbCBjbGFzcyBGbHV0dGVyQ3JlYXRlQ29tbWFuZCBleHRlbmRzIEJhc2VGbHV0dGVyQ29tbWFuZCB7CiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgICBmaW5hbCBTdHJpbmcgbmFtZSA9IGNvbnRleHQudmFyc1snbmFtZSddOwogICAgZmluYWwgU3RyaW5nIGRlc2NyaXB0aW9uID0gY29udGV4dC52YXJzWydkZXNjcmlwdGlvbiddOwogICAgZmluYWwgU3RyaW5nIG9yZ05hbWUgPSBjb250ZXh0LnZhcnNbJ29yZ19uYW1lJ10gPz8gJ2NvbS5leGFtcGxlJzsKICAgIGZpbmFsIGFwcE5hbWUgPSBuYW1lLnNuYWtlQ2FzZTsKICAgIGZpbmFsIG91dHB1dFBhdGggPSBwLm5vcm1hbGl6ZSgnJGFwcE5hbWUvYXBwcycpOwoKICAgIGF3YWl0IGNyZWF0ZUZsdXR0ZXJQcm9qZWN0KAogICAgICBjb250ZXh0OiBjb250ZXh0LAogICAgICBuYW1lOiBhcHBOYW1lLAogICAgICBkZXNjcmlwdGlvbjogZGVzY3JpcHRpb24sCiAgICAgIG91dHB1dFBhdGg6IG91dHB1dFBhdGgsCiAgICAgIG9yZ05hbWU6IG9yZ05hbWUsCiAgICApOwoKICAgIGF3YWl0IHJlbW92ZUFuYWx5c2lzT3B0aW9ucygKICAgICAgY29udGV4dDogY29udGV4dCwKICAgICAgcHJvamVjdFBhdGg6IHAuam9pbihvdXRwdXRQYXRoLCBhcHBOYW1lKSwKICAgICk7CiAgfQp9Cg==", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKaW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JyBzaG93IENsaUNvbW1hbmQsIEZpbGVVdGlsczsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6cGF0aC9wYXRoLmRhcnQnIGFzIHA7CgppbXBvcnQgJ2NoZWNrZWRfcHJvY2Vzcy5kYXJ0JzsKCi8vLyBTY2FmZm9sZHMgdGhlIGFwcGxpY2F0aW9uIHdpdGggYGZsdXR0ZXIgY3JlYXRlYC4KLy8vCi8vLyBUaGlzIHJ1bnMgaW4gcHJlLWdlbiwgc28gdGhlIGJyaWNrJ3Mgb3duIGBsaWIvYCBhbmQgYHB1YnNwZWMueWFtbGAgYXJlCi8vLyB3cml0dGVuIG92ZXIgdGhlIGNvdW50ZXItYXBwIHRoYXQgYGZsdXR0ZXIgY3JlYXRlYCBwcm9kdWNlcy4KZmluYWwgY2xhc3MgRmx1dHRlckNyZWF0ZUNvbW1hbmQgZXh0ZW5kcyBDbGlDb21tYW5kIHsKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICAgIGZpbmFsIFN0cmluZyBuYW1lID0gY29udGV4dC52YXJzWyduYW1lJ107CiAgICBmaW5hbCBTdHJpbmcgZGVzY3JpcHRpb24gPSBjb250ZXh0LnZhcnNbJ2Rlc2NyaXB0aW9uJ107CiAgICBmaW5hbCBTdHJpbmcgb3JnTmFtZSA9IGNvbnRleHQudmFyc1snb3JnX25hbWUnXSA/PyAnY29tLmV4YW1wbGUnOwogICAgZmluYWwgYXBwTmFtZSA9IG5hbWUuc25ha2VDYXNlOwogICAgZmluYWwgYXBwc1BhdGggPSBwLm5vcm1hbGl6ZSgnJGFwcE5hbWUvYXBwcycpOwoKICAgIGF3YWl0IEZpbGVVdGlscy5lbnN1cmVEaXJlY3RvcnkoYXBwc1BhdGgpOwoKICAgIGF3YWl0IHJ1bkNoZWNrZWQoCiAgICAgIGNvbnRleHQsCiAgICAgIHN0YXJ0TWVzc2FnZTogJ0NyZWF0aW5nIEZsdXR0ZXIgYXBwOiAkYXBwTmFtZScsCiAgICAgIGVuZE1lc3NhZ2U6ICdGbHV0dGVyIGFwcCBjcmVhdGVkJywKICAgICAgZXhlY3V0YWJsZTogJ2ZsdXR0ZXInLAogICAgICBhcmd1bWVudHM6IFsKICAgICAgICAnY3JlYXRlJywKICAgICAgICBhcHBOYW1lLAogICAgICAgICctLXRlbXBsYXRlPWFwcCcsCiAgICAgICAgJy0tcGxhdGZvcm1zPWlvcyxhbmRyb2lkLHdlYicsCiAgICAgICAgJy0tZGVzY3JpcHRpb249JGRlc2NyaXB0aW9uJywKICAgICAgICAnLS1vcmc9JG9yZ05hbWUnLAogICAgICBdLAogICAgICB3b3JraW5nRGlyZWN0b3J5OiBhcHBzUGF0aCwKICAgICk7CgogICAgZmluYWwgYXBwUGF0aCA9IHAuam9pbihhcHBzUGF0aCwgYXBwTmFtZSk7CgogICAgLy8gVGhlIHdvcmtzcGFjZSBvd25zIGxpbnQgY29uZmlndXJhdGlvbi4KICAgIGF3YWl0IEZpbGVVdGlscy5kZWxldGVGaWxlKHAuam9pbihhcHBQYXRoLCAnYW5hbHlzaXNfb3B0aW9ucy55YW1sJykpOwoKICAgIC8vIGBmbHV0dGVyIGNyZWF0ZWAgbGVhdmVzIGEgd2lkZ2V0IHRlc3QgZm9yIHRoZSBjb3VudGVyIGFwcCBpdCBqdXN0IHdyb3RlOwogICAgLy8gdGhlIGJyaWNrIHJlcGxhY2VzIHRoYXQgYXBwLCBzbyB0aGUgdGVzdCB3b3VsZCBub3QgY29tcGlsZS4KICAgIGF3YWl0IEZpbGVVdGlscy5kZWxldGVGaWxlKHAuam9pbihhcHBQYXRoLCAndGVzdCcsICd3aWRnZXRfdGVzdC5kYXJ0JykpOwogICAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwLmpvaW4oYXBwUGF0aCwgJ3Rlc3QnKSk7CiAgICBpZiAodGVzdERpci5leGlzdHNTeW5jKCkgJiYgdGVzdERpci5saXN0U3luYygpLmlzRW1wdHkpIHsKICAgICAgYXdhaXQgdGVzdERpci5kZWxldGUoKTsKICAgIH0KICB9Cn0K", + "type": "text" + }, + { + "path": "commands/format_command.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQ2xpQ29tbWFuZDsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjaGVja2VkX3Byb2Nlc3MuZGFydCc7CgovLy8gRm9ybWF0cyB0aGUgZ2VuZXJhdGVkIHdvcmtzcGFjZS4KLy8vCi8vLyBUZW1wbGF0ZXMgYXJlIHJlbmRlcmVkIGZyb20gbXVzdGFjaGUsIHNvIHRoZSBleGFjdCBsaW5lIGJyZWFrcyBkZXBlbmQgb24KLy8vIHdoaWNoIG1vZHVsZXMgd2VyZSBzZWxlY3RlZC4gUnVubmluZyB0aGUgZm9ybWF0dGVyIG9uY2UgaGVyZSBtZWFucyBhIGZyZXNoCi8vLyBwcm9qZWN0IHBhc3NlcyBpdHMgb3duIGBtZWxvcyBsaW50YCB3aGF0ZXZlciB0aGUgYW5zd2VycyB3ZXJlLgpmaW5hbCBjbGFzcyBGb3JtYXRDb21tYW5kIGV4dGVuZHMgQ2xpQ29tbWFuZCB7CiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgICBmaW5hbCBTdHJpbmcgbmFtZSA9IGNvbnRleHQudmFyc1snbmFtZSddOwoKICAgIGF3YWl0IHJ1bkNoZWNrZWQoCiAgICAgIGNvbnRleHQsCiAgICAgIHN0YXJ0TWVzc2FnZTogJ0Zvcm1hdHRpbmcgZ2VuZXJhdGVkIGNvZGUnLAogICAgICBlbmRNZXNzYWdlOiAnQ29kZSBmb3JtYXR0ZWQnLAogICAgICBleGVjdXRhYmxlOiAnZGFydCcsCiAgICAgIGFyZ3VtZW50czogWydmb3JtYXQnLCAnLiddLAogICAgICB3b3JraW5nRGlyZWN0b3J5OiBuYW1lLnNuYWtlQ2FzZSwKICAgICk7CiAgfQp9Cg==", "type": "text" }, { "path": "commands/melos_command.dart", "data": - "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JyBzaG93IEJhc2VNZWxvc0NvbW1hbmQ7CmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmZpbmFsIGNsYXNzIE1lbG9zQ29tbWFuZCBleHRlbmRzIEJhc2VNZWxvc0NvbW1hbmQgewogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCkgYXN5bmMgewogICAgZmluYWwgU3RyaW5nIG5hbWUgPSBjb250ZXh0LnZhcnNbJ25hbWUnXTsKICAgIGZpbmFsIGFwcE5hbWUgPSBuYW1lLnNuYWtlQ2FzZTsKCiAgICBhd2FpdCB0cmFja09wZXJhdGlvbigKICAgICAgY29udGV4dCwKICAgICAgc3RhcnRNZXNzYWdlOiAnQWN0aXZhdGluZyBNZWxvcyBnbG9iYWxseScsCiAgICAgIGVuZE1lc3NhZ2U6ICdNZWxvcyBhY3RpdmF0ZWQgZ2xvYmFsbHknLAogICAgICBvcGVyYXRpb246ICgpID0+IFByb2Nlc3MucnVuKAogICAgICAgICdkYXJ0JywKICAgICAgICBbJ3B1YicsICdnbG9iYWwnLCAnYWN0aXZhdGUnLCAnbWVsb3MnXSwKICAgICAgICBydW5JblNoZWxsOiB0cnVlLAogICAgICApLAogICAgKTsKCiAgICBhd2FpdCBib290c3RyYXAoCiAgICAgIGNvbnRleHQ6IGNvbnRleHQsCiAgICAgIHdvcmtzcGFjZVBhdGg6IGFwcE5hbWUsCiAgICApOwogIH0KfQo=", + "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQ2xpQ29tbWFuZDsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjaGVja2VkX3Byb2Nlc3MuZGFydCc7CgpmaW5hbCBjbGFzcyBNZWxvc0NvbW1hbmQgZXh0ZW5kcyBDbGlDb21tYW5kIHsKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICAgIGZpbmFsIFN0cmluZyBuYW1lID0gY29udGV4dC52YXJzWyduYW1lJ107CiAgICBmaW5hbCBhcHBOYW1lID0gbmFtZS5zbmFrZUNhc2U7CgogICAgYXdhaXQgcnVuQ2hlY2tlZCgKICAgICAgY29udGV4dCwKICAgICAgc3RhcnRNZXNzYWdlOiAnQWN0aXZhdGluZyBNZWxvcyBnbG9iYWxseScsCiAgICAgIGVuZE1lc3NhZ2U6ICdNZWxvcyBhY3RpdmF0ZWQgZ2xvYmFsbHknLAogICAgICBleGVjdXRhYmxlOiAnZGFydCcsCiAgICAgIGFyZ3VtZW50czogWydwdWInLCAnZ2xvYmFsJywgJ2FjdGl2YXRlJywgJ21lbG9zJ10sCiAgICApOwoKICAgIGF3YWl0IHJ1bkNoZWNrZWQoCiAgICAgIGNvbnRleHQsCiAgICAgIHN0YXJ0TWVzc2FnZTogJ1J1bm5pbmcgbWVsb3MgYm9vdHN0cmFwJywKICAgICAgZW5kTWVzc2FnZTogJ0RlcGVuZGVuY2llcyBpbnN0YWxsZWQnLAogICAgICBleGVjdXRhYmxlOiAnbWVsb3MnLAogICAgICBhcmd1bWVudHM6IFsnYm9vdHN0cmFwJ10sCiAgICAgIHdvcmtpbmdEaXJlY3Rvcnk6IGFwcE5hbWUsCiAgICApOwogIH0KfQo=", + "type": "text" + }, + { + "path": "commands/module_vars.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKLy8vIE9wdGlvbmFsIG1vZHVsZXMsIGVhY2ggb2Ygd2hpY2ggbWFwcyB0byBhIGRpcmVjdG9yeSBpbiB0aGUgYnJpY2suCi8vLwovLy8gQSBkaXJlY3RvcnkgbmFtZWQgYHt7PG1vZHVsZT5fZGlyfX1gIHJlbmRlcnMgdG8gYW4gZW1wdHkgcGF0aCBzZWdtZW50IHdoZW4KLy8vIHRoZSBtb2R1bGUgaXMgb2ZmLCBhbmQgbWFzb24gc2tpcHMgZXZlcnkgZmlsZSB1bmRlcm5lYXRoIGl0Lgpjb25zdCBvcHRpb25hbE1vZHVsZXMgPSA8U3RyaW5nPlsKICAnbmV0d29yaycsCiAgJ25vdGlmaWNhdGlvbnMnLAogICdhbmFseXRpY3MnLAogICdjcmFzaGx5dGljcycsCiAgJ2ZlYXR1cmVfZmxhZ3MnLAogICdkZXZlbG9wZXInLAogICdhdXRoJywKICAnZGFzaGJvYXJkJywKXTsKCi8vLyBNb2R1bGVzIHRoYXQgY2Fubm90IHdvcmsgd2l0aG91dCBgRmlyZWJhc2UuaW5pdGlhbGl6ZUFwcGAuCmNvbnN0IF9maXJlYmFzZUJhY2tlZE1vZHVsZXMgPSA8U3RyaW5nPlsKICAnbm90aWZpY2F0aW9ucycsCiAgJ2FuYWx5dGljcycsCiAgJ2NyYXNobHl0aWNzJywKICAnZmVhdHVyZV9mbGFncycsCiAgJ2F1dGgnLAogICdmaXJlc3RvcmUnLApdOwoKYm9vbCBfZmxhZyhIb29rQ29udGV4dCBjb250ZXh0LCBTdHJpbmcga2V5KSA9PiBjb250ZXh0LnZhcnNba2V5XSA9PSB0cnVlOwoKLy8vIE5vcm1hbGlzZXMgdGhlIG1vZHVsZSBhbnN3ZXJzIGFuZCBkZXJpdmVzIGV2ZXJ5dGhpbmcgdGhlIHRlbXBsYXRlcyByZWFkLgp2b2lkIHJlc29sdmVNb2R1bGVWYXJzKEhvb2tDb250ZXh0IGNvbnRleHQpIHsKICAvLyBBIG1vZHVsZSBjYW4gYmUgaW1wbGllZCBieSBhbm90aGVyIChkZXYgdG9vbHMgcmVhZCBmZWF0dXJlIGZsYWdzKSwgc28KICAvLyByZXNvbHZlIGltcGxpY2F0aW9ucyBiZWZvcmUgZGVyaXZpbmcgYW55dGhpbmcgZnJvbSB0aGVtLiBgbm9uc3RvcCBjcmVhdGVgCiAgLy8gYWxyZWFkeSBkb2VzIHRoaXMgaW4gbGliL2NvbW1hbmRzL2NyZWF0ZS9tb2R1bGVzLmRhcnQ7IGl0IGlzIHJlcGVhdGVkIGhlcmUKICAvLyBzbyBgbWFzb24gbWFrZWAgb24gaXRzIG93biBzdGlsbCBwcm9kdWNlcyBhIHByb2plY3QgdGhhdCBjb21waWxlcy4KICBpZiAoX2ZsYWcoY29udGV4dCwgJ2RldmVsb3BlcicpKSBjb250ZXh0LnZhcnNbJ2ZlYXR1cmVfZmxhZ3MnXSA9IHRydWU7CiAgaWYgKF9mbGFnKGNvbnRleHQsICdub3RpZmljYXRpb25zJykpIGNvbnRleHQudmFyc1snbmV0d29yayddID0gdHJ1ZTsKCiAgZm9yIChmaW5hbCBtb2R1bGUgaW4gb3B0aW9uYWxNb2R1bGVzKSB7CiAgICBmaW5hbCBlbmFibGVkID0gX2ZsYWcoY29udGV4dCwgbW9kdWxlKTsKICAgIGNvbnRleHQudmFyc1ttb2R1bGVdID0gZW5hYmxlZDsKICAgIGNvbnRleHQudmFyc1snJHttb2R1bGV9X2RpciddID0gZW5hYmxlZCA/IG1vZHVsZSA6ICcnOwogIH0KCiAgY29udGV4dC52YXJzWydmaXJlc3RvcmUnXSA9IF9mbGFnKGNvbnRleHQsICdmaXJlc3RvcmUnKTsKICBmaW5hbCB1c2VzRmlyZWJhc2UgPQogICAgICBfZmlyZWJhc2VCYWNrZWRNb2R1bGVzLmFueSgobW9kdWxlKSA9PiBfZmxhZyhjb250ZXh0LCBtb2R1bGUpKTsKICBjb250ZXh0LnZhcnNbJ2ZpcmViYXNlJ10gPSB1c2VzRmlyZWJhc2U7CgogIC8vIFRoZSBib290IHRlc3QgcnVucyB0aGUgcmVhbCBib290c3RyYXAsIHdoaWNoIGEgRmlyZWJhc2UgcHJvamVjdCBjYW5ub3QgZG8KICAvLyB1bnRpbCBgZmx1dHRlcmZpcmUgY29uZmlndXJlYCBoYXMgYmVlbiBydW4uIEFuIGVtcHR5IGZpbGUgbmFtZSBtYWtlcyBtYXNvbgogIC8vIHNraXAgdGhlIGZpbGUsIHNvIHRob3NlIHByb2plY3RzIHNpbXBseSBzaGlwIHdpdGhvdXQgaXQuCiAgY29udGV4dC52YXJzWydzbW9rZV90ZXN0X2ZpbGUnXSA9IHVzZXNGaXJlYmFzZSA/ICcnIDogJ3Ntb2tlX3Rlc3QuZGFydCc7Cn0K", + "type": "text" + }, + { + "path": "commands/next_steps_command.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQ2xpQ29tbWFuZDsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKLy8vIFByaW50cyB0aGUgaGFuZGZ1bCBvZiB0aGluZ3MgdGhlIGdlbmVyYXRvciBjYW5ub3QgZG8gZm9yIHlvdS4KZmluYWwgY2xhc3MgTmV4dFN0ZXBzQ29tbWFuZCBleHRlbmRzIENsaUNvbW1hbmQgewogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCkgYXN5bmMgewogICAgZmluYWwgU3RyaW5nIG5hbWUgPSBjb250ZXh0LnZhcnNbJ25hbWUnXTsKICAgIGZpbmFsIGFwcE5hbWUgPSBuYW1lLnNuYWtlQ2FzZTsKICAgIGZpbmFsIGxvZ2dlciA9IGNvbnRleHQubG9nZ2VyOwoKICAgIGlmIChjb250ZXh0LnZhcnNbJ2ZpcmViYXNlJ10gPT0gdHJ1ZSkgewogICAgICBsb2dnZXIKICAgICAgICAuLmluZm8oJycpCiAgICAgICAgLi53YXJuKCdGaXJlYmFzZSBtb2R1bGVzIGFyZSBlbmFibGVkIGJ1dCBub3QgY29uZmlndXJlZCB5ZXQuJykKICAgICAgICAuLmluZm8oJyAgZGFydCBwdWIgZ2xvYmFsIGFjdGl2YXRlIGZsdXR0ZXJmaXJlX2NsaScpCiAgICAgICAgLi5pbmZvKCcgIGNkICRhcHBOYW1lL2FwcHMvJGFwcE5hbWUgJiYgZmx1dHRlcmZpcmUgY29uZmlndXJlJykKICAgICAgICAuLmluZm8oCiAgICAgICAgICAnICAodW50aWwgdGhlbiBsaWIvZmlyZWJhc2Vfb3B0aW9ucy5kYXJ0IHRocm93cyBvbiBwdXJwb3NlKScsCiAgICAgICAgKTsKICAgIH0KCiAgICBsb2dnZXIKICAgICAgLi5pbmZvKCcnKQogICAgICAuLmluZm8oJ1J1biB0aGUgYXBwOicpCiAgICAgIC4uaW5mbygnICBjZCAkYXBwTmFtZS9hcHBzLyRhcHBOYW1lICYmIGZsdXR0ZXIgcnVuJyk7CiAgfQp9Cg==", "type": "text" }, { "path": "post_gen.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY29tbWFuZHMvZmx1dHRlcl9jcmVhdGVfY29tbWFuZC5kYXJ0JzsKaW1wb3J0ICdjb21tYW5kcy9tZWxvc19jb21tYW5kLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgY29tbWFuZHMgPSBbCiAgICBGbHV0dGVyQ3JlYXRlQ29tbWFuZCgpLAogICAgTWVsb3NDb21tYW5kKCksCiAgXTsKCiAgZm9yIChmaW5hbCBjb21tYW5kIGluIGNvbW1hbmRzKSB7CiAgICBhd2FpdCBjb21tYW5kLnJ1bihjb250ZXh0KTsKICB9Cn0K", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY29tbWFuZHMvZm9ybWF0X2NvbW1hbmQuZGFydCc7CmltcG9ydCAnY29tbWFuZHMvbWVsb3NfY29tbWFuZC5kYXJ0JzsKaW1wb3J0ICdjb21tYW5kcy9uZXh0X3N0ZXBzX2NvbW1hbmQuZGFydCc7CgpGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICBmaW5hbCBjb21tYW5kcyA9IFsKICAgIEZvcm1hdENvbW1hbmQoKSwKICAgIE1lbG9zQ29tbWFuZCgpLAogICAgTmV4dFN0ZXBzQ29tbWFuZCgpLAogIF07CgogIGZvciAoZmluYWwgY29tbWFuZCBpbiBjb21tYW5kcykgewogICAgYXdhaXQgY29tbWFuZC5ydW4oY29udGV4dCk7CiAgfQp9Cg==", "type": "text" }, { "path": "pre_gen.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgLy8gQ2hlY2sgaWYgd2UncmUgaW4gYSBtb25vIHJlcG8gdXNpbmcgdGhlIHNoYXJlZCB1dGlsaXR5CiAgY29udGV4dC52YXJzWydpc19tb25vX3JlcG8nXSA9IGF3YWl0IEZpbGVVdGlscy5pc01vbm9SZXBvKCk7Cn0K", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjb21tYW5kcy9mbHV0dGVyX2NyZWF0ZV9jb21tYW5kLmRhcnQnOwppbXBvcnQgJ2NvbW1hbmRzL21vZHVsZV92YXJzLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgY29udGV4dC52YXJzWydpc19tb25vX3JlcG8nXSA9IGF3YWl0IEZpbGVVdGlscy5pc01vbm9SZXBvKCk7CgogIC8vIFR1cm4gdGhlIG1vZHVsZSBhbnN3ZXJzIGludG8gdGhlIGAqX2RpcmAgcGF0aCB2YXJpYWJsZXMgdGhlIGJyaWNrIHVzZXMgdG8KICAvLyBpbmNsdWRlIG9yIHNraXAgd2hvbGUgZGlyZWN0b3JpZXMsIGFuZCBkZXJpdmUgYGZpcmViYXNlYC4KICByZXNvbHZlTW9kdWxlVmFycyhjb250ZXh0KTsKCiAgLy8gVGhlIGFwcCBpcyBzY2FmZm9sZGVkIGJlZm9yZSBnZW5lcmF0aW9uIHNvIHRoZSBicmljaydzIGBsaWIvYCBhbmQKICAvLyBgcHVic3BlYy55YW1sYCBsYW5kIG9uIHRvcCBvZiB3aGF0IGBmbHV0dGVyIGNyZWF0ZWAgcHJvZHVjZWQuCiAgYXdhaXQgRmx1dHRlckNyZWF0ZUNvbW1hbmQoKS5ydW4oY29udGV4dCk7Cn0K", "type": "text" }, { @@ -90,11 +1461,12 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ } ], "name": "flutter_project_with_mono_repo", - "description": "A Flutter project within a Melos-managed mono-repo.", - "version": "1.0.0", + "description": + "A production-shaped Flutter mono-repo - app shell, design system, localization and the platform modules you pick.", + "version": "2.0.0", "environment": {"mason": ">=0.1.0-dev.52 <0.1.0"}, "repository": - "https://github.com/nonstopio/flutter_forge/tree/main/plugins/nonstop_cli", + "https://github.com/nonstopio/flutter_forge/tree/main/packages/nonstop_cli", "readme": { "path": "README.md", "data": "IyBmbHV0dGVyX3Byb2plY3Rfd2l0aF9tb25vX3JlcG8=", @@ -128,6 +1500,62 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "description": "Organization name", "default": "com.example", "prompt": "What is your organization name?" + }, + "network": { + "type": "boolean", + "description": "Dio-based HTTP client with auth and logging interceptors", + "default": true, + "prompt": "Include the network package?" + }, + "auth": { + "type": "boolean", + "description": + "Firebase Authentication with email, Google and Apple sign-in", + "default": true, + "prompt": "Include authentication?" + }, + "firestore": { + "type": "boolean", + "description": "Cloud Firestore and Cloud Functions wiring", + "default": false, + "prompt": "Include Cloud Firestore?" + }, + "notifications": { + "type": "boolean", + "description": + "Firebase Cloud Messaging, permissions and token registration", + "default": true, + "prompt": "Include push notifications?" + }, + "analytics": { + "type": "boolean", + "description": "Event tracking behind a swappable client", + "default": true, + "prompt": "Include analytics?" + }, + "crashlytics": { + "type": "boolean", + "description": "Crash and non-fatal error reporting", + "default": true, + "prompt": "Include crash reporting?" + }, + "feature_flags": { + "type": "boolean", + "description": "Firebase Remote Config feature flags", + "default": true, + "prompt": "Include feature flags?" + }, + "developer": { + "type": "boolean", + "description": "In-app developer tools behind a hidden gesture", + "default": true, + "prompt": "Include developer tools?" + }, + "dashboard": { + "type": "boolean", + "description": "Bottom-navigation shell with starter tabs", + "default": true, + "prompt": "Include the dashboard shell?" } } }); diff --git a/packages/nonstop_cli/lib/commands/create/modules.dart b/packages/nonstop_cli/lib/commands/create/modules.dart new file mode 100644 index 0000000..7290f08 --- /dev/null +++ b/packages/nonstop_cli/lib/commands/create/modules.dart @@ -0,0 +1,111 @@ +/// An optional module the mono-repo template can generate. +/// +/// Each one maps to a boolean brick variable of the same [key]; the brick uses +/// it to include or skip a whole package directory and the code that wires it +/// into the app shell. +class TemplateModule { + const TemplateModule({ + required this.key, + required this.label, + required this.description, + required this.defaultValue, + this.impliedBy = const [], + }); + + /// Brick variable name, e.g. `network`. + final String key; + + /// Short name shown in the picker. + final String label; + + /// One line explaining what the user gets. + final String description; + + /// Whether the module is pre-selected. + final bool defaultValue; + + /// Modules that cannot compile without this one. + final List impliedBy; + + /// CLI flag: `--network` / `--no-network`. + String get flag => key.replaceAll('_', '-'); + + @override + String toString() => '$label โ€” $description'; +} + +/// The modules offered when creating a mono-repo. +/// +/// Order is the order they are presented in. +const templateModules = [ + TemplateModule( + key: 'network', + label: 'Network', + description: 'Dio client, auth + logging interceptors, typed errors', + defaultValue: true, + impliedBy: ['notifications'], + ), + TemplateModule( + key: 'auth', + label: 'Authentication', + description: 'Firebase Auth (email, Google, Apple) with route guards', + defaultValue: true, + ), + TemplateModule( + key: 'firestore', + label: 'Cloud Firestore', + description: 'Firestore + Cloud Functions wiring and timestamp converters', + defaultValue: false, + ), + TemplateModule( + key: 'notifications', + label: 'Push notifications', + description: 'FCM, permission handling, device-token registration', + defaultValue: true, + ), + TemplateModule( + key: 'analytics', + label: 'Analytics', + description: 'Event tracking behind a swappable client', + defaultValue: true, + ), + TemplateModule( + key: 'crashlytics', + label: 'Crash reporting', + description: 'Crashlytics for fatals and handled errors', + defaultValue: true, + ), + TemplateModule( + key: 'feature_flags', + label: 'Feature flags', + description: 'Remote Config flags with a widget wrapper', + defaultValue: true, + impliedBy: ['developer'], + ), + TemplateModule( + key: 'developer', + label: 'Developer tools', + description: 'In-app logs and flag inspector behind a hidden gesture', + defaultValue: true, + ), + TemplateModule( + key: 'dashboard', + label: 'Dashboard shell', + description: 'Bottom navigation with starter tabs and a profile tab', + defaultValue: true, + ), +]; + +/// Applies the "module A needs module B" rules to a selection. +/// +/// The brick's `hooks/commands/module_vars.dart` applies the same rules, so a +/// project generated with plain `mason make` still compiles. Change both. +Map resolveModuleImplications(Map selection) { + final resolved = Map.from(selection); + for (final module in templateModules) { + if (module.impliedBy.any((other) => resolved[other] ?? false)) { + resolved[module.key] = true; + } + } + return resolved; +} diff --git a/packages/nonstop_cli/test/src/commands/create_command_test.dart b/packages/nonstop_cli/test/src/commands/create_command_test.dart new file mode 100644 index 0000000..ef0ace2 --- /dev/null +++ b/packages/nonstop_cli/test/src/commands/create_command_test.dart @@ -0,0 +1,73 @@ +import 'package:mason_logger/mason_logger.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:nonstop_cli/commands/commands.dart'; +import 'package:nonstop_cli/commands/create/modules.dart'; +import 'package:test/test.dart'; + +class _MockLogger extends Mock implements Logger {} + +/// Parses [args] through the real `create` parser so the tests exercise the +/// same flag definitions users type. +Map modulesFor(List args) { + final command = CreateCommand(logger: _MockLogger()) + ..argResultOverrides = null; + command.argResultOverrides = command.argParser.parse(args); + return command.selectedModules(); +} + +void main() { + group('create module selection', () { + test('falls back to the recommended set with --defaults', () { + final modules = modulesFor(['--defaults', 'my_app']); + + for (final module in templateModules) { + expect( + modules[module.key], + module.defaultValue, + reason: module.key, + ); + } + }); + + test('explicit flags win over the defaults', () { + final modules = modulesFor([ + '--defaults', + '--no-network', + '--no-notifications', + '--firestore', + 'my_app', + ]); + + expect(modules['network'], isFalse); + expect(modules['firestore'], isTrue); + }); + + test('notifications pull in the network package they post tokens to', () { + final modules = modulesFor(['--defaults', '--no-network', 'my_app']); + + expect(modules['notifications'], isTrue); + // notifications imports package:network, so it cannot be dropped. + expect(modules['network'], isTrue); + }); + + test('developer tools pull in feature flags', () { + final modules = modulesFor([ + '--defaults', + '--developer', + '--no-feature-flags', + 'my_app', + ]); + + expect(modules['feature_flags'], isTrue); + }); + + test('every module can be switched off together', () { + final modules = modulesFor([ + for (final module in templateModules) '--no-${module.flag}', + 'my_app', + ]); + + expect(modules.values, everyElement(isFalse)); + }); + }); +}