From d7d8ba2cb09357cafa974a8bd21062ee7e53fe50 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Fri, 2 Sep 2022 13:17:33 -0400 Subject: [PATCH 01/12] step 4 of the dude app tutorial 90% completed --- .../tutorials/at-dude/4-onboarding/index.md | 400 +++++++++++++++++- hugo_stats.json | 8 +- 2 files changed, 386 insertions(+), 22 deletions(-) diff --git a/content_dev/docs/tutorials/at-dude/4-onboarding/index.md b/content_dev/docs/tutorials/at-dude/4-onboarding/index.md index 4ef55f821..f94b7cae4 100644 --- a/content_dev/docs/tutorials/at-dude/4-onboarding/index.md +++ b/content_dev/docs/tutorials/at-dude/4-onboarding/index.md @@ -4,11 +4,11 @@ layout: codelab title: "Onboarding" # Step Name description: How to onboard or authenticate on any app built on the atPlatform # SEO Description for this step Documentation -draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 4 # Ordering of the steps --- -In this tutorial, we will build the onboarding screen for the dude app. +In this tutorial, we will build the onboarding screen for the dude app and modify the default onboarding function to make it compatible with our app architecture. @@ -39,14 +39,14 @@ MaterialApp( The next step is to wrap our `ElevatedButton` widget with a Column widget and center its mainAxisAlignment. -``` +```dart MaterialApp( // * The onboarding screen (first screen) home: ... body: Builder( builder: (context) => Center( - child: Column( # new - mainAxisAlignment: MainAxisAlignment.center, # new + child: Column( // new + mainAxisAlignment: MainAxisAlignment.center, // new children: [ ElevatedButton( onPressed: ... @@ -67,15 +67,16 @@ In your editor create a new folder called assets and inside the assets folder cr ``` mkdir -p assets/images/ +open pubspec.yaml ``` -Open `pubspec.yaml` and add the location of the image folder as shown below. +Add the location of the image folder as shown below. ``` flutter: uses-material-design: true assets: - .env - - assets/images/ # new + - assets/images/ // new ``` Let's create the `IconButton` as shown below. @@ -84,11 +85,12 @@ Let's create the `IconButton` as shown below. Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - IconButton( # new - iconSize: 200, # new - onPressed: () {}, # new - icon: Image.asset('assets/images/dude_logo.png'), # new - ), # new + // IconButton New + IconButton( + iconSize: 200, + onPressed: () {}, + icon: Image.asset('assets/images/dude_logo.png'), + ), ElevatedButton( onPressed: () async {...}, child: const Text('Onboard an @sign'), @@ -97,7 +99,7 @@ Column( ), ``` -#### Onboarding +#### Refactoring the Onboard Function Before we get started with this section, lets define a few terms: @@ -105,18 +107,16 @@ Before we get started with this section, lets define a few terms: [atsign](https://atsign.com/what-is-an-atsign/) - An atsign is your digital identity. It ensures that your data is owned and controlled by you. You can pair your device with any app to access but not store your data. -To make the onboarding code reusable, we will remove it form the "onboard an @sign" button and move it into an authentication class. - -Copy the code inside the `onPressed` anonymous function of the ElevatedButton +To make the onboarding code compatible, we will remove it form the "onboard an @sign" button and move it into an authentication class. In your terminal type: ``` -mkdir lib/services touch lib/services/authentication_service.dart +open lib/services/authentication_service.dart ``` -Open `authentication_service.dart` file and add the following: +Add the following: ``` class AuthenticationService { @@ -132,8 +132,368 @@ class AuthenticationService { The above code creates a [singleton](https://flutterbyexample.com/lesson/singletons) of our class. -Now we'll create a method called onboard in our `AuthenticationService` class whose content will be the code we copied a few steps above. + +Now we'll create a method called `onboard` in our `AuthenticationService` class and move the `onboardingResult` variable found inside the `onPressed` anonymous function of the ElevatedButton to the `onboard` method. Replace `AtOnboardingResult onboardingResult =` with `return` as shown below. + +```dart +class AuthenticationService { + ... + Future onboard() async { + return await AtOnboarding.onboard( + context: context, + config: AtOnboardingConfig( + atClientPreference: await futurePreference, + rootEnvironment: AtEnv.rootEnvironment, + domain: AtEnv.rootDomain, + appAPIKey: AtEnv.appApiKey, + ), + ); + } +} +``` + +#### Fixing Undefined name errors + +To fix the `Undefined name` errors we need to provide the `AtOnboarding.onboard()`method with a `BuildContext` and the `AtOnboardingConfig()` class as shown below. + + +```dart +import 'package:at_app_flutter/at_app_flutter.dart'; +import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; +import 'package:path_provider/path_provider.dart' + show getApplicationSupportDirectory; + +class AuthenticationService { + ... + + Future onboard() async { + var dir = await getApplicationSupportDirectory(); + + var atClientPreference = AtClientPreference() + ..rootDomain = AtEnv.rootDomain + ..namespace = AtEnv.appNamespace + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + + return AtOnboarding.onboard( + context: context, + config: AtOnboardingConfig( + atClientPreference: atClientPreference, + rootEnvironment: AtEnv.rootEnvironment, + domain: AtEnv.rootDomain, + appAPIKey: AtEnv.appApiKey, + ), + ); + } +} +``` + +we Now need access to the BuildContext, We'll create a separate class for this since we'll be reusing our BuildContext outside of the stateful and stateless widget. + +In your terminal type: + +``` +touch lib/services/navigation_service.dart +``` +Add the below code snippet in that file. +```dart +import 'package:flutter/material.dart'; + +class NavigationService { + static GlobalKey navKey = GlobalKey(); + + static GlobalKey nestedNavKey = GlobalKey(); +} +``` +``` +touch lib/services/services.dart +open lib/services/services.dart +``` + +Add the below code so can use one import statement to import all export files: +```dart +export 'authentication_service.dart'; +export 'navigation_service.dart'; +``` + +In `main.dart` add the navigatorKey to the materialApp as shown below; + +```dart +import 'package:at_dude/services/services.dart'; +Widget build(BuildContext context) { + return MaterialApp( + // * The onboarding screen (first screen) + navigatorKey: NavigationService.navKey, + home: ... + ); + } +``` + +In `navigation_service.dart` add the below code to the `onboard()` method. + +```dart +Future onboard() async { + ... + return await AtOnboarding.onboard( + context: NavigationService.navKey.currentContext!, // new + config: ... + ); + } +``` +#### Base Command + +Remember our commands contain our application logic, before we can create our onboard command we'll first create a base command that all other commands will extend from. In your terminal type: + +``` +touch lib/commands/base_command.dart +``` + +Create the `BaseCommand` class with the needed imports as shown below: + +``` +open lib/commands/base_command.dart +``` +```dart +import 'package:provider/provider.dart'; + +import '../services/services.dart'; + +abstract class BaseCommand { + // Services + AuthenticationService authenticationService = + NavigationService.navKey.currentContext!.read(); +} +``` + +We now have to provide the services using the provider package as shown below: + +``` +flutter pub add provider +open lib/main.dart +``` +```dart +import 'package:provider/provider.dart'; +... +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return MultiProvider( //new + providers: [Provider(create: (c) => AuthenticationService.getInstance())],// new + child: MaterialApp(), + ) + } +} +``` + +#### Onboard Command + +Now that we're all set, lets create our Onboard Command. This class method will contain the instructions required to onboard on the atPlatform. In your terminal type: + +``` +touch lib/commands/onboard_command.dart +open lib/commands/onboard_command.dart +``` + +Add the below code: + +```dart +import 'package:at_dude/commands/base_command.dart'; +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + + } +} +``` +Our Commands will only have one method called `run()`. This command return a `Future`. + +Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: + +```dart +import 'package:at_dude/commands/base_command.dart'; +import 'package:at_dude/services/navigation_service.dart'; // new +import 'package:at_onboarding_flutter/at_onboarding_result.dart'; // new +import 'package:flutter/material.dart'; // new + +import '../home_screen.dart'; // new + +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + + // Everything Below New + var context = NavigationService.navKey.currentContext!; + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + Navigator.push( + context, MaterialPageRoute(builder: (_) => const HomeScreen())); + break; + case AtOnboardingResultStatus.error: + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + backgroundColor: Colors.red, + content: Text('An error has occurred'), + ), + ); + break; + case AtOnboardingResultStatus.cancel: + break; + } + } +} +``` + +If `authenticationService.onboard()` return `AtOnboardingResultStatus.success` we navigate to the HomeScreen, if it returns `AtOnboardingResultStatus.error` we display a `Snackbar` on the screen. + +We will be using the `Snackbar` widget often so lets extract it into its own method. + +#### Creating Snackbar Method + +In your terminal type + +``` +touch lib/views/widgets/snackbars.dart +open lib/views/widgets/snackbars.dart +``` + +Add the below code + +```dart +import 'package:at_dude/services/navigation_service.dart'; +import 'package:flutter/material.dart'; + +class Snackbars extends StatelessWidget { + const Snackbars({Key? key}) : super(key: key); + + static void errorSnackBar({ + required String content, + }) { + ScaffoldMessenger.of(NavigationService.navKey.currentContext!) + .showSnackBar(SnackBar( + content: Text( + content, + textAlign: TextAlign.center, + ), + backgroundColor: + Theme.of(NavigationService.navKey.currentContext!).errorColor, + )); + } + + @override + Widget build(BuildContext context) { + return Container(); + } +} +``` + +We created a class that extends `StatelessWidget`. This class contains a static void method named `errorSnackBar` that accepts an `errorMessage`. + +This method will save the broiler plate of calling `ScaffoldMessenger.of(context).showSnackBar()` every time we want so show a snackbar. + +Lets replace our snackbar part of `OnboardCommand.run()` method as shown below: + +```dart +... + +import 'package:at_dude/views/widgets/snackbars.dart'; // new + + + +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + var context = NavigationService.navKey.currentContext!; + switch (onboardingResult.status) { + ... + case AtOnboardingResultStatus.error: + Snackbars.errorSnackBar(errorMessage: 'An error has occurred'); // new + break; + case AtOnboardingResultStatus.cancel: + break; + } + } +} +``` + +All done! + +#### Cleaning up main.dart + +Now we just have to clean up `main.dart`. +Remove the code below from main.dart: + + +```dart +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; + +import 'package:path_provider/path_provider.dart' + show getApplicationSupportDirectory; + +Future loadAtClientPreference() async { + var dir = await getApplicationSupportDirectory(); + + return AtClientPreference() + ..rootDomain = AtEnv.rootDomain + ..namespace = AtEnv.appNamespace + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + // TODO + // * By default, this configuration is suitable for most applications + // * In advanced cases you may need to modify [AtClientPreference] + // * Read more here: https://pub.dev/documentation/at_client/latest/at_client/AtClientPreference-class.html +} + +class _MyAppState extends State { + // * load the AtClientPreference in the background // remove + Future futurePreference = loadAtClientPreference(); // Remove + ... +} +``` + +Add the below code to `main.dart`: + +```dart +@override + Widget build(BuildContext context) { + return MultiProvider( + ... + child: MaterialApp( + + ... + home: Scaffold( + appBar: ..., + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ..., + ElevatedButton( + onPressed: () async { + await OnboardCommand().run(); // new + }, + child: const Text('Onboard an @sign'), + ), + ], + ), + ), + ), + ), + ), + ); + } +``` + +Run your flutter app and everything should work as before. + +``` +flutter run ``` +#### Conclusion +Building a production app is like cooking your favorite food, Before you cook the food you do food preparation. You can skip food prep but then cooking become much much harder. Similarly, Following an architecture pattern, creating our export files and abstract classes are like food prep it makes cooking a whole lot easier. -``` \ No newline at end of file +Now that we've completed the onboarding process, in the next step we'll complete the first screen by add a reset atsign button and it's functionalities. diff --git a/hugo_stats.json b/hugo_stats.json index 6e21d3512..ed94cf442 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -310,12 +310,12 @@ "assets", "assignment-of-domain-name-to-your-static-ip", "assignment-of-static-ip", - "atiot", "atplatform", "atprotocol", "b-assignment-of-domain-name-to-your-static-ip", "b-create-cloud-dns-zone", "b-setting-up-billing", + "base-command", "block-list", "building-layouts", "buttonlink", @@ -327,6 +327,7 @@ "cardgroup", "cardshowcase", "cardsocial", + "cleaning-up-maindart", "cloning-the-client", "commands", "compile-jar", @@ -336,7 +337,7 @@ "contact-us", "content", "contributing-to-the-developer-site", - "controllers", + "creating-snackbar-method", "definition", "deleting-a-privatehiddenkey-example", "deleting-a-publickey-example", @@ -353,6 +354,7 @@ "example-4", "example-5", "featured-tutorials", + "fixing-undefined-name-errors", "free-atsigns", "frontmatter", "getting-a-privatehiddenkey-example", @@ -388,6 +390,7 @@ "notifyremove-verb", "offcanvasDoks", "offcanvasDoksLabel", + "onboard-command", "onboarding", "open-source-contributions", "other-services", @@ -401,6 +404,7 @@ "putting-a-publickey-example", "putting-a-selfkey-example", "putting-sharedkey-example", + "refactoring-the-onboard-function", "reference", "registration-cli", "related-resources", From 39a80b3d9b973991f4546982bb66c6279d73096e Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 6 Sep 2022 11:34:46 -0400 Subject: [PATCH 02/12] tutorial description updated --- .../tutorials/at-dude/1-introduction/index.md | 2 +- .../4-onboarding/first_onboard_screen.png | Bin 0 -> 22295 bytes .../at-dude/4-onboarding/first_screen.png | Bin 26389 -> 0 bytes .../tutorials/at-dude/4-onboarding/index.md | 10 +- .../5-reset-atsign/final_onboard_screen.png | Bin 0 -> 26301 bytes .../tutorials/at-dude/5-reset-atsign/index.md | 278 ++++++++++++++++++ .../final_onboard_screen.png | Bin 0 -> 26301 bytes .../at-dude/6-send_dude-screen-ui/index.md | 278 ++++++++++++++++++ content_dev/docs/tutorials/at-dude/_index.md | 2 +- hugo_stats.json | 5 +- 10 files changed, 567 insertions(+), 8 deletions(-) create mode 100644 content_dev/docs/tutorials/at-dude/4-onboarding/first_onboard_screen.png delete mode 100644 content_dev/docs/tutorials/at-dude/4-onboarding/first_screen.png create mode 100644 content_dev/docs/tutorials/at-dude/5-reset-atsign/final_onboard_screen.png create mode 100644 content_dev/docs/tutorials/at-dude/5-reset-atsign/index.md create mode 100644 content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png create mode 100644 content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md diff --git a/content_dev/docs/tutorials/at-dude/1-introduction/index.md b/content_dev/docs/tutorials/at-dude/1-introduction/index.md index db7ce94f8..a799fb676 100644 --- a/content_dev/docs/tutorials/at-dude/1-introduction/index.md +++ b/content_dev/docs/tutorials/at-dude/1-introduction/index.md @@ -2,7 +2,7 @@ layout: codelab title: "Introduction" # Step Name -description: Introdution to the atDude tutorial# SEO Description for this step Documentation +description: Introduction to the atDude tutorial # SEO Description for this step Documentation draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 1 # Ordering of the steps diff --git a/content_dev/docs/tutorials/at-dude/4-onboarding/first_onboard_screen.png b/content_dev/docs/tutorials/at-dude/4-onboarding/first_onboard_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..b3864c4a8cb5855696defd6a545c66c7636aed76 GIT binary patch literal 22295 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 +{{< image type="page" src="first_onboard_screen.png" >}} {{
}} {{
}} @@ -29,9 +29,10 @@ The first thing we will do is change the App bar title to "atDude" in `main.dar ``` MaterialApp( // * The onboarding screen (first screen) + debugShowCheckedModeBanner: false, // New home: Scaffold( appBar: AppBar( - title: const Text('atDude'), # Changed + title: const Text('atDude'), // Changed ), ), ); @@ -461,7 +462,6 @@ Add the below code to `main.dart`: return MultiProvider( ... child: MaterialApp( - ... home: Scaffold( appBar: ..., @@ -496,4 +496,4 @@ flutter run Building a production app is like cooking your favorite food, Before you cook the food you do food preparation. You can skip food prep but then cooking become much much harder. Similarly, Following an architecture pattern, creating our export files and abstract classes are like food prep it makes cooking a whole lot easier. -Now that we've completed the onboarding process, in the next step we'll complete the first screen by add a reset atsign button and it's functionalities. +Now that we've completed the onboarding process, in the next step we'll complete the first screen by adding a reset atsign button and it's functionalities. diff --git a/content_dev/docs/tutorials/at-dude/5-reset-atsign/final_onboard_screen.png b/content_dev/docs/tutorials/at-dude/5-reset-atsign/final_onboard_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..7aefe6af5ed04332ed21e46c1be04a411131d113 GIT binary patch literal 26301 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 KYE^64NBj@6pDlg> literal 0 HcmV?d00001 diff --git a/content_dev/docs/tutorials/at-dude/5-reset-atsign/index.md b/content_dev/docs/tutorials/at-dude/5-reset-atsign/index.md new file mode 100644 index 000000000..bb4d04825 --- /dev/null +++ b/content_dev/docs/tutorials/at-dude/5-reset-atsign/index.md @@ -0,0 +1,278 @@ +--- +layout: codelab + +title: "Reset atsign" # Step Name +description: How to reset any app built on the atPlatform # SEO Description for this step Documentation + +draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +order: 5 # Ordering of the steps +--- + +In this tutorial, we will complete the onboarding screen for the dude app and implement the reset app functionality. + + + + +At the end of this step our app will look like this, + +{{< image type="page" src="final_onboard_screen.png" >}} + +{{
}} +{{
}} + + +#### Creating the Texts util class +The first thing we will do is create a utility class that will store our texts. This will make it easy to update our texts across out app without having to search and replace all occurrence of the string. + + Follow the steps below: + +``` +mkdir lib/utils +touch lib/utils/texts.dart +open lib/utils/texts.dart +``` + +```dart +class Texts { + static const String atDude = 'atDude'; + static const String onboardAtsign = 'Onboard an atsign'; + static const String resetApp = 'Reset App'; +} +``` + +Replace the string 'Reset App' with it's equivalent `static const` for the `ResetAppButton` widget as shown below + +```dart +... +@override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () {}, + child: const Text(Texts.resetApp), // Changed + ); + } +``` + +Let us make similar changes in `main.dart` as shown below: +```dart +... +MaterialApp( + ... + home: Scaffold( + appBar: AppBar( + title: const Text(Texts.atDude), // Changed + ), + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ... + ElevatedButton( + ... + child: const Text(Texts.onboardAtsign), + ), + ], + ), + ), + ), + ), + ), +``` + +#### Adding Reset Functionality to Authentication Service + +In your terminal type: + +``` +open lib/services/authentication_service.dart +``` + +Now we'll create a method called `reset` and `getAtOnboardingConfig` in our `AuthenticationService` class and refactor the `onboard` method. + +```dart +class AuthenticationService { + ... + + AtOnboardingConfig getAtOnboardingConfig({ + required AtClientPreference atClientPreference, + }) => + AtOnboardingConfig( + atClientPreference: atClientPreference, + rootEnvironment: AtEnv.rootEnvironment, + domain: AtEnv.rootDomain, + appAPIKey: AtEnv.appApiKey, + ); + + Future onboard() async { + return await AtOnboarding.onboard( + context: context, + config: getAtOnboardingConfig(atClientPreference: atClientPreference), + ); + } +} +``` + +Here we simply moved `AtOnboardingConfig` into it's own method so we can reuse it on our reset method we're going to create below: + +```dart +import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; +... +Future reset() async { + var dir = await getApplicationSupportDirectory(); + + var atClientPreference = AtClientPreference() + ..rootDomain = AtEnv.rootDomain + ..namespace = AtEnv.appNamespace + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + + return AtOnboarding.reset( + context: NavigationService.navKey.currentContext!, + config: getAtOnboardingConfig(atClientPreference: atClientPreference), + ); + } +``` + +`AtOnboarding.reset` allows the user to remove any atsign that onboard on the app before. This allows the user to onboarding with another atsign. + +#### Onboard Command + +Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to reset the currently signed in atsign on the atPlatform. In your terminal type: + +``` +touch lib/commands/reset_command.dart +open lib/commands/reset_command.dart +``` + +Add the below code: + +```dart +import 'package:at_dude/commands/base_command.dart'; +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + + } +} +``` +This command return a `Future`. + +Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: + +```dart +import 'package:at_dude/commands/base_command.dart'; +import 'package:at_dude/commands/onboard_command.dart'; + +import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; //new +import 'package:flutter/material.dart'; // new + + + +class ResetCommand extends BaseCommand { + Future run() async { + var resetResult = await authenticationService.reset(); + + + // Everything Below New + + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + OnboardCommand().run(); + break; + case AtOnboardingResultStatus.cancelled: + break; + } + } +} +``` + +If `authenticationService.onboard()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. + + +#### Completing the first screen + +Now we just have to update the UI in `main.dart` to all the user to reset their atsign. + +Add the below code to `main.dart`: + +```dart +@override + Widget build(BuildContext context) { + return MultiProvider( + ... + child: MaterialApp( + ... + home: Scaffold( + appBar: ..., + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ..., + ElevatedButton(...), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Expanded( + child: Divider( + color: Colors.black, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + 'Or', + textAlign: TextAlign.center, + ), + ), + Expanded( + child: Divider( + color: Colors.black, + ), + ), + ]), + ), + ], + ), + ), + ), + ), + ), + ); + } +``` + +We add a divider to create separation between the two buttons. + +lets add the reset atsign button as shown below: + +```dart +import 'package:at_dude/commands/reset_command.dart'; +... +Padding(...) +ElevatedButton( + onPressed: () async { + await ResetCommand().run(); + }, + child: Text(Texts.resetApp), +) +``` + +Run your flutter app and everything should work perfectly. + +Go ahead and reset the app + +``` +flutter run +``` +#### Conclusion + +Well done, you've made it this far. In the next step we will start building our Send Dude Screen. diff --git a/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..7aefe6af5ed04332ed21e46c1be04a411131d113 GIT binary patch literal 26301 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 KYE^64NBj@6pDlg> literal 0 HcmV?d00001 diff --git a/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md new file mode 100644 index 000000000..c7560f1a5 --- /dev/null +++ b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md @@ -0,0 +1,278 @@ +--- +layout: codelab + +title: "Reset atsign" # Step Name +description: Creating the UI of the send dude screen # SEO Description for this step Documentation + +draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +order: 5 # Ordering of the steps +--- + +In this tutorial, we will complete the onboarding screen for the dude app and implement the reset app functionality. + + + + +At the end of this step our app will look like this, + +{{< image type="page" src="final_onboard_screen.png" >}} + +{{
}} +{{
}} + + +#### Creating the Texts util class +The first thing we will do is create a utility class that will store our texts. This will make it easy to update our texts across out app without having to search and replace all occurrence of the string. + + Follow the steps below: + +``` +mkdir lib/utils +touch lib/utils/texts.dart +open lib/utils/texts.dart +``` + +```dart +class Texts { + static const String atDude = 'atDude'; + static const String onboardAtsign = 'Onboard an atsign'; + static const String resetApp = 'Reset App'; +} +``` + +Replace the string 'Reset App' with it's equivalent `static const` for the `ResetAppButton` widget as shown below + +```dart +... +@override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () {}, + child: const Text(Texts.resetApp), // Changed + ); + } +``` + +Let us make similar changes in `main.dart` as shown below: +```dart +... +MaterialApp( + ... + home: Scaffold( + appBar: AppBar( + title: const Text(Texts.atDude), // Changed + ), + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ... + ElevatedButton( + ... + child: const Text(Texts.onboardAtsign), + ), + ], + ), + ), + ), + ), + ), +``` + +#### Adding Reset Functionality to Authentication Service + +In your terminal type: + +``` +open lib/services/authentication_service.dart +``` + +Now we'll create a method called `reset` and `getAtOnboardingConfig` in our `AuthenticationService` class and refactor the `onboard` method. + +```dart +class AuthenticationService { + ... + + AtOnboardingConfig getAtOnboardingConfig({ + required AtClientPreference atClientPreference, + }) => + AtOnboardingConfig( + atClientPreference: atClientPreference, + rootEnvironment: AtEnv.rootEnvironment, + domain: AtEnv.rootDomain, + appAPIKey: AtEnv.appApiKey, + ); + + Future onboard() async { + return await AtOnboarding.onboard( + context: context, + config: getAtOnboardingConfig(atClientPreference: atClientPreference), + ); + } +} +``` + +Here we simply moved `AtOnboardingConfig` into it's own method so we can reuse it on our reset method we're going to create below: + +```dart +import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; +... +Future reset() async { + var dir = await getApplicationSupportDirectory(); + + var atClientPreference = AtClientPreference() + ..rootDomain = AtEnv.rootDomain + ..namespace = AtEnv.appNamespace + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + + return AtOnboarding.reset( + context: NavigationService.navKey.currentContext!, + config: getAtOnboardingConfig(atClientPreference: atClientPreference), + ); + } +``` + +`AtOnboarding.reset` allows the user to remove any atsign that onboard on the app before. This allows the user to onboarding with another atsign. + +#### Onboard Command + +Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to reset the currently signed in atsign on the atPlatform. In your terminal type: + +``` +touch lib/commands/reset_command.dart +open lib/commands/reset_command.dart +``` + +Add the below code: + +```dart +import 'package:at_dude/commands/base_command.dart'; +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + + } +} +``` +This command return a `Future`. + +Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: + +```dart +import 'package:at_dude/commands/base_command.dart'; +import 'package:at_dude/commands/onboard_command.dart'; + +import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; //new +import 'package:flutter/material.dart'; // new + + + +class ResetCommand extends BaseCommand { + Future run() async { + var resetResult = await authenticationService.reset(); + + + // Everything Below New + + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + OnboardCommand().run(); + break; + case AtOnboardingResultStatus.cancelled: + break; + } + } +} +``` + +If `authenticationService.onboard()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. + + +#### Completing the first screen + +Now we just have to update the UI in `main.dart` to all the user to reset their atsign. + +Add the below code to `main.dart`: + +```dart +@override + Widget build(BuildContext context) { + return MultiProvider( + ... + child: MaterialApp( + ... + home: Scaffold( + appBar: ..., + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ..., + ElevatedButton(...), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Expanded( + child: Divider( + color: Colors.black, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + 'Or', + textAlign: TextAlign.center, + ), + ), + Expanded( + child: Divider( + color: Colors.black, + ), + ), + ]), + ), + ], + ), + ), + ), + ), + ), + ); + } +``` + +We add a divider to create separation between the two buttons. + +lets add the reset atsign button as shown below: + +```dart +import 'package:at_dude/commands/reset_command.dart'; +... +Padding(...) +ElevatedButton( + onPressed: () async { + await ResetCommand().run(); + }, + child: Text(Texts.resetApp), +) +``` + +Run your flutter app and everything should work perfectly. + +Go ahead and reset the app + +``` +flutter run +``` +#### Conclusion + +Well done, you've made it this far. In the next step we will start building our Send Dude Screen. diff --git a/content_dev/docs/tutorials/at-dude/_index.md b/content_dev/docs/tutorials/at-dude/_index.md index e5c59b531..381d26277 100644 --- a/content_dev/docs/tutorials/at-dude/_index.md +++ b/content_dev/docs/tutorials/at-dude/_index.md @@ -2,7 +2,7 @@ layout: codelab-list # The layout for a codelab list (think of this as a title page for the code lab) title: "atDude Tutorial" # Title of the codelab -lead: Learn how to build on the atPlatform # Description of the codelab +lead: Learn how to build production app on the atPlatform # Description of the codelab description: Learn how to build on the atPlatform doneLink: /tutorials # Where to send them if they press "Done" at the end of the Codelab diff --git a/hugo_stats.json b/hugo_stats.json index ed94cf442..d08372513 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -305,6 +305,7 @@ "a-register-domain-name-with-aws", "a-register-domain-name-with-gcp", "adding-iconbutton", + "adding-reset-functionality-to-authentication-service", "anchor-tag-a", "arrow-back", "assets", @@ -331,6 +332,7 @@ "cloning-the-client", "commands", "compile-jar", + "completing-the-first-screen", "conclusion", "configuration-parameters", "contact", @@ -338,6 +340,8 @@ "content", "contributing-to-the-developer-site", "creating-snackbar-method", + "creating-the-reset-app-widget", + "creating-the-texts-util-class", "definition", "deleting-a-privatehiddenkey-example", "deleting-a-publickey-example", @@ -391,7 +395,6 @@ "offcanvasDoks", "offcanvasDoksLabel", "onboard-command", - "onboarding", "open-source-contributions", "other-services", "overview", From 2df8512d79722e58a52eae33f27623782fda7e28 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Thu, 8 Sep 2022 15:44:36 -0400 Subject: [PATCH 03/12] step six added --- .../at-dude/6-send_dude-screen-ui/index.md | 416 ++++++++++-------- hugo_stats.json | 9 +- 2 files changed, 249 insertions(+), 176 deletions(-) diff --git a/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md index c7560f1a5..e71e8f14b 100644 --- a/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md +++ b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md @@ -1,14 +1,14 @@ --- layout: codelab -title: "Reset atsign" # Step Name +title: "Send Dude Screen AppBar" # Step Name description: Creating the UI of the send dude screen # SEO Description for this step Documentation -draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 5 # Ordering of the steps --- -In this tutorial, we will complete the onboarding screen for the dude app and implement the reset app functionality. +In this tutorial, we will build the AppBar of send dude screen. @@ -21,258 +21,324 @@ At the end of this step our app will look like this, {{
}} -#### Creating the Texts util class -The first thing we will do is create a utility class that will store our texts. This will make it easy to update our texts across out app without having to search and replace all occurrence of the string. +#### Creating the AppBar +The first thing we will do is create our send dude screen dart file with the AppBar and the widgets and properties it needs. Follow the steps below: ``` -mkdir lib/utils -touch lib/utils/texts.dart + +touch lib/views/screens/send_dude_screen.dart +open lib/views/screens/send_dude_screen.dart +``` + +```dart +import 'package:flutter/material.dart'; +import '../../utils/texts.dart'; + +class SendDudeScreen extends StatefulWidget { + SendDudeScreen({Key? key}) : super(key: key); + static String routeName = 'sendDudeScreen'; + + @override + State createState() => _SendDudeScreenState(); +} + +class _SendDudeScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + foregroundColor: Colors.transparent, + shadowColor: Colors.transparent, + title: const Text( + Texts.sendDude, + style: TextStyle(color: Colors.black), + ), + actions: const [AtsignAvatar()], + + ), + ); + } +} +``` + +We have our stateful widget with an `appBar` but we neither have a `Texts.sendDude` constant nor the `AtsignAvatar()`. Lets create them: + +``` open lib/utils/texts.dart ``` ```dart +... class Texts { - static const String atDude = 'atDude'; - static const String onboardAtsign = 'Onboard an atsign'; - static const String resetApp = 'Reset App'; + ... + static const String sendDude = 'Reset App'; } ``` -Replace the string 'Reset App' with it's equivalent `static const` for the `ResetAppButton` widget as shown below +#### AtsignAvatar + +Let us create `AtsignAvatar` as shown below: +``` +touch lib/views/widgets/atsign_avatar.dart +open lib/views/widgets/atsign_avatar.dart +``` ```dart -... -@override +import 'dart:typed_data' show Uint8List; +import 'package:flutter/material.dart'; + +class AtsignAvatar extends StatefulWidget { + const AtsignAvatar({Key? key}) : super(key: key); + + @override + State createState() => _AtsignAvatarState(); +} + +class _AtsignAvatarState extends State { + Uint8List? image; + String? profileName; + + @override Widget build(BuildContext context) { - return ElevatedButton( - onPressed: () {}, - child: const Text(Texts.resetApp), // Changed + return GestureDetector( + child: CircleAvatar( + backgroundColor: Colors.transparent, + child: image == null + ? const Icon( + Icons.person_outline, + color: Colors.black, + ) + : ClipOval(child: Image.memory(image!)), + ), + onTap: () {}, ); } +} ``` -Let us make similar changes in `main.dart` as shown below: -```dart -... -MaterialApp( - ... - home: Scaffold( - appBar: AppBar( - title: const Text(Texts.atDude), // Changed - ), - body: Builder( - builder: (context) => Center( - child: Column( - ... - children: [ - ... - ElevatedButton( - ... - child: const Text(Texts.onboardAtsign), - ), - ], - ), - ), - ), - ), - ), +Basically we have a `CircleAvatar` whose child is an image or an person_outline. We need to add the functionality that will check for the atsign contact details. + +##### Profile Data +``` +mkdir lib/data +touch lib/data/profile_data.dart +open lib/data/profile_data.dart ``` -#### Adding Reset Functionality to Authentication Service +```dart +import 'dart:typed_data' show Uint8List; -In your terminal type: +class ProfileData { + ProfileData({required this.name, required this.profileImage}); + final String name; + final Uint8List? profileImage; +} ``` -open lib/services/authentication_service.dart -``` +This class will contain the name and profile image data we'll get from ContactService class provided to us form free from the at_xxx package. -Now we'll create a method called `reset` and `getAtOnboardingConfig` in our `AuthenticationService` class and refactor the `onboard` method. +##### Contacts Model +We'll now create our contacts model that will store all our contacts information needed in our app. + +``` +touch lib/models/contacts_model.dart +open lib/models/contacts_model.dart +``` ```dart -class AuthenticationService { - ... +import 'package:flutter/material.dart'; - AtOnboardingConfig getAtOnboardingConfig({ - required AtClientPreference atClientPreference, - }) => - AtOnboardingConfig( - atClientPreference: atClientPreference, - rootEnvironment: AtEnv.rootEnvironment, - domain: AtEnv.rootDomain, - appAPIKey: AtEnv.appApiKey, - ); - - Future onboard() async { - return await AtOnboarding.onboard( - context: context, - config: getAtOnboardingConfig(atClientPreference: atClientPreference), - ); +import '../data/profile_data.dart'; + +class ContactsModel extends ChangeNotifier { + late ProfileData _profileData; + + ProfileData get profileData => _profileData; + + set profileData(ProfileData profileData) { + _profileData = profileData; + notifyListeners(); } } ``` +Our profile data extends `ChangeNotifier`, this will allow us to `notifyListeners()` of changes made to `profileData`. + +We now have to add our `ContactModel` as a `ChangeNotifierProvider` and then add it to `BaseCommand`. -Here we simply moved `AtOnboardingConfig` into it's own method so we can reuse it on our reset method we're going to create below: +``` +open lib/main.dart +``` ```dart -import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; ... -Future reset() async { - var dir = await getApplicationSupportDirectory(); - - var atClientPreference = AtClientPreference() - ..rootDomain = AtEnv.rootDomain - ..namespace = AtEnv.appNamespace - ..hiveStoragePath = dir.path - ..commitLogPath = dir.path - ..isLocalStoreRequired = true; - - return AtOnboarding.reset( - context: NavigationService.navKey.currentContext!, - config: getAtOnboardingConfig(atClientPreference: atClientPreference), +@override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + Provider(create: (c) => AuthenticationService.getInstance()), + ChangeNotifierProvider(create: (c) => ContactsModel()), // new + ], + child: MaterialApp(...), ); - } ``` -`AtOnboarding.reset` allows the user to remove any atsign that onboard on the app before. This allows the user to onboarding with another atsign. +``` +open lib/commands/base_command.dart +``` +```dart +... +import '../models/contacts_model.dart'; + +abstract class BaseCommand { + // Services + AuthenticationService authenticationService = + NavigationService.navKey.currentContext!.read(); + + // Models + ContactsModel contactsModel = NavigationService.navKey.currentContext!.read(); +``` + +#### Contact Details Command +We'll now create our Contact Details Command. We don't have to create our `ContactService` since this is provided to us from the at_contacts_flutter package. -#### Onboard Command -Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to reset the currently signed in atsign on the atPlatform. In your terminal type: +In your terminal type: ``` -touch lib/commands/reset_command.dart -open lib/commands/reset_command.dart +flutter pub add at_contacts_flutter +touch lib/commands/contact_details_command.dart +open lib/commands/contact_details_command.dart ``` -Add the below code: - ```dart +import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:at_contacts_flutter/services/contact_service.dart'; import 'package:at_dude/commands/base_command.dart'; -class OnboardCommand extends BaseCommand { +import 'package:at_dude/data/profile_data.dart'; + +class ContactDetailsCommand extends BaseCommand { Future run() async { - var onboardingResult = await authenticationService.onboard(); - + final contactService = ContactService(); + + ContactService() + .getContactDetails( + AtClientManager.getInstance().atClient.getCurrentAtSign(), null) + .then( + (value) { + contactsModel.profileData = + ProfileData(name: value['name'], profileImage: value['image']); + return null; + }, + ); } } ``` -This command return a `Future`. - -Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: - -```dart -import 'package:at_dude/commands/base_command.dart'; -import 'package:at_dude/commands/onboard_command.dart'; -import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; //new -import 'package:flutter/material.dart'; // new +We use the `AtClientManager` method to get the current atsign, then use the ContactService to get the name and profile image of the atsign. We then return the profileData to our contactsModel. -class ResetCommand extends BaseCommand { - Future run() async { - var resetResult = await authenticationService.reset(); +#### Completing the AtsignAvatar widget +Now we just have what we need to complete the AtsignAvatar Widget. - // Everything Below New +``` +open lib/views/widgets/atsign_avatar.dart +``` - switch (onboardingResult.status) { - case AtOnboardingResultStatus.success: - OnboardCommand().run(); - break; - case AtOnboardingResultStatus.cancelled: - break; - } +```dart +class _AtsignAvatarState extends State { +... +@override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ContactDetailsCommand().run(); + }); + super.initState(); } } ``` -If `authenticationService.onboard()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. - +To run an async method inside `initState` we need to call the method inside `WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {});` -#### Completing the first screen +We can now delete the `image` and `profileName` variables since the data is now inside our `ContactsModel.profileData` property. Let's use the power of provider to access this property. -Now we just have to update the UI in `main.dart` to all the user to reset their atsign. +```dart +class _AtsignAvatarState extends State { + Uint8List? image; // Delete this + String? profileName; // Delete this -Add the below code to `main.dart`: + @override + void initState() { + ... + } -```dart -@override + @override Widget build(BuildContext context) { - return MultiProvider( - ... - child: MaterialApp( - ... - home: Scaffold( - appBar: ..., - body: Builder( - builder: (context) => Center( - child: Column( - ... - children: [ - ..., - ElevatedButton(...), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Expanded( - child: Divider( - color: Colors.black, - ), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 12.0), - child: Text( - 'Or', - textAlign: TextAlign.center, - ), - ), - Expanded( - child: Divider( - color: Colors.black, - ), - ), - ]), - ), - ], - ), - ), - ), - ), + return GestureDetector( + child: CircleAvatar( + backgroundColor: Colors.transparent, + child: context.watch().profileData.profileImage == null + ? const Icon( + Icons.person_outline, + color: Colors.black, + ) + : ClipOval( + child: Image.memory( + context.watch().profileData.profileImage!)), ), + onTap: () {}, ); } +} ``` -We add a divider to create separation between the two buttons. +#### Cleaning up our SendDudeScreen + +Let's fix our "The method 'AtsignAvatar' isn't defined" error by simply importing out `AtsignAvatar` widget: -lets add the reset atsign button as shown below: +``` +open lib/views/screens/send_dude_screen.dart +``` ```dart -import 'package:at_dude/commands/reset_command.dart'; -... -Padding(...) -ElevatedButton( - onPressed: () async { - await ResetCommand().run(); - }, - child: Text(Texts.resetApp), -) +import '../widgets/atsign_avatar.dart'; // new + +class SendDudeScreen extends StatefulWidget { + ... +} + +class _SendDudeScreenState extends State { + ... +} ``` -Run your flutter app and everything should work perfectly. +#### Navigating to the SendDudeScreen -Go ahead and reset the app +We can now navigate to our SendDudeScreen now that our send Dude Screen AppBar is completed. ``` -flutter run +open lib/commands/onboard_command.dart ``` + +```dart +... +class OnboardCommand extends BaseCommand { + Future run() async { + ... + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + Navigator.popAndPushNamed(context, SendDudeScreen.routeName); // new + break; + ... + } + } +} +``` + #### Conclusion Well done, you've made it this far. In the next step we will start building our Send Dude Screen. diff --git a/hugo_stats.json b/hugo_stats.json index d08372513..dcbc02d08 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -313,6 +313,7 @@ "assignment-of-static-ip", "atplatform", "atprotocol", + "atsignavatar", "b-assignment-of-domain-name-to-your-static-ip", "b-create-cloud-dns-zone", "b-setting-up-billing", @@ -329,18 +330,22 @@ "cardshowcase", "cardsocial", "cleaning-up-maindart", + "cleaning-up-our-senddudescreen", "cloning-the-client", "commands", "compile-jar", + "completing-the-atsignavatar-widget", "completing-the-first-screen", "conclusion", "configuration-parameters", "contact", + "contact-details-command", "contact-us", + "contacts-model", "content", "contributing-to-the-developer-site", "creating-snackbar-method", - "creating-the-reset-app-widget", + "creating-the-appbar", "creating-the-texts-util-class", "definition", "deleting-a-privatehiddenkey-example", @@ -385,6 +390,7 @@ "models", "monitor", "monitor-verb", + "navigating-to-the-senddudescreen", "next-step", "notification", "notify-list", @@ -403,6 +409,7 @@ "polymorphic-data", "prerequisite", "previous-step", + "profile-data", "putting-a-privatehiddenkey-example", "putting-a-publickey-example", "putting-a-selfkey-example", From 67361350819376ce7cbd689184365766ea5e2229 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Fri, 9 Sep 2022 17:04:10 -0400 Subject: [PATCH 04/12] steps 1 to 3 of atDude tutorial reviewed for grammatical errors --- .../at-dude/3-app_architecture/index.md | 4 +- .../tutorials/at-dude/4-onboarding/index.md | 109 +++++++++--------- .../tutorials/at-dude/5-reset-atsign/index.md | 95 +++++++-------- .../index.md | 46 ++++++-- .../send_dude_screen_app_bar.png | Bin 0 -> 10208 bytes .../final_onboard_screen.png | Bin 26301 -> 0 bytes hugo_stats.json | 1 + 7 files changed, 144 insertions(+), 111 deletions(-) rename content_dev/docs/tutorials/at-dude/{6-send_dude-screen-ui => 6-send_dude-screen-app-bar}/index.md (82%) create mode 100644 content_dev/docs/tutorials/at-dude/6-send_dude-screen-app-bar/send_dude_screen_app_bar.png delete mode 100644 content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png diff --git a/content_dev/docs/tutorials/at-dude/3-app_architecture/index.md b/content_dev/docs/tutorials/at-dude/3-app_architecture/index.md index d92024402..b64726c61 100644 --- a/content_dev/docs/tutorials/at-dude/3-app_architecture/index.md +++ b/content_dev/docs/tutorials/at-dude/3-app_architecture/index.md @@ -52,7 +52,7 @@ The controller files will be saved in this folder. #### Services -The services fetch data from the network or local storage and returns it to the controllers. +The services fetch data from the network or local storage and return it to the commands. In your terminal type: @@ -65,6 +65,6 @@ The services files will be saved in this folder. #### Conclusion -The controllers will call the services to fetch data from the network and then send the fetched data to the model. The views are bound to the model using provider or an alternative. As the models are updated the views will be updated. Alternatively the controller can update the views directly. +The commands will call the services to fetch data from the network and then send the fetched data to the model. The views are bound to the model using provider or an alternative. As the models are updated the views will be updated. Alternatively the commands can update the views directly. In the next step we'll explore this further as we implement onboarding on the atPlatform. \ No newline at end of file diff --git a/content_dev/docs/tutorials/at-dude/4-onboarding/index.md b/content_dev/docs/tutorials/at-dude/4-onboarding/index.md index aa4075d81..29d825325 100644 --- a/content_dev/docs/tutorials/at-dude/4-onboarding/index.md +++ b/content_dev/docs/tutorials/at-dude/4-onboarding/index.md @@ -12,7 +12,7 @@ In this tutorial, we will build the onboarding screen for the dude app and modif -With out further ado, lets get back to building the atDude app. +With out further ado, let's get back to building the atDude app. At the end of this step our app will look like this, @@ -21,7 +21,7 @@ At the end of this step our app will look like this, {{
}} {{
}} -Before we get into the onboarding, lets make the UI changes to our app. +Before we get into the onboarding, let's make the UI changes to our app. #### Update AppBar The first thing we will do is change the App bar title to "atDude" in `main.dart`. @@ -38,7 +38,7 @@ MaterialApp( ); ``` -The next step is to wrap our `ElevatedButton` widget with a Column widget and center its mainAxisAlignment. +The next step is to wrap our `ElevatedButton` widget with a `Column` widget and center its mainAxisAlignment. ```dart MaterialApp( @@ -62,9 +62,9 @@ MaterialApp( #### Adding IconButton -Before add an `IconButton` widget with the dude logo as the icon property. We need to add the logo to our project. +Before we add an `IconButton` widget with the dude logo as the icon property. We need to add the logo to our project. -In your editor create a new folder called assets and inside the assets folder create a folder name images or type the following in your terminal: +Type the following in your terminal: ``` mkdir -p assets/images/ @@ -82,7 +82,7 @@ flutter: Let's create the `IconButton` as shown below. -``` +```dart Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -102,13 +102,13 @@ Column( #### Refactoring the Onboard Function -Before we get started with this section, lets define a few terms: +Before we get started with this section, let's define a few terms: [Onboarding](../../../sdk/flutter/onboarding) - The process of activating an atSign and/or authenticating the atSign into its secondary server. -[atsign](https://atsign.com/what-is-an-atsign/) - An atsign is your digital identity. It ensures that your data is owned and controlled by you. You can pair your device with any app to access but not store your data. +[atsign](https://atsign.com/what-is-an-atsign/) - An atsign is your digital identity; it ensures that your data is owned and controlled by you. You can pair your device with any app on the atPlatform to access but not store your data. -To make the onboarding code compatible, we will remove it form the "onboard an @sign" button and move it into an authentication class. +We will make the onboarding code compatible with our architecture by removing it from the “onboard an @sign” button and moving it into an authentication class. In your terminal type: @@ -119,7 +119,7 @@ open lib/services/authentication_service.dart Add the following: -``` +```dart class AuthenticationService { static final AuthenticationService _singleton = AuthenticationService._internal(); @@ -134,7 +134,7 @@ The above code creates a [singleton](https://flutterbyexample.com/lesson/singlet -Now we'll create a method called `onboard` in our `AuthenticationService` class and move the `onboardingResult` variable found inside the `onPressed` anonymous function of the ElevatedButton to the `onboard` method. Replace `AtOnboardingResult onboardingResult =` with `return` as shown below. +Now we'll create an async method called `onboard` in our `AuthenticationService` class. We will then move the contents of the `onboardingResult` variable inside the `ElevatedButton` `onPressed` anonymous function in `main.dart`, to the this method as shown below. ```dart class AuthenticationService { @@ -155,7 +155,7 @@ class AuthenticationService { #### Fixing Undefined name errors -To fix the `Undefined name` errors we need to provide the `AtOnboarding.onboard()`method with a `BuildContext` and the `AtOnboardingConfig()` class as shown below. +To fix the `Undefined name` errors we need to provide the `AtOnboarding.onboard()`method with a `BuildContext` and the `AtClientPreference` class as shown below. ```dart @@ -169,19 +169,19 @@ class AuthenticationService { ... Future onboard() async { - var dir = await getApplicationSupportDirectory(); + var dir = await getApplicationSupportDirectory(); // new - var atClientPreference = AtClientPreference() - ..rootDomain = AtEnv.rootDomain - ..namespace = AtEnv.appNamespace - ..hiveStoragePath = dir.path - ..commitLogPath = dir.path - ..isLocalStoreRequired = true; + var atClientPreference = AtClientPreference() // new + ..rootDomain = AtEnv.rootDomain //new + ..namespace = AtEnv.appNamespace //new + ..hiveStoragePath = dir.path //new + ..commitLogPath = dir.path //new + ..isLocalStoreRequired = true; //new return AtOnboarding.onboard( context: context, config: AtOnboardingConfig( - atClientPreference: atClientPreference, + atClientPreference: atClientPreference, // new rootEnvironment: AtEnv.rootEnvironment, domain: AtEnv.rootDomain, appAPIKey: AtEnv.appApiKey, @@ -191,7 +191,7 @@ class AuthenticationService { } ``` -we Now need access to the BuildContext, We'll create a separate class for this since we'll be reusing our BuildContext outside of the stateful and stateless widget. +We Now need access to the BuildContext, We'll create a separate class for this since we'll be reusing our BuildContext outside of the stateful and stateless widgets. In your terminal type: @@ -213,7 +213,7 @@ touch lib/services/services.dart open lib/services/services.dart ``` -Add the below code so can use one import statement to import all export files: +Add the below code to use one import statement to import all export files: ```dart export 'authentication_service.dart'; export 'navigation_service.dart'; @@ -222,11 +222,12 @@ export 'navigation_service.dart'; In `main.dart` add the navigatorKey to the materialApp as shown below; ```dart -import 'package:at_dude/services/services.dart'; +import 'package:at_dude/services/services.dart'; // new +... Widget build(BuildContext context) { return MaterialApp( // * The onboarding screen (first screen) - navigatorKey: NavigationService.navKey, + navigatorKey: NavigationService.navKey, // new home: ... ); } @@ -243,19 +244,20 @@ Future onboard() async { ); } ``` + +That's it, our `AuthenticatinService` can reach out to the atPlatform to return our `AtOnboardingResult`. We now need our command to call this service and decide what action to take depending on the returned `AtOnboardingResult`. + #### Base Command -Remember our commands contain our application logic, before we can create our onboard command we'll first create a base command that all other commands will extend from. In your terminal type: + Remember, our commands contain our application logic; we’ll first create a base command that all other commands will extend from before creating our onboard command In your terminal type: ``` touch lib/commands/base_command.dart +open lib/commands/base_command.dart ``` Create the `BaseCommand` class with the needed imports as shown below: -``` -open lib/commands/base_command.dart -``` ```dart import 'package:provider/provider.dart'; @@ -268,29 +270,31 @@ abstract class BaseCommand { } ``` -We now have to provide the services using the provider package as shown below: + We need to provide the services to the widget tree in order for `BaseCommand` to successfully read the services from the BuildContext. To achieve this we'll make use of the [provider package](https://pub.dev/packages/provider) as shown below: ``` flutter pub add provider open lib/main.dart ``` ```dart -import 'package:provider/provider.dart'; +import 'package:provider/provider.dart'; //new ... class _MyAppState extends State { @override Widget build(BuildContext context) { return MultiProvider( //new - providers: [Provider(create: (c) => AuthenticationService.getInstance())],// new + providers: [Provider(create: (c) => AuthenticationService.getInstance())] // new child: MaterialApp(), ) } } ``` +To learn more about state management using provider check out this [flutter article](https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple) + #### Onboard Command -Now that we're all set, lets create our Onboard Command. This class method will contain the instructions required to onboard on the atPlatform. In your terminal type: +Now that we're all set, let's create our Onboard Command. This class method will contain the instructions required to onboard on the atPlatform. In your terminal type: ``` touch lib/commands/onboard_command.dart @@ -310,7 +314,7 @@ class OnboardCommand extends BaseCommand { ``` Our Commands will only have one method called `run()`. This command return a `Future`. -Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: +Move the remaining code inside the `ElevatedButton onPressed` anonymous function in `main.dart`, to the `run()` method of the `OnboardCommand()` class as show below: ```dart import 'package:at_dude/commands/base_command.dart'; @@ -348,7 +352,7 @@ class OnboardCommand extends BaseCommand { If `authenticationService.onboard()` return `AtOnboardingResultStatus.success` we navigate to the HomeScreen, if it returns `AtOnboardingResultStatus.error` we display a `Snackbar` on the screen. -We will be using the `Snackbar` widget often so lets extract it into its own method. +We will be using the `Snackbar` widget often so let's extract it into its own method. #### Creating Snackbar Method @@ -369,12 +373,12 @@ class Snackbars extends StatelessWidget { const Snackbars({Key? key}) : super(key: key); static void errorSnackBar({ - required String content, + required String errorMessage, }) { ScaffoldMessenger.of(NavigationService.navKey.currentContext!) .showSnackBar(SnackBar( content: Text( - content, + errorMessage, textAlign: TextAlign.center, ), backgroundColor: @@ -391,21 +395,18 @@ class Snackbars extends StatelessWidget { We created a class that extends `StatelessWidget`. This class contains a static void method named `errorSnackBar` that accepts an `errorMessage`. -This method will save the broiler plate of calling `ScaffoldMessenger.of(context).showSnackBar()` every time we want so show a snackbar. +This method will save us from calling `ScaffoldMessenger.of(context).showSnackBar()` every time we want so show a snackbar. -Lets replace our snackbar part of `OnboardCommand.run()` method as shown below: +Let's replace our snackbar part of `OnboardCommand.run()` method as shown below: ```dart ... import 'package:at_dude/views/widgets/snackbars.dart'; // new - - class OnboardCommand extends BaseCommand { Future run() async { - var onboardingResult = await authenticationService.onboard(); - var context = NavigationService.navKey.currentContext!; + ... switch (onboardingResult.status) { ... case AtOnboardingResultStatus.error: @@ -427,20 +428,20 @@ Remove the code below from main.dart: ```dart -import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; // remove -import 'package:path_provider/path_provider.dart' - show getApplicationSupportDirectory; +import 'package:path_provider/path_provider.dart' // remove + show getApplicationSupportDirectory; // remove -Future loadAtClientPreference() async { - var dir = await getApplicationSupportDirectory(); +Future loadAtClientPreference() async { // remove + var dir = await getApplicationSupportDirectory(); //remove - return AtClientPreference() - ..rootDomain = AtEnv.rootDomain - ..namespace = AtEnv.appNamespace - ..hiveStoragePath = dir.path - ..commitLogPath = dir.path - ..isLocalStoreRequired = true; + return AtClientPreference() // remove + ..rootDomain = AtEnv.rootDomain // remove + ..namespace = AtEnv.appNamespace // remove + ..hiveStoragePath = dir.path // remove + ..commitLogPath = dir.path // remove + ..isLocalStoreRequired = true; // remove // TODO // * By default, this configuration is suitable for most applications // * In advanced cases you may need to modify [AtClientPreference] @@ -494,6 +495,6 @@ flutter run ``` #### Conclusion -Building a production app is like cooking your favorite food, Before you cook the food you do food preparation. You can skip food prep but then cooking become much much harder. Similarly, Following an architecture pattern, creating our export files and abstract classes are like food prep it makes cooking a whole lot easier. +Following an architecture is like having an organized room, it takes extra effort but it makes navigating and understand your codebase a whole lot easier and cleaner. Now that we've completed the onboarding process, in the next step we'll complete the first screen by adding a reset atsign button and it's functionalities. diff --git a/content_dev/docs/tutorials/at-dude/5-reset-atsign/index.md b/content_dev/docs/tutorials/at-dude/5-reset-atsign/index.md index bb4d04825..ed285a992 100644 --- a/content_dev/docs/tutorials/at-dude/5-reset-atsign/index.md +++ b/content_dev/docs/tutorials/at-dude/5-reset-atsign/index.md @@ -10,9 +10,6 @@ order: 5 # Ordering of the steps In this tutorial, we will complete the onboarding screen for the dude app and implement the reset app functionality. - - - At the end of this step our app will look like this, {{< image type="page" src="final_onboard_screen.png" >}} @@ -20,8 +17,8 @@ At the end of this step our app will look like this, {{
}} {{
}} - #### Creating the Texts util class + The first thing we will do is create a utility class that will store our texts. This will make it easy to update our texts across out app without having to search and replace all occurrence of the string. Follow the steps below: @@ -70,7 +67,7 @@ MaterialApp( ... ElevatedButton( ... - child: const Text(Texts.onboardAtsign), + child: const Text(Texts.onboardAtsign), // changed ), ], ), @@ -82,7 +79,7 @@ MaterialApp( #### Adding Reset Functionality to Authentication Service -In your terminal type: +We'll repeat the same pattern of the onboarding functionality for the reset app functionality. In your terminal type: ``` open lib/services/authentication_service.dart @@ -93,7 +90,7 @@ Now we'll create a method called `reset` and `getAtOnboardingConfig` in our `Aut ```dart class AuthenticationService { ... - + // new method AtOnboardingConfig getAtOnboardingConfig({ required AtClientPreference atClientPreference, }) => @@ -102,12 +99,12 @@ class AuthenticationService { rootEnvironment: AtEnv.rootEnvironment, domain: AtEnv.rootDomain, appAPIKey: AtEnv.appApiKey, - ); + ); // new Future onboard() async { return await AtOnboarding.onboard( context: context, - config: getAtOnboardingConfig(atClientPreference: atClientPreference), + config: getAtOnboardingConfig(atClientPreference: atClientPreference), // changed ); } } @@ -137,9 +134,11 @@ Future reset() async { `AtOnboarding.reset` allows the user to remove any atsign that onboard on the app before. This allows the user to onboarding with another atsign. -#### Onboard Command +#### Reset Command -Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to reset the currently signed in atsign on the atPlatform. In your terminal type: +Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to remove any atsign associated with our app. + +In your terminal type: ``` touch lib/commands/reset_command.dart @@ -150,16 +149,14 @@ Add the below code: ```dart import 'package:at_dude/commands/base_command.dart'; -class OnboardCommand extends BaseCommand { +class ResetCommand extends BaseCommand { Future run() async { - var onboardingResult = await authenticationService.onboard(); + var resetResult = await authenticationService.reset(); } } ``` -This command return a `Future`. - -Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: +Now that we have our variable `resetResult`. Let's decide what we'll do depending on the `resetResult`. ```dart import 'package:at_dude/commands/base_command.dart'; @@ -177,25 +174,26 @@ class ResetCommand extends BaseCommand { // Everything Below New - switch (onboardingResult.status) { - case AtOnboardingResultStatus.success: + switch (resetResult) { + case AtOnboardingResetResult.success: OnboardCommand().run(); break; - case AtOnboardingResultStatus.cancelled: + + case AtOnboardingResetResult.cancelled: break; } } } ``` -If `authenticationService.onboard()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. +If `authenticationService.reset()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. #### Completing the first screen -Now we just have to update the UI in `main.dart` to all the user to reset their atsign. +Now we just have to update the UI in `main.dart` to allow the user to reset the app. -Add the below code to `main.dart`: +Edit `main.dart` as shown below: ```dart @override @@ -204,42 +202,46 @@ Add the below code to `main.dart`: ... child: MaterialApp( ... - home: Scaffold( - appBar: ..., + home: Scaffold(...), body: Builder( builder: (context) => Center( child: Column( ... children: [ - ..., - ElevatedButton(...), + IconButton(...), + ElevatedButton( + ... + child: const Text(Texts.onboardAtsign), + ), + // new Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Expanded( - child: Divider( - color: Colors.black, - ), + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Expanded( + child: Divider( + color: Colors.black, ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 12.0), - child: Text( - 'Or', - textAlign: TextAlign.center, - ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + 'Or', + textAlign: TextAlign.center, ), - Expanded( - child: Divider( - color: Colors.black, - ), + ), + Expanded( + child: Divider( + color: Colors.black, ), - ]), - ), + ), + ], + ), + ), // new end ], ), ), @@ -255,15 +257,16 @@ We add a divider to create separation between the two buttons. lets add the reset atsign button as shown below: ```dart -import 'package:at_dude/commands/reset_command.dart'; +import 'package:at_dude/commands/reset_command.dart'; // new ... Padding(...) +// new ElevatedButton( onPressed: () async { await ResetCommand().run(); }, child: Text(Texts.resetApp), -) +) // new end ``` Run your flutter app and everything should work perfectly. diff --git a/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md similarity index 82% rename from content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md rename to content_dev/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md index e71e8f14b..3605c06da 100644 --- a/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md +++ b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md @@ -15,7 +15,7 @@ In this tutorial, we will build the AppBar of send dude screen. At the end of this step our app will look like this, -{{< image type="page" src="final_onboard_screen.png" >}} +{{< image type="page" src="send_dude_screen_app_bar.png" >}} {{
}} {{
}} @@ -74,7 +74,7 @@ open lib/utils/texts.dart ... class Texts { ... - static const String sendDude = 'Reset App'; + static const String sendDude = 'Send Dude'; } ``` @@ -119,7 +119,7 @@ class _AtsignAvatarState extends State { } ``` -Basically we have a `CircleAvatar` whose child is an image or an person_outline. We need to add the functionality that will check for the atsign contact details. +Basically we have a `CircleAvatar` whose child is a profile image or an person_outline icon if no image is available. We need to add the functionality that will check for the atsign contact details. ##### Profile Data ``` @@ -134,11 +134,11 @@ import 'dart:typed_data' show Uint8List; class ProfileData { ProfileData({required this.name, required this.profileImage}); - final String name; + final String? name; final Uint8List? profileImage; } ``` -This class will contain the name and profile image data we'll get from ContactService class provided to us form free from the at_xxx package. +This class will contain the name and profile image data we'll get from the ContactService class provided to us for free from the [at_contacts_flutter package](https://pub.dev/packages/at_contacts_flutter). ##### Contacts Model We'll now create our contacts model that will store all our contacts information needed in our app. @@ -202,7 +202,7 @@ abstract class BaseCommand { ``` #### Contact Details Command -We'll now create our Contact Details Command. We don't have to create our `ContactService` since this is provided to us from the at_contacts_flutter package. +We'll now create our Contact Details Command. We don't have to create our `ContactService` since this is provided to us from the [at_contacts_flutter package](https://pub.dev/packages/at_contacts_flutter). In your terminal type: @@ -237,7 +237,7 @@ class ContactDetailsCommand extends BaseCommand { } ``` -We use the `AtClientManager` method to get the current atsign, then use the ContactService to get the name and profile image of the atsign. We then return the profileData to our contactsModel. +We use the `getCurrentAtSign()` method to get the current atsign, then use the `getContactDetails()` to get the name and profile image of the atsign. We then return the `profileData` to our `contactsModel`. @@ -296,9 +296,11 @@ class _AtsignAvatarState extends State { } ``` +We accessed the `profileData` generated by calling `ContactDetailsCommand().run()` through `context.watch().profileData` + #### Cleaning up our SendDudeScreen -Let's fix our "The method 'AtsignAvatar' isn't defined" error by simply importing out `AtsignAvatar` widget: +Let's fix our "The method 'AtsignAvatar' isn't defined" error by simply importing `AtsignAvatar` widget: ``` open lib/views/screens/send_dude_screen.dart @@ -339,6 +341,32 @@ class OnboardCommand extends BaseCommand { } ``` +Instead of navigating to the `HomeScreen` we now navigate to `SendDudeScreen` but we also need to add this screen as route. +``` +open lib/main.dart +``` + +```dart +import 'package:at_dude/views/screens/send_dude_screen.dart'; + +... + +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: ..., + child: MaterialApp( + ... + routes: { + SendDudeScreen.routeName: ((context) => SendDudeScreen()), // new + }, + home: Scaffold(...), + ), + ); + } +} +``` #### Conclusion -Well done, you've made it this far. In the next step we will start building our Send Dude Screen. +We're all done. In the next step we will start working on the bottom navigation bar. diff --git a/content_dev/docs/tutorials/at-dude/6-send_dude-screen-app-bar/send_dude_screen_app_bar.png b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-app-bar/send_dude_screen_app_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..c7af21707a1ddbb340ecd9d818ae1dc3175d376c GIT binary patch literal 10208 zcmeG?cUV)&w@Jheh>NJJQUoGcus{e=Y8t|V1qe0-rHB|J(h~#{Le<4}6;Y8M5?t4S zD^f%Rq$&b|08v3gM-3rHnh;tDkatjGm2dxHg0IdQbFz^F` z)j-SK00?vhto}EA98lmJ4g>_c<_(hj6Xy)jE?+i|3nt-?e)KqEsuO7`@^np;!w-X%pAPK2)@o%-hKpsGN2m9@Bq>f^@p7Q82U%k ze~~)7xY6{OJ3#d z_T)};qaVu8ttyONefH$9zt(^Ei*~Hqi0&T85o;?4?O0bG?U~NloX(>^gvYDdJuUB| zN#X~v40M@vtdiF!c1t9F=qI;UAjPc|uc1({AqJsjw7YnCq!AYliqX?8h)~Q+Jf)3` zuGt668j+Wi_jLw?iwVZ@n{;qETo+Vcx$`Lma*5-ww%0m3I{N84Wmo}WwOo60-+srN zATXGq1;d@=tdY;rEBDS-0Ye~77=TyuCS`}SA#F|wh@9NLh-E>0->5kdnWF={1G><) zzpU?sK+Iosct~et7x?{^;hguK&;5E_v|^HZp1rAv;$vET)CQP=ooPosBC#QeK3Ho0 zCa8!;_j^sA>SCifZO`S}Z)uo&6;#B4?;HHT8`ziirAx9iUJ1x#V1#nrdeNLyWXp)jS zL8+2YGCoyVXp0R*y1J>$E61)eSl6EXs?|?S5&NcRKRB;<=scIx?JGSVB4Ztu8UG{NPczibn4& z(OV9P<9pH3-9Kcc$tyek6dir$r>B}guphzU{C;V%m6Ojsh{HWO=)4XhXS4&BWmM<2 z6C&rh4VLA2KNkvyWNc7&$T*MO2!=dXR(5#o&~XJ6W2YdWV^^4V9Tam)UOwlP?yyC4 z4NFe0oz>ZF6D8D_8?SYY6E7 z6ZK9k!?2wg`2TDG@x0b7q(~=GQxCtd;m3Ih!Ufp%F z=aEk&4fA?Dr5Q8Li|Ax&c%@p5uWYoZ1aBTXM#r*4#{1F6{hyb*=G~e3abFgAJJe+d zx4d{m>F;Eq!&#N#vw7QpYc=8D6ag8Pt{*vZgGod`jWiD#Nez7?IDaXl&p9O}i6@)q zRS&)0Qr=nDu}7T;yX{9m*A%i;tF>e>{`hpor%l2|!UJhGGAHi)xq)X}KTZ`f7k%eP zh*bN3(nSXP&qH1gCx6ZBe{%I7yP+pqJke!#a$%d^PcXKv?Zm zOF$%+MLuNm_@VH#T3*O#8WDpDXGq6ERGQI;}PTGLRLV;@Y!q|?EG8l$lhURb#Srf zLp?(~rO$G2QmPjOBBjvMT3P||0g+fb>^g3S7MgO?7_tQ+GyF_u<_=4x!G-O*hWnuJ zQ}v-#1{~UO9?fA7x7nNf&A-o&JOB@vIDN@^i5s;f7R9WieW^U?$`Lhk`e0Q-A59K<4^K!ZkUF;L&!XQ>|Fmkr15;Z1~9q_gbD#Sa^1GzSC z3A0H22i~^zDm}U{rx>#p#QBfwqD%s$>iu8i+J@a_^CG~;frzlxO7 za7Mn6J#j){Y%ddC@+V1w~(+|Fosr1bXNdtWB_Xw3L4E1FLviP>j-t)_6CDqp{m5#3nS{L4tK4ecdO0F)>s8aw$;$J6t|`-bhAbFX)?_PTsrBkuCY$dP5@rBd%r?(INT;~Nc+W_i_1)23PVG3m%e6!+n$ zx$YVh^5Gf+yERz@4wVXk)-%jd`nD|dS&BJXVN?E+6e zx`)iML4P?W7B9yH85ggt5=G_O-wAAt$-p%kgxsd52_&BuV~%kaJi7}w4qXm;J?)}T zV63HVZA++L+OaSBnr%rZvAxJrm>=WGo+rHM-G|q!4dYU@RJS~K`E_V`i)qhI=OF}8 zJfzm6dbVpR@1h63nrb>-HHoq#4P|gDrG4X(C;Zo4h}tUcqU>Cft%qESGCb=`3yIx& z4!bDA3pwd@gur%>x+&8^;j!-=jYLp^%HG;&p`)nrC5bhv(5X(-dQX-!jlsL76&a>E z6z-Z*HT6(<1;b$z&SN&M3$EG4HXKB7S{L(EDn|%ye*WYPks7bG-sNn?YjrN!@tMis zyJjJSi_d|<+ND&21UbV111+<+LW!wmVrJ?{DU!Cknf7=XCj*QP7 z16Bly3k8^OOjQ03^KSG0iCdCL`wl`B_&_~TL!5Z@8LeCVIRm<&+Jjt*_)qT46hUca?j$GWC1(X0KE$+1@lEFlw0cVS~ zc*`pnC3U4L+;cJpMs}o2?Yl}yS5O0-qc}mFCO^GA$$(F}MLvgx{!@ zPrRwlZSZW(B0aws&5oZ~TP8~icPIm!#ro>+TTn|m%eP+AMT-vO(`$D}1vOKL-oa** zpSBJ8!Y%tPSx1?Ln!Ku+;_P0V7ee&7DKwbHX<4vM$#-4LyoMg}5T_gz7vDe()`ZQs zF);%-@mZq*nGI8#Jh;JgyniH-U6nR^2if7YZf>I=;YVOu)Q-;Q=wdPBbZ8*qZuCIH_xqvdvhJY<=&Iy0I}b zs@0xEHpO;5dIxV4lpi+rIg(F9qQHrGwqP6hHnQBMRd82t_->Hs4(FB2rT7!l_WFmX zF|J5=3`HC;{0?rBf&8sd)47ChL7=r()Q9>_Y8u)lC%L5=52r@CC5k3Bt+|5fX{s3F ziRCzx{LL>gkAMVIW$esBV>|=)vNk>FzHjuWY7$+l>3p@7v z=y?Kl3S6IDW)#v}r^#u{4@D3FmwNs8Tgba2PpHsW7gw1FOw!X z{2AV0jnGM&(G~`glm7uCRlL6#@9uL<&`%CD{oHzKfEqlDB@zmn-V;H)^uVPdk8kNJuU`3n7Pd3nud{Vq^n36d%d7; zV7fKaT)6IbUwJ6L2RLK$$tQft1r2^*;A&%%896U%Q`ki}$fQj+k{wJlE_JH-JhN39 z1(3}?{j9@!kbt3@Eqtxbru{7c=sRet=P6y{=?`I7wa z^{3uav5;5ZScAQ4qYuJ=eA5jaLI?tm9t0uD0Hh5-+86zRLkOicGH_h=J4#dC)cxgi PYlnZdx5_(sF803whHrXI literal 0 HcmV?d00001 diff --git a/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png b/content_dev/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png deleted file mode 100644 index 7aefe6af5ed04332ed21e46c1be04a411131d113..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26301 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 KYE^64NBj@6pDlg> diff --git a/hugo_stats.json b/hugo_stats.json index dcbc02d08..30515d9d9 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -419,6 +419,7 @@ "registration-cli", "related-resources", "requirements", + "reset-command", "root-secondary", "root-server", "secondary-server", From aaaff2af74623c3cb858cc588053b0f665752cf5 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Fri, 2 Sep 2022 13:17:33 -0400 Subject: [PATCH 05/12] step 4 of the dude app tutorial 90% completed --- .../tutorials/at-dude/4-onboarding/index.md | 400 +++++++++++++++++- hugo_stats.json | 9 + 2 files changed, 389 insertions(+), 20 deletions(-) diff --git a/content/docs/tutorials/at-dude/4-onboarding/index.md b/content/docs/tutorials/at-dude/4-onboarding/index.md index 4ef55f821..f94b7cae4 100644 --- a/content/docs/tutorials/at-dude/4-onboarding/index.md +++ b/content/docs/tutorials/at-dude/4-onboarding/index.md @@ -4,11 +4,11 @@ layout: codelab title: "Onboarding" # Step Name description: How to onboard or authenticate on any app built on the atPlatform # SEO Description for this step Documentation -draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 4 # Ordering of the steps --- -In this tutorial, we will build the onboarding screen for the dude app. +In this tutorial, we will build the onboarding screen for the dude app and modify the default onboarding function to make it compatible with our app architecture. @@ -39,14 +39,14 @@ MaterialApp( The next step is to wrap our `ElevatedButton` widget with a Column widget and center its mainAxisAlignment. -``` +```dart MaterialApp( // * The onboarding screen (first screen) home: ... body: Builder( builder: (context) => Center( - child: Column( # new - mainAxisAlignment: MainAxisAlignment.center, # new + child: Column( // new + mainAxisAlignment: MainAxisAlignment.center, // new children: [ ElevatedButton( onPressed: ... @@ -67,15 +67,16 @@ In your editor create a new folder called assets and inside the assets folder cr ``` mkdir -p assets/images/ +open pubspec.yaml ``` -Open `pubspec.yaml` and add the location of the image folder as shown below. +Add the location of the image folder as shown below. ``` flutter: uses-material-design: true assets: - .env - - assets/images/ # new + - assets/images/ // new ``` Let's create the `IconButton` as shown below. @@ -84,11 +85,12 @@ Let's create the `IconButton` as shown below. Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - IconButton( # new - iconSize: 200, # new - onPressed: () {}, # new - icon: Image.asset('assets/images/dude_logo.png'), # new - ), # new + // IconButton New + IconButton( + iconSize: 200, + onPressed: () {}, + icon: Image.asset('assets/images/dude_logo.png'), + ), ElevatedButton( onPressed: () async {...}, child: const Text('Onboard an @sign'), @@ -97,7 +99,7 @@ Column( ), ``` -#### Onboarding +#### Refactoring the Onboard Function Before we get started with this section, lets define a few terms: @@ -105,18 +107,16 @@ Before we get started with this section, lets define a few terms: [atsign](https://atsign.com/what-is-an-atsign/) - An atsign is your digital identity. It ensures that your data is owned and controlled by you. You can pair your device with any app to access but not store your data. -To make the onboarding code reusable, we will remove it form the "onboard an @sign" button and move it into an authentication class. - -Copy the code inside the `onPressed` anonymous function of the ElevatedButton +To make the onboarding code compatible, we will remove it form the "onboard an @sign" button and move it into an authentication class. In your terminal type: ``` -mkdir lib/services touch lib/services/authentication_service.dart +open lib/services/authentication_service.dart ``` -Open `authentication_service.dart` file and add the following: +Add the following: ``` class AuthenticationService { @@ -132,8 +132,368 @@ class AuthenticationService { The above code creates a [singleton](https://flutterbyexample.com/lesson/singletons) of our class. -Now we'll create a method called onboard in our `AuthenticationService` class whose content will be the code we copied a few steps above. + +Now we'll create a method called `onboard` in our `AuthenticationService` class and move the `onboardingResult` variable found inside the `onPressed` anonymous function of the ElevatedButton to the `onboard` method. Replace `AtOnboardingResult onboardingResult =` with `return` as shown below. + +```dart +class AuthenticationService { + ... + Future onboard() async { + return await AtOnboarding.onboard( + context: context, + config: AtOnboardingConfig( + atClientPreference: await futurePreference, + rootEnvironment: AtEnv.rootEnvironment, + domain: AtEnv.rootDomain, + appAPIKey: AtEnv.appApiKey, + ), + ); + } +} +``` + +#### Fixing Undefined name errors + +To fix the `Undefined name` errors we need to provide the `AtOnboarding.onboard()`method with a `BuildContext` and the `AtOnboardingConfig()` class as shown below. + + +```dart +import 'package:at_app_flutter/at_app_flutter.dart'; +import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; +import 'package:path_provider/path_provider.dart' + show getApplicationSupportDirectory; + +class AuthenticationService { + ... + + Future onboard() async { + var dir = await getApplicationSupportDirectory(); + + var atClientPreference = AtClientPreference() + ..rootDomain = AtEnv.rootDomain + ..namespace = AtEnv.appNamespace + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + + return AtOnboarding.onboard( + context: context, + config: AtOnboardingConfig( + atClientPreference: atClientPreference, + rootEnvironment: AtEnv.rootEnvironment, + domain: AtEnv.rootDomain, + appAPIKey: AtEnv.appApiKey, + ), + ); + } +} +``` + +we Now need access to the BuildContext, We'll create a separate class for this since we'll be reusing our BuildContext outside of the stateful and stateless widget. + +In your terminal type: + +``` +touch lib/services/navigation_service.dart +``` +Add the below code snippet in that file. +```dart +import 'package:flutter/material.dart'; + +class NavigationService { + static GlobalKey navKey = GlobalKey(); + + static GlobalKey nestedNavKey = GlobalKey(); +} +``` +``` +touch lib/services/services.dart +open lib/services/services.dart +``` + +Add the below code so can use one import statement to import all export files: +```dart +export 'authentication_service.dart'; +export 'navigation_service.dart'; +``` + +In `main.dart` add the navigatorKey to the materialApp as shown below; + +```dart +import 'package:at_dude/services/services.dart'; +Widget build(BuildContext context) { + return MaterialApp( + // * The onboarding screen (first screen) + navigatorKey: NavigationService.navKey, + home: ... + ); + } +``` + +In `navigation_service.dart` add the below code to the `onboard()` method. + +```dart +Future onboard() async { + ... + return await AtOnboarding.onboard( + context: NavigationService.navKey.currentContext!, // new + config: ... + ); + } +``` +#### Base Command + +Remember our commands contain our application logic, before we can create our onboard command we'll first create a base command that all other commands will extend from. In your terminal type: + +``` +touch lib/commands/base_command.dart +``` + +Create the `BaseCommand` class with the needed imports as shown below: + +``` +open lib/commands/base_command.dart +``` +```dart +import 'package:provider/provider.dart'; + +import '../services/services.dart'; + +abstract class BaseCommand { + // Services + AuthenticationService authenticationService = + NavigationService.navKey.currentContext!.read(); +} +``` + +We now have to provide the services using the provider package as shown below: + +``` +flutter pub add provider +open lib/main.dart +``` +```dart +import 'package:provider/provider.dart'; +... +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return MultiProvider( //new + providers: [Provider(create: (c) => AuthenticationService.getInstance())],// new + child: MaterialApp(), + ) + } +} +``` + +#### Onboard Command + +Now that we're all set, lets create our Onboard Command. This class method will contain the instructions required to onboard on the atPlatform. In your terminal type: + +``` +touch lib/commands/onboard_command.dart +open lib/commands/onboard_command.dart +``` + +Add the below code: + +```dart +import 'package:at_dude/commands/base_command.dart'; +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + + } +} +``` +Our Commands will only have one method called `run()`. This command return a `Future`. + +Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: + +```dart +import 'package:at_dude/commands/base_command.dart'; +import 'package:at_dude/services/navigation_service.dart'; // new +import 'package:at_onboarding_flutter/at_onboarding_result.dart'; // new +import 'package:flutter/material.dart'; // new + +import '../home_screen.dart'; // new + +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + + // Everything Below New + var context = NavigationService.navKey.currentContext!; + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + Navigator.push( + context, MaterialPageRoute(builder: (_) => const HomeScreen())); + break; + case AtOnboardingResultStatus.error: + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + backgroundColor: Colors.red, + content: Text('An error has occurred'), + ), + ); + break; + case AtOnboardingResultStatus.cancel: + break; + } + } +} +``` + +If `authenticationService.onboard()` return `AtOnboardingResultStatus.success` we navigate to the HomeScreen, if it returns `AtOnboardingResultStatus.error` we display a `Snackbar` on the screen. + +We will be using the `Snackbar` widget often so lets extract it into its own method. + +#### Creating Snackbar Method + +In your terminal type + +``` +touch lib/views/widgets/snackbars.dart +open lib/views/widgets/snackbars.dart +``` + +Add the below code + +```dart +import 'package:at_dude/services/navigation_service.dart'; +import 'package:flutter/material.dart'; + +class Snackbars extends StatelessWidget { + const Snackbars({Key? key}) : super(key: key); + + static void errorSnackBar({ + required String content, + }) { + ScaffoldMessenger.of(NavigationService.navKey.currentContext!) + .showSnackBar(SnackBar( + content: Text( + content, + textAlign: TextAlign.center, + ), + backgroundColor: + Theme.of(NavigationService.navKey.currentContext!).errorColor, + )); + } + + @override + Widget build(BuildContext context) { + return Container(); + } +} +``` + +We created a class that extends `StatelessWidget`. This class contains a static void method named `errorSnackBar` that accepts an `errorMessage`. + +This method will save the broiler plate of calling `ScaffoldMessenger.of(context).showSnackBar()` every time we want so show a snackbar. + +Lets replace our snackbar part of `OnboardCommand.run()` method as shown below: + +```dart +... + +import 'package:at_dude/views/widgets/snackbars.dart'; // new + + + +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + var context = NavigationService.navKey.currentContext!; + switch (onboardingResult.status) { + ... + case AtOnboardingResultStatus.error: + Snackbars.errorSnackBar(errorMessage: 'An error has occurred'); // new + break; + case AtOnboardingResultStatus.cancel: + break; + } + } +} +``` + +All done! + +#### Cleaning up main.dart + +Now we just have to clean up `main.dart`. +Remove the code below from main.dart: + + +```dart +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; + +import 'package:path_provider/path_provider.dart' + show getApplicationSupportDirectory; + +Future loadAtClientPreference() async { + var dir = await getApplicationSupportDirectory(); + + return AtClientPreference() + ..rootDomain = AtEnv.rootDomain + ..namespace = AtEnv.appNamespace + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + // TODO + // * By default, this configuration is suitable for most applications + // * In advanced cases you may need to modify [AtClientPreference] + // * Read more here: https://pub.dev/documentation/at_client/latest/at_client/AtClientPreference-class.html +} + +class _MyAppState extends State { + // * load the AtClientPreference in the background // remove + Future futurePreference = loadAtClientPreference(); // Remove + ... +} +``` + +Add the below code to `main.dart`: + +```dart +@override + Widget build(BuildContext context) { + return MultiProvider( + ... + child: MaterialApp( + + ... + home: Scaffold( + appBar: ..., + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ..., + ElevatedButton( + onPressed: () async { + await OnboardCommand().run(); // new + }, + child: const Text('Onboard an @sign'), + ), + ], + ), + ), + ), + ), + ), + ); + } +``` + +Run your flutter app and everything should work as before. + +``` +flutter run ``` +#### Conclusion +Building a production app is like cooking your favorite food, Before you cook the food you do food preparation. You can skip food prep but then cooking become much much harder. Similarly, Following an architecture pattern, creating our export files and abstract classes are like food prep it makes cooking a whole lot easier. -``` \ No newline at end of file +Now that we've completed the onboarding process, in the next step we'll complete the first screen by add a reset atsign button and it's functionalities. diff --git a/hugo_stats.json b/hugo_stats.json index df92b4a88..cc2931254 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -299,6 +299,7 @@ "b-assignment-of-domain-name-to-your-static-ip", "b-create-cloud-dns-zone", "b-setting-up-billing", + "base-command", "block-list", "building-layouts", "buttonlink", @@ -310,6 +311,7 @@ "cardgroup", "cardshowcase", "cardsocial", + "cleaning-up-maindart", "cloning-the-client", "commands", "compile-jar", @@ -318,6 +320,8 @@ "contact", "contact-us", "content", + "contributing-to-the-developer-site", + "creating-snackbar-method", "definition", "deleting-a-publickey-example", "deleting-a-selfkey-example", @@ -331,6 +335,7 @@ "example-4", "example-5", "featured-tutorials", + "fixing-undefined-name-errors", "free-atsigns", "frontmatter", "getting-a-publickey-example", @@ -367,6 +372,9 @@ "notifylist-verb", "notifyremove-verb", "offcanvasDoks", + "offcanvasDoksLabel", + "onboard-command", + "onboarding", "open-source-contributions", "other-services", "overview", @@ -378,6 +386,7 @@ "putting-a-publickey-example", "putting-a-selfkey-example", "putting-sharedkey-example", + "refactoring-the-onboard-function", "reference", "registration-cli", "related-resources", From 029cefb14790ff68a46e126c9ebc83aaa3c80846 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 6 Sep 2022 11:34:46 -0400 Subject: [PATCH 06/12] tutorial description updated --- .../tutorials/at-dude/1-introduction/index.md | 2 +- .../4-onboarding/first_onboard_screen.png | Bin 0 -> 22295 bytes .../tutorials/at-dude/4-onboarding/index.md | 10 +- .../5-reset-atsign/final_onboard_screen.png | Bin 0 -> 26301 bytes .../tutorials/at-dude/5-reset-atsign/index.md | 278 ++++++++++++++++++ .../final_onboard_screen.png | Bin 0 -> 26301 bytes .../at-dude/6-send_dude-screen-ui/index.md | 278 ++++++++++++++++++ content/docs/tutorials/at-dude/_index.md | 2 +- hugo_stats.json | 6 +- 9 files changed, 568 insertions(+), 8 deletions(-) create mode 100644 content/docs/tutorials/at-dude/4-onboarding/first_onboard_screen.png create mode 100644 content/docs/tutorials/at-dude/5-reset-atsign/final_onboard_screen.png create mode 100644 content/docs/tutorials/at-dude/5-reset-atsign/index.md create mode 100644 content/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png create mode 100644 content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md diff --git a/content/docs/tutorials/at-dude/1-introduction/index.md b/content/docs/tutorials/at-dude/1-introduction/index.md index 33bd6e5b2..a799fb676 100644 --- a/content/docs/tutorials/at-dude/1-introduction/index.md +++ b/content/docs/tutorials/at-dude/1-introduction/index.md @@ -2,7 +2,7 @@ layout: codelab title: "Introduction" # Step Name -description: Introduction to the atDude tutorial# SEO Description for this step Documentation +description: Introduction to the atDude tutorial # SEO Description for this step Documentation draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 1 # Ordering of the steps diff --git a/content/docs/tutorials/at-dude/4-onboarding/first_onboard_screen.png b/content/docs/tutorials/at-dude/4-onboarding/first_onboard_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..b3864c4a8cb5855696defd6a545c66c7636aed76 GIT binary patch literal 22295 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 +{{< image type="page" src="first_onboard_screen.png" >}} {{
}} {{
}} @@ -29,9 +29,10 @@ The first thing we will do is change the App bar title to "atDude" in `main.dar ``` MaterialApp( // * The onboarding screen (first screen) + debugShowCheckedModeBanner: false, // New home: Scaffold( appBar: AppBar( - title: const Text('atDude'), # Changed + title: const Text('atDude'), // Changed ), ), ); @@ -461,7 +462,6 @@ Add the below code to `main.dart`: return MultiProvider( ... child: MaterialApp( - ... home: Scaffold( appBar: ..., @@ -496,4 +496,4 @@ flutter run Building a production app is like cooking your favorite food, Before you cook the food you do food preparation. You can skip food prep but then cooking become much much harder. Similarly, Following an architecture pattern, creating our export files and abstract classes are like food prep it makes cooking a whole lot easier. -Now that we've completed the onboarding process, in the next step we'll complete the first screen by add a reset atsign button and it's functionalities. +Now that we've completed the onboarding process, in the next step we'll complete the first screen by adding a reset atsign button and it's functionalities. diff --git a/content/docs/tutorials/at-dude/5-reset-atsign/final_onboard_screen.png b/content/docs/tutorials/at-dude/5-reset-atsign/final_onboard_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..7aefe6af5ed04332ed21e46c1be04a411131d113 GIT binary patch literal 26301 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 KYE^64NBj@6pDlg> literal 0 HcmV?d00001 diff --git a/content/docs/tutorials/at-dude/5-reset-atsign/index.md b/content/docs/tutorials/at-dude/5-reset-atsign/index.md new file mode 100644 index 000000000..bb4d04825 --- /dev/null +++ b/content/docs/tutorials/at-dude/5-reset-atsign/index.md @@ -0,0 +1,278 @@ +--- +layout: codelab + +title: "Reset atsign" # Step Name +description: How to reset any app built on the atPlatform # SEO Description for this step Documentation + +draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +order: 5 # Ordering of the steps +--- + +In this tutorial, we will complete the onboarding screen for the dude app and implement the reset app functionality. + + + + +At the end of this step our app will look like this, + +{{< image type="page" src="final_onboard_screen.png" >}} + +{{
}} +{{
}} + + +#### Creating the Texts util class +The first thing we will do is create a utility class that will store our texts. This will make it easy to update our texts across out app without having to search and replace all occurrence of the string. + + Follow the steps below: + +``` +mkdir lib/utils +touch lib/utils/texts.dart +open lib/utils/texts.dart +``` + +```dart +class Texts { + static const String atDude = 'atDude'; + static const String onboardAtsign = 'Onboard an atsign'; + static const String resetApp = 'Reset App'; +} +``` + +Replace the string 'Reset App' with it's equivalent `static const` for the `ResetAppButton` widget as shown below + +```dart +... +@override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () {}, + child: const Text(Texts.resetApp), // Changed + ); + } +``` + +Let us make similar changes in `main.dart` as shown below: +```dart +... +MaterialApp( + ... + home: Scaffold( + appBar: AppBar( + title: const Text(Texts.atDude), // Changed + ), + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ... + ElevatedButton( + ... + child: const Text(Texts.onboardAtsign), + ), + ], + ), + ), + ), + ), + ), +``` + +#### Adding Reset Functionality to Authentication Service + +In your terminal type: + +``` +open lib/services/authentication_service.dart +``` + +Now we'll create a method called `reset` and `getAtOnboardingConfig` in our `AuthenticationService` class and refactor the `onboard` method. + +```dart +class AuthenticationService { + ... + + AtOnboardingConfig getAtOnboardingConfig({ + required AtClientPreference atClientPreference, + }) => + AtOnboardingConfig( + atClientPreference: atClientPreference, + rootEnvironment: AtEnv.rootEnvironment, + domain: AtEnv.rootDomain, + appAPIKey: AtEnv.appApiKey, + ); + + Future onboard() async { + return await AtOnboarding.onboard( + context: context, + config: getAtOnboardingConfig(atClientPreference: atClientPreference), + ); + } +} +``` + +Here we simply moved `AtOnboardingConfig` into it's own method so we can reuse it on our reset method we're going to create below: + +```dart +import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; +... +Future reset() async { + var dir = await getApplicationSupportDirectory(); + + var atClientPreference = AtClientPreference() + ..rootDomain = AtEnv.rootDomain + ..namespace = AtEnv.appNamespace + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + + return AtOnboarding.reset( + context: NavigationService.navKey.currentContext!, + config: getAtOnboardingConfig(atClientPreference: atClientPreference), + ); + } +``` + +`AtOnboarding.reset` allows the user to remove any atsign that onboard on the app before. This allows the user to onboarding with another atsign. + +#### Onboard Command + +Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to reset the currently signed in atsign on the atPlatform. In your terminal type: + +``` +touch lib/commands/reset_command.dart +open lib/commands/reset_command.dart +``` + +Add the below code: + +```dart +import 'package:at_dude/commands/base_command.dart'; +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + + } +} +``` +This command return a `Future`. + +Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: + +```dart +import 'package:at_dude/commands/base_command.dart'; +import 'package:at_dude/commands/onboard_command.dart'; + +import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; //new +import 'package:flutter/material.dart'; // new + + + +class ResetCommand extends BaseCommand { + Future run() async { + var resetResult = await authenticationService.reset(); + + + // Everything Below New + + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + OnboardCommand().run(); + break; + case AtOnboardingResultStatus.cancelled: + break; + } + } +} +``` + +If `authenticationService.onboard()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. + + +#### Completing the first screen + +Now we just have to update the UI in `main.dart` to all the user to reset their atsign. + +Add the below code to `main.dart`: + +```dart +@override + Widget build(BuildContext context) { + return MultiProvider( + ... + child: MaterialApp( + ... + home: Scaffold( + appBar: ..., + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ..., + ElevatedButton(...), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Expanded( + child: Divider( + color: Colors.black, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + 'Or', + textAlign: TextAlign.center, + ), + ), + Expanded( + child: Divider( + color: Colors.black, + ), + ), + ]), + ), + ], + ), + ), + ), + ), + ), + ); + } +``` + +We add a divider to create separation between the two buttons. + +lets add the reset atsign button as shown below: + +```dart +import 'package:at_dude/commands/reset_command.dart'; +... +Padding(...) +ElevatedButton( + onPressed: () async { + await ResetCommand().run(); + }, + child: Text(Texts.resetApp), +) +``` + +Run your flutter app and everything should work perfectly. + +Go ahead and reset the app + +``` +flutter run +``` +#### Conclusion + +Well done, you've made it this far. In the next step we will start building our Send Dude Screen. diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..7aefe6af5ed04332ed21e46c1be04a411131d113 GIT binary patch literal 26301 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 KYE^64NBj@6pDlg> literal 0 HcmV?d00001 diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md new file mode 100644 index 000000000..c7560f1a5 --- /dev/null +++ b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md @@ -0,0 +1,278 @@ +--- +layout: codelab + +title: "Reset atsign" # Step Name +description: Creating the UI of the send dude screen # SEO Description for this step Documentation + +draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +order: 5 # Ordering of the steps +--- + +In this tutorial, we will complete the onboarding screen for the dude app and implement the reset app functionality. + + + + +At the end of this step our app will look like this, + +{{< image type="page" src="final_onboard_screen.png" >}} + +{{
}} +{{
}} + + +#### Creating the Texts util class +The first thing we will do is create a utility class that will store our texts. This will make it easy to update our texts across out app without having to search and replace all occurrence of the string. + + Follow the steps below: + +``` +mkdir lib/utils +touch lib/utils/texts.dart +open lib/utils/texts.dart +``` + +```dart +class Texts { + static const String atDude = 'atDude'; + static const String onboardAtsign = 'Onboard an atsign'; + static const String resetApp = 'Reset App'; +} +``` + +Replace the string 'Reset App' with it's equivalent `static const` for the `ResetAppButton` widget as shown below + +```dart +... +@override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () {}, + child: const Text(Texts.resetApp), // Changed + ); + } +``` + +Let us make similar changes in `main.dart` as shown below: +```dart +... +MaterialApp( + ... + home: Scaffold( + appBar: AppBar( + title: const Text(Texts.atDude), // Changed + ), + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ... + ElevatedButton( + ... + child: const Text(Texts.onboardAtsign), + ), + ], + ), + ), + ), + ), + ), +``` + +#### Adding Reset Functionality to Authentication Service + +In your terminal type: + +``` +open lib/services/authentication_service.dart +``` + +Now we'll create a method called `reset` and `getAtOnboardingConfig` in our `AuthenticationService` class and refactor the `onboard` method. + +```dart +class AuthenticationService { + ... + + AtOnboardingConfig getAtOnboardingConfig({ + required AtClientPreference atClientPreference, + }) => + AtOnboardingConfig( + atClientPreference: atClientPreference, + rootEnvironment: AtEnv.rootEnvironment, + domain: AtEnv.rootDomain, + appAPIKey: AtEnv.appApiKey, + ); + + Future onboard() async { + return await AtOnboarding.onboard( + context: context, + config: getAtOnboardingConfig(atClientPreference: atClientPreference), + ); + } +} +``` + +Here we simply moved `AtOnboardingConfig` into it's own method so we can reuse it on our reset method we're going to create below: + +```dart +import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; +... +Future reset() async { + var dir = await getApplicationSupportDirectory(); + + var atClientPreference = AtClientPreference() + ..rootDomain = AtEnv.rootDomain + ..namespace = AtEnv.appNamespace + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + + return AtOnboarding.reset( + context: NavigationService.navKey.currentContext!, + config: getAtOnboardingConfig(atClientPreference: atClientPreference), + ); + } +``` + +`AtOnboarding.reset` allows the user to remove any atsign that onboard on the app before. This allows the user to onboarding with another atsign. + +#### Onboard Command + +Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to reset the currently signed in atsign on the atPlatform. In your terminal type: + +``` +touch lib/commands/reset_command.dart +open lib/commands/reset_command.dart +``` + +Add the below code: + +```dart +import 'package:at_dude/commands/base_command.dart'; +class OnboardCommand extends BaseCommand { + Future run() async { + var onboardingResult = await authenticationService.onboard(); + + } +} +``` +This command return a `Future`. + +Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: + +```dart +import 'package:at_dude/commands/base_command.dart'; +import 'package:at_dude/commands/onboard_command.dart'; + +import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; //new +import 'package:flutter/material.dart'; // new + + + +class ResetCommand extends BaseCommand { + Future run() async { + var resetResult = await authenticationService.reset(); + + + // Everything Below New + + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + OnboardCommand().run(); + break; + case AtOnboardingResultStatus.cancelled: + break; + } + } +} +``` + +If `authenticationService.onboard()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. + + +#### Completing the first screen + +Now we just have to update the UI in `main.dart` to all the user to reset their atsign. + +Add the below code to `main.dart`: + +```dart +@override + Widget build(BuildContext context) { + return MultiProvider( + ... + child: MaterialApp( + ... + home: Scaffold( + appBar: ..., + body: Builder( + builder: (context) => Center( + child: Column( + ... + children: [ + ..., + ElevatedButton(...), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Expanded( + child: Divider( + color: Colors.black, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + 'Or', + textAlign: TextAlign.center, + ), + ), + Expanded( + child: Divider( + color: Colors.black, + ), + ), + ]), + ), + ], + ), + ), + ), + ), + ), + ); + } +``` + +We add a divider to create separation between the two buttons. + +lets add the reset atsign button as shown below: + +```dart +import 'package:at_dude/commands/reset_command.dart'; +... +Padding(...) +ElevatedButton( + onPressed: () async { + await ResetCommand().run(); + }, + child: Text(Texts.resetApp), +) +``` + +Run your flutter app and everything should work perfectly. + +Go ahead and reset the app + +``` +flutter run +``` +#### Conclusion + +Well done, you've made it this far. In the next step we will start building our Send Dude Screen. diff --git a/content/docs/tutorials/at-dude/_index.md b/content/docs/tutorials/at-dude/_index.md index e5c59b531..381d26277 100644 --- a/content/docs/tutorials/at-dude/_index.md +++ b/content/docs/tutorials/at-dude/_index.md @@ -2,7 +2,7 @@ layout: codelab-list # The layout for a codelab list (think of this as a title page for the code lab) title: "atDude Tutorial" # Title of the codelab -lead: Learn how to build on the atPlatform # Description of the codelab +lead: Learn how to build production app on the atPlatform # Description of the codelab description: Learn how to build on the atPlatform doneLink: /tutorials # Where to send them if they press "Done" at the end of the Codelab diff --git a/hugo_stats.json b/hugo_stats.json index cc2931254..589b5d98c 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -289,6 +289,8 @@ "a-assignment-of-static-ip", "a-register-domain-name-with-aws", "a-register-domain-name-with-gcp", + "adding-iconbutton", + "adding-reset-functionality-to-authentication-service", "anchor-tag-a", "arrow-back", "assets", @@ -315,6 +317,7 @@ "cloning-the-client", "commands", "compile-jar", + "completing-the-first-screen", "conclusion", "configuration-parameters", "contact", @@ -322,6 +325,8 @@ "content", "contributing-to-the-developer-site", "creating-snackbar-method", + "creating-the-reset-app-widget", + "creating-the-texts-util-class", "definition", "deleting-a-publickey-example", "deleting-a-selfkey-example", @@ -374,7 +379,6 @@ "offcanvasDoks", "offcanvasDoksLabel", "onboard-command", - "onboarding", "open-source-contributions", "other-services", "overview", From 3d2af0df24f8546cdf16b33e6ed9d03b083a9da2 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Thu, 8 Sep 2022 15:44:36 -0400 Subject: [PATCH 07/12] step six added --- .../at-dude/6-send_dude-screen-ui/index.md | 416 ++++++++++-------- hugo_stats.json | 10 +- 2 files changed, 250 insertions(+), 176 deletions(-) diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md index c7560f1a5..e71e8f14b 100644 --- a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md +++ b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md @@ -1,14 +1,14 @@ --- layout: codelab -title: "Reset atsign" # Step Name +title: "Send Dude Screen AppBar" # Step Name description: Creating the UI of the send dude screen # SEO Description for this step Documentation -draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 5 # Ordering of the steps --- -In this tutorial, we will complete the onboarding screen for the dude app and implement the reset app functionality. +In this tutorial, we will build the AppBar of send dude screen. @@ -21,258 +21,324 @@ At the end of this step our app will look like this, {{
}} -#### Creating the Texts util class -The first thing we will do is create a utility class that will store our texts. This will make it easy to update our texts across out app without having to search and replace all occurrence of the string. +#### Creating the AppBar +The first thing we will do is create our send dude screen dart file with the AppBar and the widgets and properties it needs. Follow the steps below: ``` -mkdir lib/utils -touch lib/utils/texts.dart + +touch lib/views/screens/send_dude_screen.dart +open lib/views/screens/send_dude_screen.dart +``` + +```dart +import 'package:flutter/material.dart'; +import '../../utils/texts.dart'; + +class SendDudeScreen extends StatefulWidget { + SendDudeScreen({Key? key}) : super(key: key); + static String routeName = 'sendDudeScreen'; + + @override + State createState() => _SendDudeScreenState(); +} + +class _SendDudeScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + foregroundColor: Colors.transparent, + shadowColor: Colors.transparent, + title: const Text( + Texts.sendDude, + style: TextStyle(color: Colors.black), + ), + actions: const [AtsignAvatar()], + + ), + ); + } +} +``` + +We have our stateful widget with an `appBar` but we neither have a `Texts.sendDude` constant nor the `AtsignAvatar()`. Lets create them: + +``` open lib/utils/texts.dart ``` ```dart +... class Texts { - static const String atDude = 'atDude'; - static const String onboardAtsign = 'Onboard an atsign'; - static const String resetApp = 'Reset App'; + ... + static const String sendDude = 'Reset App'; } ``` -Replace the string 'Reset App' with it's equivalent `static const` for the `ResetAppButton` widget as shown below +#### AtsignAvatar + +Let us create `AtsignAvatar` as shown below: +``` +touch lib/views/widgets/atsign_avatar.dart +open lib/views/widgets/atsign_avatar.dart +``` ```dart -... -@override +import 'dart:typed_data' show Uint8List; +import 'package:flutter/material.dart'; + +class AtsignAvatar extends StatefulWidget { + const AtsignAvatar({Key? key}) : super(key: key); + + @override + State createState() => _AtsignAvatarState(); +} + +class _AtsignAvatarState extends State { + Uint8List? image; + String? profileName; + + @override Widget build(BuildContext context) { - return ElevatedButton( - onPressed: () {}, - child: const Text(Texts.resetApp), // Changed + return GestureDetector( + child: CircleAvatar( + backgroundColor: Colors.transparent, + child: image == null + ? const Icon( + Icons.person_outline, + color: Colors.black, + ) + : ClipOval(child: Image.memory(image!)), + ), + onTap: () {}, ); } +} ``` -Let us make similar changes in `main.dart` as shown below: -```dart -... -MaterialApp( - ... - home: Scaffold( - appBar: AppBar( - title: const Text(Texts.atDude), // Changed - ), - body: Builder( - builder: (context) => Center( - child: Column( - ... - children: [ - ... - ElevatedButton( - ... - child: const Text(Texts.onboardAtsign), - ), - ], - ), - ), - ), - ), - ), +Basically we have a `CircleAvatar` whose child is an image or an person_outline. We need to add the functionality that will check for the atsign contact details. + +##### Profile Data +``` +mkdir lib/data +touch lib/data/profile_data.dart +open lib/data/profile_data.dart ``` -#### Adding Reset Functionality to Authentication Service +```dart +import 'dart:typed_data' show Uint8List; -In your terminal type: +class ProfileData { + ProfileData({required this.name, required this.profileImage}); + final String name; + final Uint8List? profileImage; +} ``` -open lib/services/authentication_service.dart -``` +This class will contain the name and profile image data we'll get from ContactService class provided to us form free from the at_xxx package. -Now we'll create a method called `reset` and `getAtOnboardingConfig` in our `AuthenticationService` class and refactor the `onboard` method. +##### Contacts Model +We'll now create our contacts model that will store all our contacts information needed in our app. + +``` +touch lib/models/contacts_model.dart +open lib/models/contacts_model.dart +``` ```dart -class AuthenticationService { - ... +import 'package:flutter/material.dart'; - AtOnboardingConfig getAtOnboardingConfig({ - required AtClientPreference atClientPreference, - }) => - AtOnboardingConfig( - atClientPreference: atClientPreference, - rootEnvironment: AtEnv.rootEnvironment, - domain: AtEnv.rootDomain, - appAPIKey: AtEnv.appApiKey, - ); - - Future onboard() async { - return await AtOnboarding.onboard( - context: context, - config: getAtOnboardingConfig(atClientPreference: atClientPreference), - ); +import '../data/profile_data.dart'; + +class ContactsModel extends ChangeNotifier { + late ProfileData _profileData; + + ProfileData get profileData => _profileData; + + set profileData(ProfileData profileData) { + _profileData = profileData; + notifyListeners(); } } ``` +Our profile data extends `ChangeNotifier`, this will allow us to `notifyListeners()` of changes made to `profileData`. + +We now have to add our `ContactModel` as a `ChangeNotifierProvider` and then add it to `BaseCommand`. -Here we simply moved `AtOnboardingConfig` into it's own method so we can reuse it on our reset method we're going to create below: +``` +open lib/main.dart +``` ```dart -import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; ... -Future reset() async { - var dir = await getApplicationSupportDirectory(); - - var atClientPreference = AtClientPreference() - ..rootDomain = AtEnv.rootDomain - ..namespace = AtEnv.appNamespace - ..hiveStoragePath = dir.path - ..commitLogPath = dir.path - ..isLocalStoreRequired = true; - - return AtOnboarding.reset( - context: NavigationService.navKey.currentContext!, - config: getAtOnboardingConfig(atClientPreference: atClientPreference), +@override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + Provider(create: (c) => AuthenticationService.getInstance()), + ChangeNotifierProvider(create: (c) => ContactsModel()), // new + ], + child: MaterialApp(...), ); - } ``` -`AtOnboarding.reset` allows the user to remove any atsign that onboard on the app before. This allows the user to onboarding with another atsign. +``` +open lib/commands/base_command.dart +``` +```dart +... +import '../models/contacts_model.dart'; + +abstract class BaseCommand { + // Services + AuthenticationService authenticationService = + NavigationService.navKey.currentContext!.read(); + + // Models + ContactsModel contactsModel = NavigationService.navKey.currentContext!.read(); +``` + +#### Contact Details Command +We'll now create our Contact Details Command. We don't have to create our `ContactService` since this is provided to us from the at_contacts_flutter package. -#### Onboard Command -Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to reset the currently signed in atsign on the atPlatform. In your terminal type: +In your terminal type: ``` -touch lib/commands/reset_command.dart -open lib/commands/reset_command.dart +flutter pub add at_contacts_flutter +touch lib/commands/contact_details_command.dart +open lib/commands/contact_details_command.dart ``` -Add the below code: - ```dart +import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:at_contacts_flutter/services/contact_service.dart'; import 'package:at_dude/commands/base_command.dart'; -class OnboardCommand extends BaseCommand { +import 'package:at_dude/data/profile_data.dart'; + +class ContactDetailsCommand extends BaseCommand { Future run() async { - var onboardingResult = await authenticationService.onboard(); - + final contactService = ContactService(); + + ContactService() + .getContactDetails( + AtClientManager.getInstance().atClient.getCurrentAtSign(), null) + .then( + (value) { + contactsModel.profileData = + ProfileData(name: value['name'], profileImage: value['image']); + return null; + }, + ); } } ``` -This command return a `Future`. - -Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: - -```dart -import 'package:at_dude/commands/base_command.dart'; -import 'package:at_dude/commands/onboard_command.dart'; -import 'package:at_onboarding_flutter/screen/at_onboarding_reset_screen.dart'; //new -import 'package:flutter/material.dart'; // new +We use the `AtClientManager` method to get the current atsign, then use the ContactService to get the name and profile image of the atsign. We then return the profileData to our contactsModel. -class ResetCommand extends BaseCommand { - Future run() async { - var resetResult = await authenticationService.reset(); +#### Completing the AtsignAvatar widget +Now we just have what we need to complete the AtsignAvatar Widget. - // Everything Below New +``` +open lib/views/widgets/atsign_avatar.dart +``` - switch (onboardingResult.status) { - case AtOnboardingResultStatus.success: - OnboardCommand().run(); - break; - case AtOnboardingResultStatus.cancelled: - break; - } +```dart +class _AtsignAvatarState extends State { +... +@override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ContactDetailsCommand().run(); + }); + super.initState(); } } ``` -If `authenticationService.onboard()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. - +To run an async method inside `initState` we need to call the method inside `WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {});` -#### Completing the first screen +We can now delete the `image` and `profileName` variables since the data is now inside our `ContactsModel.profileData` property. Let's use the power of provider to access this property. -Now we just have to update the UI in `main.dart` to all the user to reset their atsign. +```dart +class _AtsignAvatarState extends State { + Uint8List? image; // Delete this + String? profileName; // Delete this -Add the below code to `main.dart`: + @override + void initState() { + ... + } -```dart -@override + @override Widget build(BuildContext context) { - return MultiProvider( - ... - child: MaterialApp( - ... - home: Scaffold( - appBar: ..., - body: Builder( - builder: (context) => Center( - child: Column( - ... - children: [ - ..., - ElevatedButton(...), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Expanded( - child: Divider( - color: Colors.black, - ), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 12.0), - child: Text( - 'Or', - textAlign: TextAlign.center, - ), - ), - Expanded( - child: Divider( - color: Colors.black, - ), - ), - ]), - ), - ], - ), - ), - ), - ), + return GestureDetector( + child: CircleAvatar( + backgroundColor: Colors.transparent, + child: context.watch().profileData.profileImage == null + ? const Icon( + Icons.person_outline, + color: Colors.black, + ) + : ClipOval( + child: Image.memory( + context.watch().profileData.profileImage!)), ), + onTap: () {}, ); } +} ``` -We add a divider to create separation between the two buttons. +#### Cleaning up our SendDudeScreen + +Let's fix our "The method 'AtsignAvatar' isn't defined" error by simply importing out `AtsignAvatar` widget: -lets add the reset atsign button as shown below: +``` +open lib/views/screens/send_dude_screen.dart +``` ```dart -import 'package:at_dude/commands/reset_command.dart'; -... -Padding(...) -ElevatedButton( - onPressed: () async { - await ResetCommand().run(); - }, - child: Text(Texts.resetApp), -) +import '../widgets/atsign_avatar.dart'; // new + +class SendDudeScreen extends StatefulWidget { + ... +} + +class _SendDudeScreenState extends State { + ... +} ``` -Run your flutter app and everything should work perfectly. +#### Navigating to the SendDudeScreen -Go ahead and reset the app +We can now navigate to our SendDudeScreen now that our send Dude Screen AppBar is completed. ``` -flutter run +open lib/commands/onboard_command.dart ``` + +```dart +... +class OnboardCommand extends BaseCommand { + Future run() async { + ... + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + Navigator.popAndPushNamed(context, SendDudeScreen.routeName); // new + break; + ... + } + } +} +``` + #### Conclusion Well done, you've made it this far. In the next step we will start building our Send Dude Screen. diff --git a/hugo_stats.json b/hugo_stats.json index 589b5d98c..450cca5d2 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -298,6 +298,7 @@ "assignment-of-static-ip", "atplatform", "atprotocol", + "atsignavatar", "b-assignment-of-domain-name-to-your-static-ip", "b-create-cloud-dns-zone", "b-setting-up-billing", @@ -314,18 +315,22 @@ "cardshowcase", "cardsocial", "cleaning-up-maindart", + "cleaning-up-our-senddudescreen", "cloning-the-client", "commands", "compile-jar", + "completing-the-atsignavatar-widget", "completing-the-first-screen", "conclusion", "configuration-parameters", "contact", + "contact-details-command", "contact-us", + "contacts-model", "content", "contributing-to-the-developer-site", "creating-snackbar-method", - "creating-the-reset-app-widget", + "creating-the-appbar", "creating-the-texts-util-class", "definition", "deleting-a-publickey-example", @@ -369,6 +374,7 @@ "monitor", "monitor-verb", "need-more-information-read-the-following", + "navigating-to-the-senddudescreen", "next-step", "notification", "notify-list", @@ -387,6 +393,8 @@ "polymorphic-data", "prerequisite", "previous-step", + "profile-data", + "putting-a-privatehiddenkey-example", "putting-a-publickey-example", "putting-a-selfkey-example", "putting-sharedkey-example", From 2c38d6a2cb9324f43e8222bd8efbc29eb3c3b530 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Fri, 9 Sep 2022 17:04:10 -0400 Subject: [PATCH 08/12] steps 1 to 3 of atDude tutorial reviewed for grammatical errors --- .../at-dude/3-app_architecture/index.md | 4 +- .../tutorials/at-dude/4-onboarding/index.md | 109 ++--- .../tutorials/at-dude/5-reset-atsign/index.md | 95 ++--- .../6-send_dude-screen-app-bar/index.md | 372 ++++++++++++++++++ .../send_dude_screen_app_bar.png | Bin 0 -> 10208 bytes .../at-dude/6-send_dude-screen-ui/index.md | 46 ++- hugo_stats.json | 1 + 7 files changed, 516 insertions(+), 111 deletions(-) create mode 100644 content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md create mode 100644 content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/send_dude_screen_app_bar.png diff --git a/content/docs/tutorials/at-dude/3-app_architecture/index.md b/content/docs/tutorials/at-dude/3-app_architecture/index.md index d92024402..b64726c61 100644 --- a/content/docs/tutorials/at-dude/3-app_architecture/index.md +++ b/content/docs/tutorials/at-dude/3-app_architecture/index.md @@ -52,7 +52,7 @@ The controller files will be saved in this folder. #### Services -The services fetch data from the network or local storage and returns it to the controllers. +The services fetch data from the network or local storage and return it to the commands. In your terminal type: @@ -65,6 +65,6 @@ The services files will be saved in this folder. #### Conclusion -The controllers will call the services to fetch data from the network and then send the fetched data to the model. The views are bound to the model using provider or an alternative. As the models are updated the views will be updated. Alternatively the controller can update the views directly. +The commands will call the services to fetch data from the network and then send the fetched data to the model. The views are bound to the model using provider or an alternative. As the models are updated the views will be updated. Alternatively the commands can update the views directly. In the next step we'll explore this further as we implement onboarding on the atPlatform. \ No newline at end of file diff --git a/content/docs/tutorials/at-dude/4-onboarding/index.md b/content/docs/tutorials/at-dude/4-onboarding/index.md index aa4075d81..29d825325 100644 --- a/content/docs/tutorials/at-dude/4-onboarding/index.md +++ b/content/docs/tutorials/at-dude/4-onboarding/index.md @@ -12,7 +12,7 @@ In this tutorial, we will build the onboarding screen for the dude app and modif -With out further ado, lets get back to building the atDude app. +With out further ado, let's get back to building the atDude app. At the end of this step our app will look like this, @@ -21,7 +21,7 @@ At the end of this step our app will look like this, {{
}} {{
}} -Before we get into the onboarding, lets make the UI changes to our app. +Before we get into the onboarding, let's make the UI changes to our app. #### Update AppBar The first thing we will do is change the App bar title to "atDude" in `main.dart`. @@ -38,7 +38,7 @@ MaterialApp( ); ``` -The next step is to wrap our `ElevatedButton` widget with a Column widget and center its mainAxisAlignment. +The next step is to wrap our `ElevatedButton` widget with a `Column` widget and center its mainAxisAlignment. ```dart MaterialApp( @@ -62,9 +62,9 @@ MaterialApp( #### Adding IconButton -Before add an `IconButton` widget with the dude logo as the icon property. We need to add the logo to our project. +Before we add an `IconButton` widget with the dude logo as the icon property. We need to add the logo to our project. -In your editor create a new folder called assets and inside the assets folder create a folder name images or type the following in your terminal: +Type the following in your terminal: ``` mkdir -p assets/images/ @@ -82,7 +82,7 @@ flutter: Let's create the `IconButton` as shown below. -``` +```dart Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -102,13 +102,13 @@ Column( #### Refactoring the Onboard Function -Before we get started with this section, lets define a few terms: +Before we get started with this section, let's define a few terms: [Onboarding](../../../sdk/flutter/onboarding) - The process of activating an atSign and/or authenticating the atSign into its secondary server. -[atsign](https://atsign.com/what-is-an-atsign/) - An atsign is your digital identity. It ensures that your data is owned and controlled by you. You can pair your device with any app to access but not store your data. +[atsign](https://atsign.com/what-is-an-atsign/) - An atsign is your digital identity; it ensures that your data is owned and controlled by you. You can pair your device with any app on the atPlatform to access but not store your data. -To make the onboarding code compatible, we will remove it form the "onboard an @sign" button and move it into an authentication class. +We will make the onboarding code compatible with our architecture by removing it from the “onboard an @sign” button and moving it into an authentication class. In your terminal type: @@ -119,7 +119,7 @@ open lib/services/authentication_service.dart Add the following: -``` +```dart class AuthenticationService { static final AuthenticationService _singleton = AuthenticationService._internal(); @@ -134,7 +134,7 @@ The above code creates a [singleton](https://flutterbyexample.com/lesson/singlet -Now we'll create a method called `onboard` in our `AuthenticationService` class and move the `onboardingResult` variable found inside the `onPressed` anonymous function of the ElevatedButton to the `onboard` method. Replace `AtOnboardingResult onboardingResult =` with `return` as shown below. +Now we'll create an async method called `onboard` in our `AuthenticationService` class. We will then move the contents of the `onboardingResult` variable inside the `ElevatedButton` `onPressed` anonymous function in `main.dart`, to the this method as shown below. ```dart class AuthenticationService { @@ -155,7 +155,7 @@ class AuthenticationService { #### Fixing Undefined name errors -To fix the `Undefined name` errors we need to provide the `AtOnboarding.onboard()`method with a `BuildContext` and the `AtOnboardingConfig()` class as shown below. +To fix the `Undefined name` errors we need to provide the `AtOnboarding.onboard()`method with a `BuildContext` and the `AtClientPreference` class as shown below. ```dart @@ -169,19 +169,19 @@ class AuthenticationService { ... Future onboard() async { - var dir = await getApplicationSupportDirectory(); + var dir = await getApplicationSupportDirectory(); // new - var atClientPreference = AtClientPreference() - ..rootDomain = AtEnv.rootDomain - ..namespace = AtEnv.appNamespace - ..hiveStoragePath = dir.path - ..commitLogPath = dir.path - ..isLocalStoreRequired = true; + var atClientPreference = AtClientPreference() // new + ..rootDomain = AtEnv.rootDomain //new + ..namespace = AtEnv.appNamespace //new + ..hiveStoragePath = dir.path //new + ..commitLogPath = dir.path //new + ..isLocalStoreRequired = true; //new return AtOnboarding.onboard( context: context, config: AtOnboardingConfig( - atClientPreference: atClientPreference, + atClientPreference: atClientPreference, // new rootEnvironment: AtEnv.rootEnvironment, domain: AtEnv.rootDomain, appAPIKey: AtEnv.appApiKey, @@ -191,7 +191,7 @@ class AuthenticationService { } ``` -we Now need access to the BuildContext, We'll create a separate class for this since we'll be reusing our BuildContext outside of the stateful and stateless widget. +We Now need access to the BuildContext, We'll create a separate class for this since we'll be reusing our BuildContext outside of the stateful and stateless widgets. In your terminal type: @@ -213,7 +213,7 @@ touch lib/services/services.dart open lib/services/services.dart ``` -Add the below code so can use one import statement to import all export files: +Add the below code to use one import statement to import all export files: ```dart export 'authentication_service.dart'; export 'navigation_service.dart'; @@ -222,11 +222,12 @@ export 'navigation_service.dart'; In `main.dart` add the navigatorKey to the materialApp as shown below; ```dart -import 'package:at_dude/services/services.dart'; +import 'package:at_dude/services/services.dart'; // new +... Widget build(BuildContext context) { return MaterialApp( // * The onboarding screen (first screen) - navigatorKey: NavigationService.navKey, + navigatorKey: NavigationService.navKey, // new home: ... ); } @@ -243,19 +244,20 @@ Future onboard() async { ); } ``` + +That's it, our `AuthenticatinService` can reach out to the atPlatform to return our `AtOnboardingResult`. We now need our command to call this service and decide what action to take depending on the returned `AtOnboardingResult`. + #### Base Command -Remember our commands contain our application logic, before we can create our onboard command we'll first create a base command that all other commands will extend from. In your terminal type: + Remember, our commands contain our application logic; we’ll first create a base command that all other commands will extend from before creating our onboard command In your terminal type: ``` touch lib/commands/base_command.dart +open lib/commands/base_command.dart ``` Create the `BaseCommand` class with the needed imports as shown below: -``` -open lib/commands/base_command.dart -``` ```dart import 'package:provider/provider.dart'; @@ -268,29 +270,31 @@ abstract class BaseCommand { } ``` -We now have to provide the services using the provider package as shown below: + We need to provide the services to the widget tree in order for `BaseCommand` to successfully read the services from the BuildContext. To achieve this we'll make use of the [provider package](https://pub.dev/packages/provider) as shown below: ``` flutter pub add provider open lib/main.dart ``` ```dart -import 'package:provider/provider.dart'; +import 'package:provider/provider.dart'; //new ... class _MyAppState extends State { @override Widget build(BuildContext context) { return MultiProvider( //new - providers: [Provider(create: (c) => AuthenticationService.getInstance())],// new + providers: [Provider(create: (c) => AuthenticationService.getInstance())] // new child: MaterialApp(), ) } } ``` +To learn more about state management using provider check out this [flutter article](https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple) + #### Onboard Command -Now that we're all set, lets create our Onboard Command. This class method will contain the instructions required to onboard on the atPlatform. In your terminal type: +Now that we're all set, let's create our Onboard Command. This class method will contain the instructions required to onboard on the atPlatform. In your terminal type: ``` touch lib/commands/onboard_command.dart @@ -310,7 +314,7 @@ class OnboardCommand extends BaseCommand { ``` Our Commands will only have one method called `run()`. This command return a `Future`. -Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: +Move the remaining code inside the `ElevatedButton onPressed` anonymous function in `main.dart`, to the `run()` method of the `OnboardCommand()` class as show below: ```dart import 'package:at_dude/commands/base_command.dart'; @@ -348,7 +352,7 @@ class OnboardCommand extends BaseCommand { If `authenticationService.onboard()` return `AtOnboardingResultStatus.success` we navigate to the HomeScreen, if it returns `AtOnboardingResultStatus.error` we display a `Snackbar` on the screen. -We will be using the `Snackbar` widget often so lets extract it into its own method. +We will be using the `Snackbar` widget often so let's extract it into its own method. #### Creating Snackbar Method @@ -369,12 +373,12 @@ class Snackbars extends StatelessWidget { const Snackbars({Key? key}) : super(key: key); static void errorSnackBar({ - required String content, + required String errorMessage, }) { ScaffoldMessenger.of(NavigationService.navKey.currentContext!) .showSnackBar(SnackBar( content: Text( - content, + errorMessage, textAlign: TextAlign.center, ), backgroundColor: @@ -391,21 +395,18 @@ class Snackbars extends StatelessWidget { We created a class that extends `StatelessWidget`. This class contains a static void method named `errorSnackBar` that accepts an `errorMessage`. -This method will save the broiler plate of calling `ScaffoldMessenger.of(context).showSnackBar()` every time we want so show a snackbar. +This method will save us from calling `ScaffoldMessenger.of(context).showSnackBar()` every time we want so show a snackbar. -Lets replace our snackbar part of `OnboardCommand.run()` method as shown below: +Let's replace our snackbar part of `OnboardCommand.run()` method as shown below: ```dart ... import 'package:at_dude/views/widgets/snackbars.dart'; // new - - class OnboardCommand extends BaseCommand { Future run() async { - var onboardingResult = await authenticationService.onboard(); - var context = NavigationService.navKey.currentContext!; + ... switch (onboardingResult.status) { ... case AtOnboardingResultStatus.error: @@ -427,20 +428,20 @@ Remove the code below from main.dart: ```dart -import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; // remove -import 'package:path_provider/path_provider.dart' - show getApplicationSupportDirectory; +import 'package:path_provider/path_provider.dart' // remove + show getApplicationSupportDirectory; // remove -Future loadAtClientPreference() async { - var dir = await getApplicationSupportDirectory(); +Future loadAtClientPreference() async { // remove + var dir = await getApplicationSupportDirectory(); //remove - return AtClientPreference() - ..rootDomain = AtEnv.rootDomain - ..namespace = AtEnv.appNamespace - ..hiveStoragePath = dir.path - ..commitLogPath = dir.path - ..isLocalStoreRequired = true; + return AtClientPreference() // remove + ..rootDomain = AtEnv.rootDomain // remove + ..namespace = AtEnv.appNamespace // remove + ..hiveStoragePath = dir.path // remove + ..commitLogPath = dir.path // remove + ..isLocalStoreRequired = true; // remove // TODO // * By default, this configuration is suitable for most applications // * In advanced cases you may need to modify [AtClientPreference] @@ -494,6 +495,6 @@ flutter run ``` #### Conclusion -Building a production app is like cooking your favorite food, Before you cook the food you do food preparation. You can skip food prep but then cooking become much much harder. Similarly, Following an architecture pattern, creating our export files and abstract classes are like food prep it makes cooking a whole lot easier. +Following an architecture is like having an organized room, it takes extra effort but it makes navigating and understand your codebase a whole lot easier and cleaner. Now that we've completed the onboarding process, in the next step we'll complete the first screen by adding a reset atsign button and it's functionalities. diff --git a/content/docs/tutorials/at-dude/5-reset-atsign/index.md b/content/docs/tutorials/at-dude/5-reset-atsign/index.md index bb4d04825..ed285a992 100644 --- a/content/docs/tutorials/at-dude/5-reset-atsign/index.md +++ b/content/docs/tutorials/at-dude/5-reset-atsign/index.md @@ -10,9 +10,6 @@ order: 5 # Ordering of the steps In this tutorial, we will complete the onboarding screen for the dude app and implement the reset app functionality. - - - At the end of this step our app will look like this, {{< image type="page" src="final_onboard_screen.png" >}} @@ -20,8 +17,8 @@ At the end of this step our app will look like this, {{
}} {{
}} - #### Creating the Texts util class + The first thing we will do is create a utility class that will store our texts. This will make it easy to update our texts across out app without having to search and replace all occurrence of the string. Follow the steps below: @@ -70,7 +67,7 @@ MaterialApp( ... ElevatedButton( ... - child: const Text(Texts.onboardAtsign), + child: const Text(Texts.onboardAtsign), // changed ), ], ), @@ -82,7 +79,7 @@ MaterialApp( #### Adding Reset Functionality to Authentication Service -In your terminal type: +We'll repeat the same pattern of the onboarding functionality for the reset app functionality. In your terminal type: ``` open lib/services/authentication_service.dart @@ -93,7 +90,7 @@ Now we'll create a method called `reset` and `getAtOnboardingConfig` in our `Aut ```dart class AuthenticationService { ... - + // new method AtOnboardingConfig getAtOnboardingConfig({ required AtClientPreference atClientPreference, }) => @@ -102,12 +99,12 @@ class AuthenticationService { rootEnvironment: AtEnv.rootEnvironment, domain: AtEnv.rootDomain, appAPIKey: AtEnv.appApiKey, - ); + ); // new Future onboard() async { return await AtOnboarding.onboard( context: context, - config: getAtOnboardingConfig(atClientPreference: atClientPreference), + config: getAtOnboardingConfig(atClientPreference: atClientPreference), // changed ); } } @@ -137,9 +134,11 @@ Future reset() async { `AtOnboarding.reset` allows the user to remove any atsign that onboard on the app before. This allows the user to onboarding with another atsign. -#### Onboard Command +#### Reset Command -Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to reset the currently signed in atsign on the atPlatform. In your terminal type: +Now that we're all set, lets create our Reset Command. This class method will contain the instructions required to remove any atsign associated with our app. + +In your terminal type: ``` touch lib/commands/reset_command.dart @@ -150,16 +149,14 @@ Add the below code: ```dart import 'package:at_dude/commands/base_command.dart'; -class OnboardCommand extends BaseCommand { +class ResetCommand extends BaseCommand { Future run() async { - var onboardingResult = await authenticationService.onboard(); + var resetResult = await authenticationService.reset(); } } ``` -This command return a `Future`. - -Move the remaining code in the `onPressed` anoymous function and paste in in the `run()` method of the `OnboardCommand()` class as show below: +Now that we have our variable `resetResult`. Let's decide what we'll do depending on the `resetResult`. ```dart import 'package:at_dude/commands/base_command.dart'; @@ -177,25 +174,26 @@ class ResetCommand extends BaseCommand { // Everything Below New - switch (onboardingResult.status) { - case AtOnboardingResultStatus.success: + switch (resetResult) { + case AtOnboardingResetResult.success: OnboardCommand().run(); break; - case AtOnboardingResultStatus.cancelled: + + case AtOnboardingResetResult.cancelled: break; } } } ``` -If `authenticationService.onboard()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. +If `authenticationService.reset()` return `AtOnboardingResetResultStatus.success` we call `'OnboardCommand().run()` to initiate the onboarding process, if it returns `AtOnboardingResetResultStatus.cancelled` we do nothing. #### Completing the first screen -Now we just have to update the UI in `main.dart` to all the user to reset their atsign. +Now we just have to update the UI in `main.dart` to allow the user to reset the app. -Add the below code to `main.dart`: +Edit `main.dart` as shown below: ```dart @override @@ -204,42 +202,46 @@ Add the below code to `main.dart`: ... child: MaterialApp( ... - home: Scaffold( - appBar: ..., + home: Scaffold(...), body: Builder( builder: (context) => Center( child: Column( ... children: [ - ..., - ElevatedButton(...), + IconButton(...), + ElevatedButton( + ... + child: const Text(Texts.onboardAtsign), + ), + // new Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Expanded( - child: Divider( - color: Colors.black, - ), + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Expanded( + child: Divider( + color: Colors.black, ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 12.0), - child: Text( - 'Or', - textAlign: TextAlign.center, - ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + 'Or', + textAlign: TextAlign.center, ), - Expanded( - child: Divider( - color: Colors.black, - ), + ), + Expanded( + child: Divider( + color: Colors.black, ), - ]), - ), + ), + ], + ), + ), // new end ], ), ), @@ -255,15 +257,16 @@ We add a divider to create separation between the two buttons. lets add the reset atsign button as shown below: ```dart -import 'package:at_dude/commands/reset_command.dart'; +import 'package:at_dude/commands/reset_command.dart'; // new ... Padding(...) +// new ElevatedButton( onPressed: () async { await ResetCommand().run(); }, child: Text(Texts.resetApp), -) +) // new end ``` Run your flutter app and everything should work perfectly. diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md b/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md new file mode 100644 index 000000000..3605c06da --- /dev/null +++ b/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md @@ -0,0 +1,372 @@ +--- +layout: codelab + +title: "Send Dude Screen AppBar" # Step Name +description: Creating the UI of the send dude screen # SEO Description for this step Documentation + +draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +order: 5 # Ordering of the steps +--- + +In this tutorial, we will build the AppBar of send dude screen. + + + + +At the end of this step our app will look like this, + +{{< image type="page" src="send_dude_screen_app_bar.png" >}} + +{{
}} +{{
}} + + +#### Creating the AppBar +The first thing we will do is create our send dude screen dart file with the AppBar and the widgets and properties it needs. + + Follow the steps below: + +``` + +touch lib/views/screens/send_dude_screen.dart +open lib/views/screens/send_dude_screen.dart +``` + +```dart +import 'package:flutter/material.dart'; +import '../../utils/texts.dart'; + +class SendDudeScreen extends StatefulWidget { + SendDudeScreen({Key? key}) : super(key: key); + static String routeName = 'sendDudeScreen'; + + @override + State createState() => _SendDudeScreenState(); +} + +class _SendDudeScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + foregroundColor: Colors.transparent, + shadowColor: Colors.transparent, + title: const Text( + Texts.sendDude, + style: TextStyle(color: Colors.black), + ), + actions: const [AtsignAvatar()], + + ), + ); + } +} +``` + +We have our stateful widget with an `appBar` but we neither have a `Texts.sendDude` constant nor the `AtsignAvatar()`. Lets create them: + +``` +open lib/utils/texts.dart +``` + +```dart +... +class Texts { + ... + static const String sendDude = 'Send Dude'; +} +``` + +#### AtsignAvatar + +Let us create `AtsignAvatar` as shown below: + +``` +touch lib/views/widgets/atsign_avatar.dart +open lib/views/widgets/atsign_avatar.dart +``` +```dart +import 'dart:typed_data' show Uint8List; +import 'package:flutter/material.dart'; + +class AtsignAvatar extends StatefulWidget { + const AtsignAvatar({Key? key}) : super(key: key); + + @override + State createState() => _AtsignAvatarState(); +} + +class _AtsignAvatarState extends State { + Uint8List? image; + String? profileName; + + @override + Widget build(BuildContext context) { + return GestureDetector( + child: CircleAvatar( + backgroundColor: Colors.transparent, + child: image == null + ? const Icon( + Icons.person_outline, + color: Colors.black, + ) + : ClipOval(child: Image.memory(image!)), + ), + onTap: () {}, + ); + } +} +``` + +Basically we have a `CircleAvatar` whose child is a profile image or an person_outline icon if no image is available. We need to add the functionality that will check for the atsign contact details. + +##### Profile Data +``` +mkdir lib/data +touch lib/data/profile_data.dart +open lib/data/profile_data.dart +``` + +```dart +import 'dart:typed_data' show Uint8List; + +class ProfileData { + ProfileData({required this.name, required this.profileImage}); + + final String? name; + final Uint8List? profileImage; +} +``` +This class will contain the name and profile image data we'll get from the ContactService class provided to us for free from the [at_contacts_flutter package](https://pub.dev/packages/at_contacts_flutter). + +##### Contacts Model +We'll now create our contacts model that will store all our contacts information needed in our app. + +``` +touch lib/models/contacts_model.dart +open lib/models/contacts_model.dart +``` + +```dart +import 'package:flutter/material.dart'; + +import '../data/profile_data.dart'; + +class ContactsModel extends ChangeNotifier { + late ProfileData _profileData; + + ProfileData get profileData => _profileData; + + set profileData(ProfileData profileData) { + _profileData = profileData; + notifyListeners(); + } +} +``` +Our profile data extends `ChangeNotifier`, this will allow us to `notifyListeners()` of changes made to `profileData`. + +We now have to add our `ContactModel` as a `ChangeNotifierProvider` and then add it to `BaseCommand`. + +``` +open lib/main.dart +``` + +```dart +... +@override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + Provider(create: (c) => AuthenticationService.getInstance()), + ChangeNotifierProvider(create: (c) => ContactsModel()), // new + ], + child: MaterialApp(...), + ); +``` + +``` +open lib/commands/base_command.dart +``` +```dart +... +import '../models/contacts_model.dart'; + +abstract class BaseCommand { + // Services + AuthenticationService authenticationService = + NavigationService.navKey.currentContext!.read(); + + // Models + ContactsModel contactsModel = NavigationService.navKey.currentContext!.read(); +``` + +#### Contact Details Command +We'll now create our Contact Details Command. We don't have to create our `ContactService` since this is provided to us from the [at_contacts_flutter package](https://pub.dev/packages/at_contacts_flutter). + + +In your terminal type: + +``` +flutter pub add at_contacts_flutter +touch lib/commands/contact_details_command.dart +open lib/commands/contact_details_command.dart +``` + +```dart +import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:at_contacts_flutter/services/contact_service.dart'; +import 'package:at_dude/commands/base_command.dart'; +import 'package:at_dude/data/profile_data.dart'; + +class ContactDetailsCommand extends BaseCommand { + Future run() async { + final contactService = ContactService(); + + ContactService() + .getContactDetails( + AtClientManager.getInstance().atClient.getCurrentAtSign(), null) + .then( + (value) { + contactsModel.profileData = + ProfileData(name: value['name'], profileImage: value['image']); + return null; + }, + ); + } +} +``` + +We use the `getCurrentAtSign()` method to get the current atsign, then use the `getContactDetails()` to get the name and profile image of the atsign. We then return the `profileData` to our `contactsModel`. + + + +#### Completing the AtsignAvatar widget + +Now we just have what we need to complete the AtsignAvatar Widget. + +``` +open lib/views/widgets/atsign_avatar.dart +``` + +```dart +class _AtsignAvatarState extends State { +... +@override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ContactDetailsCommand().run(); + }); + super.initState(); + } +} +``` + +To run an async method inside `initState` we need to call the method inside `WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {});` + +We can now delete the `image` and `profileName` variables since the data is now inside our `ContactsModel.profileData` property. Let's use the power of provider to access this property. + +```dart +class _AtsignAvatarState extends State { + Uint8List? image; // Delete this + String? profileName; // Delete this + + @override + void initState() { + ... + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + child: CircleAvatar( + backgroundColor: Colors.transparent, + child: context.watch().profileData.profileImage == null + ? const Icon( + Icons.person_outline, + color: Colors.black, + ) + : ClipOval( + child: Image.memory( + context.watch().profileData.profileImage!)), + ), + onTap: () {}, + ); + } +} +``` + +We accessed the `profileData` generated by calling `ContactDetailsCommand().run()` through `context.watch().profileData` + +#### Cleaning up our SendDudeScreen + +Let's fix our "The method 'AtsignAvatar' isn't defined" error by simply importing `AtsignAvatar` widget: + +``` +open lib/views/screens/send_dude_screen.dart +``` + +```dart +import '../widgets/atsign_avatar.dart'; // new + +class SendDudeScreen extends StatefulWidget { + ... +} + +class _SendDudeScreenState extends State { + ... +} +``` + +#### Navigating to the SendDudeScreen + +We can now navigate to our SendDudeScreen now that our send Dude Screen AppBar is completed. + +``` +open lib/commands/onboard_command.dart +``` + +```dart +... +class OnboardCommand extends BaseCommand { + Future run() async { + ... + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + Navigator.popAndPushNamed(context, SendDudeScreen.routeName); // new + break; + ... + } + } +} +``` + +Instead of navigating to the `HomeScreen` we now navigate to `SendDudeScreen` but we also need to add this screen as route. +``` +open lib/main.dart +``` + +```dart +import 'package:at_dude/views/screens/send_dude_screen.dart'; + +... + +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: ..., + child: MaterialApp( + ... + routes: { + SendDudeScreen.routeName: ((context) => SendDudeScreen()), // new + }, + home: Scaffold(...), + ), + ); + } +} +``` +#### Conclusion + +We're all done. In the next step we will start working on the bottom navigation bar. diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/send_dude_screen_app_bar.png b/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/send_dude_screen_app_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..c7af21707a1ddbb340ecd9d818ae1dc3175d376c GIT binary patch literal 10208 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 PYlnZdx5_(sF803whHrXI literal 0 HcmV?d00001 diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md index e71e8f14b..3605c06da 100644 --- a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md +++ b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md @@ -15,7 +15,7 @@ In this tutorial, we will build the AppBar of send dude screen. At the end of this step our app will look like this, -{{< image type="page" src="final_onboard_screen.png" >}} +{{< image type="page" src="send_dude_screen_app_bar.png" >}} {{
}} {{
}} @@ -74,7 +74,7 @@ open lib/utils/texts.dart ... class Texts { ... - static const String sendDude = 'Reset App'; + static const String sendDude = 'Send Dude'; } ``` @@ -119,7 +119,7 @@ class _AtsignAvatarState extends State { } ``` -Basically we have a `CircleAvatar` whose child is an image or an person_outline. We need to add the functionality that will check for the atsign contact details. +Basically we have a `CircleAvatar` whose child is a profile image or an person_outline icon if no image is available. We need to add the functionality that will check for the atsign contact details. ##### Profile Data ``` @@ -134,11 +134,11 @@ import 'dart:typed_data' show Uint8List; class ProfileData { ProfileData({required this.name, required this.profileImage}); - final String name; + final String? name; final Uint8List? profileImage; } ``` -This class will contain the name and profile image data we'll get from ContactService class provided to us form free from the at_xxx package. +This class will contain the name and profile image data we'll get from the ContactService class provided to us for free from the [at_contacts_flutter package](https://pub.dev/packages/at_contacts_flutter). ##### Contacts Model We'll now create our contacts model that will store all our contacts information needed in our app. @@ -202,7 +202,7 @@ abstract class BaseCommand { ``` #### Contact Details Command -We'll now create our Contact Details Command. We don't have to create our `ContactService` since this is provided to us from the at_contacts_flutter package. +We'll now create our Contact Details Command. We don't have to create our `ContactService` since this is provided to us from the [at_contacts_flutter package](https://pub.dev/packages/at_contacts_flutter). In your terminal type: @@ -237,7 +237,7 @@ class ContactDetailsCommand extends BaseCommand { } ``` -We use the `AtClientManager` method to get the current atsign, then use the ContactService to get the name and profile image of the atsign. We then return the profileData to our contactsModel. +We use the `getCurrentAtSign()` method to get the current atsign, then use the `getContactDetails()` to get the name and profile image of the atsign. We then return the `profileData` to our `contactsModel`. @@ -296,9 +296,11 @@ class _AtsignAvatarState extends State { } ``` +We accessed the `profileData` generated by calling `ContactDetailsCommand().run()` through `context.watch().profileData` + #### Cleaning up our SendDudeScreen -Let's fix our "The method 'AtsignAvatar' isn't defined" error by simply importing out `AtsignAvatar` widget: +Let's fix our "The method 'AtsignAvatar' isn't defined" error by simply importing `AtsignAvatar` widget: ``` open lib/views/screens/send_dude_screen.dart @@ -339,6 +341,32 @@ class OnboardCommand extends BaseCommand { } ``` +Instead of navigating to the `HomeScreen` we now navigate to `SendDudeScreen` but we also need to add this screen as route. +``` +open lib/main.dart +``` + +```dart +import 'package:at_dude/views/screens/send_dude_screen.dart'; + +... + +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: ..., + child: MaterialApp( + ... + routes: { + SendDudeScreen.routeName: ((context) => SendDudeScreen()), // new + }, + home: Scaffold(...), + ), + ); + } +} +``` #### Conclusion -Well done, you've made it this far. In the next step we will start building our Send Dude Screen. +We're all done. In the next step we will start working on the bottom navigation bar. diff --git a/hugo_stats.json b/hugo_stats.json index 450cca5d2..56b1e7322 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -403,6 +403,7 @@ "registration-cli", "related-resources", "requirements", + "reset-command", "root-secondary", "root-server", "search", From a9cb4811bae1cf1889983057cff245f9b1c2eb76 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Thu, 20 Oct 2022 12:06:29 -0400 Subject: [PATCH 09/12] step 4,5 and 6 are draft --- .../6-send_dude-screen-app-bar/index.md | 2 +- .../final_onboard_screen.png | Bin 26301 -> 0 bytes .../at-dude/6-send_dude-screen-ui/index.md | 372 ------------------ 3 files changed, 1 insertion(+), 373 deletions(-) delete mode 100644 content/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png delete mode 100644 content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md b/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md index 3605c06da..ef487af6b 100644 --- a/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md +++ b/content/docs/tutorials/at-dude/6-send_dude-screen-app-bar/index.md @@ -4,7 +4,7 @@ layout: codelab title: "Send Dude Screen AppBar" # Step Name description: Creating the UI of the send dude screen # SEO Description for this step Documentation -draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 5 # Ordering of the steps --- diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/final_onboard_screen.png deleted file mode 100644 index 7aefe6af5ed04332ed21e46c1be04a411131d113..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26301 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 KYE^64NBj@6pDlg> diff --git a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md b/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md deleted file mode 100644 index 3605c06da..000000000 --- a/content/docs/tutorials/at-dude/6-send_dude-screen-ui/index.md +++ /dev/null @@ -1,372 +0,0 @@ ---- -layout: codelab - -title: "Send Dude Screen AppBar" # Step Name -description: Creating the UI of the send dude screen # SEO Description for this step Documentation - -draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE -order: 5 # Ordering of the steps ---- - -In this tutorial, we will build the AppBar of send dude screen. - - - - -At the end of this step our app will look like this, - -{{< image type="page" src="send_dude_screen_app_bar.png" >}} - -{{
}} -{{
}} - - -#### Creating the AppBar -The first thing we will do is create our send dude screen dart file with the AppBar and the widgets and properties it needs. - - Follow the steps below: - -``` - -touch lib/views/screens/send_dude_screen.dart -open lib/views/screens/send_dude_screen.dart -``` - -```dart -import 'package:flutter/material.dart'; -import '../../utils/texts.dart'; - -class SendDudeScreen extends StatefulWidget { - SendDudeScreen({Key? key}) : super(key: key); - static String routeName = 'sendDudeScreen'; - - @override - State createState() => _SendDudeScreenState(); -} - -class _SendDudeScreenState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.transparent, - foregroundColor: Colors.transparent, - shadowColor: Colors.transparent, - title: const Text( - Texts.sendDude, - style: TextStyle(color: Colors.black), - ), - actions: const [AtsignAvatar()], - - ), - ); - } -} -``` - -We have our stateful widget with an `appBar` but we neither have a `Texts.sendDude` constant nor the `AtsignAvatar()`. Lets create them: - -``` -open lib/utils/texts.dart -``` - -```dart -... -class Texts { - ... - static const String sendDude = 'Send Dude'; -} -``` - -#### AtsignAvatar - -Let us create `AtsignAvatar` as shown below: - -``` -touch lib/views/widgets/atsign_avatar.dart -open lib/views/widgets/atsign_avatar.dart -``` -```dart -import 'dart:typed_data' show Uint8List; -import 'package:flutter/material.dart'; - -class AtsignAvatar extends StatefulWidget { - const AtsignAvatar({Key? key}) : super(key: key); - - @override - State createState() => _AtsignAvatarState(); -} - -class _AtsignAvatarState extends State { - Uint8List? image; - String? profileName; - - @override - Widget build(BuildContext context) { - return GestureDetector( - child: CircleAvatar( - backgroundColor: Colors.transparent, - child: image == null - ? const Icon( - Icons.person_outline, - color: Colors.black, - ) - : ClipOval(child: Image.memory(image!)), - ), - onTap: () {}, - ); - } -} -``` - -Basically we have a `CircleAvatar` whose child is a profile image or an person_outline icon if no image is available. We need to add the functionality that will check for the atsign contact details. - -##### Profile Data -``` -mkdir lib/data -touch lib/data/profile_data.dart -open lib/data/profile_data.dart -``` - -```dart -import 'dart:typed_data' show Uint8List; - -class ProfileData { - ProfileData({required this.name, required this.profileImage}); - - final String? name; - final Uint8List? profileImage; -} -``` -This class will contain the name and profile image data we'll get from the ContactService class provided to us for free from the [at_contacts_flutter package](https://pub.dev/packages/at_contacts_flutter). - -##### Contacts Model -We'll now create our contacts model that will store all our contacts information needed in our app. - -``` -touch lib/models/contacts_model.dart -open lib/models/contacts_model.dart -``` - -```dart -import 'package:flutter/material.dart'; - -import '../data/profile_data.dart'; - -class ContactsModel extends ChangeNotifier { - late ProfileData _profileData; - - ProfileData get profileData => _profileData; - - set profileData(ProfileData profileData) { - _profileData = profileData; - notifyListeners(); - } -} -``` -Our profile data extends `ChangeNotifier`, this will allow us to `notifyListeners()` of changes made to `profileData`. - -We now have to add our `ContactModel` as a `ChangeNotifierProvider` and then add it to `BaseCommand`. - -``` -open lib/main.dart -``` - -```dart -... -@override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - Provider(create: (c) => AuthenticationService.getInstance()), - ChangeNotifierProvider(create: (c) => ContactsModel()), // new - ], - child: MaterialApp(...), - ); -``` - -``` -open lib/commands/base_command.dart -``` -```dart -... -import '../models/contacts_model.dart'; - -abstract class BaseCommand { - // Services - AuthenticationService authenticationService = - NavigationService.navKey.currentContext!.read(); - - // Models - ContactsModel contactsModel = NavigationService.navKey.currentContext!.read(); -``` - -#### Contact Details Command -We'll now create our Contact Details Command. We don't have to create our `ContactService` since this is provided to us from the [at_contacts_flutter package](https://pub.dev/packages/at_contacts_flutter). - - -In your terminal type: - -``` -flutter pub add at_contacts_flutter -touch lib/commands/contact_details_command.dart -open lib/commands/contact_details_command.dart -``` - -```dart -import 'package:at_client_mobile/at_client_mobile.dart'; -import 'package:at_contacts_flutter/services/contact_service.dart'; -import 'package:at_dude/commands/base_command.dart'; -import 'package:at_dude/data/profile_data.dart'; - -class ContactDetailsCommand extends BaseCommand { - Future run() async { - final contactService = ContactService(); - - ContactService() - .getContactDetails( - AtClientManager.getInstance().atClient.getCurrentAtSign(), null) - .then( - (value) { - contactsModel.profileData = - ProfileData(name: value['name'], profileImage: value['image']); - return null; - }, - ); - } -} -``` - -We use the `getCurrentAtSign()` method to get the current atsign, then use the `getContactDetails()` to get the name and profile image of the atsign. We then return the `profileData` to our `contactsModel`. - - - -#### Completing the AtsignAvatar widget - -Now we just have what we need to complete the AtsignAvatar Widget. - -``` -open lib/views/widgets/atsign_avatar.dart -``` - -```dart -class _AtsignAvatarState extends State { -... -@override - void initState() { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - await ContactDetailsCommand().run(); - }); - super.initState(); - } -} -``` - -To run an async method inside `initState` we need to call the method inside `WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {});` - -We can now delete the `image` and `profileName` variables since the data is now inside our `ContactsModel.profileData` property. Let's use the power of provider to access this property. - -```dart -class _AtsignAvatarState extends State { - Uint8List? image; // Delete this - String? profileName; // Delete this - - @override - void initState() { - ... - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - child: CircleAvatar( - backgroundColor: Colors.transparent, - child: context.watch().profileData.profileImage == null - ? const Icon( - Icons.person_outline, - color: Colors.black, - ) - : ClipOval( - child: Image.memory( - context.watch().profileData.profileImage!)), - ), - onTap: () {}, - ); - } -} -``` - -We accessed the `profileData` generated by calling `ContactDetailsCommand().run()` through `context.watch().profileData` - -#### Cleaning up our SendDudeScreen - -Let's fix our "The method 'AtsignAvatar' isn't defined" error by simply importing `AtsignAvatar` widget: - -``` -open lib/views/screens/send_dude_screen.dart -``` - -```dart -import '../widgets/atsign_avatar.dart'; // new - -class SendDudeScreen extends StatefulWidget { - ... -} - -class _SendDudeScreenState extends State { - ... -} -``` - -#### Navigating to the SendDudeScreen - -We can now navigate to our SendDudeScreen now that our send Dude Screen AppBar is completed. - -``` -open lib/commands/onboard_command.dart -``` - -```dart -... -class OnboardCommand extends BaseCommand { - Future run() async { - ... - switch (onboardingResult.status) { - case AtOnboardingResultStatus.success: - Navigator.popAndPushNamed(context, SendDudeScreen.routeName); // new - break; - ... - } - } -} -``` - -Instead of navigating to the `HomeScreen` we now navigate to `SendDudeScreen` but we also need to add this screen as route. -``` -open lib/main.dart -``` - -```dart -import 'package:at_dude/views/screens/send_dude_screen.dart'; - -... - -class _MyAppState extends State { - @override - Widget build(BuildContext context) { - return MultiProvider( - providers: ..., - child: MaterialApp( - ... - routes: { - SendDudeScreen.routeName: ((context) => SendDudeScreen()), // new - }, - home: Scaffold(...), - ), - ); - } -} -``` -#### Conclusion - -We're all done. In the next step we will start working on the bottom navigation bar. From 7125601535fd5e00c1a4b454e94c78f27b2158c5 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Thu, 20 Oct 2022 12:26:19 -0400 Subject: [PATCH 10/12] step 4,5 and 6 are now draft --- content/docs/tutorials/at-dude/4-onboarding/index.md | 2 +- content/docs/tutorials/at-dude/5-reset-atsign/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/tutorials/at-dude/4-onboarding/index.md b/content/docs/tutorials/at-dude/4-onboarding/index.md index 29d825325..910697b44 100644 --- a/content/docs/tutorials/at-dude/4-onboarding/index.md +++ b/content/docs/tutorials/at-dude/4-onboarding/index.md @@ -4,7 +4,7 @@ layout: codelab title: "Onboarding" # Step Name description: How to onboard or authenticate on any app built on the atPlatform # SEO Description for this step Documentation -draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 4 # Ordering of the steps --- diff --git a/content/docs/tutorials/at-dude/5-reset-atsign/index.md b/content/docs/tutorials/at-dude/5-reset-atsign/index.md index ed285a992..1fc5db41b 100644 --- a/content/docs/tutorials/at-dude/5-reset-atsign/index.md +++ b/content/docs/tutorials/at-dude/5-reset-atsign/index.md @@ -4,7 +4,7 @@ layout: codelab title: "Reset atsign" # Step Name description: How to reset any app built on the atPlatform # SEO Description for this step Documentation -draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE +draft: true # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE order: 5 # Ordering of the steps --- From 4c6a054d6b81700912648f8beecb1f369fa6a2ef Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Thu, 20 Oct 2022 12:43:07 -0400 Subject: [PATCH 11/12] deleted the empty content_dev folder and subfolders --- .../tutorials/at-dude/4-onboarding/index.md | 500 ------------------ 1 file changed, 500 deletions(-) delete mode 100644 content_dev/docs/tutorials/at-dude/4-onboarding/index.md diff --git a/content_dev/docs/tutorials/at-dude/4-onboarding/index.md b/content_dev/docs/tutorials/at-dude/4-onboarding/index.md deleted file mode 100644 index 29d825325..000000000 --- a/content_dev/docs/tutorials/at-dude/4-onboarding/index.md +++ /dev/null @@ -1,500 +0,0 @@ ---- -layout: codelab - -title: "Onboarding" # Step Name -description: How to onboard or authenticate on any app built on the atPlatform # SEO Description for this step Documentation - -draft: false # TODO CHANGE THIS TO FALSE WHEN YOU ARE READY TO PUBLISH THE PAGE -order: 4 # Ordering of the steps ---- - -In this tutorial, we will build the onboarding screen for the dude app and modify the default onboarding function to make it compatible with our app architecture. - - - -With out further ado, let's get back to building the atDude app. - -At the end of this step our app will look like this, - -{{< image type="page" src="first_onboard_screen.png" >}} - -{{
}} -{{
}} - -Before we get into the onboarding, let's make the UI changes to our app. - -#### Update AppBar -The first thing we will do is change the App bar title to "atDude" in `main.dart`. - -``` -MaterialApp( - // * The onboarding screen (first screen) - debugShowCheckedModeBanner: false, // New - home: Scaffold( - appBar: AppBar( - title: const Text('atDude'), // Changed - ), - ), - ); -``` - -The next step is to wrap our `ElevatedButton` widget with a `Column` widget and center its mainAxisAlignment. - -```dart -MaterialApp( - // * The onboarding screen (first screen) - home: ... - body: Builder( - builder: (context) => Center( - child: Column( // new - mainAxisAlignment: MainAxisAlignment.center, // new - children: [ - ElevatedButton( - onPressed: ... - child: const Text('Onboard an @sign'), - ), - ], - ), - ), - ), - ); -``` - -#### Adding IconButton - -Before we add an `IconButton` widget with the dude logo as the icon property. We need to add the logo to our project. - -Type the following in your terminal: - -``` -mkdir -p assets/images/ -open pubspec.yaml -``` - -Add the location of the image folder as shown below. -``` -flutter: - uses-material-design: true - assets: - - .env - - assets/images/ // new -``` - -Let's create the `IconButton` as shown below. - -```dart -Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // IconButton New - IconButton( - iconSize: 200, - onPressed: () {}, - icon: Image.asset('assets/images/dude_logo.png'), - ), - ElevatedButton( - onPressed: () async {...}, - child: const Text('Onboard an @sign'), - ), - ], -), -``` - -#### Refactoring the Onboard Function - -Before we get started with this section, let's define a few terms: - -[Onboarding](../../../sdk/flutter/onboarding) - The process of activating an atSign and/or authenticating the atSign into its secondary server. - -[atsign](https://atsign.com/what-is-an-atsign/) - An atsign is your digital identity; it ensures that your data is owned and controlled by you. You can pair your device with any app on the atPlatform to access but not store your data. - -We will make the onboarding code compatible with our architecture by removing it from the “onboard an @sign” button and moving it into an authentication class. - -In your terminal type: - -``` -touch lib/services/authentication_service.dart -open lib/services/authentication_service.dart -``` - -Add the following: - -```dart -class AuthenticationService { - static final AuthenticationService _singleton = - AuthenticationService._internal(); - AuthenticationService._internal(); - - factory AuthenticationService.getInstance() { - return _singleton; - } -} -``` -The above code creates a [singleton](https://flutterbyexample.com/lesson/singletons) of our class. - - - -Now we'll create an async method called `onboard` in our `AuthenticationService` class. We will then move the contents of the `onboardingResult` variable inside the `ElevatedButton` `onPressed` anonymous function in `main.dart`, to the this method as shown below. - -```dart -class AuthenticationService { - ... - Future onboard() async { - return await AtOnboarding.onboard( - context: context, - config: AtOnboardingConfig( - atClientPreference: await futurePreference, - rootEnvironment: AtEnv.rootEnvironment, - domain: AtEnv.rootDomain, - appAPIKey: AtEnv.appApiKey, - ), - ); - } -} -``` - -#### Fixing Undefined name errors - -To fix the `Undefined name` errors we need to provide the `AtOnboarding.onboard()`method with a `BuildContext` and the `AtClientPreference` class as shown below. - - -```dart -import 'package:at_app_flutter/at_app_flutter.dart'; -import 'package:at_client_mobile/at_client_mobile.dart'; -import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; -import 'package:path_provider/path_provider.dart' - show getApplicationSupportDirectory; - -class AuthenticationService { - ... - - Future onboard() async { - var dir = await getApplicationSupportDirectory(); // new - - var atClientPreference = AtClientPreference() // new - ..rootDomain = AtEnv.rootDomain //new - ..namespace = AtEnv.appNamespace //new - ..hiveStoragePath = dir.path //new - ..commitLogPath = dir.path //new - ..isLocalStoreRequired = true; //new - - return AtOnboarding.onboard( - context: context, - config: AtOnboardingConfig( - atClientPreference: atClientPreference, // new - rootEnvironment: AtEnv.rootEnvironment, - domain: AtEnv.rootDomain, - appAPIKey: AtEnv.appApiKey, - ), - ); - } -} -``` - -We Now need access to the BuildContext, We'll create a separate class for this since we'll be reusing our BuildContext outside of the stateful and stateless widgets. - -In your terminal type: - -``` -touch lib/services/navigation_service.dart -``` -Add the below code snippet in that file. -```dart -import 'package:flutter/material.dart'; - -class NavigationService { - static GlobalKey navKey = GlobalKey(); - - static GlobalKey nestedNavKey = GlobalKey(); -} -``` -``` -touch lib/services/services.dart -open lib/services/services.dart -``` - -Add the below code to use one import statement to import all export files: -```dart -export 'authentication_service.dart'; -export 'navigation_service.dart'; -``` - -In `main.dart` add the navigatorKey to the materialApp as shown below; - -```dart -import 'package:at_dude/services/services.dart'; // new -... -Widget build(BuildContext context) { - return MaterialApp( - // * The onboarding screen (first screen) - navigatorKey: NavigationService.navKey, // new - home: ... - ); - } -``` - -In `navigation_service.dart` add the below code to the `onboard()` method. - -```dart -Future onboard() async { - ... - return await AtOnboarding.onboard( - context: NavigationService.navKey.currentContext!, // new - config: ... - ); - } -``` - -That's it, our `AuthenticatinService` can reach out to the atPlatform to return our `AtOnboardingResult`. We now need our command to call this service and decide what action to take depending on the returned `AtOnboardingResult`. - -#### Base Command - - Remember, our commands contain our application logic; we’ll first create a base command that all other commands will extend from before creating our onboard command In your terminal type: - -``` -touch lib/commands/base_command.dart -open lib/commands/base_command.dart -``` - -Create the `BaseCommand` class with the needed imports as shown below: - -```dart -import 'package:provider/provider.dart'; - -import '../services/services.dart'; - -abstract class BaseCommand { - // Services - AuthenticationService authenticationService = - NavigationService.navKey.currentContext!.read(); -} -``` - - We need to provide the services to the widget tree in order for `BaseCommand` to successfully read the services from the BuildContext. To achieve this we'll make use of the [provider package](https://pub.dev/packages/provider) as shown below: - -``` -flutter pub add provider -open lib/main.dart -``` -```dart -import 'package:provider/provider.dart'; //new -... -class _MyAppState extends State { - @override - Widget build(BuildContext context) { - return MultiProvider( //new - providers: [Provider(create: (c) => AuthenticationService.getInstance())] // new - child: MaterialApp(), - ) - } -} -``` - -To learn more about state management using provider check out this [flutter article](https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple) - -#### Onboard Command - -Now that we're all set, let's create our Onboard Command. This class method will contain the instructions required to onboard on the atPlatform. In your terminal type: - -``` -touch lib/commands/onboard_command.dart -open lib/commands/onboard_command.dart -``` - -Add the below code: - -```dart -import 'package:at_dude/commands/base_command.dart'; -class OnboardCommand extends BaseCommand { - Future run() async { - var onboardingResult = await authenticationService.onboard(); - - } -} -``` -Our Commands will only have one method called `run()`. This command return a `Future`. - -Move the remaining code inside the `ElevatedButton onPressed` anonymous function in `main.dart`, to the `run()` method of the `OnboardCommand()` class as show below: - -```dart -import 'package:at_dude/commands/base_command.dart'; -import 'package:at_dude/services/navigation_service.dart'; // new -import 'package:at_onboarding_flutter/at_onboarding_result.dart'; // new -import 'package:flutter/material.dart'; // new - -import '../home_screen.dart'; // new - -class OnboardCommand extends BaseCommand { - Future run() async { - var onboardingResult = await authenticationService.onboard(); - - // Everything Below New - var context = NavigationService.navKey.currentContext!; - switch (onboardingResult.status) { - case AtOnboardingResultStatus.success: - Navigator.push( - context, MaterialPageRoute(builder: (_) => const HomeScreen())); - break; - case AtOnboardingResultStatus.error: - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - backgroundColor: Colors.red, - content: Text('An error has occurred'), - ), - ); - break; - case AtOnboardingResultStatus.cancel: - break; - } - } -} -``` - -If `authenticationService.onboard()` return `AtOnboardingResultStatus.success` we navigate to the HomeScreen, if it returns `AtOnboardingResultStatus.error` we display a `Snackbar` on the screen. - -We will be using the `Snackbar` widget often so let's extract it into its own method. - -#### Creating Snackbar Method - -In your terminal type - -``` -touch lib/views/widgets/snackbars.dart -open lib/views/widgets/snackbars.dart -``` - -Add the below code - -```dart -import 'package:at_dude/services/navigation_service.dart'; -import 'package:flutter/material.dart'; - -class Snackbars extends StatelessWidget { - const Snackbars({Key? key}) : super(key: key); - - static void errorSnackBar({ - required String errorMessage, - }) { - ScaffoldMessenger.of(NavigationService.navKey.currentContext!) - .showSnackBar(SnackBar( - content: Text( - errorMessage, - textAlign: TextAlign.center, - ), - backgroundColor: - Theme.of(NavigationService.navKey.currentContext!).errorColor, - )); - } - - @override - Widget build(BuildContext context) { - return Container(); - } -} -``` - -We created a class that extends `StatelessWidget`. This class contains a static void method named `errorSnackBar` that accepts an `errorMessage`. - -This method will save us from calling `ScaffoldMessenger.of(context).showSnackBar()` every time we want so show a snackbar. - -Let's replace our snackbar part of `OnboardCommand.run()` method as shown below: - -```dart -... - -import 'package:at_dude/views/widgets/snackbars.dart'; // new - -class OnboardCommand extends BaseCommand { - Future run() async { - ... - switch (onboardingResult.status) { - ... - case AtOnboardingResultStatus.error: - Snackbars.errorSnackBar(errorMessage: 'An error has occurred'); // new - break; - case AtOnboardingResultStatus.cancel: - break; - } - } -} -``` - -All done! - -#### Cleaning up main.dart - -Now we just have to clean up `main.dart`. -Remove the code below from main.dart: - - -```dart -import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; // remove - -import 'package:path_provider/path_provider.dart' // remove - show getApplicationSupportDirectory; // remove - -Future loadAtClientPreference() async { // remove - var dir = await getApplicationSupportDirectory(); //remove - - return AtClientPreference() // remove - ..rootDomain = AtEnv.rootDomain // remove - ..namespace = AtEnv.appNamespace // remove - ..hiveStoragePath = dir.path // remove - ..commitLogPath = dir.path // remove - ..isLocalStoreRequired = true; // remove - // TODO - // * By default, this configuration is suitable for most applications - // * In advanced cases you may need to modify [AtClientPreference] - // * Read more here: https://pub.dev/documentation/at_client/latest/at_client/AtClientPreference-class.html -} - -class _MyAppState extends State { - // * load the AtClientPreference in the background // remove - Future futurePreference = loadAtClientPreference(); // Remove - ... -} -``` - -Add the below code to `main.dart`: - -```dart -@override - Widget build(BuildContext context) { - return MultiProvider( - ... - child: MaterialApp( - ... - home: Scaffold( - appBar: ..., - body: Builder( - builder: (context) => Center( - child: Column( - ... - children: [ - ..., - ElevatedButton( - onPressed: () async { - await OnboardCommand().run(); // new - }, - child: const Text('Onboard an @sign'), - ), - ], - ), - ), - ), - ), - ), - ); - } -``` - -Run your flutter app and everything should work as before. - -``` -flutter run -``` -#### Conclusion - -Following an architecture is like having an organized room, it takes extra effort but it makes navigating and understand your codebase a whole lot easier and cleaner. - -Now that we've completed the onboarding process, in the next step we'll complete the first screen by adding a reset atsign button and it's functionalities. From df89016904ac81f34b8811e46f96092427df84f6 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 1 Nov 2022 14:40:13 -0400 Subject: [PATCH 12/12] #chore: update _index.md --- content/docs/tutorials/at-dude/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/tutorials/at-dude/_index.md b/content/docs/tutorials/at-dude/_index.md index 381d26277..dd5f397d3 100644 --- a/content/docs/tutorials/at-dude/_index.md +++ b/content/docs/tutorials/at-dude/_index.md @@ -3,7 +3,7 @@ layout: codelab-list # The layout for a codelab list (think of this as a title p title: "atDude Tutorial" # Title of the codelab lead: Learn how to build production app on the atPlatform # Description of the codelab -description: Learn how to build on the atPlatform +description: Learn how to build production app on the atPlatform doneLink: /tutorials # Where to send them if they press "Done" at the end of the Codelab exitLink: /tutorials # Where to send them if they press "Exit Codelab"