diff --git a/example/lib/config.dart b/example/lib/config.dart index fc094c0..8aa998a 100644 --- a/example/lib/config.dart +++ b/example/lib/config.dart @@ -1,3 +1,5 @@ +import "package:flutter/material.dart"; + enum Feature { logging(true), authentication(true), @@ -6,3 +8,14 @@ enum Feature { final bool enabledAtStartup; const Feature(this.enabledAtStartup); } + +// Some colors we'll use for our example +const List colors = [ + Colors.red, + Colors.orange, + Colors.yellow, + Colors.green, + Colors.blue, + Colors.purple, + Colors.deepPurple, +]; diff --git a/example/lib/main.dart b/example/lib/main.dart index d892e26..c10f960 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -74,20 +74,47 @@ class MainApp extends StatelessWidget { appBar: AppBar( title: const Text("Arcane Framework Example"), ), - body: const HomeScreen(), + body: Column( + children: [ + Expanded( + child: GridView.extent( + maxCrossAxisExtent: 300, + padding: const EdgeInsets.all(16), + children: const [ + ArcaneThemeExample(), + ArcaneAuthExample(), + ArcaneFeatureFlagsExample(), + ArcaneEnvironmentExample(), + ArcaneServicesExample(), + ], + ), + ), + const ArcaneLoggingExample(), + ], + ), ), ); } } -class HomeScreen extends StatefulWidget { - const HomeScreen({super.key}); +// * Logging +// Arcane's logging system gives developers the power to dynamically add and +// remove logging interfaces on-the-fly: try enabling a debug logging interface +// when the app is running in debug mode, adding a third-party logging interface +// when in production, and waiting until after the user has gone through the +// login process to ask them for permission to track. Include useful metadata, +// including persistent metadata, in your log messages. All of these things, and +// more, are possible when using Arcane's logging system. +class ArcaneLoggingExample extends StatefulWidget { + const ArcaneLoggingExample({ + super.key, + }); @override - State createState() => _HomeScreenState(); + State createState() => _ArcaneLoggingExampleState(); } -class _HomeScreenState extends State { +class _ArcaneLoggingExampleState extends State { // Set up a subscriber that we can use to listen to logs in realtime. // Note: this is completely optional and does _not_ impact whether logs are // sent to any registered logging interfaces. @@ -118,494 +145,482 @@ class _HomeScreenState extends State { super.dispose(); } - // Some colors we'll use for our example - static const List colors = [ - Colors.red, - Colors.orange, - Colors.yellow, - Colors.green, - Colors.blue, - Colors.purple, - Colors.deepPurple, - ]; - @override Widget build(BuildContext context) { - return Column( - children: [ - Expanded( - child: GridView.extent( - maxCrossAxisExtent: 300, - padding: const EdgeInsets.all(16), - children: [ - // * Theme - // Arcane enables easy, dynamic theme switching. Themes can be switched - // at any time between light mode and dark mode, or set to follow the - // system theme. In addition, themes can be swapped out on-the-fly, - // enabling dynamic customizations and remote theme fetching. - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Theme", - style: Theme.of(context).textTheme.headlineSmall, - ), - Column( - children: [ - Switch( - value: - Arcane.theme.currentThemeMode == ThemeMode.dark, - thumbIcon: - WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return const Icon(Icons.dark_mode); - } - return const Icon(Icons.light_mode); - }), - onChanged: (_) { - final ThemeMode oldTheme = - Arcane.theme.currentThemeMode; - Arcane.theme.switchTheme(); - Arcane.log( - "Switching theme", - metadata: { - "followingSystemTheme": - "${Arcane.theme.isFollowingSystemTheme}", - "newMode": Arcane.theme.currentThemeMode.name, - "oldMode": oldTheme.name, - }, - ); - }, - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Checkbox( - value: Arcane.theme.isFollowingSystemTheme, - onChanged: (value) { - final ThemeMode oldTheme = - Arcane.theme.currentThemeMode; - if (value == true) { - Arcane.theme.followSystemTheme(context); - Arcane.log( - "Switching theme", - metadata: { - "followingSystemTheme": - "${Arcane.theme.isFollowingSystemTheme}", - "newMode": - Arcane.theme.currentThemeMode.name, - "oldMode": oldTheme.name, - }, - ); - } else { - Arcane.theme.switchTheme( - themeMode: Arcane.theme.systemThemeMode, - ); - Arcane.log( - "Switching theme", - metadata: { - "followingSystemTheme": - "${Arcane.theme.isFollowingSystemTheme}", - "newMode": - Arcane.theme.currentThemeMode.name, - "oldMode": oldTheme.name, - }, - ); - } - }, - ), - const Text("Follow system"), - ], - ), - ], - ), - SizedBox( - height: 20, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 8, - children: [ - const Text("Color"), - Expanded( - child: ListView.separated( - itemCount: colors.length, - scrollDirection: Axis.horizontal, - separatorBuilder: (_, __) => - const SizedBox(width: 4), - itemBuilder: (context, index) { - return InkWell( - onTap: () { - if (Arcane.theme.currentThemeMode == - ThemeMode.dark) { - Arcane.theme.setDarkTheme( - ThemeData( - brightness: Brightness.dark, - colorSchemeSeed: colors[index], - ), - ); - } else if (Arcane - .theme.currentThemeMode == - ThemeMode.light) { - Arcane.theme.setLightTheme( - ThemeData( - brightness: Brightness.light, - colorSchemeSeed: colors[index], - ), - ); - } - - Arcane.log( - "Setting ${Arcane.theme.currentThemeMode.name} theme color to ${colors[index].name}", - ); - }, - child: Container( - decoration: BoxDecoration( - color: colors[index], - border: Arcane.theme.currentTheme - .colorScheme.primary.name == - colors[index].name - ? Border.all(width: 2) - : null, - ), - width: 20, - height: 20, - ), - ); - }, - ), - ), - ], - ), - ), - Text( - "The current theme mode is ${Arcane.theme.currentModeOf(context).name} and " - "is ${Arcane.theme.isFollowingSystemTheme ? "" : "not "}" - "following the system theme.", - ), - ], - ), - ), - ), - - // * Authentication - // Arcane's authentication system provides a simple, standard interface - // for common authentication tasks - including registration and account - // management, logging in and out, etc. Authentication status is reflected - // in realtime within the application as changes happen, so you can focus - // on what's most important. - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Authentication", - style: Theme.of(context).textTheme.headlineSmall, - ), - ElevatedButton( - onPressed: Feature.authentication.enabled - ? () async { - if (Arcane.auth.isSignedIn.value) { - await Arcane.auth.logOut( - onLoggedOut: () async { - setState(() {}); - }, - ); - } else { - await Arcane.auth.login( - input: ( - email: "email", - password: "password", - ), - onLoggedIn: () async { - setState(() {}); - }, - ); - } - } - : null, - child: Text( - Arcane.auth.isSignedIn.value ? "Sign out" : "Sign in", - ), - ), - Center( - child: Text("Status: ${Arcane.auth.status.name}"), - ), - ], - ), - ), - ), - - // * Feature flags - // Arcane's feature flag system is extremely simple and flexible to use. - // By registering _any_ enum (or even multiple enums!), features can be - // toggled on and off at any point. The feature flag system even offers - // a notifier, so you can listen to changes as they happen. Fetch your - // remote config and use it to dynamically enable and disable features - // with ease! - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Feature Flags", - style: Theme.of(context).textTheme.headlineSmall, - ), - Expanded( - child: ListView.builder( - itemCount: Feature.values.length, - itemBuilder: (context, i) { - final Feature feature = Feature.values[i]; - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(feature.name), - Switch( - value: feature.enabled, - onChanged: (_) { - feature.enabled - ? feature.disable() - : feature.enable(); - }, - ), - ], - ); - }, - ), - ), - ], - ), - ), - ), - - // * Environment - // Quickly and easily toggle between a "normal" and "debug" environment - // within your application. This is particularly useful during development - // when you may want to change the behavior of the application under - // certain conditions. - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Environment", - style: Theme.of(context).textTheme.headlineSmall, - ), - ElevatedButton( - onPressed: () { - final Environment currentEnvironment = - ArcaneEnvironment.of(context).environment; - if (currentEnvironment == Environment.normal) { - ArcaneEnvironment.of(context).enableDebugMode(); - Arcane.log( - "Environment changed.", - metadata: { - "previous": ArcaneEnvironment.of(context) - .environment - .name, - "current": Environment.debug.name, - }, - ); - } else { - ArcaneEnvironment.of(context).disableDebugMode(); - Arcane.log( - "Environment changed.", - metadata: { - "previous": ArcaneEnvironment.of(context) - .environment - .name, - "current": Environment.normal.name, - }, - ); - } - }, - child: const Text("Switch environment"), - ), - Text( - "Environment: ${ArcaneEnvironment.of(context).environment.name}", - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - - // * Services - // Arcane's services system is flexible and minimal, leaving the power - // and control in developers' hands. This system powers much of Arcane - // internally, so you know it's reliable. - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Services", - style: Theme.of(context).textTheme.headlineSmall, - ), - ValueListenableBuilder( - valueListenable: - ArcaneService.ofType( - context, - )?.notifier ?? - ValueNotifier(null), - builder: (context, color, _) { - return Text( - color != null - ? "Favorite color: ${color.name}" - : "", - ); - }, - ), - ElevatedButton( - onPressed: ArcaneServiceProvider.serviceOfType< - FavoriteColorService>(context) == - null - ? () { - ArcaneServiceProvider.of(context).addService( - FavoriteColorService.I, - ); - Arcane.log( - "Service registered.", - metadata: { - "service": "FavoriteColorService", - }, - ); - } - : () { - ArcaneServiceProvider.of(context) - .removeService(); - Arcane.log( - "Service removed.", - metadata: { - "service": "FavoriteColorService", - }, - ); - }, - child: Text( - '${ArcaneServiceProvider.serviceOfType(context) == null ? 'Register' : 'Remove'} service', - ), - ), - SizedBox( - height: 20, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 8, - children: [ - const Text("Color"), - Expanded( - child: ListView.separated( - itemCount: colors.length, - scrollDirection: Axis.horizontal, - separatorBuilder: (_, __) => - const SizedBox(width: 4), - itemBuilder: (context, index) { - return InkWell( - onTap: () { - ArcaneService.ofType< - FavoriteColorService>( - context, - )?.setMyFavoriteColor(colors[index]); - Arcane.log( - "Set a color in FavoriteColorService", - metadata: { - "color": - colors[index].name ?? "Unknown", - }, - ); - }, - child: Container( - decoration: BoxDecoration( - color: colors[index], - border: ArcaneService.ofType< - FavoriteColorService>( - context, - )?.myFavoriteColor?.name == - colors[index].name - ? Border.all(width: 2) - : null, - ), - width: 20, - height: 20, - ), - ); - }, - ), - ), - ], - ), - ), - Text( - "Service is ${ArcaneService.ofType(context) != null ? "" : "not "}registered", - ), - ], - ), - ), - ), - ], - ), - ), - - // * Logging - // Arcane's logging system gives developers the power to dynamically add and - // remove logging interfaces on-the-fly: try enabling a debug logging interface - // when the app is running in debug mode, adding a third-party logging interface - // when in production, and waiting until after the user has gone through the - // login process to ask them for permission to track. Include useful metadata, - // including persistent metadata, in your log messages. All of these things, and - // more, are possible when using Arcane's logging system. - Padding( - padding: const EdgeInsets.all(16.0), - child: SizedBox( - height: 200, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Logging", - style: Theme.of(context).textTheme.headlineSmall, - ), - if (latestLogs.isEmpty) - Text( - "Log messages will appear here", - style: Theme.of(context).textTheme.labelSmall?.copyWith( - fontStyle: FontStyle.italic, - ), - ), - if (Feature.logging.disabled) - Text( - "Logging feature is disabled.", - style: Theme.of(context).textTheme.labelSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - Expanded( - child: ListView.builder( - itemCount: latestLogs.length, - itemBuilder: (context, index) { - return Text(latestLogs[index]); - }, - ), - ), - ], + return Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + height: 200, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Logging", + style: Theme.of(context).textTheme.headlineSmall, ), - ), + if (latestLogs.isEmpty) + Text( + "Log messages will appear here", + style: Theme.of(context).textTheme.labelSmall?.copyWith( + fontStyle: FontStyle.italic, + ), + ), + if (Feature.logging.disabled) + Text( + "Logging feature is disabled.", + style: Theme.of(context).textTheme.labelSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + Expanded( + child: ListView.builder( + itemCount: latestLogs.length, + itemBuilder: (context, index) { + return Text(latestLogs[index]); + }, + ), + ), + ], ), - ], + ), + ); + } +} + +// * Authentication +// Arcane's authentication system provides a simple, standard interface +// for common authentication tasks - including registration and account +// management, logging in and out, etc. Authentication status is reflected +// in realtime within the application as changes happen, so you can focus +// on what's most important. +class ArcaneAuthExample extends StatelessWidget { + const ArcaneAuthExample({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ValueListenableBuilder( + valueListenable: Arcane.auth.isSignedIn, + builder: (context, isSignedIn, _) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Authentication", + style: Theme.of(context).textTheme.headlineSmall, + ), + ElevatedButton( + onPressed: Feature.authentication.enabled + ? () async { + if (isSignedIn) { + await Arcane.auth.logOut(); + } else { + await Arcane.auth.login( + input: ( + email: "email", + password: "password", + ), + ); + } + } + : null, + child: Text( + isSignedIn ? "Sign out" : "Sign in", + ), + ), + Center( + child: Text("Status: ${Arcane.auth.status.name}"), + ), + ], + ); + }, + ), + ), + ); + } +} + +// * Theme +// Arcane enables easy, dynamic theme switching. Themes can be switched +// at any time between light mode and dark mode, or set to follow the +// system theme. In addition, themes can be swapped out on-the-fly, +// enabling dynamic customizations and remote theme fetching. +class ArcaneThemeExample extends StatelessWidget { + const ArcaneThemeExample({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Theme", + style: Theme.of(context).textTheme.headlineSmall, + ), + Column( + children: [ + Switch( + value: Arcane.theme.currentThemeMode == ThemeMode.dark, + thumbIcon: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return const Icon(Icons.dark_mode); + } + return const Icon(Icons.light_mode); + }), + onChanged: (_) { + final ThemeMode oldTheme = Arcane.theme.currentThemeMode; + Arcane.theme.switchTheme(); + Arcane.log( + "Switching theme", + metadata: { + "followingSystemTheme": + "${Arcane.theme.isFollowingSystemTheme}", + "newMode": Arcane.theme.currentThemeMode.name, + "oldMode": oldTheme.name, + }, + ); + }, + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: Arcane.theme.isFollowingSystemTheme, + onChanged: (value) { + final ThemeMode oldTheme = + Arcane.theme.currentThemeMode; + if (value == true) { + Arcane.theme.followSystemTheme(context); + Arcane.log( + "Switching theme", + metadata: { + "followingSystemTheme": + "${Arcane.theme.isFollowingSystemTheme}", + "newMode": Arcane.theme.currentThemeMode.name, + "oldMode": oldTheme.name, + }, + ); + } else { + Arcane.theme.switchTheme( + themeMode: Arcane.theme.systemThemeMode, + ); + Arcane.log( + "Switching theme", + metadata: { + "followingSystemTheme": + "${Arcane.theme.isFollowingSystemTheme}", + "newMode": Arcane.theme.currentThemeMode.name, + "oldMode": oldTheme.name, + }, + ); + } + }, + ), + const Text("Follow system"), + ], + ), + ], + ), + SizedBox( + height: 20, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 8, + children: [ + const Text("Color"), + Expanded( + child: ListView.separated( + itemCount: colors.length, + scrollDirection: Axis.horizontal, + separatorBuilder: (_, __) => const SizedBox(width: 4), + itemBuilder: (context, index) { + return InkWell( + onTap: () { + if (Arcane.theme.currentThemeMode == + ThemeMode.dark) { + Arcane.theme.setDarkTheme( + ThemeData( + brightness: Brightness.dark, + colorSchemeSeed: colors[index], + ), + ); + } else if (Arcane.theme.currentThemeMode == + ThemeMode.light) { + Arcane.theme.setLightTheme( + ThemeData( + brightness: Brightness.light, + colorSchemeSeed: colors[index], + ), + ); + } + + Arcane.log( + "Setting ${Arcane.theme.currentThemeMode.name} theme color to ${colors[index].name}", + ); + }, + child: Container( + decoration: BoxDecoration( + color: colors[index], + border: Arcane.theme.currentTheme.colorScheme + .primary.name == + colors[index].name + ? Border.all(width: 2) + : null, + ), + width: 20, + height: 20, + ), + ); + }, + ), + ), + ], + ), + ), + Text( + "The current theme mode is ${Arcane.theme.currentModeOf(context).name} and " + "is ${Arcane.theme.isFollowingSystemTheme ? "" : "not "}" + "following the system theme.", + ), + ], + ), + ), + ); + } +} + +// * Feature flags +// Arcane's feature flag system is extremely simple and flexible to use. +// By registering _any_ enum (or even multiple enums!), features can be +// toggled on and off at any point. The feature flag system even offers +// a notifier, so you can listen to changes as they happen. Fetch your +// remote config and use it to dynamically enable and disable features +// with ease! +class ArcaneFeatureFlagsExample extends StatelessWidget { + const ArcaneFeatureFlagsExample({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Feature Flags", + style: Theme.of(context).textTheme.headlineSmall, + ), + Expanded( + child: ListView.builder( + itemCount: Feature.values.length, + itemBuilder: (context, i) { + final Feature feature = Feature.values[i]; + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(feature.name), + Switch( + value: feature.enabled, + onChanged: (_) { + feature.enabled + ? feature.disable() + : feature.enable(); + }, + ), + ], + ); + }, + ), + ), + ], + ), + ), + ); + } +} + +// * Environment +// Quickly and easily toggle between a "normal" and "debug" environment +// within your application. This is particularly useful during development +// when you may want to change the behavior of the application under +// certain conditions. +class ArcaneEnvironmentExample extends StatelessWidget { + const ArcaneEnvironmentExample({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Environment", + style: Theme.of(context).textTheme.headlineSmall, + ), + ElevatedButton( + onPressed: () { + final Environment currentEnvironment = + ArcaneEnvironment.of(context).environment; + if (currentEnvironment == Environment.normal) { + ArcaneEnvironment.of(context).enableDebugMode(); + Arcane.log( + "Environment changed.", + metadata: { + "previous": + ArcaneEnvironment.of(context).environment.name, + "current": Environment.debug.name, + }, + ); + } else { + ArcaneEnvironment.of(context).disableDebugMode(); + Arcane.log( + "Environment changed.", + metadata: { + "previous": + ArcaneEnvironment.of(context).environment.name, + "current": Environment.normal.name, + }, + ); + } + }, + child: const Text("Switch environment"), + ), + Text( + "Environment: ${ArcaneEnvironment.of(context).environment.name}", + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} + +/// * Services +/// Arcane's services system is flexible and minimal, leaving the power +/// and control in developers' hands. This system powers much of Arcane +/// internally, so you know it's reliable. +class ArcaneServicesExample extends StatelessWidget { + const ArcaneServicesExample({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final FavoriteColorService? service = + ArcaneServiceProvider.serviceOfType(context); + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Services", + style: Theme.of(context).textTheme.headlineSmall, + ), + ValueListenableBuilder( + valueListenable: ArcaneService.ofType( + context, + )?.notifier ?? + ValueNotifier(null), + builder: (context, color, _) { + return Text( + color != null ? "Favorite color: ${color.name}" : "", + ); + }, + ), + ElevatedButton( + onPressed: () { + if (service == null) { + ArcaneServiceProvider.of(context).addService( + FavoriteColorService.I, + ); + + Arcane.log( + "Service registered.", + metadata: {"service": "FavoriteColorService"}, + ); + } else { + ArcaneServiceProvider.of(context) + .removeService(); + + Arcane.log( + "Service removed.", + metadata: {"service": "FavoriteColorService"}, + ); + } + }, + child: Text('${service == null ? 'Register' : 'Remove'} service'), + ), + SizedBox( + height: 20, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 8, + children: [ + const Text("Color"), + Expanded( + child: ListView.separated( + itemCount: colors.length, + scrollDirection: Axis.horizontal, + separatorBuilder: (_, __) => const SizedBox(width: 4), + itemBuilder: (context, index) { + return InkWell( + onTap: () { + service?.setMyFavoriteColor(colors[index]); + Arcane.log( + "Set a color in FavoriteColorService", + metadata: { + "color": colors[index].name ?? "Unknown", + }, + ); + }, + child: Container( + decoration: BoxDecoration( + color: colors[index], + border: service?.myFavoriteColor?.name == + colors[index].name + ? Border.all(width: 2) + : null, + ), + width: 20, + height: 20, + ), + ); + }, + ), + ), + ], + ), + ), + Text( + "Service is ${service != null ? "" : "not "}registered", + ), + ], + ), + ), ); } }